Private/Get-WUSystemHealth.ps1
function Get-WUSystemHealth { <# .SYNOPSIS Performs comprehensive Windows Update system health assessment. .DESCRIPTION Comprehensive health check based on the original ResolveUpdateFailures.ps1 logic. Checks services, pending updates, event logs, component store, and configuration to determine if remediation is needed when no specific issues are detected. .PARAMETER LogPath Path to the log file for detailed logging. .EXAMPLE $health = Get-WUSystemHealth -LogPath "C:\Logs\wu.log" .NOTES This is a private function used internally by the WindowsUpdateTools module. Ported from the original Test-SystemHealthIndicators function. Returns comprehensive health status and specific issue indicators. #> [CmdletBinding()] param( [string]$LogPath ) Write-WULog -Message "Performing comprehensive Windows Update system health assessment" -LogPath $LogPath # Initialize health result $healthResult = [PSCustomObject]@{ Healthy = $true ComponentStoreCorruption = $false ServiceIssues = $false DiskSpaceIssues = $false RecentErrors = $false ConfigurationIssues = $false PendingUpdatesIssues = $false Issues = @() Summary = "" } try { # Check Windows Update service status (from original script logic) Write-WULog -Message "Checking Windows Update service status..." -LogPath $LogPath try { $wuService = Get-Service -Name "wuauserv" -ErrorAction Stop if ($wuService.Status -ne "Running") { $healthResult.ServiceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "Windows Update service not running ($($wuService.Status))" } } catch { $healthResult.ServiceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "Windows Update service not found or accessible" } # Check BITS service status try { $bitsService = Get-Service -Name "BITS" -ErrorAction Stop if ($bitsService.Status -ne "Running") { $healthResult.ServiceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "BITS service not running ($($bitsService.Status))" } } catch { $healthResult.ServiceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "BITS service not found or accessible" } # Check cryptographic services try { $cryptSvc = Get-Service -Name "cryptsvc" -ErrorAction Stop if ($cryptSvc.Status -ne "Running") { $healthResult.ServiceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "Cryptographic Services not running" } } catch { $healthResult.ServiceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "Cryptographic Services not accessible" } # Check for pending updates (from original script) Write-WULog -Message "Checking for pending updates..." -LogPath $LogPath try { $updateSession = New-Object -ComObject Microsoft.Update.Session $updateSearcher = $updateSession.CreateUpdateSearcher() $searchResult = $updateSearcher.Search("IsInstalled=0") if ($searchResult.Updates.Count -gt 0) { $healthResult.Issues += "Found $($searchResult.Updates.Count) pending updates" Write-WULog -Message "Found $($searchResult.Updates.Count) pending updates" -LogPath $LogPath # Check for very old pending updates (might indicate stuck updates) $oldUpdates = 0 foreach ($update in $searchResult.Updates) { if ($update.LastDeploymentChangeTime -and (Get-Date) - $update.LastDeploymentChangeTime -gt [TimeSpan]::FromDays(30)) { $oldUpdates++ } } if ($oldUpdates -gt 0) { $healthResult.PendingUpdatesIssues = $true $healthResult.Issues += "$oldUpdates pending updates are over 30 days old" } } } catch { $healthResult.Issues += "Could not check for pending updates" Write-WULog -Message "Could not check for pending updates: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } # Check for recent Windows Update errors (from original logic) Write-WULog -Message "Checking for recent Windows Update errors..." -LogPath $LogPath try { $recentErrors = Get-WinEvent -FilterHashtable @{ LogName = @('System', 'Application', 'Microsoft-Windows-WindowsUpdateClient/Operational') Level = 1..2 # Critical and Error only StartTime = (Get-Date).AddDays(-3) } -MaxEvents 50 -ErrorAction SilentlyContinue | Where-Object { $_.LevelDisplayName -eq "Error" -and ($_.Message -like "*update*" -or $_.Message -like "*0x8*" -or $_.ProviderName -like "*Update*") } if ($recentErrors) { $healthResult.RecentErrors = $true $healthResult.Issues += "Found $($recentErrors.Count) recent update-related errors in event logs" if ($recentErrors.Count -gt 10) { $healthResult.Healthy = $false } } } catch { Write-WULog -Message "Could not check recent update errors: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } # Check component store health (enhanced from original) Write-WULog -Message "Checking component store health..." -LogPath $LogPath try { $dismOutput = & dism.exe /online /cleanup-image /checkhealth 2>&1 $dismExitCode = $LASTEXITCODE if ($dismExitCode -ne 0) { $healthResult.ComponentStoreCorruption = $true $healthResult.Healthy = $false $healthResult.Issues += "Component store health check failed (Exit code: $dismExitCode)" } elseif ($dismOutput -like "*Component Store is repairable*") { $healthResult.ComponentStoreCorruption = $true $healthResult.Healthy = $false $healthResult.Issues += "Component store corruption detected" } } catch { Write-WULog -Message "Could not check component store health: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } # Check disk space (from original script logic) Write-WULog -Message "Checking system disk space..." -LogPath $LogPath try { $systemDrive = Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive } $freeSpaceGB = [math]::Round($systemDrive.FreeSpace / 1GB, 2) if ($freeSpaceGB -lt 5) { $healthResult.DiskSpaceIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "Critical disk space - only $freeSpaceGB GB free" } elseif ($freeSpaceGB -lt 10) { $healthResult.DiskSpaceIssues = $true $healthResult.Issues += "Low disk space - $freeSpaceGB GB free" } } catch { Write-WULog -Message "Could not check disk space: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } # Check Windows Update configuration (from original) Write-WULog -Message "Checking Windows Update configuration..." -LogPath $LogPath try { # Check if Windows Update service is disabled $wuServiceConfig = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv" -ErrorAction SilentlyContinue if ($wuServiceConfig -and $wuServiceConfig.Start -eq 4) { # 4 = Disabled $healthResult.ConfigurationIssues = $true $healthResult.Healthy = $false $healthResult.Issues += "Windows Update service is disabled at the system level" } # Check if automatic updates are disabled via policy $auDisabled = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -ErrorAction SilentlyContinue if ($auDisabled -and $auDisabled.NoAutoUpdate -eq 1) { $healthResult.ConfigurationIssues = $true $healthResult.Issues += "Automatic updates are disabled via Group Policy" } } catch { Write-WULog -Message "Could not check Windows Update configuration: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } # Check for pending reboot (from original script logic) try { $pendingReboot = $false # Windows Update pending reboot if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { $pendingReboot = $true } # Component Based Servicing pending reboot if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { $pendingReboot = $true } # Pending file rename operations $pendingFileRenames = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue if ($pendingFileRenames) { $pendingReboot = $true } if ($pendingReboot) { $healthResult.Issues += "Pending reboot detected - may affect Windows Update operations" Write-WULog -Message "Pending reboot detected" -Level Warning -LogPath $LogPath } } catch { Write-WULog -Message "Could not check pending reboot status: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } # Generate summary (simple, no scoring nonsense) if ($healthResult.Healthy) { $healthResult.Summary = "System is healthy for Windows Update operations" Write-WULog -Message "System health assessment: HEALTHY" -LogPath $LogPath } else { $issueCount = $healthResult.Issues.Count $healthResult.Summary = "System health issues detected ($issueCount issues found)" Write-WULog -Message "System health assessment: ISSUES DETECTED ($issueCount issues)" -Level Warning -LogPath $LogPath } # Log specific issue areas (from original categorization) $issueAreas = @() if ($healthResult.ServiceIssues) { $issueAreas += "Services" } if ($healthResult.ComponentStoreCorruption) { $issueAreas += "Component Store" } if ($healthResult.DiskSpaceIssues) { $issueAreas += "Disk Space" } if ($healthResult.RecentErrors) { $issueAreas += "Recent Errors" } if ($healthResult.ConfigurationIssues) { $issueAreas += "Configuration" } if ($healthResult.PendingUpdatesIssues) { $issueAreas += "Pending Updates" } if ($issueAreas.Count -gt 0) { Write-WULog -Message "Issue areas identified: $($issueAreas -join ', ')" -LogPath $LogPath } if ($healthResult.Issues.Count -gt 0) { Write-WULog -Message "Specific issues found:" -LogPath $LogPath foreach ($issue in $healthResult.Issues) { Write-WULog -Message " - $issue" -LogPath $LogPath } } } catch { $healthResult.Healthy = $false $healthResult.Issues += "Error during health assessment: $($_.Exception.Message)" $healthResult.Summary = "Health assessment failed" Write-WULog -Message "Error during system health assessment: $($_.Exception.Message)" -Level Error -LogPath $LogPath } return $healthResult } return $healthResult |