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
}