Public/Test-WindowsUpdateHealth.ps1
function Test-WindowsUpdateHealth { <# .SYNOPSIS Performs comprehensive Windows Update health assessment without making changes. .DESCRIPTION Test-WindowsUpdateHealth conducts a thorough analysis of Windows Update components, services, configuration, and recent activity to identify potential issues. This is a detection-only cmdlet that does not perform any remediation actions. .PARAMETER OutputPath Specifies the directory path where log files will be created. Default: C:\Windows\Temp .PARAMETER SkipSetupDiag Skip SetupDiag execution. Useful for systems without recent upgrade attempts or when .NET Framework 4.7.2+ is not available. .PARAMETER SkipTroubleshooter Skip Windows Update troubleshooter execution for faster assessment. .PARAMETER DetailedEventLogs Analyze extended event log history (30 days instead of 7 days). May take longer but provides more comprehensive failure analysis. .PARAMETER IncludeWindows11Compatibility Perform comprehensive Windows 11 compatibility assessment including CPU, TPM, Secure Boot, UEFI, memory, storage, and other requirements. Also checks for Microsoft-imposed compatibility holds. .PARAMETER PassThru Return detailed results as PowerShell objects for further analysis. When not specified, only writes to host and log file. .PARAMETER LogPath Specifies a custom path for the log file. If not provided, automatically generates a timestamped log file in OutputPath. .EXAMPLE Test-WindowsUpdateHealth Performs basic Windows Update health assessment with default settings. .EXAMPLE Test-WindowsUpdateHealth -DetailedEventLogs -PassThru Performs detailed assessment with extended event log analysis and returns structured results for further processing. .EXAMPLE $results = Test-WindowsUpdateHealth -SkipSetupDiag -PassThru if ($results.IssuesFound) { Write-Warning "Windows Update issues detected" } Performs assessment without SetupDiag and checks if any issues were found. .EXAMPLE Test-WindowsUpdateHealth -OutputPath "C:\Logs" -Verbose Performs assessment with verbose logging to custom directory. .EXAMPLE Test-WindowsUpdateHealth -IncludeWindows11Compatibility -PassThru Performs health assessment with Windows 11 compatibility check and returns structured results including upgrade readiness. .EXAMPLE $results = Test-WindowsUpdateHealth -IncludeWindows11Compatibility -DetailedEventLogs -PassThru if ($results.Windows11Compatibility -and -not $results.Windows11Compatibility.Compatible) { Write-Warning "System not ready for Windows 11: $($results.Windows11Compatibility.OverallAssessment)" } Performs comprehensive assessment including Windows 11 compatibility and checks upgrade readiness. .OUTPUTS When -PassThru is specified, returns a PSCustomObject with: - IssuesFound: Boolean indicating if any issues were detected - CriticalIssues: Count of critical issues found - Warnings: Count of warning-level issues - Issues: Array of detailed issue objects - SystemInfo: Basic system information - LastSuccessfulUpdate: Date of last successful update - Services: Windows Update service status - Configuration: Update configuration details .NOTES Name: Test-WindowsUpdateHealth Author: Anthony Balloi - CSOLVE Version: 1.0.0 Requires Administrator privileges for complete analysis. Some features require .NET Framework 4.7.2+ for SetupDiag functionality. #> [CmdletBinding()] param( [Parameter(Position = 0)] [ValidateScript({ Test-Path $_ -PathType Container })] [string]$OutputPath = $script:DefaultOutputPath, [switch]$SkipSetupDiag, [switch]$SkipTroubleshooter, [switch]$DetailedEventLogs, [switch]$IncludeWindows11Compatibility, [switch]$PassThru, [string]$LogPath ) begin { # Initialize logging if (-not $LogPath) { $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' $LogPath = Join-Path $OutputPath "WU-HealthCheck-$timestamp.log" } # Initialize the assessment results object $results = [PSCustomObject]@{ IssuesFound = $false CriticalIssues = 0 Warnings = 0 Issues = @() SystemInfo = $null LastSuccessfulUpdate = $null Services = @() Configuration = $null SetupDiagResults = $null EventLogSummary = $null ComponentStoreHealth = $null Windows11Compatibility = $null TroubleshooterResults = $null AssessmentDate = Get-Date LogPath = $LogPath } # Start logging Write-WULog -Message "=== Windows Update Health Assessment Started ===" -LogPath $LogPath Write-WULog -Message "Version: $script:ModuleVersion" -LogPath $LogPath Write-WULog -Message "Parameters: OutputPath=$OutputPath, SkipSetupDiag=$SkipSetupDiag, SkipTroubleshooter=$SkipTroubleshooter, DetailedEventLogs=$DetailedEventLogs" -LogPath $LogPath $assessmentErrors = 0 } process { try { # System Information Assessment Write-Progress -Activity "Windows Update Health Assessment" -Status "Gathering system information..." -PercentComplete 10 Write-Verbose "Collecting system information" try { $results.SystemInfo = Get-WUSystemInfo -LogPath $LogPath Write-WULog -Message "System information collected successfully" -LogPath $LogPath } catch { Write-WULog -Message "Failed to gather system information: $($_.Exception.Message)" -Level Error -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "SystemInfo" -Severity "Warning" -Description "Could not gather complete system information" $results.Warnings++ $assessmentErrors++ } # Windows Update Services Assessment Write-Progress -Activity "Windows Update Health Assessment" -Status "Checking Windows Update services..." -PercentComplete 20 Write-Verbose "Analyzing Windows Update services" try { $serviceResults = Test-WUServices -LogPath $LogPath $results.Services = $serviceResults.Services if ($serviceResults.CriticalServicesDown -gt 0) { $unhealthyServices = $serviceResults.Services | Where-Object { $_.Critical -and -not $_.Healthy } $results.Issues += New-WUHealthIssue -Type "Services" -Severity "Critical" -Description "$($serviceResults.CriticalServicesDown) critical Windows Update services not running" -Details @{ UnhealthyServices = $unhealthyServices UnhealthyServiceCount = $serviceResults.CriticalServicesDown TotalServicesChecked = $serviceResults.Services.Count } $results.CriticalIssues++ $results.IssuesFound = $true } if ($serviceResults.NonCriticalServicesDown -gt 0) { $results.Issues += New-WUHealthIssue -Type "Services" -Severity "Warning" -Description "$($serviceResults.NonCriticalServicesDown) non-critical Windows Update services not running" $results.Warnings++ $results.IssuesFound = $true } } catch { Write-WULog -Message "Failed to check Windows Update services: $($_.Exception.Message)" -Level Error -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "Services" -Severity "Critical" -Description "Could not assess Windows Update services" $results.CriticalIssues++ $results.IssuesFound = $true $assessmentErrors++ } # Pending Updates Check Write-Progress -Activity "Windows Update Health Assessment" -Status "Checking pending updates..." -PercentComplete 30 Write-Verbose "Checking for pending updates" try { $pendingUpdates = Get-WUPendingUpdates -LogPath $LogPath if ($pendingUpdates.Count -gt 0) { Write-WULog -Message "Found $($pendingUpdates.Count) pending updates" -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "PendingUpdates" -Severity "Info" -Description "$($pendingUpdates.Count) pending updates found" -Details @{ PendingUpdates = $pendingUpdates TotalCount = $pendingUpdates.Count ImportantUpdates = @($pendingUpdates | Where-Object { $_.Importance -eq "Important" -or $_.Importance -eq "Critical" }) UpdateSizes = @($pendingUpdates | ForEach-Object { [PSCustomObject]@{ Title = $_.Title; SizeMB = [math]::Round($_.MaxDownloadSize / 1MB, 2) } }) } } } catch { Write-WULog -Message "Failed to check pending updates: $($_.Exception.Message)" -Level Warning -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "PendingUpdates" -Severity "Warning" -Description "Could not check for pending updates" $results.Warnings++ $assessmentErrors++ } # Update History Analysis Write-Progress -Activity "Windows Update Health Assessment" -Status "Analyzing update history..." -PercentComplete 40 Write-Verbose "Analyzing recent update history" try { $historyDays = if ($DetailedEventLogs) { 30 } else { 7 } $historyResults = Get-WUUpdateHistory -Days $historyDays -LogPath $LogPath $results.LastSuccessfulUpdate = $historyResults.LastSuccessfulUpdate if ($historyResults.FailedUpdates -gt 0) { $results.Issues += New-WUHealthIssue -Type "UpdateHistory" -Severity "Warning" -Description "$($historyResults.FailedUpdates) failed updates in recent history" -Details @{ FailedUpdates = $historyResults.FailedUpdates SuccessfulUpdates = $historyResults.SuccessfulUpdates TotalUpdates = $historyResults.TotalUpdates SuccessRate = $historyResults.SuccessRate CommonErrors = $historyResults.CommonErrors FailedUpdateDetails = $historyResults.FailedUpdateDetails LastFailedUpdate = $historyResults.LastFailedUpdate } $results.Warnings++ $results.IssuesFound = $true } if ($historyResults.DaysSinceLastSuccess -gt 30) { $results.Issues += New-WUHealthIssue -Type "UpdateHistory" -Severity "Critical" -Description "No successful updates in over 30 days" $results.CriticalIssues++ $results.IssuesFound = $true } } catch { $errorMessage = $_.Exception.Message Write-WULog -Message "Failed to analyze update history: $errorMessage" -Level Warning -LogPath $LogPath # Check for specific error types if ($errorMessage -like "*COM*" -or $errorMessage -like "*0x*") { Write-WULog -Message "This may be a COM object or Windows Update service issue. Try restarting Windows Update services." -Level Warning -LogPath $LogPath } $results.Issues += New-WUHealthIssue -Type "UpdateHistory" -Severity "Warning" -Description "Could not analyze update history" $results.Warnings++ $assessmentErrors++ } # Event Log Analysis Write-Progress -Activity "Windows Update Health Assessment" -Status "Analyzing event logs..." -PercentComplete 50 Write-Verbose "Analyzing Windows Update event logs" try { $eventLogDays = if ($DetailedEventLogs) { 30 } else { 7 } $eventLogResults = Get-WUEventLogs -Days $eventLogDays -LogPath $LogPath $results.EventLogSummary = $eventLogResults if ($eventLogResults.CriticalErrors -gt 0) { $results.Issues += New-WUHealthIssue -Type "EventLogs" -Severity "Critical" -Description "$($eventLogResults.CriticalErrors) critical errors in event logs" -Details @{ SignificantErrorCodes = $eventLogResults.SignificantErrorCodes CommonFailures = $eventLogResults.CommonFailures CriticalErrors = $eventLogResults.CriticalErrors ErrorEvents = $eventLogResults.ErrorEvents TotalEvents = $eventLogResults.TotalEvents UpdateAttempts = $eventLogResults.UpdateAttempts FailedUpdates = $eventLogResults.FailedUpdates LastFailedUpdate = $eventLogResults.LastFailedUpdate AnalysisPeriod = $eventLogResults.AnalysisPeriod } $results.CriticalIssues++ $results.IssuesFound = $true } if ($eventLogResults.ErrorEvents -gt 5) { $results.Issues += New-WUHealthIssue -Type "EventLogs" -Severity "Warning" -Description "High number of error events ($($eventLogResults.ErrorEvents)) in recent logs" -Details @{ SignificantErrorCodes = $eventLogResults.SignificantErrorCodes CommonFailures = $eventLogResults.CommonFailures TotalEvents = $eventLogResults.TotalEvents ErrorEvents = $eventLogResults.ErrorEvents WarningEvents = $eventLogResults.WarningEvents UpdateAttempts = $eventLogResults.UpdateAttempts FailedUpdates = $eventLogResults.FailedUpdates SuccessfulUpdates = $eventLogResults.SuccessfulUpdates LastFailedUpdate = $eventLogResults.LastFailedUpdate AnalysisPeriod = $eventLogResults.AnalysisPeriod } $results.Warnings++ $results.IssuesFound = $true } } catch { $errorMessage = $_.Exception.Message Write-WULog -Message "Failed to analyze event logs: $errorMessage" -Level Warning -LogPath $LogPath # Check for specific error types if ($errorMessage -like "*Access*denied*" -or $errorMessage -like "*permission*") { Write-WULog -Message "This may be a permissions issue. Try running as Administrator." -Level Warning -LogPath $LogPath } elseif ($errorMessage -like "*event log*" -or $errorMessage -like "*log file*") { Write-WULog -Message "Event log service may not be running or logs may be corrupted." -Level Warning -LogPath $LogPath } $results.Issues += New-WUHealthIssue -Type "EventLogs" -Severity "Warning" -Description "Could not analyze event logs" $results.Warnings++ $assessmentErrors++ } # Component Store Health Check Write-Progress -Activity "Windows Update Health Assessment" -Status "Checking component store health..." -PercentComplete 60 Write-Verbose "Checking component store health" try { $componentResults = Test-WUComponentStore -LogPath $LogPath $results.ComponentStoreHealth = $componentResults if ($componentResults.CorruptionDetected) { $results.Issues += New-WUHealthIssue -Type "ComponentStore" -Severity "Critical" -Description "Component store corruption detected" -Details @{ CorruptionDetected = $componentResults.CorruptionDetected HealthCheckPassed = $componentResults.HealthCheckPassed ScanHealthPassed = $componentResults.ScanHealthPassed CBSLogIssues = $componentResults.CBSLogIssues Issues = $componentResults.Issues CheckHealthExitCode = $componentResults.CheckHealthExitCode ScanHealthExitCode = $componentResults.ScanHealthExitCode Remediation = "Run 'DISM /Online /Cleanup-Image /RestoreHealth' to repair component store corruption" } $results.CriticalIssues++ $results.IssuesFound = $true } } catch { Write-WULog -Message "Failed to check component store health: $($_.Exception.Message)" -Level Warning -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "ComponentStore" -Severity "Warning" -Description "Could not check component store health" $results.Warnings++ $assessmentErrors++ } # Windows 11 Compatibility Assessment (if requested) if ($IncludeWindows11Compatibility) { Write-Progress -Activity "Windows Update Health Assessment" -Status "Checking Windows 11 compatibility..." -PercentComplete 65 Write-Verbose "Assessing Windows 11 upgrade compatibility" try { $win11CompatResults = Test-WUWindows11Compatibility -LogPath $LogPath $results.Windows11Compatibility = $win11CompatResults if (-not $win11CompatResults.Compatible) { $results.Issues += New-WUHealthIssue -Type "Windows11Compatibility" -Severity "Warning" -Description "System not compatible with Windows 11" -Details @{ OverallAssessment = $win11CompatResults.OverallAssessment FailedChecks = $win11CompatResults.FailedChecks CompatibilityHolds = $win11CompatResults.CompatibilityHolds RequirementFailures = $win11CompatResults.Issues } $results.Warnings++ $results.IssuesFound = $true # Log specific failures Write-WULog -Message "Windows 11 compatibility issues detected:" -Level Warning -LogPath $LogPath foreach ($issue in $win11CompatResults.Issues) { Write-WULog -Message " - $issue" -Level Warning -LogPath $LogPath } } elseif ($win11CompatResults.WarningChecks.Count -gt 0) { $results.Issues += New-WUHealthIssue -Type "Windows11Compatibility" -Severity "Info" -Description "System compatible with Windows 11 with warnings" -Details @{ OverallAssessment = $win11CompatResults.OverallAssessment WarningChecks = $win11CompatResults.WarningChecks Recommendations = $win11CompatResults.Issues | Where-Object { $_ -like "Warning:*" } } Write-WULog -Message "Windows 11 compatibility: Compatible with warnings" -LogPath $LogPath foreach ($warning in $win11CompatResults.Issues | Where-Object { $_ -like "Warning:*" }) { Write-WULog -Message " $warning" -Level Warning -LogPath $LogPath } } else { Write-WULog -Message "Windows 11 compatibility: Fully compatible" -LogPath $LogPath } } catch { Write-WULog -Message "Failed to assess Windows 11 compatibility: $($_.Exception.Message)" -Level Warning -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "Windows11Compatibility" -Severity "Warning" -Description "Could not assess Windows 11 compatibility" $results.Warnings++ $assessmentErrors++ } } # WSUS Configuration Check Write-Progress -Activity "Windows Update Health Assessment" -Status "Checking update configuration..." -PercentComplete 70 Write-Verbose "Analyzing Windows Update configuration" try { $configResults = Get-WUConfiguration -LogPath $LogPath $results.Configuration = $configResults if ($configResults.Issues.Count -gt 0) { foreach ($issue in $configResults.Issues) { $results.Issues += $issue if ($issue.Severity -eq "Critical") { $results.CriticalIssues++ } else { $results.Warnings++ } $results.IssuesFound = $true } } } catch { Write-WULog -Message "Failed to check update configuration: $($_.Exception.Message)" -Level Warning -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "Configuration" -Severity "Warning" -Description "Could not analyze update configuration" $results.Warnings++ $assessmentErrors++ } # SetupDiag Analysis (if not skipped) if (-not $SkipSetupDiag) { Write-Progress -Activity "Windows Update Health Assessment" -Status "Running SetupDiag analysis..." -PercentComplete 80 Write-Verbose "Executing SetupDiag analysis" try { $setupDiagResults = Invoke-WUSetupDiag -OutputPath $OutputPath -LogPath $LogPath $results.SetupDiagResults = $setupDiagResults if ($setupDiagResults.FailuresDetected -gt 0) { $results.Issues += New-WUHealthIssue -Type "SetupDiag" -Severity "Critical" -Description "$($setupDiagResults.FailuresDetected) upgrade failures detected by SetupDiag" -Details @{ FailureReasons = $setupDiagResults.FailureReasons FailuresDetected = $setupDiagResults.FailuresDetected UpgradeAttemptDetected = $setupDiagResults.UpgradeAttemptDetected ExecutionSuccessful = $setupDiagResults.ExecutionSuccessful SetupDiagVersion = $setupDiagResults.SetupDiagVersion OutputFile = $setupDiagResults.OutputFile LogAnalysis = $setupDiagResults.LogAnalysis SystemInfo = $setupDiagResults.SystemInfo } $results.CriticalIssues++ $results.IssuesFound = $true } } catch { Write-WULog -Message "SetupDiag analysis failed: $($_.Exception.Message)" -Level Warning -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "SetupDiag" -Severity "Warning" -Description "SetupDiag analysis could not be completed" $results.Warnings++ $assessmentErrors++ } } # Windows Update Troubleshooter (if not skipped) if (-not $SkipTroubleshooter) { Write-Progress -Activity "Windows Update Health Assessment" -Status "Running Windows Update troubleshooter..." -PercentComplete 90 Write-Verbose "Executing Windows Update troubleshooter" try { $troubleshooterResults = Invoke-WUTroubleshooter -LogPath $LogPath $results.TroubleshooterResults = $troubleshooterResults if ($troubleshooterResults.IssuesDetected -gt 0) { $resolvedCount = $troubleshooterResults.IssuesResolved $unresolvedCount = $troubleshooterResults.IssuesDetected - $resolvedCount if ($unresolvedCount -gt 0) { $results.Issues += New-WUHealthIssue -Type "Troubleshooter" -Severity "Warning" -Description "$unresolvedCount unresolved issues detected by troubleshooter" $results.Warnings++ $results.IssuesFound = $true } } } catch { Write-WULog -Message "Windows Update troubleshooter failed: $($_.Exception.Message)" -Level Warning -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "Troubleshooter" -Severity "Warning" -Description "Windows Update troubleshooter could not run" $results.Warnings++ $assessmentErrors++ } } # Ensure IssuesFound flag is set correctly if ($results.CriticalIssues -gt 0 -or $results.Warnings -gt 0) { $results.IssuesFound = $true } } catch { Write-WULog -Message "Critical error during health assessment: $($_.Exception.Message)" -Level Error -LogPath $LogPath $results.Issues += New-WUHealthIssue -Type "Assessment" -Severity "Critical" -Description "Critical error during health assessment" $results.CriticalIssues++ $results.IssuesFound = $true $assessmentErrors++ } finally { Write-Progress -Activity "Windows Update Health Assessment" -Completed } } end { # Generate summary Write-WULog -Message "=== ASSESSMENT SUMMARY ===" -LogPath $LogPath Write-WULog -Message "Issues Found: $($results.IssuesFound)" -LogPath $LogPath Write-WULog -Message "Critical Issues: $($results.CriticalIssues)" -LogPath $LogPath Write-WULog -Message "Warnings: $($results.Warnings)" -LogPath $LogPath Write-WULog -Message "Assessment Errors: $assessmentErrors" -LogPath $LogPath if ($results.Issues.Count -gt 0) { Write-WULog -Message "ISSUES DETECTED:" -LogPath $LogPath foreach ($issue in $results.Issues) { # Map Critical severity to Error for logging compatibility and ensure valid level $logLevel = switch ($issue.Severity) { "Critical" { "Error" } "Warning" { "Warning" } "Error" { "Error" } "Info" { "Info" } default { "Info" } } Write-WULog -Message " $($issue.Type): $($issue.Description)" -Level $logLevel -LogPath $LogPath } } else { Write-WULog -Message "No significant issues detected" -LogPath $LogPath } Write-WULog -Message "Log file saved to: $LogPath" -LogPath $LogPath Write-WULog -Message "=== Windows Update Assessment Completed ===" -LogPath $LogPath # Display summary to host Write-Host "`n=== Windows Update Assessment Results ===" -ForegroundColor Cyan if ($results.CriticalIssues -gt 0) { Write-Host "Critical Issues: $($results.CriticalIssues)" -ForegroundColor Red } if ($results.Warnings -gt 0) { Write-Host "Warnings: $($results.Warnings)" -ForegroundColor Yellow } if (-not $results.IssuesFound) { Write-Host "No significant issues detected" -ForegroundColor Green } # Windows 11 Compatibility Summary if ($IncludeWindows11Compatibility -and $results.Windows11Compatibility) { Write-Host "`nWindows 11 Compatibility: " -NoNewline if ($results.Windows11Compatibility.Compatible) { if ($results.Windows11Compatibility.WarningChecks.Count -gt 0) { Write-Host $results.Windows11Compatibility.OverallAssessment -ForegroundColor Yellow } else { Write-Host $results.Windows11Compatibility.OverallAssessment -ForegroundColor Green } } else { Write-Host $results.Windows11Compatibility.OverallAssessment -ForegroundColor Red if ($results.Windows11Compatibility.FailedChecks.Count -gt 0) { Write-Host "Failed requirements: $($results.Windows11Compatibility.FailedChecks -join ', ')" -ForegroundColor Red } } } Write-Host "Log file: $LogPath" -ForegroundColor Gray # Return results if PassThru requested if ($PassThru) { return $results } # Set exit code based on results if ($assessmentErrors -gt 5) { $global:LASTEXITCODE = $script:ExitCodes.MajorFailure } elseif ($assessmentErrors -gt 0) { $global:LASTEXITCODE = $script:ExitCodes.PartialFailure } else { $global:LASTEXITCODE = $script:ExitCodes.Success } } } |