Core/Test-SystemIntegrity.ps1
|
function Test-SystemIntegrity { <# .SYNOPSIS Tests Windows system file integrity .DESCRIPTION Runs SFC and DISM commands to check and verify system file integrity .PARAMETER RepairFiles Attempt to repair corrupted files if found .PARAMETER DetailedScan Perform a more thorough system scan .EXAMPLE Test-SystemIntegrity Test-SystemIntegrity -RepairFiles -DetailedScan .OUTPUTS PSCustomObject with integrity check results #> [CmdletBinding()] param( [Parameter()] [switch]$RepairFiles, [Parameter()] [switch]$DetailedScan ) begin { # Check for admin privileges $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { throw "This function requires Administrator privileges. Please run PowerShell as Administrator." } Write-Host "Starting System Integrity Check..." -ForegroundColor Cyan $results = [PSCustomObject]@{ ScanDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" ComputerName = $env:COMPUTERNAME SFCResult = $null DISMResult = $null CorruptedFiles = @() RepairAttempted = $false RepairSuccessful = $false WindowsUpdateServices = @() # Added this property } } process { try { # Run SFC scan Write-Host "Running System File Checker (SFC)..." -ForegroundColor Yellow $scanStart = Get-Date $sfcOutput = & sfc /scannow 2>&1 | Out-String $sfcExitCode = $LASTEXITCODE # SFC output text is fully localized; the exit code and the CBS.log # '[SR]'/'Cannot repair' tokens (written in English by the SFC engine # regardless of UI language) are the locale-invariant sources $cbsLogPath = "$env:windir\Logs\CBS\CBS.log" $cbsLogAvailable = Test-Path $cbsLogPath $cannotRepairLines = @() $cbsTimestampsParseable = $false if ($cbsLogAvailable) { $cbsTail = @(Get-Content $cbsLogPath -Tail 500 -ErrorAction SilentlyContinue) $cbsTailFilter = Select-CbsTailForCurrentScan -Lines $cbsTail -Since $scanStart $cbsTimestampsParseable = $cbsTailFilter.TimestampsParseable $cannotRepairLines = @($cbsTailFilter.Lines | Where-Object { $_ -match '\[SR\]' -and $_ -match 'Cannot repair' }) } if ($sfcExitCode -ne 0) { $results.SFCResult = "SFC could not complete scan - may need to run in Safe Mode" Write-Warning "SFC could not complete the scan (exit code $sfcExitCode)" } elseif ($cannotRepairLines.Count -gt 0 -or (-not $cbsLogAvailable -and $sfcOutput -match "Windows Resource Protection found corrupt files")) { $results.SFCResult = "Corrupt files found" if (-not $cbsTimestampsParseable -and $cannotRepairLines.Count -gt 0) { $results.SFCResult = "Corrupt files found (possible stale CBS.log entries)" } if ($cannotRepairLines.Count -gt 0) { $results.CorruptedFiles = $cannotRepairLines } elseif ($cbsLogAvailable) { $cbsLog = Get-Content $cbsLogPath -Tail 100 -ErrorAction SilentlyContinue | Where-Object { $_ -match "corrupt" -or $_ -match "repaired" } if ($cbsLog) { $results.CorruptedFiles = $cbsLog } } if ($RepairFiles) { Write-Host "Attempting to repair corrupt files..." -ForegroundColor Yellow $results.RepairAttempted = $true } } elseif ($cbsLogAvailable -or $sfcOutput -match "Windows Resource Protection did not find any integrity violations") { $results.SFCResult = "No integrity violations found" Write-Host "SFC: No integrity violations found" -ForegroundColor Green } elseif ($sfcOutput -match "Windows Resource Protection could not perform") { $results.SFCResult = "SFC could not complete scan - may need to run in Safe Mode" Write-Warning "SFC could not complete the scan" } else { $results.SFCResult = "Scan completed - check CBS.log for details" } # Run DISM if detailed scan requested if ($DetailedScan) { Write-Host "Running DISM health check..." -ForegroundColor Yellow # Check image health $dismCheck = & dism /Online /Cleanup-Image /CheckHealth 2>&1 | Out-String $dismCheckExitCode = $LASTEXITCODE # DISM output text is fully localized and CheckHealth returns 0 for # both 'healthy' and 'repairable', so the exit code alone cannot # separate those; servicing-stack CBS.log markers ('[SR]'/'Cannot # repair', component-store corruption) are written in English $storeCorruptionLines = @() $dismTimestampsParseable = $false if ($cbsLogAvailable) { $dismTail = @(Get-Content $cbsLogPath -Tail 500 -ErrorAction SilentlyContinue) $dismTailFilter = Select-CbsTailForCurrentScan -Lines $dismTail -Since $scanStart $dismTimestampsParseable = $dismTailFilter.TimestampsParseable $storeCorruptionLines = @($dismTailFilter.Lines | Where-Object { $_ -match '(?i)component store' -and $_ -match '(?i)corrupt' }) } $repairable = ($storeCorruptionLines.Count -gt 0) -or ($dismCheck -match "The component store is repairable") if ($dismCheckExitCode -ne 0) { $results.DISMResult = "DISM CheckHealth failed (exit code $dismCheckExitCode) - review CBS.log" Write-Warning "DISM CheckHealth failed with exit code $dismCheckExitCode" } elseif ($repairable) { $results.DISMResult = "Repairable corruption detected" if (-not $dismTimestampsParseable -and $storeCorruptionLines.Count -gt 0) { $results.DISMResult = "Repairable corruption detected (possible stale CBS.log entries)" } if ($RepairFiles) { Write-Host "Attempting DISM repair..." -ForegroundColor Yellow $null = & dism /Online /Cleanup-Image /RestoreHealth 2>&1 | Out-String $dismRepairExitCode = $LASTEXITCODE if ($dismRepairExitCode -eq 0) { $results.RepairSuccessful = $true $results.DISMResult = "Repaired successfully" Write-Host "DISM repair completed successfully" -ForegroundColor Green } else { $results.DISMResult = "Repair failed (exit code $dismRepairExitCode) - review CBS.log" Write-Warning "DISM RestoreHealth failed with exit code $dismRepairExitCode" } } } elseif ($dismCheck -match "No component store corruption detected") { $results.DISMResult = "Healthy" Write-Host "DISM: System image is healthy" -ForegroundColor Green } else { $results.DISMResult = "Check completed - review CBS.log" } } # Check Windows Update components Write-Host "Checking Windows Update components..." -ForegroundColor Yellow $serviceNames = @('wuauserv', 'cryptSvc', 'bits', 'msiserver') $services = @() foreach ($serviceName in $serviceNames) { $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service) { $services += [PSCustomObject]@{ Name = $service.Name DisplayName = $service.DisplayName Status = $service.Status StartType = $service.StartType } } } $results.WindowsUpdateServices = $services } catch { Write-Error "Error during system integrity check: $_" throw } } end { return $results } } function Select-CbsTailForCurrentScan { [CmdletBinding()] param( [Parameter()] [AllowEmptyCollection()] [string[]]$Lines = @(), [Parameter(Mandatory = $true)] [datetime]$Since ) $windowStart = $Since.AddMinutes(-5) $currentScanLines = @() $timestampsParseable = $false foreach ($line in $Lines) { if ($null -eq $line) { continue } if ($line -match '^(?<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})') { try { $lineTime = [datetime]::ParseExact($Matches.ts, 'yyyy-MM-dd HH:mm:ss', [System.Globalization.CultureInfo]::InvariantCulture) $timestampsParseable = $true if ($lineTime -ge $windowStart) { $currentScanLines += $line } } catch { } } } $filteredLines = @($Lines) if ($timestampsParseable) { $filteredLines = @($currentScanLines) } [PSCustomObject]@{ Lines = $filteredLines TimestampsParseable = $timestampsParseable } } |