dist/temp/WindowsUpdateTools/Public/Get-ServicingDiagnostics.ps1

function Get-ServicingDiagnostics {
    <#
    .SYNOPSIS
        Analyzes DISM (dism.log) and CBS (CBS.log) servicing logs for corruption and categorized servicing errors.
 
    .DESCRIPTION
        Lightweight (tail-first) servicing diagnostics that reads DISM and CBS logs and produces
        a structured taxonomy of detected issues. Extracts and classifies HRESULT / Win32 style
        error codes, CBS corruption summaries, SFC ([SR]) events, pending reboot indicators and
        maps them to actionable recommendation guidance. Designed to scale – new codes can be
        added to the internal taxonomy table without altering parsing logic.
 
        Default behaviour tails only the most recent lines (Tail / MaxLines). Specify -Full for
        exhaustive parsing (may be expensive on systems with very large logs).
 
    .PARAMETER MaxLines
        (Deprecated alias of Tail) Number of trailing lines to read (ignored with -Full). Default 800.
 
    .PARAMETER Tail
        Number of trailing lines to sample (preferred over MaxLines). Default 800.
 
    .PARAMETER Full
        Scan entire logs instead of just the tail portion.
 
    .PARAMETER IncludeRaw
        Include truncated raw matched lines in the output object for external review (capped for size).
 
    .PARAMETER IncludeSFCEvents
        Parse and include SFC ([SR]) events extracted from CBS.log.
 
    .PARAMETER ExportJsonPath
        If specified, writes the full diagnostics object (JSON) to the given path.
 
    .PARAMETER LogPath
        Optional path to append diagnostic summary (same log used by other module functions).
 
    .EXAMPLE
        Get-ServicingDiagnostics
 
    .EXAMPLE
        Get-ServicingDiagnostics -Full -IncludeRaw -LogPath C:\Windows\Temp\WU-Diag.log
    #>

    [CmdletBinding()] param(
        [Alias('MaxLines')]
        [int]$Tail = 800,
        [switch]$Full,
        [switch]$IncludeRaw,
        [switch]$IncludeSFCEvents,
        [string]$ExportJsonPath,
        [string]$LogPath
    )

    $dismLog = Join-Path $env:WINDIR 'Logs\DISM\dism.log'
    $cbsLog  = Join-Path $env:WINDIR 'Logs\CBS\CBS.log'

    # Structured taxonomy (extendable) - Enhanced with community findings
    $errorTaxonomy = [ordered]@{
        '0x800700c1' = @{ Category='InvalidBinary'; Recommendation='Servicing binary invalid (%%1 is not a valid Win32). Apply latest SSU or perform in-place upgrade repair.' }
        '0x800f081f' = @{ Category='MissingPayload'; Recommendation='Payload/source files missing. Provide matching install media and rerun DISM with /Source and /LimitAccess.' }
        '0x80070005' = @{ Category='AccessDenied'; Recommendation='Access denied. Run elevated, review AV/EDR interference, validate ACLs on WinSxS and Servicing directories.' }
        '0x800f0831' = @{ Category='MissingPackage'; Recommendation='Referenced package/manifest missing. Supply cumulative update baseline or specify full media as /Source.' }
        '0x800f0922' = @{ Category='ServicingStackIssue'; Recommendation='Servicing stack / system reserved partition constraints. Ensure partition has > 1GB free and latest SSU installed.' }
        '0x8007007b' = @{ Category='InvalidBinary'; Recommendation='Invalid filename/path or corrupted binary during servicing operation. Clear WinSxS cache and retry.' }
        '0xc142011c' = @{ Category='InvalidBinary'; Recommendation='Component Based Servicing corruption. Run DISM cleanup operations before RestoreHealth.' }
        '0x800f0906' = @{ Category='MissingPayload'; Recommendation='Source files could not be downloaded. Check network connectivity and Windows Update service status.' }
        '0x800f0907' = @{ Category='MissingPayload'; Recommendation='DISM source files not found. Specify /Source with Windows installation media path.' }
        '0xc0000005' = @{ Category='InvalidBinary'; Recommendation='Access violation during DISM operation. Severe component store corruption - consider in-place upgrade.' }
        '0x80240022' = @{ Category='NetworkConnectivity'; Recommendation='Windows Update connectivity failure. Verify DHCP Client service, network connectivity, and proxy settings.' }
        '0x80240069' = @{ Category='WSUSCompatibility'; Recommendation='WSUS deployment error. Apply Windows 11 24H2 compatibility registry fix for FeatureManagement.' }
    }

    # Helper to classify codes
    function New-CodeRecord {
        param($Code, $Lines)
        $norm = $Code.ToLower()
        if ($errorTaxonomy.Contains($norm)) {
            [PSCustomObject]@{
                Code = $norm
                Category = $errorTaxonomy[$norm].Category
                Recommendation = $errorTaxonomy[$norm].Recommendation
                Count = $Lines.Count
                Sample = $Lines | Select-Object -First 3
            }
        } else {
            [PSCustomObject]@{
                Code = $norm
                Category = 'Other'
                Recommendation = 'Review dism.log / CBS.log for context; add to taxonomy if recurrent.'
                Count = $Lines.Count
                Sample = $Lines | Select-Object -First 3
            }
        }
    }

    $result = [PSCustomObject]@{
        Success = $false
        Timestamp = Get-Date
        Version = $script:ModuleVersion
        DismLogPresent = Test-Path $dismLog
        CbsLogPresent = Test-Path $cbsLog
        Codes = @()               # Detailed per-code records
        Categories = @()          # Distinct categories observed
        DismHResults = @()
        DismInvalidWin32 = $false
        DismMissingSource = $false
        CbsCannotRepairEntries = @()
        CbsCannotRepairCount = 0
        CbsRepairedCount = 0
        CbsCorruptMentions = 0
        SFCEvents = @()
        PendingReboot = $false
        PendingRebootReasons = @()
        Recommendations = @()
        Raw = $null
        Taxonomy = $errorTaxonomy
    }

    try {
        if (-not $result.DismLogPresent -and -not $result.CbsLogPresent) {
            throw 'Neither DISM nor CBS log files are present.'
        }

        $dismLines = @()
        if ($result.DismLogPresent) {
            if ($Full) { $dismLines = Get-Content -Path $dismLog -ErrorAction SilentlyContinue } else { $dismLines = Get-Content -Path $dismLog -Tail $Tail -ErrorAction SilentlyContinue }
        }
        $cbsLines = @()
        if ($result.CbsLogPresent) {
            if ($Full) { $cbsLines = Get-Content -Path $cbsLog -ErrorAction SilentlyContinue } else { $cbsLines = Get-Content -Path $cbsLog -Tail $Tail -ErrorAction SilentlyContinue }
        }

        # --- DISM Parsing ---
        if ($dismLines.Count -gt 0) {
            $hresultRegex = '0x[0-9a-fA-F]{8}'
            $dismErrors = $dismLines | Where-Object { $_ -match '(?i)error|failed|0x[0-9a-fA-F]{8}' }
            $hrs = $dismErrors | ForEach-Object { [regex]::Matches($_, $hresultRegex) } | ForEach-Object { $_.Value.ToLower() } | Select-Object -Unique
            if ($hrs) { $result.DismHResults = $hrs }
            if ($hrs -contains '0x800700c1') { $result.DismInvalidWin32 = $true }
            if ($hrs -contains '0x800f081f') { $result.DismMissingSource = $true }

            # Group per code for classification
            $codeGroups = @{}
            foreach ($code in $hrs) { $codeGroups[$code] = @() }
            foreach ($line in $dismErrors) {
                foreach ($code in [regex]::Matches($line,$hresultRegex) | ForEach-Object { $_.Value.ToLower() }) {
                    if (-not $codeGroups.Contains($code)) { $codeGroups[$code] = @() }
                    if ($codeGroups[$code].Count -lt 25) { $codeGroups[$code] += $line }
                }
            }
            $records = foreach ($kv in $codeGroups.GetEnumerator()) { New-CodeRecord -Code $kv.Key -Lines $kv.Value }
            if ($records) { $result.Codes = $result.Codes + $records }
            $result.Categories = ($result.Codes | Select-Object -ExpandProperty Category -Unique)
            if ($IncludeRaw) {
                $result | Add-Member -NotePropertyName DismErrorLines -NotePropertyValue ($dismErrors | Select-Object -First 60) -Force
            }
        }

        # --- CBS Parsing ---
        if ($cbsLines.Count -gt 0) {
            $cannotRepair = $cbsLines | Where-Object { $_ -match 'Cannot repair member file' }
            $repaired     = $cbsLines | Where-Object { $_ -match 'Repairing corrupted file' }
            $corruptMentions = $cbsLines | Where-Object { $_ -match '(?i)corrupt' }
            $result.CbsCannotRepairEntries = if ($IncludeRaw) { $cannotRepair | Select-Object -First 30 } else { @() }
            $result.CbsCannotRepairCount = $cannotRepair.Count
            $result.CbsRepairedCount = $repaired.Count
            $result.CbsCorruptMentions = $corruptMentions.Count

            if ($IncludeSFCEvents) {
                $sfc = $cbsLines | Where-Object { $_ -match '\[SR\]' }
                if ($sfc) {
                    $result.SFCEvents = $sfc | Select-Object -First 120
                }
            }
        }

        # Pending reboot detection (registry based quick check)
        try {
            $pending = $false; $reasons = @()
            $keys = @(
                'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending',
                'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired',
                'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\SessionsPending'
            )
            foreach ($k in $keys) { if (Test-Path $k) { $pending = $true; $reasons += (Split-Path $k -Leaf) } }
            $result.PendingReboot = $pending
            $result.PendingRebootReasons = $reasons
        } catch {}

        # Build recommendations
        foreach ($rec in ($result.Codes | Select-Object -Unique Code,Recommendation)) { $result.Recommendations += $rec.Recommendation }
        if ($result.CbsCannotRepairCount -gt 0) {
            $result.Recommendations += "CBS reports $($result.CbsCannotRepairCount) unrepaired component(s). Source payload or perform in-place repair."
        }
        if ($result.PendingReboot) { $result.Recommendations += 'Pending reboot detected. Reboot before further servicing attempts.' }
        if (-not $result.Recommendations) { $result.Recommendations += 'No major servicing red flags detected in sampled log scope.' }
        $result.Recommendations = $result.Recommendations | Select-Object -Unique

        # Update categories with derived flags
        if ($result.DismInvalidWin32 -and ($result.Categories -notcontains 'InvalidBinary')) { $result.Categories += 'InvalidBinary' }
        if ($result.DismMissingSource -and ($result.Categories -notcontains 'MissingPayload')) { $result.Categories += 'MissingPayload' }

        $result.Success = $true

        if ($LogPath) {
            Write-WULog -Message '=== Servicing Diagnostics Summary ===' -LogPath $LogPath
            Write-WULog -Message "DISM Codes: $(if ($result.DismHResults) { [string]::Join(',', $result.DismHResults) } else { 'None' })" -LogPath $LogPath
            Write-WULog -Message "Categories: $(if ($result.Categories) { [string]::Join(',', $result.Categories) } else { 'None' })" -LogPath $LogPath
            Write-WULog -Message "CBS CannotRepair=$($result.CbsCannotRepairCount) Repaired=$($result.CbsRepairedCount) CorruptMentions=$($result.CbsCorruptMentions)" -LogPath $LogPath
            Write-WULog -Message "PendingReboot=$($result.PendingReboot) Reasons=$(if ($result.PendingRebootReasons) { [string]::Join(',', $result.PendingRebootReasons) } else { 'None' })" -LogPath $LogPath
            Write-WULog -Message 'Recommendations:' -LogPath $LogPath
            foreach ($rec in $result.Recommendations) { Write-WULog -Message " - $rec" -LogPath $LogPath }
        }
    }
    catch {
        $err = $_.Exception.Message
        if ($LogPath) { Write-WULog -Message "Servicing diagnostics failed: $err" -Level Error -LogPath $LogPath }
        $result | Add-Member -NotePropertyName Error -NotePropertyValue $err -Force
    }

    if (-not $IncludeRaw) { $result.Raw = $null }
    else { $result.Raw = [PSCustomObject]@{ DismSample = ($result.PSObject.Properties['DismErrorLines'].Value); CbsCannotRepair = $result.CbsCannotRepairEntries } }

    if ($ExportJsonPath) {
        try { $result | ConvertTo-Json -Depth 6 | Set-Content -Path $ExportJsonPath -Encoding UTF8 } catch { if ($LogPath) { Write-WULog -Message "Failed to export diagnostics JSON: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } }
    }

    return $result
}