Last active
November 25, 2025 01:33
-
-
Save SweetAsNZ/d8b85e4e1194211066282edb3c95d89c to your computer and use it in GitHub Desktop.
Test WinRM Health
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Test-WinRMHealth { | |
| <# | |
| .EXAMPLE | |
| # Local auto-detect and try-all | |
| Test-WinRMHealth | |
| .EXAMPLE | |
| # Remote over HTTP (default 5985), auto-try enabled auths | |
| Test-WinRMHealth -ComputerName 'srv01' | |
| .EXAMPLE | |
| # Remote over HTTPS (default 5986), auto-try | |
| Test-WinRMHealth -ComputerName 'srv01.domain.local' -UseSSL | |
| .EXAMPLE | |
| # Force a specific auth only | |
| Test-WinRMHealth -ComputerName 'srv01' -Authentication Kerberos | |
| .EXAMPLE | |
| # Use explicit creds (needed for Basic or when your current token can’t auth) | |
| $cred = Get-Credential | |
| Test-WinRMHealth -ComputerName 'edge-host' -UseSSL -Authentication Basic -Credential $cred | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] | |
| [string]$ComputerName = 'localhost', | |
| [switch]$UseSSL, | |
| [int]$Port, | |
| [ValidateSet('Default','Kerberos','Negotiate','Basic','CredSSP')] | |
| [string]$Authentication = 'Default', | |
| [System.Management.Automation.PSCredential]$Credential | |
| ) | |
| begin { | |
| $results = @() | |
| function _IsLocal { | |
| param([string]$Name) | |
| try { | |
| $trim = $Name.Trim('.') | |
| if ($trim -eq '' -or $trim -ieq 'localhost' -or $trim -ieq $env:COMPUTERNAME) { return $true } | |
| $fqdn = try { ([System.Net.Dns]::GetHostEntry($env:COMPUTERNAME)).HostName } catch { $null } | |
| if ($fqdn -and $trim -ieq $fqdn) { return $true } | |
| return $false | |
| } catch { return $false } | |
| } | |
| function _safe { | |
| param([ScriptBlock]$ScriptBlock) | |
| try { & $ScriptBlock } catch { $_ } | |
| } | |
| } | |
| process { | |
| $target = $ComputerName | |
| $localTarget = _IsLocal -Name $target | |
| if (-not $Port) { $Port = ($(if ($UseSSL) { 5986 } else { 5985 })) } | |
| # ---------------- Client config (local) ---------------- | |
| $clientAuth = _safe { Get-Item -Path WSMan:\localhost\Client\Auth | Select-Object * } | |
| $clientCfg = _safe { Get-Item -Path WSMan:\localhost\Client | Select-Object * } | |
| $trustedHosts = _safe { (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value } | |
| $allowUnenc = $null | |
| if ($clientCfg -and $clientCfg.PSObject.Properties['AllowUnencrypted']) { $allowUnenc = [bool]$clientCfg.AllowUnencrypted } | |
| # Build the candidate auth list from current client config (order matters) | |
| $enabledAuths = @() | |
| if ($clientAuth -and $clientAuth.PSObject.Properties['Kerberos'] -and $clientAuth.Kerberos) { $enabledAuths += 'Kerberos' } | |
| if ($clientAuth -and $clientAuth.PSObject.Properties['Negotiate'] -and $clientAuth.Negotiate) { $enabledAuths += 'Negotiate' } | |
| if ($clientAuth -and $clientAuth.PSObject.Properties['CredSSP'] -and $clientAuth.CredSSP) { $enabledAuths += 'CredSSP' } | |
| if ($clientAuth -and $clientAuth.PSObject.Properties['Basic'] -and $clientAuth.Basic) { $enabledAuths += 'Basic' } | |
| # If user forced a specific method, use just that; else try Default then each enabled auth | |
| $authQueue = @() | |
| if ($Authentication -ne 'Default') { | |
| $authQueue = @($Authentication) | |
| } else { | |
| $authQueue = @('Default') + $enabledAuths | |
| } | |
| # --------------- Server-side snapshot (local only) --------------- | |
| $serverSummary = $null | |
| if ($localTarget) { | |
| $svc = _safe { Get-Service -Name WinRM -ErrorAction Stop } | |
| $listeners = _safe { | |
| $items = Get-ChildItem WSMan:\localhost\Listener -ErrorAction Stop | |
| foreach ($i in $items) { | |
| [PSCustomObject]@{ | |
| Transport = $i.Keys['Transport'] | |
| Port = $i.Keys['Port'] | |
| Address = $i.Keys['Address'] | |
| Hostname = $i.Keys['Hostname'] | |
| CertificateThumbprint = $i.Keys['CertificateThumbprint'] | |
| Enabled = $i.ValueSet['Enabled'] | |
| } | |
| } | |
| } | |
| $fw = _safe { | |
| Get-NetFirewallRule -ErrorAction Stop | | |
| Where-Object { $_.DisplayName -match 'Windows Remote Management' } | | |
| Select-Object DisplayName, Enabled, Direction, Profile | |
| } | |
| $svcStatus = if ($svc -is [System.ServiceProcess.ServiceController]) { $svc.Status } else { $svc } | |
| $serverSummary = [PSCustomObject]@{ | |
| Type = 'Server' | |
| Machine = $env:COMPUTERNAME | |
| WinRMService = $svcStatus | |
| Listeners = $listeners | |
| Firewall = $fw | |
| } | |
| } else { | |
| $serverSummary = [PSCustomObject]@{ | |
| Type = 'Server' | |
| Machine = $target | |
| WinRMService = 'Unknown (remote query requires WSMan)' | |
| Listeners = $null | |
| Firewall = $null | |
| } | |
| } | |
| # --------------- Connectivity baseline ---------------- | |
| $endpoint = "http{0}://{1}:{2}/wsman" -f ($(if($UseSSL){'s'}else{''}), $target, $Port) | |
| $tnc = _safe { Test-NetConnection -ComputerName $target -Port $Port -WarningAction SilentlyContinue } | |
| $tncOK = $false | |
| if ($tnc -and $tnc.PSObject.Properties['TcpTestSucceeded']) { $tncOK = [bool]$tnc.TcpTestSucceeded } | |
| # --------------- Try all viable auths ---------------- | |
| $attempts = @() | |
| foreach ($auth in $authQueue) { | |
| # Skip obviously bad combos: Basic over HTTP with client disallowing unencrypted | |
| if ($auth -eq 'Basic' -and -not $UseSSL -and $allowUnenc -ne $true) { | |
| $attempts += [PSCustomObject]@{ | |
| Authentication = $auth | |
| UseSSL = [bool]$UseSSL | |
| Port = $Port | |
| Result = 'Skipped' | |
| Reason = 'Client AllowUnencrypted = false; Basic over HTTP would send creds in cleartext' | |
| } | |
| continue | |
| } | |
| $wsmanParams = @{ | |
| ComputerName = $target | |
| ErrorAction = 'Stop' | |
| Port = $Port | |
| } | |
| if ($UseSSL) { $wsmanParams['UseSSL'] = $true } | |
| if ($auth -ne 'Default') { $wsmanParams['Authentication'] = $auth } | |
| if ($Credential) { $wsmanParams['Credential'] = $Credential } | |
| $ok = $false | |
| $err = $null | |
| $detail = $null | |
| try { | |
| $res = Test-WSMan @wsmanParams | |
| if ($res) { | |
| $ok = $true | |
| $detail = 'Identify succeeded' | |
| } else { | |
| $detail = 'No response object returned' | |
| } | |
| } catch { | |
| $err = $_.Exception.Message | |
| } | |
| $attempts += [PSCustomObject]@{ | |
| Authentication = $auth | |
| UseSSL = [bool]$UseSSL | |
| Port = $Port | |
| Result = if ($ok) { 'Success' } elseif ($err) { 'Failed' } else { 'Unknown' } | |
| Detail = if ($ok) { $detail } else { $err } | |
| } | |
| # If Default worked, we’re done. No need to smash the server with more requests. | |
| if ($ok -and $auth -eq 'Default') { break } | |
| } | |
| # --------------- Pick the verdict ---------------- | |
| $firstSuccess = $attempts | Where-Object { $_.Result -eq 'Success' } | Select-Object -First 1 | |
| $status = if ($firstSuccess) { 'Healthy' } else { 'Unhealthy' } | |
| $reasonText = $null | |
| if ($firstSuccess) { | |
| $reasonText = "WSMan OK via $($firstSuccess.Authentication)" | |
| } else { | |
| if ($tncOK) { | |
| $reasonText = 'Port open, WSMan handshake failed for all tried auth methods' | |
| } else { | |
| $reasonText = 'Port closed or filtered' | |
| } | |
| } | |
| # --------------- Pack summaries ---------------- | |
| $clientSummary = [PSCustomObject]@{ | |
| Type = 'Client' | |
| Machine = $env:COMPUTERNAME | |
| WinRMService = (_safe { (Get-Service -Name WinRM -ErrorAction Stop).Status }) | |
| Auth_Basic = if ($clientAuth -and $clientAuth.PSObject.Properties['Basic']) { $clientAuth.Basic } else { $null } | |
| Auth_Kerberos = if ($clientAuth -and $clientAuth.PSObject.Properties['Kerberos']) { $clientAuth.Kerberos } else { $null } | |
| Auth_Negotiate = if ($clientAuth -and $clientAuth.PSObject.Properties['Negotiate']) { $clientAuth.Negotiate } else { $null } | |
| Auth_CredSSP = if ($clientAuth -and $clientAuth.PSObject.Properties['CredSSP']) { $clientAuth.CredSSP } else { $null } | |
| AllowUnencrypted = $allowUnenc | |
| TrustedHosts = $trustedHosts | |
| } | |
| $connectivity = [PSCustomObject]@{ | |
| Type = 'Connectivity' | |
| Target = $target | |
| Endpoint = $endpoint | |
| TcpReachable = $tncOK | |
| TcpDetail = if ($tncOK) { 'TCP succeeded' } else { ($tnc | Out-String).Trim() } | |
| Attempts = $attempts | |
| } | |
| $health = [PSCustomObject]@{ | |
| Target = $target | |
| Status = $status | |
| Reason = $reasonText | |
| Client = $clientSummary | |
| Server = $serverSummary | |
| Connectivity = $connectivity | |
| Timestamp = (Get-Date) | |
| } | |
| $results += $health | |
| } | |
| end { | |
| $results | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment