Skip to main content

Tests a TCP or UPP port for connectivity.

function Test-Port
{
    <#
    .SYNOPSIS
        Tests TCP or UDP port connectivity to target hosts.

    .DESCRIPTION
        Tests whether specific TCP or UDP ports are open and accessible on target hosts.
        Provides detailed connection information including success status and connection details.
        Supports pipeline input for port numbers, allowing easy testing of multiple ports.
        Compatible with PowerShell Desktop 5.1+ on Windows, macOS, and Linux.

    .PARAMETER Port
        Port numbers to test. Accepts an array of port numbers (1-65535).
        Supports pipeline input for easy testing of multiple ports (e.g., 22,80,443 | Test-Port).

    .PARAMETER ComputerName
        Target hosts to test. Accepts an array of computer names or IP addresses.
        If not specified, 'localhost' is used as the default.
        Supports pipeline input by property name for object-based input.

    .PARAMETER Timeout
        Sets a timeout (in milliseconds) for port query.
        The default is 3000 (3 seconds). Valid range: 100-300000 (5 minutes).

    .PARAMETER Tcp
        Use this switch to test TCP ports. If neither Tcp nor Udp is specified, Tcp is used by default.

    .PARAMETER Udp
        Use this switch to test UDP ports.

    .EXAMPLE
        PS > Test-Port -ComputerName 'server' -Port 80
        Tests if TCP port 80 is open on server 'server'.

    .EXAMPLE
        PS > 80 | Test-Port
        Tests if TCP port 80 is open on localhost using pipeline input for the port.

    .EXAMPLE
        PS > 22,80,443 | Test-Port
        Tests if TCP ports 22, 80, and 443 are open on localhost using pipeline input.

    .EXAMPLE
        PS > 1..100 | Test-Port -ComputerName 'server'
        Tests TCP ports 1-100 on 'server' using pipeline input for port range.

    .EXAMPLE
        PS > Test-Port -ComputerName @("server1","server2") -Port 80
        Tests if TCP port 80 is open on both server1 and server2.

    .EXAMPLE
        PS > Test-Port -ComputerName dc1 -Port 17 -Udp -Timeout 10000
        Tests if UDP port 17 is open on server dc1 with a 10-second timeout.

    .EXAMPLE
        PS > 80,443,8080 | Test-Port -ComputerName @("server1","server2")
        Tests multiple ports on multiple servers using pipeline input for ports.

    .EXAMPLE
        PS > Test-Port -ComputerName (Get-Content hosts.txt) -Port @(1..59)
        Tests a range of ports from 1-59 on all servers in the hosts.txt file.

    .OUTPUTS
        System.Object[]
        Returns custom objects with Server, Port, Protocol, Open, Status, and ResponseTime properties.

    .NOTES
        Name: Test-Port.ps1
        Author: Boe Prox
        DateCreated: 18Aug2010
        Updated by Jon LaBelle, 9/29/2022
        Enhanced: 8/16/2025 - Improved cross-platform compatibility, reliability, and performance

        Ports reference: http://www.iana.org/assignments/port-numbers

    .LINK
        https://learn-powershell.net/2011/02/21/querying-udp-ports-with-powershell/

    .LINK
        https://jonlabelle.com/snippets/view/powershell/test-tcp-or-udp-network-port-in-powershell
    #>
    [CmdletBinding(ConfirmImpact = 'Low')]
    [OutputType([System.Object[]])]
    param(
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Port numbers to test (1-65535)')]
        [ValidateRange(1, 65535)]
        [Int[]]$Port,

        [Parameter(Position = 1, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Target hosts to test')]
        [AllowNull()]
        [AllowEmptyString()]
        [Alias('IPAddress', '__Server', 'CN', 'Server', 'Target')]
        [String[]]$ComputerName,

        [Parameter(HelpMessage = 'Timeout in milliseconds (100-300000)')]
        [ValidateRange(100, 300000)]
        [Int]$Timeout = 3000,

        [Parameter(HelpMessage = 'Test TCP ports')]
        [Switch]$Tcp,

        [Parameter(HelpMessage = 'Test UDP ports')]
        [Switch]$Udp
    )

    begin
    {
        # Set default protocol if neither is specified
        if (-not $Tcp -and -not $Udp)
        {
            $Tcp = $true
        }

        # Initialize results collection using ArrayList for better performance
        $results = New-Object System.Collections.ArrayList

        # Initialize port collection for pipeline input
        $allPorts = New-Object System.Collections.ArrayList
    }

    process
    {
        # Collect ports from pipeline input
        if ($Port)
        {
            foreach ($p in $Port)
            {
                [void]$allPorts.Add($p)
            }
        }
    }

    end
    {
        # Handle case where no ports were provided
        if ($allPorts.Count -eq 0)
        {
            Write-Error 'No ports specified for testing.'
            return
        }

        # Handle pipeline input and set default if empty
        if (-not $ComputerName -or $ComputerName.Count -eq 0)
        {
            $ComputerName = @('localhost')
        }

        foreach ($computer in $ComputerName)
        {
            # Skip empty/null computer names
            if ([string]::IsNullOrWhiteSpace($computer))
            {
                continue
            }

            Write-Verbose "Testing ports on $computer"

            foreach ($targetPort in $allPorts)
            {
                Write-Verbose "Testing $computer`:$targetPort"

                if ($Tcp)
                {
                    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                    $tcpClient = $null

                    try
                    {
                        Write-Verbose "Testing TCP connection to ${computer}:${targetPort}"

                        $tcpClient = New-Object System.Net.Sockets.TcpClient
                        $connectTask = $tcpClient.BeginConnect($computer, $targetPort, $null, $null)
                        $success = $connectTask.AsyncWaitHandle.WaitOne($Timeout, $false)

                        if ($success)
                        {
                            try
                            {
                                $tcpClient.EndConnect($connectTask)
                                $stopwatch.Stop()

                                Write-Verbose "TCP connection to ${computer}:${targetPort} successful"

                                $result = [PSCustomObject]@{
                                    PSTypeName = 'PortTest.Result'
                                    Server = $computer
                                    Port = $targetPort
                                    Protocol = 'TCP'
                                    Open = $true
                                    Status = 'Connection successful'
                                    ResponseTime = $stopwatch.ElapsedMilliseconds
                                }
                                [void]$results.Add($result)
                            }
                            catch
                            {
                                $stopwatch.Stop()
                                $errorMessage = $_.Exception.Message

                                # Parse common connection errors for better user feedback
                                if ($errorMessage -match 'refused|rejected')
                                {
                                    $status = 'Connection refused'
                                }
                                elseif ($errorMessage -match 'unreachable')
                                {
                                    $status = 'Host unreachable'
                                }
                                elseif ($errorMessage -match 'timeout|timed out')
                                {
                                    $status = 'Connection timed out'
                                }
                                else
                                {
                                    $status = "Connection failed: $($errorMessage -replace '.*:', '' -replace '"', '' | ForEach-Object Trim)"
                                }

                                Write-Verbose "TCP connection to ${computer}:${targetPort} failed: $status"

                                $result = [PSCustomObject]@{
                                    PSTypeName = 'PortTest.Result'
                                    Server = $computer
                                    Port = $targetPort
                                    Protocol = 'TCP'
                                    Open = $false
                                    Status = $status
                                    ResponseTime = $stopwatch.ElapsedMilliseconds
                                }
                                [void]$results.Add($result)
                            }
                        }
                        else
                        {
                            $stopwatch.Stop()
                            Write-Verbose "TCP connection to ${computer}:${targetPort} timed out"

                            $result = [PSCustomObject]@{
                                PSTypeName = 'PortTest.Result'
                                Server = $computer
                                Port = $targetPort
                                Protocol = 'TCP'
                                Open = $false
                                Status = 'Connection timed out'
                                ResponseTime = $Timeout
                            }
                            [void]$results.Add($result)
                        }
                    }
                    catch
                    {
                        $stopwatch.Stop()
                        Write-Verbose "TCP test error for ${computer}:${targetPort}: $($_.Exception.Message)"

                        $result = [PSCustomObject]@{
                            PSTypeName = 'PortTest.Result'
                            Server = $computer
                            Port = $targetPort
                            Protocol = 'TCP'
                            Open = $false
                            Status = "Error: $($_.Exception.Message)"
                            ResponseTime = $stopwatch.ElapsedMilliseconds
                        }
                        [void]$results.Add($result)
                    }
                    finally
                    {
                        if ($null -ne $tcpClient)
                        {
                            try
                            {
                                $tcpClient.Close()
                            }
                            catch
                            {
                                Write-Debug "Error closing TCP client: $($_.Exception.Message)"
                            }
                            $tcpClient.Dispose()
                        }
                    }
                }

                if ($Udp)
                {
                    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                    $udpClient = $null

                    try
                    {
                        Write-Verbose "Testing UDP connection to ${computer}:${targetPort}"

                        $udpClient = New-Object System.Net.Sockets.UdpClient
                        $udpClient.Client.ReceiveTimeout = $Timeout
                        $udpClient.Client.SendTimeout = $Timeout

                        # Connect to the remote endpoint
                        $udpClient.Connect($computer, $targetPort)

                        # Send a small test packet
                        $encoder = [System.Text.Encoding]::ASCII
                        $testData = $encoder.GetBytes("PowerShell-Test-$(Get-Date -Format 'yyyyMMddHHmmss')")
                        [void]$udpClient.Send($testData, $testData.Length)

                        # Try to receive a response
                        $remoteEndpoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)

                        try
                        {
                            $responseBytes = $udpClient.Receive([ref]$remoteEndpoint)
                            $stopwatch.Stop()

                            if ($responseBytes.Length -gt 0)
                            {
                                Write-Verbose "UDP response received from ${computer}:${targetPort}"

                                $result = [PSCustomObject]@{
                                    PSTypeName = 'PortTest.Result'
                                    Server = $computer
                                    Port = $targetPort
                                    Protocol = 'UDP'
                                    Open = $true
                                    Status = 'Response received'
                                    ResponseTime = $stopwatch.ElapsedMilliseconds
                                }
                                [void]$results.Add($result)
                            }
                        }
                        catch [System.Net.Sockets.SocketException]
                        {
                            $stopwatch.Stop()

                            # UDP is connectionless, so we need to interpret the exception
                            $errorCode = $_.Exception.SocketErrorCode

                            if ($errorCode -eq [System.Net.Sockets.SocketError]::TimedOut)
                            {
                                # No response doesn't necessarily mean the port is closed for UDP
                                # For simplicity, we'll consider it potentially open but filtered
                                Write-Verbose "UDP test to ${computer}:${targetPort} - no response (may be open but not responding)"

                                $result = [PSCustomObject]@{
                                    PSTypeName = 'PortTest.Result'
                                    Server = $computer
                                    Port = $targetPort
                                    Protocol = 'UDP'
                                    Open = $true
                                    Status = 'No response (likely filtered or open)'
                                    ResponseTime = $Timeout
                                }
                                [void]$results.Add($result)
                            }
                            elseif ($errorCode -eq [System.Net.Sockets.SocketError]::ConnectionReset)
                            {
                                Write-Verbose "UDP test to ${computer}:${targetPort} - port closed (ICMP unreachable received)"

                                $result = [PSCustomObject]@{
                                    PSTypeName = 'PortTest.Result'
                                    Server = $computer
                                    Port = $targetPort
                                    Protocol = 'UDP'
                                    Open = $false
                                    Status = 'Port closed (ICMP unreachable)'
                                    ResponseTime = $stopwatch.ElapsedMilliseconds
                                }
                                [void]$results.Add($result)
                            }
                            else
                            {
                                Write-Verbose "UDP test to ${computer}:${targetPort} - socket error: $errorCode"

                                $result = [PSCustomObject]@{
                                    PSTypeName = 'PortTest.Result'
                                    Server = $computer
                                    Port = $targetPort
                                    Protocol = 'UDP'
                                    Open = $false
                                    Status = "Socket error: $errorCode"
                                    ResponseTime = $stopwatch.ElapsedMilliseconds
                                }
                                [void]$results.Add($result)
                            }
                        }
                        catch
                        {
                            $stopwatch.Stop()
                            Write-Verbose "UDP test to ${computer}:${targetPort} - unexpected error: $($_.Exception.Message)"

                            $result = [PSCustomObject]@{
                                PSTypeName = 'PortTest.Result'
                                Server = $computer
                                Port = $targetPort
                                Protocol = 'UDP'
                                Open = $false
                                Status = "Error: $($_.Exception.Message)"
                                ResponseTime = $stopwatch.ElapsedMilliseconds
                            }
                            [void]$results.Add($result)
                        }
                    }
                    catch
                    {
                        $stopwatch.Stop()
                        Write-Verbose "UDP test setup error for ${computer}:${targetPort}: $($_.Exception.Message)"

                        $result = [PSCustomObject]@{
                            PSTypeName = 'PortTest.Result'
                            Server = $computer
                            Port = $targetPort
                            Protocol = 'UDP'
                            Open = $false
                            Status = "Setup error: $($_.Exception.Message)"
                            ResponseTime = $stopwatch.ElapsedMilliseconds
                        }
                        [void]$results.Add($result)
                    }
                    finally
                    {
                        if ($null -ne $udpClient)
                        {
                            try
                            {
                                $udpClient.Close()
                            }
                            catch
                            {
                                Write-Debug "Error closing UDP client: $($_.Exception.Message)"
                            }
                            $udpClient.Dispose()
                        }
                    }
                }
            }
        }

        return $results.ToArray()
    }
}