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 } |