Public/healthcheck/Get-ADFSHealth.ps1

#Requires -Version 5.1
function Get-ADFSHealth {
    <#
        .SYNOPSIS
            Retrieves AD FS health status from one or more Windows servers
 
        .DESCRIPTION
            Collects comprehensive AD FS health information including service status, SSL certificate
            expiry, relying party trusts, enabled endpoints, and server health test results.
            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-ADFSHealth
 
            Queries the local server for AD FS health information.
 
        .EXAMPLE
            Get-ADFSHealth -ComputerName 'ADFS01'
 
            Queries a single remote AD FS server by name.
 
        .EXAMPLE
            'ADFS01', 'ADFS02' | Get-ADFSHealth -Credential (Get-Credential)
 
            Queries multiple remote AD FS servers via the pipeline with alternate credentials.
 
        .OUTPUTS
            PSWinOps.ADFSHealth
            Returns one object per server with service status, SSL certificate details,
            relying party counts, endpoint counts, health test results, and overall health.
 
        .NOTES
            Author: Franck SALLET
            Version: 1.0.0
            Last Modified: 2026-03-26
            Requires: PowerShell 5.1+ / Windows only
            Requires: AD FS role installed on target servers
 
        .LINK
            https://github.com/k9fr4n/PSWinOps
 
        .LINK
            https://learn.microsoft.com/en-us/powershell/module/adfs/
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.ADFSHealth')]
    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         = 'Unknown'
                ModuleAvailable       = $false
                FederationServiceName = 'Unknown'
                SslCertExpiry         = 'Unknown'
                SslCertDaysRemaining  = -1
                TotalRelyingParties   = 0
                EnabledRelyingParties = 0
                EnabledEndpoints      = 0
                ServerHealthOK        = $true
            }

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

            $adfsModule = Get-Module -Name 'ADFS' -ListAvailable -ErrorAction SilentlyContinue
            if ($adfsModule) { $data.ModuleAvailable = $true }

            if ($data.ModuleAvailable -and $data.ServiceStatus -eq 'Running') {
                try {
                    $adfsProps = Get-AdfsProperties -ErrorAction Stop
                    $data.FederationServiceName = [string]$adfsProps.HostName
                }
                catch { Write-Warning -Message "Failed to retrieve ADFS properties: $_" }

                try {
                    $sslCerts = Get-AdfsSslCertificate -ErrorAction Stop
                    if ($sslCerts) {
                        $thumbprint = ($sslCerts | Select-Object -First 1).CertificateHash
                        if ($thumbprint) {
                            $cert = Get-Item -Path "Cert:\LocalMachine\My\$thumbprint" -ErrorAction SilentlyContinue
                            if ($cert -and $cert.NotAfter) {
                                $data.SslCertExpiry = $cert.NotAfter.ToString('yyyy-MM-dd HH:mm:ss')
                                $data.SslCertDaysRemaining = [int]($cert.NotAfter - (Get-Date)).Days
                            }
                        }
                    }
                }
                catch { Write-Warning -Message "Failed to retrieve SSL certificate info: $_" }

                try {
                    $rpTrusts = @(Get-AdfsRelyingPartyTrust -ErrorAction Stop)
                    $data.TotalRelyingParties = $rpTrusts.Count
                    $data.EnabledRelyingParties = @($rpTrusts | Where-Object -FilterScript { $_.Enabled -eq $true }).Count
                }
                catch { Write-Warning -Message "Failed to retrieve relying party trusts: $_" }

                try {
                    $endpoints = @(Get-AdfsEndpoint -ErrorAction Stop)
                    $data.EnabledEndpoints = @($endpoints | Where-Object -FilterScript { $_.Enabled -eq $true }).Count
                }
                catch { Write-Warning -Message "Failed to retrieve ADFS endpoints: $_" }

                $testCmd = Get-Command -Name 'Test-AdfsServerHealth' -ErrorAction SilentlyContinue
                if ($testCmd) {
                    try {
                        $healthResults = Test-AdfsServerHealth -ErrorAction Stop
                        if ($healthResults) {
                            $failures = @($healthResults | Where-Object -FilterScript { $_.Result -ne 'Pass' })
                            if ($failures.Count -gt 0) { $data.ServerHealthOK = $false }
                        }
                    }
                    catch {
                        Write-Warning -Message "Failed to run Test-AdfsServerHealth: $_"
                        $data.ServerHealthOK = $false
                    }
                }
            }

            $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
                }

                if (-not $result.ModuleAvailable) {
                    $healthStatus = 'RoleUnavailable'
                }
                elseif ($result.ServiceStatus -ne 'Running' -or $result.SslCertDaysRemaining -le 0 -or -not $result.ServerHealthOK) {
                    $healthStatus = 'Critical'
                }
                elseif ($result.SslCertDaysRemaining -lt 30 -or $result.EnabledRelyingParties -eq 0) {
                    $healthStatus = 'Degraded'
                }
                else {
                    $healthStatus = 'Healthy'
                }

                [PSCustomObject]@{
                    PSTypeName            = 'PSWinOps.ADFSHealth'
                    ComputerName          = $displayName
                    ServiceName           = 'adfssrv'
                    ServiceStatus         = [string]$result.ServiceStatus
                    FederationServiceName = [string]$result.FederationServiceName
                    SslCertExpiry         = [string]$result.SslCertExpiry
                    SslCertDaysRemaining  = [int]$result.SslCertDaysRemaining
                    TotalRelyingParties   = [int]$result.TotalRelyingParties
                    EnabledRelyingParties = [int]$result.EnabledRelyingParties
                    EnabledEndpoints      = [int]$result.EnabledEndpoints
                    ServerHealthOK        = [bool]$result.ServerHealthOK
                    OverallHealth         = $healthStatus
                    Timestamp             = Get-Date -Format 'o'
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"
                continue
            }
        }
    }

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