Public/network/Test-PortConnectivity.ps1

#Requires -Version 5.1

function Test-PortConnectivity {
    <#
    .SYNOPSIS
        Tests TCP port connectivity on one or more remote hosts.
    .DESCRIPTION
        Uses System.Net.Sockets.TcpClient to test whether specified TCP ports are
        reachable on target computers. Much faster than Test-NetConnection for
        bulk testing because it uses a configurable timeout and tests multiple
        port/host combinations sequentially.
 
        Returns structured objects suitable for pipeline processing and reporting.
    .PARAMETER ComputerName
        One or more target hostnames or IP addresses to test.
        Accepts pipeline input.
    .PARAMETER Port
        One or more TCP port numbers to test. Valid range: 1-65535.
    .PARAMETER TimeoutMs
        Connection timeout in milliseconds per port test. Default: 1000 (1 second).
        Valid range: 100-30000.
    .EXAMPLE
        Test-PortConnectivity -ComputerName 'SRV01' -Port 443
 
        Tests if port 443 is open on SRV01.
    .EXAMPLE
        Test-PortConnectivity -ComputerName 'SRV01', 'SRV02' -Port 80, 443, 3389
 
        Tests 3 ports on 2 servers (6 tests total).
    .EXAMPLE
        'WEB01', 'WEB02', 'WEB03' | Test-PortConnectivity -Port 443, 8080
 
        Pipeline input: tests 2 ports on 3 servers.
    .EXAMPLE
        Test-PortConnectivity -ComputerName '10.0.0.1' -Port 1..1024 -TimeoutMs 500
 
        Scans ports 1-1024 on a host with a 500ms timeout.
    .OUTPUTS
    PSWinOps.PortConnectivity
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-03-21
        Requires: PowerShell 5.1+ / Windows only
        Permissions: No admin required
    .LINK
    https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.tcpclient
    #>

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

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

        [Parameter(Mandatory = $false)]
        [ValidateRange(100, 30000)]
        [int]$TimeoutMs = 1000
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting port connectivity tests (timeout: ${TimeoutMs}ms)"
    }

    process {
        foreach ($targetComputer in $ComputerName) {
            foreach ($targetPort in $Port) {
                $tcpClient = $null
                try {
                    Write-Verbose "[$($MyInvocation.MyCommand)] Testing ${targetComputer}:${targetPort}"
                    $tcpClient = New-Object System.Net.Sockets.TcpClient
                    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                    $connectTask = $tcpClient.ConnectAsync($targetComputer, $targetPort)
                    $completed = $connectTask.Wait($TimeoutMs)
                    $stopwatch.Stop()

                    if ($completed -and -not $connectTask.IsFaulted) {
                        [PSCustomObject]@{
                            PSTypeName     = 'PSWinOps.PortConnectivity'
                            ComputerName   = $targetComputer
                            Port           = $targetPort
                            Protocol       = 'TCP'
                            Open           = $true
                            ResponseTimeMs = [math]::Round($stopwatch.Elapsed.TotalMilliseconds, 1)
                            Timestamp      = Get-Date -Format 'o'
                        }
                    } else {
                        [PSCustomObject]@{
                            PSTypeName     = 'PSWinOps.PortConnectivity'
                            ComputerName   = $targetComputer
                            Port           = $targetPort
                            Protocol       = 'TCP'
                            Open           = $false
                            ResponseTimeMs = $null
                            Timestamp      = Get-Date -Format 'o'
                        }
                    }
                } catch {
                    [PSCustomObject]@{
                        PSTypeName     = 'PSWinOps.PortConnectivity'
                        ComputerName   = $targetComputer
                        Port           = $targetPort
                        Protocol       = 'TCP'
                        Open           = $false
                        ResponseTimeMs = $null
                        Timestamp      = Get-Date -Format 'o'
                    }
                } finally {
                    if ($tcpClient) {
                        $tcpClient.Close()
                        $tcpClient.Dispose()
                    }
                }
            }
        }
    }

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