# ============================================================
# Config
# ============================================================

$ErrorActionPreference = "Stop"

$isoPath = "D:\ae\iso\feiq_zh-cn_windows_10_business_editions_version_22h2_updated_oct_2025_x64_dvd_d4e92df7.iso"
$index = 2

$baseDir   = "D:\sysmbols_1405"
$mountDir  = Join-Path $baseDir "mount"
$symbolDir = Join-Path $baseDir "symbols"
$logDir    = Join-Path $baseDir "symbol_logs"

$symchk = "D:\ae\env\EWDK_vb_release_svc_prod1_19041_201201-2105\Program Files\Windows Kits\10\Debuggers\x64\symchk.exe"

# true  = symchk 输出更详细，但更慢
# false = 输出少一些
$verboseSymchk = $true

# ============================================================
# Precomputed paths
# ============================================================

$winDir       = Join-Path $mountDir "Windows"
$system32Dir  = Join-Path $winDir "System32"
$driversDir   = Join-Path $system32Dir "drivers"
$ntoskrnlPath = Join-Path $system32Dir "ntoskrnl.exe"

$symbolPath = "srv*$symbolDir*https://msdl.microsoft.com/download/symbols"

$coreSymbolListFile    = Join-Path $logDir "core_symbol_files.txt"
$missingSymbolListFile = Join-Path $logDir "core_symbol_missing.txt"
$coreSymchkLogFile     = Join-Path $logDir "core_symbols_symchk.log"

# 只下载核心符号，不递归扫描整个 drivers 目录
$coreSymbolRelativePaths = @(
# Kernel / HAL / debug transport
    "Windows\System32\ntoskrnl.exe"
    "Windows\System32\hal.dll"
    "Windows\System32\kdcom.dll"

# Code integrity / crypto / security
    "Windows\System32\ci.dll"
    "Windows\System32\drivers\ksecdd.sys"
    "Windows\System32\drivers\ksecpkg.sys"
    "Windows\System32\drivers\cng.sys"

# Win32 kernel side
    "Windows\System32\win32k.sys"
    "Windows\System32\win32kbase.sys"
    "Windows\System32\win32kfull.sys"

# Plug and Play / ACPI / PCI
    "Windows\System32\drivers\ACPI.sys"
    "Windows\System32\drivers\pci.sys"
    "Windows\System32\drivers\msisadrv.sys"
    "Windows\System32\drivers\vdrvroot.sys"

# Storage stack
    "Windows\System32\drivers\disk.sys"
    "Windows\System32\drivers\partmgr.sys"
    "Windows\System32\drivers\mountmgr.sys"
    "Windows\System32\drivers\volmgr.sys"
    "Windows\System32\drivers\volsnap.sys"
    "Windows\System32\drivers\classpnp.sys"
    "Windows\System32\drivers\storport.sys"
    "Windows\System32\drivers\storahci.sys"
    "Windows\System32\drivers\stornvme.sys"

# File system stack
    "Windows\System32\drivers\ntfs.sys"
    "Windows\System32\drivers\fltmgr.sys"
    "Windows\System32\drivers\fileinfo.sys"

# Network stack
    "Windows\System32\drivers\ndis.sys"
    "Windows\System32\drivers\NETIO.SYS"
    "Windows\System32\drivers\tcpip.sys"
    "Windows\System32\drivers\fwpkclnt.sys"

# Driver framework
    "Windows\System32\drivers\Wdf01000.sys"
    "Windows\System32\drivers\WppRecorder.sys"

# Graphics kernel
    "Windows\System32\drivers\dxgkrnl.sys"
    "Windows\System32\drivers\dxgmms2.sys"
)

# ============================================================
# Helpers
# ============================================================

function Ensure-Dirs {
    New-Item -ItemType Directory -Force -Path $baseDir   | Out-Null
    New-Item -ItemType Directory -Force -Path $mountDir  | Out-Null
    New-Item -ItemType Directory -Force -Path $symbolDir | Out-Null
    New-Item -ItemType Directory -Force -Path $logDir    | Out-Null
}

function Test-WimMounted {
    param(
        [Parameter(Mandatory = $true)]
        [string]$MountDir
    )

    $info = dism /Get-MountedWimInfo 2>&1

    return ($info -match [regex]::Escape($MountDir))
}

function Ensure-MountDirReady {
    if (Test-WimMounted -MountDir $mountDir) {
        Write-Host "WIM already mounted by DISM: $mountDir"

        if (-not (Test-Path -LiteralPath $ntoskrnlPath)) {
            throw "DISM 认为 WIM 已挂载，但找不到 ntoskrnl.exe：$ntoskrnlPath。请先执行 clean_env 或手动 dism /Unmount-Wim /Discard。"
        }

        return $true
    }

    if (Test-Path -LiteralPath $mountDir) {
        $items = Get-ChildItem -LiteralPath $mountDir -Force -ErrorAction SilentlyContinue

        if ($items.Count -gt 0) {
            Write-Host "Mount directory is not empty but DISM says not mounted. Cleaning: $mountDir" -ForegroundColor Yellow

            Get-ChildItem -LiteralPath $mountDir -Force |
                    Remove-Item -Recurse -Force -ErrorAction Stop
        }
    }
    else {
        New-Item -ItemType Directory -Force -Path $mountDir | Out-Null
    }

    return $false
}

function Get-IsoRoot {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ImagePath
    )

    $diskImage = Get-DiskImage -ImagePath $ImagePath -ErrorAction SilentlyContinue

    if ($null -eq $diskImage -or -not $diskImage.Attached) {
        Write-Host "Mounting ISO..."
        Mount-DiskImage -ImagePath $ImagePath | Out-Null
        Start-Sleep -Seconds 2
    }
    else {
        Write-Host "ISO already mounted."
    }

    $diskImage = Get-DiskImage -ImagePath $ImagePath

    $volume = $diskImage |
            Get-Volume |
            Where-Object { $_.DriveLetter } |
            Select-Object -First 1

    if ($null -eq $volume) {
        throw "无法获取 ISO 盘符"
    }

    return "$($volume.DriveLetter):\"
}

function Show-Config {
    Write-Host ""
    Write-Host "========== Config =========="
    Write-Host "isoPath     = $isoPath"
    Write-Host "index       = $index"
    Write-Host "baseDir     = $baseDir"
    Write-Host "mountDir    = $mountDir"
    Write-Host "symbolDir   = $symbolDir"
    Write-Host "logDir      = $logDir"
    Write-Host "symchk      = $symchk"
    Write-Host "symbolPath  = $symbolPath"
    Write-Host "ntoskrnl    = $ntoskrnlPath"
    Write-Host "driversDir  = $driversDir"
    Write-Host "============================"
    Write-Host ""
}

function Assert-BaseTools {
    if (-not (Test-Path -LiteralPath $isoPath)) {
        throw "ISO 文件不存在：$isoPath"
    }

    if (-not (Test-Path -LiteralPath $symchk)) {
        throw "symchk.exe 不存在：$symchk"
    }
}

function Assert-ImageReady {
    if (-not (Test-Path -LiteralPath $ntoskrnlPath)) {
        throw "离线 Windows 镜像还没有准备好，找不到：$ntoskrnlPath。请先执行 init_env。"
    }

    if (-not (Test-Path -LiteralPath $driversDir)) {
        throw "离线 Windows drivers 目录不存在：$driversDir。请先执行 init_env。"
    }
}

function Write-CoreSymbolFileList {
    Ensure-Dirs
    Assert-ImageReady

    $existing = New-Object System.Collections.Generic.List[string]
    $missing  = New-Object System.Collections.Generic.List[string]

    foreach ($rel in $coreSymbolRelativePaths) {
        $fullPath = Join-Path $mountDir $rel

        if (Test-Path -LiteralPath $fullPath) {
            $existing.Add($fullPath)
        }
        else {
            $missing.Add($fullPath)
        }
    }

    if ($missing.Count -gt 0) {
        $missing |
                Set-Content -LiteralPath $missingSymbolListFile -Encoding UTF8

        Write-Host ""
        Write-Host "部分核心文件不存在，已记录到：" -ForegroundColor Yellow
        Write-Host $missingSymbolListFile -ForegroundColor Yellow
    }

    if ($existing.Count -eq 0) {
        throw "核心符号文件列表为空，无法执行 symchk。"
    }

    $existing |
            Set-Content -LiteralPath $coreSymbolListFile -Encoding ASCII

    Write-Host ""
    Write-Host "Core symbol files: $($existing.Count)"
    Write-Host "Core symbol list : $coreSymbolListFile"

    return $coreSymbolListFile
}

function Invoke-SymChk {
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$Arguments,

        [Parameter(Mandatory = $true)]
        [string]$LogFile
    )

    Write-Host ""
    Write-Host "symchk arguments:" -ForegroundColor Cyan
    Write-Host ($Arguments -join " ")

    $oldErrorActionPreference = $ErrorActionPreference

    $hasNativeCommandPreference = Test-Path Variable:\PSNativeCommandUseErrorActionPreference
    $oldNativeCommandUseErrorActionPreference = $null

    if ($hasNativeCommandPreference) {
        $oldNativeCommandUseErrorActionPreference = $PSNativeCommandUseErrorActionPreference
    }

    $exitCode = $null

    try {
        # symchk 的详细信息可能写到 stderr。
        # 这里临时放宽 ErrorActionPreference，避免正常 stderr 被 PowerShell 当成 NativeCommandError 中断。
        $ErrorActionPreference = "Continue"

        if ($hasNativeCommandPreference) {
            $global:PSNativeCommandUseErrorActionPreference = $false
        }

        & $symchk @Arguments 2>&1 |
                ForEach-Object {
                    $line = $_.ToString()

                    Write-Host $line

                    Add-Content `
                    -LiteralPath $LogFile `
                    -Value $line `
                    -Encoding UTF8
                }

        $exitCode = $LASTEXITCODE
    }
    finally {
        $ErrorActionPreference = $oldErrorActionPreference

        if ($hasNativeCommandPreference) {
            $global:PSNativeCommandUseErrorActionPreference = $oldNativeCommandUseErrorActionPreference
        }
    }

    if ($exitCode -ne 0) {
        throw "symchk 执行失败，ExitCode=$exitCode，日志：$LogFile"
    }
}

# ============================================================
# Main functions
# ============================================================

function init_env {
    Write-Host ""
    Write-Host "Initializing environment..."

    Ensure-Dirs
    Assert-BaseTools
    Show-Config

    Write-Host "[init] Getting ISO root..."
    $isoRoot = Get-IsoRoot -ImagePath $isoPath

    Write-Host "[init] Checking install.wim..."
    $wimFile = Join-Path $isoRoot "sources\install.wim"

    if (-not (Test-Path -LiteralPath $wimFile)) {
        throw "install.wim 不存在：$wimFile"
    }

    Write-Host "ISO Root: $isoRoot"
    Write-Host "WIM File: $wimFile"

    $alreadyMounted = Ensure-MountDirReady

    if (-not $alreadyMounted) {
        Write-Host "[init] Mounting Windows image, Index $index..."

        dism /Mount-Wim "/WimFile:$wimFile" "/Index:$index" "/MountDir:$mountDir" /ReadOnly

        if ($LASTEXITCODE -ne 0) {
            throw "DISM 挂载 install.wim 失败，ExitCode=$LASTEXITCODE"
        }

        Write-Host "[init] DISM mount finished."
    }
    else {
        Write-Host "Skip mounting. WIM is already mounted: $mountDir"
    }

    Write-Host "[init] Checking mounted image..."
    Assert-ImageReady

    Write-Host "[init] Writing core symbol file list..."
    Write-CoreSymbolFileList | Out-Null

    Write-Host ""
    Write-Host "init_env finished." -ForegroundColor Green
}

function exec {
    Write-Host ""
    Write-Host "Downloading core Windows symbols..."

    Ensure-Dirs
    Assert-BaseTools
    Assert-ImageReady
    Show-Config

    $listFile = Write-CoreSymbolFileList

    $coreFiles = Get-Content -LiteralPath $listFile |
            Where-Object {
                -not [string]::IsNullOrWhiteSpace($_)
            }

    if ($coreFiles.Count -eq 0) {
        throw "核心符号文件列表为空：$listFile"
    }

    if (Test-Path -LiteralPath $coreSymchkLogFile) {
        Remove-Item -LiteralPath $coreSymchkLogFile -Force
    }

    Write-Host ""
    Write-Host "Core files to process: $($coreFiles.Count)"
    Write-Host "Log file: $coreSymchkLogFile"
    Write-Host ""

    $current = 0

    foreach ($file in $coreFiles) {
        $current++

        Write-Host "============================================================"
        Write-Host "[$current/$($coreFiles.Count)] Checking symbols for:" -ForegroundColor Yellow
        Write-Host $file -ForegroundColor Yellow
        Write-Host "============================================================"

        if (-not (Test-Path -LiteralPath $file)) {
            Write-Host "File missing, skipped: $file" -ForegroundColor Red
            continue
        }

        $symchkArgs = @(
            "/if", $file
            "/s",  $symbolPath
            "/od"
            "/ob"
            "/os"
            "/ot"
        )

        if ($verboseSymchk) {
            $symchkArgs += "/v"
        }

        Invoke-SymChk `
            -Arguments $symchkArgs `
            -LogFile $coreSymchkLogFile

        Write-Host ""
    }

    Write-Host ""
    Write-Host "Done." -ForegroundColor Green
    Write-Host "Symbols saved to: $symbolDir"
    Write-Host "Logs saved to: $logDir"
}

function clean_env {
    Write-Host ""
    Write-Host "Cleaning up..."

    Get-Process symchk -ErrorAction SilentlyContinue |
            Stop-Process -Force -ErrorAction SilentlyContinue

    if (Test-WimMounted -MountDir $mountDir) {
        Write-Host "Unmounting WIM: $mountDir"

        dism /Unmount-Wim "/MountDir:$mountDir" /Discard

        if ($LASTEXITCODE -ne 0) {
            throw "DISM 卸载 WIM 失败，ExitCode=$LASTEXITCODE"
        }

        Write-Host "WIM unmounted: $mountDir"
    }
    else {
        Write-Host "WIM is not mounted according to DISM: $mountDir"
    }

    Write-Host "Running DISM cleanup..."
    dism /Cleanup-Wim | Out-Host

    $diskImage = Get-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue

    if ($null -ne $diskImage -and $diskImage.Attached) {
        Write-Host "Dismounting ISO..."
        Dismount-DiskImage -ImagePath $isoPath | Out-Null
    }
    else {
        Write-Host "ISO is not mounted."
    }

    Write-Host "Cleanup finished." -ForegroundColor Green
}

# ============================================================
# Run
# ============================================================

function Test-Admin {
    $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($currentIdentity)

    return $principal.IsInRole(
            [Security.Principal.WindowsBuiltInRole]::Administrator
    )
}

if (-not (Test-Admin)) {
    Write-Host "错误：当前 PowerShell 不是管理员权限。" -ForegroundColor Red
    Write-Host "请右键 PowerShell / CLion，选择“以管理员身份运行”，然后重新执行脚本。" -ForegroundColor Yellow

    Read-Host "按回车退出"
    exit 1
}

try {
#    init_env
#    exec
        clean_env
}
finally {
    Write-Host ""
    Read-Host "执行完毕，按回车退出"
}