Private/Test-EFTcpPort.ps1

function Test-EFTcpPort {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$HostName,

        [Parameter(Mandatory)]
        [ValidateRange(1, 65535)]
        [int]$Port,

        [ValidateRange(100, 10000)]
        [int]$TimeoutMilliseconds = 3000
    )

    $client = [Net.Sockets.TcpClient]::new()
    $waitHandle = $null
    $stopwatch = [Diagnostics.Stopwatch]::StartNew()

    try {
        try {
            $connection = $client.BeginConnect($HostName, $Port, $null, $null)
            $waitHandle = $connection.AsyncWaitHandle
            if (-not $waitHandle.WaitOne($TimeoutMilliseconds, $false)) {
                return [pscustomobject]@{
                    Connected           = $false
                    FailureReason       = 'TimedOut'
                    IsEvaluationError   = $false
                    ElapsedMilliseconds = [int]$stopwatch.ElapsedMilliseconds
                }
            }

            $client.EndConnect($connection)
            return [pscustomobject]@{
                Connected           = [bool]$client.Connected
                FailureReason       = if ($client.Connected) { 'None' } else { 'ConnectionFailed' }
                IsEvaluationError   = $false
                ElapsedMilliseconds = [int]$stopwatch.ElapsedMilliseconds
            }
        }
        catch [Net.Sockets.SocketException] {
            $socketError = $_.Exception.SocketErrorCode
            $nameResolutionErrors = @(
                [Net.Sockets.SocketError]::HostNotFound,
                [Net.Sockets.SocketError]::NoData,
                [Net.Sockets.SocketError]::NoRecovery,
                [Net.Sockets.SocketError]::TryAgain
            )
            $expectedConnectionFailures = @(
                [Net.Sockets.SocketError]::AddressNotAvailable,
                [Net.Sockets.SocketError]::ConnectionAborted,
                [Net.Sockets.SocketError]::ConnectionRefused,
                [Net.Sockets.SocketError]::ConnectionReset,
                [Net.Sockets.SocketError]::HostDown,
                [Net.Sockets.SocketError]::HostUnreachable,
                [Net.Sockets.SocketError]::NetworkDown,
                [Net.Sockets.SocketError]::NetworkUnreachable,
                [Net.Sockets.SocketError]::TimedOut
            )

            return [pscustomobject]@{
                Connected           = $false
                FailureReason       = if ($socketError -in $nameResolutionErrors) { 'NameResolutionFailed' } else { [string]$socketError }
                IsEvaluationError   = $socketError -notin $nameResolutionErrors -and $socketError -notin $expectedConnectionFailures
                ElapsedMilliseconds = [int]$stopwatch.ElapsedMilliseconds
            }
        }
    }
    finally {
        $stopwatch.Stop()
        if ($null -ne $waitHandle) {
            $waitHandle.Close()
        }
        $client.Close()
    }
}