Private/Test-WUServices.ps1
function Test-WUServices { <# .SYNOPSIS Tests the status and configuration of Windows Update related services. .DESCRIPTION Comprehensive analysis of Windows Update services including status, startup type, dependencies, and configuration. Identifies services that may be preventing Windows Update from functioning correctly. .PARAMETER LogPath Path to the log file for detailed logging. .EXAMPLE $serviceResults = Test-WUServices -LogPath "C:\Logs\wu.log" .NOTES This is a private function used internally by the WindowsUpdateTools module. Returns detailed information about Windows Update service health. #> [CmdletBinding()] param( [string]$LogPath ) Write-WULog -Message "Analyzing Windows Update services" -LogPath $LogPath # Initialize results object $results = [PSCustomObject]@{ Services = @() CriticalServicesDown = 0 NonCriticalServicesDown = 0 ServicesWithIssues = 0 OverallHealthy = $true Issues = @() } try { # Define Windows Update related services with their criticality $wuServices = @( @{ Name = 'wuauserv'; DisplayName = 'Windows Update'; Critical = $true; ExpectedStatus = 'Manual'; Description = 'Enables the detection, download, and installation of updates' }, @{ Name = 'BITS'; DisplayName = 'Background Intelligent Transfer Service'; Critical = $true; ExpectedStatus = 'Manual'; Description = 'Transfers files in the background using idle network bandwidth' }, @{ Name = 'cryptsvc'; DisplayName = 'Cryptographic Services'; Critical = $true; ExpectedStatus = 'Automatic'; Description = 'Provides cryptographic services including certificate validation' }, @{ Name = 'msiserver'; DisplayName = 'Windows Installer'; Critical = $false; ExpectedStatus = 'Manual'; Description = 'Installs, modifies, and removes applications' }, @{ Name = 'TrustedInstaller'; DisplayName = 'Windows Modules Installer'; Critical = $false; ExpectedStatus = 'Manual'; Description = 'Enables installation, modification, and removal of Windows updates and optional components' }, @{ Name = 'Themes'; DisplayName = 'Themes'; Critical = $false; ExpectedStatus = 'Automatic'; Description = 'Provides theme management (required for some updates)' }, @{ Name = 'AppIDSvc'; DisplayName = 'Application Identity'; Critical = $false; ExpectedStatus = 'Manual'; Description = 'Determines and verifies the identity of an application' } ) foreach ($serviceInfo in $wuServices) { Write-WULog -Message "Checking service: $($serviceInfo.DisplayName) ($($serviceInfo.Name))" -LogPath $LogPath $serviceDetail = [PSCustomObject]@{ Name = $serviceInfo.Name DisplayName = $serviceInfo.DisplayName Critical = $serviceInfo.Critical Status = "Unknown" StartType = "Unknown" ExpectedStartType = $serviceInfo.ExpectedStatus CanStart = $false CanStop = $false ProcessId = $null Issues = @() Healthy = $false LastStartTime = $null Dependencies = @() DependentServices = @() } try { # Get service object $service = Get-Service -Name $serviceInfo.Name -ErrorAction Stop $serviceDetail.Status = $service.Status.ToString() $serviceDetail.CanStart = $service.CanStop # If it can stop, it can start $serviceDetail.CanStop = $service.CanStop # Get additional service information from WMI $serviceWmi = Get-CimInstance -ClassName Win32_Service -Filter "Name='$($serviceInfo.Name)'" -ErrorAction SilentlyContinue if ($serviceWmi) { $serviceDetail.StartType = $serviceWmi.StartMode $serviceDetail.ProcessId = $serviceWmi.ProcessId # Get service dependencies if ($serviceWmi.ServicesDependedOn) { $serviceDetail.Dependencies = $serviceWmi.ServicesDependedOn } } # Check if service startup type is appropriate if ($serviceInfo.ExpectedStatus -eq 'Automatic' -and $serviceDetail.StartType -ne 'Auto') { $serviceDetail.Issues += "Service should be set to Automatic but is $($serviceDetail.StartType)" } # For critical services, check if they're in a good state if ($serviceInfo.Critical) { if ($service.Status -eq 'Stopped') { $serviceDetail.Issues += "Critical service is stopped" $results.CriticalServicesDown++ $results.OverallHealthy = $false } elseif ($service.Status -eq 'StartPending') { $serviceDetail.Issues += "Critical service is stuck in StartPending state" $results.CriticalServicesDown++ $results.OverallHealthy = $false } elseif ($service.Status -eq 'StopPending') { $serviceDetail.Issues += "Critical service is stuck in StopPending state" $results.CriticalServicesDown++ $results.OverallHealthy = $false } } else { # Non-critical services if ($service.Status -ne 'Running' -and $service.Status -ne 'Stopped') { $serviceDetail.Issues += "Non-critical service in problematic state: $($service.Status)" $results.NonCriticalServicesDown++ } } # Check service dependencies for critical services if ($serviceInfo.Critical -and $serviceDetail.Dependencies.Count -gt 0) { foreach ($dependency in $serviceDetail.Dependencies) { try { $depService = Get-Service -Name $dependency -ErrorAction SilentlyContinue if ($depService -and $depService.Status -ne 'Running') { $serviceDetail.Issues += "Dependency service '$dependency' is not running" $results.OverallHealthy = $false } } catch { $serviceDetail.Issues += "Could not check dependency service '$dependency'" } } } # Determine overall service health if ($serviceDetail.Issues.Count -eq 0) { $serviceDetail.Healthy = $true } else { $results.ServicesWithIssues++ } # Special checks for specific services switch ($serviceInfo.Name) { 'wuauserv' { # Windows Update service specific checks try { # Check if Windows Update is disabled via registry $wuDisabled = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -ErrorAction SilentlyContinue if ($wuDisabled -and $wuDisabled.NoAutoUpdate -eq 1) { $serviceDetail.Issues += "Windows Update is disabled via Group Policy" $results.OverallHealthy = $false } } catch { # Registry check failed, but not critical } } 'BITS' { # BITS specific checks try { # Check BITS job queue $bitsJobs = Get-BitsTransfer -AllUsers -ErrorAction SilentlyContinue | Where-Object { $_.JobState -eq 'Error' } if ($bitsJobs) { $serviceDetail.Issues += "Found $($bitsJobs.Count) BITS jobs in error state" } } catch { # BITS check failed, but not critical for service assessment } } 'cryptsvc' { # Cryptographic Services specific checks try { # Check if certificate store is accessible $certStore = Get-ChildItem Cert:\LocalMachine\Root -ErrorAction SilentlyContinue if (-not $certStore) { $serviceDetail.Issues += "Certificate store may not be accessible" } } catch { $serviceDetail.Issues += "Could not verify certificate store accessibility" } } } Write-WULog -Message " Status: $($serviceDetail.Status), StartType: $($serviceDetail.StartType), Issues: $($serviceDetail.Issues.Count)" -LogPath $LogPath } catch { $serviceDetail.Issues += "Could not retrieve service information: $($_.Exception.Message)" $serviceDetail.Status = "NotFound" $results.ServicesWithIssues++ $results.OverallHealthy = $false Write-WULog -Message " ERROR: Could not access service $($serviceInfo.Name): $($_.Exception.Message)" -Level Error -LogPath $LogPath } $results.Services += $serviceDetail } # Additional system-level checks Write-WULog -Message "Performing additional Windows Update system checks..." -LogPath $LogPath # Check if Windows Update service is disabled at the system level try { $wuServiceConfig = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv" -ErrorAction SilentlyContinue if ($wuServiceConfig -and $wuServiceConfig.Start -eq 4) { # 4 = Disabled $results.Issues += "Windows Update service is disabled at the system level" $results.OverallHealthy = $false Write-WULog -Message " Windows Update service is disabled at system level" -Level Warning -LogPath $LogPath } } catch { Write-WULog -Message " Could not check Windows Update service registry configuration" -Level Warning -LogPath $LogPath } # Check for Windows Update Medic Service (Windows 10 1703+) try { $medicService = Get-Service -Name "WaaSMedicSvc" -ErrorAction SilentlyContinue if ($medicService) { Write-WULog -Message " Windows Update Medic Service found: $($medicService.Status)" -LogPath $LogPath if ($medicService.Status -ne 'Running' -and $medicService.Status -ne 'Stopped') { $results.Issues += "Windows Update Medic Service in problematic state: $($medicService.Status)" } } } catch { # Medic service not available or accessible } # Summary logging Write-WULog -Message "Service analysis complete:" -LogPath $LogPath Write-WULog -Message " Total services checked: $($results.Services.Count)" -LogPath $LogPath Write-WULog -Message " Critical services down: $($results.CriticalServicesDown)" -LogPath $LogPath Write-WULog -Message " Non-critical services down: $($results.NonCriticalServicesDown)" -LogPath $LogPath Write-WULog -Message " Services with issues: $($results.ServicesWithIssues)" -LogPath $LogPath Write-WULog -Message " Overall healthy: $($results.OverallHealthy)" -LogPath $LogPath if ($results.Issues.Count -gt 0) { Write-WULog -Message "System-level issues found:" -LogPath $LogPath foreach ($issue in $results.Issues) { Write-WULog -Message " - $issue" -Level Warning -LogPath $LogPath } } } catch { Write-WULog -Message "Critical error during service analysis: $($_.Exception.Message)" -Level Error -LogPath $LogPath $results.OverallHealthy = $false $results.Issues += "Critical error during service analysis" } return $results } |