Public/healthcheck/Get-RDSHealth.ps1

#Requires -Version 5.1
function Get-RDSHealth {
    <#
        .SYNOPSIS
            Retrieves Remote Desktop Services health status from one or more servers
 
        .DESCRIPTION
            Checks the health of Remote Desktop Services on target computers by inspecting
            TermService and SessionEnv services, RDS module availability, installed RD roles,
            active and disconnected sessions, and licensing configuration.
            Returns a single typed object per server with an overall health assessment.
 
        .PARAMETER ComputerName
            One or more computer names to query. Defaults to the local machine.
            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-RDSHealth
 
            Checks RDS health on the local computer.
 
        .EXAMPLE
            Get-RDSHealth -ComputerName 'RDS01'
 
            Checks RDS health on a single remote server.
 
        .EXAMPLE
            'RDS01', 'RDS02' | Get-RDSHealth -Credential (Get-Credential)
 
            Checks RDS health on multiple servers via pipeline with alternate credentials.
 
        .OUTPUTS
            PSWinOps.RDSHealth
            Returns one object per server with service status, session counts,
            installed roles, licensing mode, and overall health assessment.
 
        .NOTES
            Author: Franck SALLET
            Version: 1.0.0
            Last Modified: 2026-03-26
            Requires: PowerShell 5.1+ / Windows only
            Requires: Admin rights on target servers for full RDS enumeration
 
        .LINK
            https://github.com/k9fr4n/PSWinOps
 
        .LINK
            https://learn.microsoft.com/en-us/powershell/module/remotedesktop/
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.RDSHealth')]
    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 = {
            $data = @{
                ServiceStatus        = 'NotFound'
                SessionEnvStatus     = 'NotFound'
                RDModuleAvailable    = $false
                InstalledRoles       = ''
                ActiveSessions       = 0
                DisconnectedSessions = 0
                LicensingMode        = 'Unknown'
            }

            $termSvc = Get-Service -Name 'TermService' -ErrorAction SilentlyContinue
            if ($termSvc) { $data.ServiceStatus = $termSvc.Status.ToString() }

            $sessionEnv = Get-Service -Name 'SessionEnv' -ErrorAction SilentlyContinue
            if ($sessionEnv) { $data.SessionEnvStatus = $sessionEnv.Status.ToString() }

            $rdModule = Get-Module -Name 'RemoteDesktop' -ListAvailable -ErrorAction SilentlyContinue
            if ($rdModule) {
                $data.RDModuleAvailable = $true

                try {
                    $rdServers = Get-RDServer -ErrorAction Stop
                    $roleList = [System.Collections.Generic.List[string]]::new()
                    foreach ($rdServer in $rdServers) {
                        if ($rdServer.Server -eq $env:COMPUTERNAME) {
                            foreach ($role in $rdServer.Roles) { $roleList.Add($role) }
                        }
                    }
                    if ($roleList.Count -gt 0) { $data.InstalledRoles = $roleList -join ', ' }

                    try {
                        $rdSessions = Get-RDUserSession -ErrorAction Stop
                        foreach ($s in $rdSessions) {
                            if ($s.SessionState -eq 'STATE_ACTIVE') { $data.ActiveSessions++ }
                            elseif ($s.SessionState -eq 'STATE_DISCONNECTED') { $data.DisconnectedSessions++ }
                        }
                    }
                    catch { Write-Verbose -Message "RD user session query failed: $_" }

                    if ($roleList -contains 'RDS-LICENSING') {
                        try {
                            $licConfig = Get-RDLicenseConfiguration -ErrorAction Stop
                            $data.LicensingMode = if ($licConfig.Mode) { $licConfig.Mode.ToString() } else { 'NotConfigured' }
                        }
                        catch { $data.LicensingMode = 'Unknown' }
                    }
                }
                catch { Write-Verbose -Message "RemoteDesktop module import/query failed: $_" }
            }

            # Fallback: quser.exe for session counts on standalone hosts
            if ($data.ActiveSessions -eq 0 -and $data.DisconnectedSessions -eq 0) {
                try {
                    $quserOutput = quser.exe 2>$null
                    if ($quserOutput) {
                        $dataLines = $quserOutput | Select-Object -Skip 1
                        foreach ($line in $dataLines) {
                            if ($line -match '\bActive\b') { $data.ActiveSessions++ }
                            elseif ($line -match '\bDisc\b') { $data.DisconnectedSessions++ }
                        }
                    }
                }
                catch { Write-Verbose -Message "quser.exe fallback failed: $_" }
            }

            $data
        }
    }

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

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

                $activeSessions = [int]$result.ActiveSessions
                $disconnectedSessions = [int]$result.DisconnectedSessions
                $totalSessions = $activeSessions + $disconnectedSessions

                if ([string]$result.ServiceStatus -eq 'NotFound') {
                    $healthStatus = 'RoleUnavailable'
                }
                elseif ([string]$result.ServiceStatus -ne 'Running' -or [string]$result.SessionEnvStatus -ne 'Running') {
                    $healthStatus = 'Critical'
                }
                elseif ($disconnectedSessions -gt $activeSessions -or [string]$result.LicensingMode -eq 'NotConfigured') {
                    $healthStatus = 'Degraded'
                }
                else {
                    $healthStatus = 'Healthy'
                }

                [PSCustomObject]@{
                    PSTypeName           = 'PSWinOps.RDSHealth'
                    ComputerName         = $displayName
                    ServiceName          = 'TermService'
                    ServiceStatus        = [string]$result.ServiceStatus
                    SessionEnvStatus     = [string]$result.SessionEnvStatus
                    RDModuleAvailable    = [bool]$result.RDModuleAvailable
                    InstalledRoles       = [string]$result.InstalledRoles
                    ActiveSessions       = $activeSessions
                    DisconnectedSessions = $disconnectedSessions
                    TotalSessions        = $totalSessions
                    LicensingMode        = [string]$result.LicensingMode
                    OverallHealth        = $healthStatus
                    Timestamp            = Get-Date -Format 'o'
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"
                continue
            }
        }
    }

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