Public/healthcheck/Get-WSUSHealth.ps1

#Requires -Version 5.1
function Get-WSUSHealth {
    <#
        .SYNOPSIS
            Retrieves WSUS server health and status information
 
        .DESCRIPTION
            Collects comprehensive health data from Windows Server Update Services (WSUS) servers.
            Checks service status, client statistics, database configuration, and content directory
            disk space to produce an overall health assessment per target machine.
 
        .PARAMETER ComputerName
            One or more computer names to query. Defaults to the local computer.
            Accepts pipeline input by value and by property name.
 
        .PARAMETER Credential
            Optional PSCredential for authenticating to remote computers.
            Not used for local queries.
 
        .EXAMPLE
            Get-WSUSHealth
 
            Checks WSUS health on the local computer.
 
        .EXAMPLE
            Get-WSUSHealth -ComputerName 'WSUS01'
 
            Checks WSUS health on a single remote server.
 
        .EXAMPLE
            'WSUS01', 'WSUS02' | Get-WSUSHealth -Credential (Get-Credential)
 
            Checks WSUS health on multiple remote servers via pipeline with alternate credentials.
 
        .OUTPUTS
            PSWinOps.WSUSHealth
            Returns an object per target with service status, client statistics,
            database configuration, content directory space, and overall health.
 
        .NOTES
            Author: Franck SALLET
            Version: 1.0.0
            Last Modified: 2026-03-26
            Requires: PowerShell 5.1+ / Windows only
            Requires: WSUS role (UpdateServices)
 
        .LINK
            https://github.com/k9fr4n/PSWinOps
 
        .LINK
            https://learn.microsoft.com/en-us/powershell/module/updateservices/
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.WSUSHealth')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"
        $localNames = @($env:COMPUTERNAME, 'localhost', '.')

        $scriptBlock = {
            $serviceStatus = 'NotFound'
            $moduleAvailable = $false
            $wsusServerName = $null
            $wsusPort = 0
            $isSsl = $false
            $dbType = $null
            $totalClients = 0
            $clientsNeedingUpdates = 0
            $clientsWithErrors = 0
            $unapprovedUpdates = 0
            $contentDirPath = $null
            $contentDirFreeSpaceGB = [decimal]0

            try {
                $svc = Get-Service -Name 'WsusService' -ErrorAction Stop
                $serviceStatus = $svc.Status.ToString()
            }
            catch {
                $serviceStatus = 'NotFound'
            }

            $modCheck = Get-Module -Name 'UpdateServices' -ListAvailable -ErrorAction SilentlyContinue
            if ($modCheck) { $moduleAvailable = $true }

            if ($moduleAvailable -and $serviceStatus -eq 'Running') {
                try {
                    $wsusServer = Get-WsusServer
                    $wsusServerName = $wsusServer.ServerName
                    $wsusPort = $wsusServer.PortNumber
                    $isSsl = $wsusServer.UseSecureConnection

                    $wsusStatus = $wsusServer.GetStatus()
                    $totalClients = $wsusStatus.ComputerTargetCount
                    $clientsNeedingUpdates = $wsusStatus.ComputerTargetsNeedingUpdatesCount
                    $clientsWithErrors = $wsusStatus.ComputerTargetsWithUpdateErrorsCount
                    $unapprovedUpdates = $wsusStatus.NotApprovedUpdateCount

                    $dbConfig = $wsusServer.GetDatabaseConfiguration()
                    $dbType = if ($dbConfig.IsUsingWindowsInternalDatabase) { 'WID' } else { 'SQL' }

                    $regPath = 'HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup'
                    $contentDirPath = (Get-ItemProperty -Path $regPath -Name 'ContentDir' -ErrorAction Stop).ContentDir
                    $driveLetter = (Split-Path -Path $contentDirPath -Qualifier).TrimEnd(':')
                    $diskInfo = Get-CimInstance -ClassName 'Win32_LogicalDisk' -Filter "DeviceID='${driveLetter}:'" -ErrorAction Stop
                    $contentDirFreeSpaceGB = [math]::Round($diskInfo.FreeSpace / 1GB, 2)
                }
                catch {
                    Write-Warning -Message "Failed to collect WSUS data: $_"
                }
            }

            @{
                ServiceStatus         = $serviceStatus
                ModuleAvailable       = $moduleAvailable
                WSUSServerName        = $wsusServerName
                WSUSPort              = $wsusPort
                IsSSL                 = $isSsl
                DatabaseType          = $dbType
                TotalClients          = $totalClients
                ClientsNeedingUpdates = $clientsNeedingUpdates
                ClientsWithErrors     = $clientsWithErrors
                UnapprovedUpdates     = $unapprovedUpdates
                ContentDirPath        = $contentDirPath
                ContentDirFreeSpaceGB = $contentDirFreeSpaceGB
            }
        }
    }

    process {
        foreach ($machine in $ComputerName) {
            $displayName = $machine.ToUpper()
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying '${machine}'"

            try {
                $isLocal = $localNames -contains $machine
                if ($isLocal) {
                    $data = & $scriptBlock
                }
                else {
                    $invokeParams = @{
                        ComputerName = $machine
                        ScriptBlock  = $scriptBlock
                        ErrorAction  = 'Stop'
                    }
                    if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
                        $invokeParams['Credential'] = $Credential
                    }
                    $data = Invoke-Command @invokeParams
                }

                if (-not $data.ModuleAvailable) {
                    $healthStatus = 'RoleUnavailable'
                }
                elseif ($data.ServiceStatus -ne 'Running' -or $data.ClientsWithErrors -gt 0 -or $data.ContentDirFreeSpaceGB -lt 5) {
                    $healthStatus = 'Critical'
                }
                elseif (($data.TotalClients -gt 0 -and $data.ClientsNeedingUpdates -gt ($data.TotalClients * 0.3)) -or
                        $data.ContentDirFreeSpaceGB -lt 20 -or $data.UnapprovedUpdates -gt 100) {
                    $healthStatus = 'Degraded'
                }
                else {
                    $healthStatus = 'Healthy'
                }

                [PSCustomObject]@{
                    PSTypeName            = 'PSWinOps.WSUSHealth'
                    ComputerName          = $displayName
                    ServiceName           = 'WsusService'
                    ServiceStatus         = $data.ServiceStatus
                    WSUSServerName        = $data.WSUSServerName
                    WSUSPort              = [int]$data.WSUSPort
                    IsSSL                 = [bool]$data.IsSSL
                    DatabaseType          = $data.DatabaseType
                    TotalClients          = [int]$data.TotalClients
                    ClientsNeedingUpdates = [int]$data.ClientsNeedingUpdates
                    ClientsWithErrors     = [int]$data.ClientsWithErrors
                    UnapprovedUpdates     = [int]$data.UnapprovedUpdates
                    ContentDirPath        = $data.ContentDirPath
                    ContentDirFreeSpaceGB = [decimal]$data.ContentDirFreeSpaceGB
                    OverallHealth         = $healthStatus
                    Timestamp             = Get-Date -Format 'o'
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"
                continue
            }
        }
    }

    end {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}