Private/Test-WUComponentStore.ps1
|
function Test-WUComponentStore { <# .SYNOPSIS Tests Windows Component Store health for corruption issues. .DESCRIPTION Performs component store health assessment using DISM CheckHealth and ScanHealth operations. Also analyzes CBS.log for corruption indicators. .PARAMETER LogPath Path to the log file for detailed logging. .EXAMPLE $componentHealth = Test-WUComponentStore -LogPath "C:\Logs\wu.log" .NOTES This is a private function used internally by the WindowsUpdateTools module. Returns component store health status and corruption indicators. #> [CmdletBinding()] param( [string]$LogPath ) Write-WULog -Message "Testing Component Store health" -LogPath $LogPath # Initialize results object $results = [PSCustomObject]@{ CorruptionDetected = $false HealthCheckPassed = $false ScanHealthPassed = $false CBSLogIssues = 0 CBSLogIndicators = @() Issues = @() CheckHealthExitCode = $null ScanHealthExitCode = $null ErrorMessage = $null } try { # Step 1: DISM CheckHealth (quick check) Write-WULog -Message "Running DISM CheckHealth..." -LogPath $LogPath try { $checkHealthOutput = & dism.exe /online /cleanup-image /checkhealth 2>&1 $results.CheckHealthExitCode = $LASTEXITCODE Write-WULog -Message "DISM CheckHealth exit code: $($results.CheckHealthExitCode)" -LogPath $LogPath if ($results.CheckHealthExitCode -eq 0) { # Check for actual corruption indicators in the output # Exit code 0 means DISM ran successfully, but we need to check the output text $checkHealthOutputStr = $checkHealthOutput -join "`n" if ($checkHealthOutputStr -like "*Component Store is repairable*" -or $checkHealthOutputStr -like "*Component Store corruption was detected*") { $results.CorruptionDetected = $true $results.Issues += "DISM CheckHealth detected repairable component store corruption" Write-WULog -Message "Component store corruption detected by CheckHealth" -Level Warning -LogPath $LogPath } elseif ($checkHealthOutputStr -like "*No component store corruption detected*" -or $checkHealthOutputStr -like "*component store is healthy*") { $results.HealthCheckPassed = $true Write-WULog -Message "DISM CheckHealth passed - no corruption detected" -LogPath $LogPath } else { # Exit code 0 but no clear corruption message - assume healthy $results.HealthCheckPassed = $true Write-WULog -Message "DISM CheckHealth completed successfully" -LogPath $LogPath } } else { $results.Issues += "DISM CheckHealth failed with exit code: $($results.CheckHealthExitCode)" Write-WULog -Message "DISM CheckHealth failed with exit code: $($results.CheckHealthExitCode)" -Level Warning -LogPath $LogPath } } catch { $results.Issues += "Error running DISM CheckHealth: $($_.Exception.Message)" Write-WULog -Message "Error running DISM CheckHealth: $($_.Exception.Message)" -Level Error -LogPath $LogPath } # Step 2: DISM ScanHealth (more thorough check) - only if CheckHealth passed or we want to be thorough if ($results.HealthCheckPassed -or $results.CorruptionDetected) { Write-WULog -Message "Running DISM ScanHealth for thorough analysis..." -LogPath $LogPath try { $scanHealthOutput = & dism.exe /online /cleanup-image /scanhealth 2>&1 $results.ScanHealthExitCode = $LASTEXITCODE Write-WULog -Message "DISM ScanHealth exit code: $($results.ScanHealthExitCode)" -LogPath $LogPath if ($results.ScanHealthExitCode -eq 0) { if ($scanHealthOutput -like "*Component Store corruption was detected*" -or $scanHealthOutput -like "*Component Store is repairable*") { $results.CorruptionDetected = $true $results.Issues += "DISM ScanHealth confirmed component store corruption" Write-WULog -Message "Component store corruption confirmed by ScanHealth" -Level Warning -LogPath $LogPath } else { $results.ScanHealthPassed = $true Write-WULog -Message "DISM ScanHealth passed - no corruption detected" -LogPath $LogPath # ScanHealth is more thorough than CheckHealth - if ScanHealth passes, # override any CheckHealth corruption flag (CheckHealth can have false positives) if ($results.CorruptionDetected) { Write-WULog -Message "ScanHealth passed - clearing CheckHealth false positive" -LogPath $LogPath $results.CorruptionDetected = $false $results.HealthCheckPassed = $true # Remove any CheckHealth-related issues $results.Issues = $results.Issues | Where-Object { $_ -notlike "*CheckHealth*" } } } } else { $results.Issues += "DISM ScanHealth failed with exit code: $($results.ScanHealthExitCode)" Write-WULog -Message "DISM ScanHealth failed with exit code: $($results.ScanHealthExitCode)" -Level Warning -LogPath $LogPath } } catch { $results.Issues += "Error running DISM ScanHealth: $($_.Exception.Message)" Write-WULog -Message "Error running DISM ScanHealth: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } } # Step 3: Analyze CBS.log for corruption details - ONLY if DISM detected corruption # This provides additional context about what's corrupted, not whether corruption exists if ($results.CorruptionDetected) { Write-WULog -Message "DISM detected corruption - analyzing CBS.log for details..." -LogPath $LogPath try { $cbsLogPath = "$env:WINDIR\Logs\CBS\CBS.log" if (Test-Path $cbsLogPath) { # Get recent entries (last 500 lines for better coverage of detailed errors) $recentCbsEntries = Get-Content $cbsLogPath -Tail 500 -ErrorAction SilentlyContinue if ($recentCbsEntries) { $corruptionIndicators = @() foreach ($line in $recentCbsEntries) { $lineLower = $line.ToLower() # Skip normal operational messages and zero-corruption summary lines if ($lineLower -like "*corruption detection*" -or $lineLower -like "*storecorruptionrepair transaction*" -or $lineLower -like "*numcorruptions = 0*" -or $lineLower -like "*starting corruption detection*" -or $lineLower -like "*corruption: 0*" -or $lineLower -like "*corruption: 0*" -or $lineLower -like "*corruption complete*" -or $lineLower -like "*corruption detected: 0*" -or $lineLower -like "*times corruption detected: 0*" -or $lineLower -like "*successfully*" -or $lineLower -like "*completed successfully*" -or $lineLower -like "*operation completed*" -or $lineLower -like "*manifest corruption:*0*" -or # CBS/CSI Manifest Corruption: 0 $lineLower -like "*csi manifest corruption:*0*" -or # CSI Manifest Corruption: 0 $lineLower -like "*cbs manifest corruption:*0*" -or # CBS Manifest Corruption: 0 $lineLower -like "*corruption:*0*" -or # Any corruption count showing 0 $lineLower -like "*corruption*:*0*" -or # Any corruption field showing 0 $lineLower -like "*corruption detected*0*" -or # Any corruption detected showing 0 $lineLower -like "*times*corruption*0*") { # Any times corruption showing 0 continue } # Look for actual corruption/error indicators - both summary counts and specific details $isCorruptionIndicator = $false # Summary patterns (non-zero corruption counts) - use regex for precise matching if ($lineLower -match "corruption detected:\s*[1-9]\d*" -or $lineLower -match "times corruption detected:\s*[1-9]\d*" -or $lineLower -match "numcorruptions\s*=\s*[1-9]\d*" -or ($lineLower -match "corruption:\s*[1-9]\d*" -and $lineLower -notlike "*manifest corruption:*")) { $isCorruptionIndicator = $true } # Specific corruption detail patterns - exclude summary/informational lines $detailPatterns = @( '*failed to resolve component*', # Component resolution failures '*component identity*failed*', # Component identity issues '*manifest file is corrupt*', # Direct manifest corruption '*payload file is corrupt*', # Direct payload corruption '*cannot repair*', # Repair failures '*failed to repair*', # Repair failures '*repair failed*', # Repair failures '*corruption*cannot*repair*', # Unrepairable corruption '*hash mismatch*', # File integrity issues '*checksum*mismatch*', # File integrity issues '*invalid*manifest*', # Invalid manifest files '*component*missing*', # Missing components '*missing*component*', # Missing components '*error 0x8007*', # Common corruption error codes '*error 0x800f*', # CBS-specific error codes '*hresult 0x8*', # HRESULT corruption errors '*component store corruption was detected*', # Actual detection messages '*servicing corruption*', # Servicing corruption '*catalog*corrupt*', # Catalog corruption '*corrupt*catalog*', # Catalog corruption '*identity*corrupt*', # Identity corruption '*corrupt*identity*' # Identity corruption ) # Only check detail patterns if not already flagged and avoid generic corruption mentions if (-not $isCorruptionIndicator) { foreach ($pattern in $detailPatterns) { if ($lineLower -like $pattern -and $lineLower -notlike "*manifest corruption:*" -and $lineLower -notlike "*csi manifest corruption:*" -and $lineLower -notlike "*cbs manifest corruption:*") { $isCorruptionIndicator = $true break } } } if ($isCorruptionIndicator) { $corruptionIndicators += $line.Trim() } } $results.CBSLogIssues = $corruptionIndicators.Count $results.CBSLogIndicators = $corruptionIndicators if ($corruptionIndicators.Count -gt 0) { $results.Issues += "Found $($corruptionIndicators.Count) corruption details in CBS.log" Write-WULog -Message "Found $($corruptionIndicators.Count) corruption details in CBS.log" -LogPath $LogPath # Log a summary with limited details to avoid excessive output if ($corruptionIndicators.Count -le 3) { # If few indicators, log them all Write-WULog -Message "CBS Log Corruption Details:" -LogPath $LogPath foreach ($indicator in $corruptionIndicators) { Write-WULog -Message " $indicator" -LogPath $LogPath } } else { # If many indicators, just show first 2 and provide summary $sampleIndicators = $corruptionIndicators | Select-Object -First 2 Write-WULog -Message "CBS Log Corruption Sample (first 2 of $($corruptionIndicators.Count)):" -LogPath $LogPath foreach ($indicator in $sampleIndicators) { Write-WULog -Message " $indicator" -LogPath $LogPath } Write-WULog -Message " ... and $($corruptionIndicators.Count - 2) more indicators (see full list in CBSLogIndicators property)" -LogPath $LogPath } # Note: CorruptionDetected already set by DISM - CBS.log just provides details } else { Write-WULog -Message "CBS.log analyzed but no specific corruption patterns found" -LogPath $LogPath } } else { Write-WULog -Message "Could not read CBS.log entries for details" -LogPath $LogPath } } else { Write-WULog -Message "CBS.log not found - cannot provide corruption details" -LogPath $LogPath } } catch { Write-WULog -Message "Error analyzing CBS.log: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } } else { # No DISM corruption - skip CBS.log analysis entirely Write-WULog -Message "DISM reports component store is healthy - skipping CBS.log analysis" -LogPath $LogPath } # Step 4: Check for pending component store operations try { $pendingOperations = Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" if ($pendingOperations) { $results.Issues += "Pending component store operations detected - reboot may be required" Write-WULog -Message "Pending component store operations detected" -Level Warning -LogPath $LogPath } } catch { Write-WULog -Message "Could not check for pending component store operations" -Level Warning -LogPath $LogPath } } catch { $results.ErrorMessage = $_.Exception.Message Write-WULog -Message "Critical error during component store health test: $($_.Exception.Message)" -Level Error -LogPath $LogPath } # Summary Write-WULog -Message "Component Store health test completed:" -LogPath $LogPath Write-WULog -Message " Corruption detected: $($results.CorruptionDetected)" -LogPath $LogPath Write-WULog -Message " Health check passed: $($results.HealthCheckPassed)" -LogPath $LogPath Write-WULog -Message " Scan health passed: $($results.ScanHealthPassed)" -LogPath $LogPath Write-WULog -Message " CBS log issues: $($results.CBSLogIssues)" -LogPath $LogPath Write-WULog -Message " Total issues found: $($results.Issues.Count)" -LogPath $LogPath if ($results.CorruptionDetected) { Write-WULog -Message "RECOMMENDATION: Run component store repair (DISM RestoreHealth)" -Level Warning -LogPath $LogPath } return $results } |