Public/network/Test-WinRM.ps1

#Requires -Version 5.1

function Test-WinRM {
    <#
    .SYNOPSIS
        Tests WinRM connectivity and configuration on remote computers.
    .DESCRIPTION
        Performs a comprehensive WinRM connectivity test:
        1. Tests TCP port 5985 (HTTP) and/or 5986 (HTTPS)
        2. Tests WSMan connection via Test-WSMan
        3. Optionally tests Invoke-Command execution
 
        Returns structured results for each step, making it easy to pinpoint
        where the connection fails.
    .PARAMETER ComputerName
        One or more computer names to test. Accepts pipeline input.
    .PARAMETER Credential
        Optional credential for authentication.
    .PARAMETER UseSSL
        Test HTTPS port 5986 instead of HTTP port 5985.
    .PARAMETER TestExecution
        Also test actual command execution via Invoke-Command.
        Default: $false (only tests port and WSMan).
    .PARAMETER TimeoutMs
        TCP port test timeout in milliseconds. Default: 3000.
    .EXAMPLE
        Test-WinRM -ComputerName 'SRV01'
 
        Tests WinRM on SRV01 (port 5985 + WSMan).
    .EXAMPLE
        Test-WinRM -ComputerName 'SRV01' -TestExecution -Credential (Get-Credential)
 
        Full test including command execution with credentials.
    .EXAMPLE
        'SRV01', 'SRV02', 'SRV03' | Test-WinRM
 
        Pipeline: tests WinRM on 3 servers.
    .EXAMPLE
        Test-WinRM -ComputerName 'SRV01' -UseSSL
 
        Tests WinRM over HTTPS (port 5986).
    .OUTPUTS
    PSWinOps.WinRMTestResult
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-03-21
        Requires: PowerShell 5.1+ / Windows only
        Permissions: No admin required for testing, target must allow WinRM
    #>

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

        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,

        [Parameter(Mandatory = $false)]
        [switch]$UseSSL,

        [Parameter(Mandatory = $false)]
        [switch]$TestExecution,

        [Parameter(Mandatory = $false)]
        [ValidateRange(500, 30000)]
        [int]$TimeoutMs = 3000
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting WinRM tests"
        $hasCredential = $PSBoundParameters.ContainsKey('Credential')
    }

    process {
        foreach ($targetComputer in $ComputerName) {
            try {
                $winrmPort = if ($UseSSL) { 5986 } else { 5985 }
                $portOpen = $false
                $wsmanOK = $false
                $execOK = $null
                $wsmanVersion = $null
                $errorMessage = $null

                Write-Verbose "[$($MyInvocation.MyCommand)] Testing '$targetComputer' port $winrmPort"

                # Step 1: TCP port test
                $tcpClient = $null
                try {
                    $tcpClient = New-Object System.Net.Sockets.TcpClient
                    $connectTask = $tcpClient.ConnectAsync($targetComputer, $winrmPort)
                    $portOpen = $connectTask.Wait($TimeoutMs) -and -not $connectTask.IsFaulted
                } catch {
                    Write-Verbose "[$($MyInvocation.MyCommand)] Port $winrmPort closed on '$targetComputer': $_"
                } finally {
                    if ($tcpClient) { $tcpClient.Close(); $tcpClient.Dispose() }
                }

                # Step 2: WSMan test (only if port is open)
                if ($portOpen) {
                    try {
                        $wsmanParams = @{
                            ComputerName = $targetComputer
                            ErrorAction  = 'Stop'
                        }
                        if ($hasCredential) { $wsmanParams['Credential'] = $Credential }

                        $wsmanResult = Test-WSMan @wsmanParams
                        $wsmanOK = $true
                        $wsmanVersion = $wsmanResult.ProductVersion
                    } catch {
                        $errorMessage = "WSMan failed: $($_.Exception.Message)"
                        Write-Verbose "[$($MyInvocation.MyCommand)] WSMan test failed on '$targetComputer': $_"
                    }
                } else {
                    $errorMessage = "Port $winrmPort is not reachable"
                }

                # Step 3: Execution test (only if WSMan succeeded and requested)
                if ($wsmanOK -and $TestExecution) {
                    try {
                        $invokeParams = @{
                            ComputerName = $targetComputer
                            ScriptBlock  = { $env:COMPUTERNAME }
                            ErrorAction  = 'Stop'
                        }
                        if ($hasCredential) { $invokeParams['Credential'] = $Credential }

                        $execResult = Invoke-Command @invokeParams
                        $execOK = ($null -ne $execResult)
                    } catch {
                        $execOK = $false
                        $errorMessage = "Execution failed: $($_.Exception.Message)"
                        Write-Verbose "[$($MyInvocation.MyCommand)] Execution test failed on '$targetComputer': $_"
                    }
                }

                [PSCustomObject]@{
                    PSTypeName     = 'PSWinOps.WinRMTestResult'
                    ComputerName   = $targetComputer
                    Port           = $winrmPort
                    PortOpen       = $portOpen
                    WSManConnected = $wsmanOK
                    ExecutionOK    = $execOK
                    WSManVersion   = $wsmanVersion
                    Protocol       = if ($UseSSL) { 'HTTPS' } else { 'HTTP' }
                    ErrorMessage   = $errorMessage
                    Timestamp      = Get-Date -Format 'o'
                }
            } catch {
                Write-Error "[$($MyInvocation.MyCommand)] Failed on '$targetComputer': $_"
            }
        }
    }

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