10

In Windows 8.1, the Test-NetConnection cmdlet is useful for checking the state of a network port on a remote system. However, it can be unnecessarily slow sometimes. I'd like to know if there's some options I could tweak, or an alternative PowerShell command I could use, to make this process faster.

Test-NetConnection can take around 10 seconds to return results if a remote system is not responding. Whenever a port is specified, it runs two connection tests which take about 5 seconds each to timeout. The first test is a basic ICMP echo check. This will timeout if the system is offline, or if it (or any intervening infrastructure) is configured to block or not respond to ICMP echo requests.The second test is the actual check against the specified port. This will timeout if the system is offline, or if there is a firewall along the path that is blocking the port.

In my current use case, the remote system is only two hops away on a reliable Gigabit Ethernet connection. So, a five-second timeout for any request is quite excessive - I could probably still get reliable results with a timeout of 30 ms or less! Additionally, the system is known to be non-responsive to ICMP echo even though it may be online and have all other services available. So it would be great if I could do without the ICMP echo test entirely, and reduce the timeout for the TCP connection test, in order to speed up my scripts that use Test-NetConnection for this purpose.

Does Test-NetConnection have options to change these behaviors? (I've read the detailed help file, and the answer seems to be no - but I'd be happy to be told there's something I've missed.) Or is there another way I can use PowerShell to run the same checks, but faster?

For various reasons, I prefer to keep my scripts limited to using functionality built-in to the Operating System wherever possible. Presume the environment is a fresh build of Windows 8.1, with all appropriate Windows Updates applied, and third-party tools are not an option.

Iszi
  • 13,585
  • 44
  • 113
  • 181
  • 1
    Related question: [How to check open ports using powershell?](http://stackoverflow.com/q/19213830) Some articles that might be useful as well: [Powershell – Test TCP ports on remote servers](http://noamwajnman.wordpress.com/2014/04/02/powershell-test-tcp-ports-on-remote-servers/), [Check for open TCP ports using PowerShell](http://www.powershelladmin.com/wiki/Check_for_open_TCP_ports_using_PowerShell), [Testing TCP Ports with a Possible Header Response](http://learn-powershell.net/2014/04/18/testing-tcp-ports-with-a-possible-header-response/) – and31415 Sep 01 '14 at 13:47

7 Answers7

11

Very basic (timeout 100 ms):

function testport ($hostname='yahoo.com',$port=80,$timeout=100) {
  $requestCallback = $state = $null
  $client = New-Object System.Net.Sockets.TcpClient
  $beginConnect = $client.BeginConnect($hostname,$port,$requestCallback,$state)
  Start-Sleep -milli $timeOut
  if ($client.Connected) { $open = $true } else { $open = $false }
  $client.Close()
  [pscustomobject]@{hostname=$hostname;port=$port;open=$open}
}

testport

hostname  port  open
--------  ----  ----
yahoo.com   80  True
js2010
  • 575
  • 5
  • 6
  • 4
    The problem with this solution is that you're forced to wait the $timeout period for every check. Even it the connection succeeds and is 10 times faster. – JustAGuy Sep 25 '19 at 14:55
6

You can use this to test the connection - Taken from the PowerShell Code Repository (author 'BSonPosh'):

"Test-Port creates a TCP connection to specified port. By default it connects to port 135 with a timeout of 3secs."

Param([string]$srv,$port=135,$timeout=3000,[switch]$verbose)

# Test-Port.ps1
# Does a TCP connection on specified port (135 by default)

$ErrorActionPreference = "SilentlyContinue"

# Create TCP Client
$tcpclient = new-Object system.Net.Sockets.TcpClient

# Tell TCP Client to connect to machine on Port
$iar = $tcpclient.BeginConnect($srv,$port,$null,$null)

# Set the wait time
$wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)

# Check to see if the connection is done
if(!$wait)
{
    # Close the connection and report timeout
    $tcpclient.Close()
    if($verbose){Write-Host "Connection Timeout"}
    Return $false
}
else
{
    # Close the connection and report the error if there is one
    $error.Clear()
    $tcpclient.EndConnect($iar) | out-Null
    if(!$?){if($verbose){write-host $error[0]};$failed = $true}
    $tcpclient.Close()
}

# Return $true if connection Establish else $False
if($failed){return $false}else{return $true}

You can go to that repository page for follow-ups (this answer is already too much of a copy-job)

Jan Doggen
  • 4,108
  • 10
  • 36
  • 51
5

The shortest way to test a TCP port that I have seen is:

(New-Object System.Net.Sockets.TcpClient).ConnectAsync("google.com", 80).Wait(100)

or

[System.Net.Sockets.TcpClient]::new().ConnectAsync("google.com", 80).Wait(100) 

Wait is in ms. This is an old PowerShell 2.0 compatible method.

Brian McMahon
  • 151
  • 1
  • 1
  • I like this, the latter one is very clean. Doesn't throw an exception, but it's so short one can if/else it if they need to, and exits out correctly when host is unreachable. I don't know if it leaves any sockets open and unclosed, but this looks like the ticket if it does clean itself up automatically. I'll leave my answer up for posterity – Hashbrown Jun 02 '23 at 01:04
4

Taking @Jan's answer I made it less messy and now it works for me in spawned tasks.
It's nice to throw exceptions and not rely on $error/API users using non-standard "verbose" stuff too (getting .Connected appears to spawn the SocketException which is neat).

        Function TestTCP { Param($address, $port, $timeout=2000)
            $socket=New-Object System.Net.Sockets.TcpClient
            try {
                $result=$socket.BeginConnect($address, $port, $NULL, $NULL)
                if (!$result.AsyncWaitHandle.WaitOne($timeout, $False)) {
                    throw [System.Exception]::new('Connection Timeout')
                }
                $socket.EndConnect($result) | Out-Null
                $socket.Connected
            }
            finally {
                $socket.Close()
            }
        }
Hashbrown
  • 2,782
  • 4
  • 32
  • 47
1

An even quicker way could be :

param($ip,$port)
New-Object System.Net.Sockets.TCPClient -ArgumentList $ip, $port

The result would be :

Client              : System.Net.Sockets.Socket
Available           : 0
Connected           : True
ExclusiveAddressUse : False
ReceiveBufferSize   : 65536
SendBufferSize      : 65536
ReceiveTimeout      : 0
SendTimeout         : 0
LingerState         : System.Net.Sockets.LingerOption
NoDelay             : False

The interessting value is "Connected"

edit : one more reason : Test-NetConnection only works from Powershell v5 (if I remember correctly), while this solution works from v2 :)

laurent
  • 19
  • 1
1

I had been searching for a super fast way ping many IPs and stumbled across this question (among others).

Eventually, I found a script that was easy to integrate into what I wanted to do. The guy calls it Fast Ping Sweep Asynchronous.

Even being a Power Shell n00b, I was able to pipe its output, and then modify its output to only include what I wanted. I came across other scripts, but could not decipher their scripts to modify them for my purposes.

I'm not sure what version Power Shell this requires, but it works on v4 and v5.

I have seen most of the Powershell IP scanner, ping sweeping scripts but none of them uses the PingASync method.The "problem" with synchronous scripts is that they have to wait until a node replies or times out before continuing to the next address.Using this approach may take s

function Global:Ping-IPRange {
    <#
    .SYNOPSIS
        Sends ICMP echo request packets to a range of IPv4 addresses between two given addresses.

    .DESCRIPTION
        This function lets you sends ICMP echo request packets ("pings") to 
        a range of IPv4 addresses using an asynchronous method.

        Therefore this technique is very fast but comes with a warning.
        Ping sweeping a large subnet or network with many swithes may result in 
        a peak of broadcast traffic.
        Use the -Interval parameter to adjust the time between each ping request.
        For example, an interval of 60 milliseconds is suitable for wireless networks.
        The RawOutput parameter switches the output to an unformated
        [System.Net.NetworkInformation.PingReply[]].

    .INPUTS
        None
        You cannot pipe input to this funcion.

    .OUTPUTS
        The function only returns output from successful pings.

        Type: System.Net.NetworkInformation.PingReply

        The RawOutput parameter switches the output to an unformated
        [System.Net.NetworkInformation.PingReply[]].

    .NOTES
        Author  : G.A.F.F. Jakobs
        Created : August 30, 2014
        Version : 6

    .EXAMPLE
        Ping-IPRange -StartAddress 192.168.1.1 -EndAddress 192.168.1.254 -Interval 20

        IPAddress                                 Bytes                     Ttl           ResponseTime
        ---------                                 -----                     ---           ------------
        192.168.1.41                                 32                      64                    371
        192.168.1.57                                 32                     128                      0
        192.168.1.64                                 32                     128                      1
        192.168.1.63                                 32                      64                     88
        192.168.1.254                                32                      64                      0

        In this example all the ip addresses between 192.168.1.1 and 192.168.1.254 are pinged using 
        a 20 millisecond interval between each request.
        All the addresses that reply the ping request are listed.

    .LINK
        http://gallery.technet.microsoft.com/Fast-asynchronous-ping-IP-d0a5cf0e

    #>
    [CmdletBinding(ConfirmImpact='Low')]
    Param(
        [parameter(Mandatory = $true, Position = 0)]
        [System.Net.IPAddress]$StartAddress,
        [parameter(Mandatory = $true, Position = 1)]
        [System.Net.IPAddress]$EndAddress,
        [int]$Interval = 30,
        [Switch]$RawOutput = $false
    )

    $timeout = 2000

    function New-Range ($start, $end) {

        [byte[]]$BySt = $start.GetAddressBytes()
        [Array]::Reverse($BySt)
        [byte[]]$ByEn = $end.GetAddressBytes()
        [Array]::Reverse($ByEn)
        $i1 = [System.BitConverter]::ToUInt32($BySt,0)
        $i2 = [System.BitConverter]::ToUInt32($ByEn,0)
        for($x = $i1;$x -le $i2;$x++){
            $ip = ([System.Net.IPAddress]$x).GetAddressBytes()
            [Array]::Reverse($ip)
            [System.Net.IPAddress]::Parse($($ip -join '.'))
        }
    }

    $IPrange = New-Range $StartAddress $EndAddress

    $IpTotal = $IPrange.Count

    Get-Event -SourceIdentifier "ID-Ping*" | Remove-Event
    Get-EventSubscriber -SourceIdentifier "ID-Ping*" | Unregister-Event

    $IPrange | foreach{

        [string]$VarName = "Ping_" + $_.Address

        New-Variable -Name $VarName -Value (New-Object System.Net.NetworkInformation.Ping)

        Register-ObjectEvent -InputObject (Get-Variable $VarName -ValueOnly) -EventName PingCompleted -SourceIdentifier "ID-$VarName"

        (Get-Variable $VarName -ValueOnly).SendAsync($_,$timeout,$VarName)

        Remove-Variable $VarName

        try{

            $pending = (Get-Event -SourceIdentifier "ID-Ping*").Count

        }catch [System.InvalidOperationException]{}

        $index = [array]::indexof($IPrange,$_)

        Write-Progress -Activity "Sending ping to" -Id 1 -status $_.IPAddressToString -PercentComplete (($index / $IpTotal)  * 100)

        Write-Progress -Activity "ICMP requests pending" -Id 2 -ParentId 1 -Status ($index - $pending) -PercentComplete (($index - $pending)/$IpTotal * 100)

        Start-Sleep -Milliseconds $Interval
    }

    Write-Progress -Activity "Done sending ping requests" -Id 1 -Status 'Waiting' -PercentComplete 100 

    While($pending -lt $IpTotal){

        Wait-Event -SourceIdentifier "ID-Ping*" | Out-Null

        Start-Sleep -Milliseconds 10

        $pending = (Get-Event -SourceIdentifier "ID-Ping*").Count

        Write-Progress -Activity "ICMP requests pending" -Id 2 -ParentId 1 -Status ($IpTotal - $pending) -PercentComplete (($IpTotal - $pending)/$IpTotal * 100)
    }

    if($RawOutput){

        $Reply = Get-Event -SourceIdentifier "ID-Ping*" | ForEach { 
            If($_.SourceEventArgs.Reply.Status -eq "Success"){
                $_.SourceEventArgs.Reply
            }
            Unregister-Event $_.SourceIdentifier
            Remove-Event $_.SourceIdentifier
        }

    }else{

        $Reply = Get-Event -SourceIdentifier "ID-Ping*" | ForEach { 
            If($_.SourceEventArgs.Reply.Status -eq "Success"){
                $_.SourceEventArgs.Reply | select @{
                      Name="IPAddress"   ; Expression={$_.Address}},
                    @{Name="Bytes"       ; Expression={$_.Buffer.Length}},
                    @{Name="Ttl"         ; Expression={$_.Options.Ttl}},
                    @{Name="ResponseTime"; Expression={$_.RoundtripTime}}
            }
            Unregister-Event $_.SourceIdentifier
            Remove-Event $_.SourceIdentifier
        }
    }
    if($Reply -eq $Null){
        Write-Verbose "Ping-IPrange : No ip address responded" -Verbose
    }

    return $Reply
}
YetAnotherRandomUser
  • 1,919
  • 4
  • 24
  • 49
  • This is pretty cool. Users should note that it sends icmp packets which might behave differently on the network, whereas the test-networkconnection based commands allow you to test a particular tcp port. Our environment actively denies icmp but allows port 443. – Ro Yo Mi May 20 '22 at 21:15
1

Here is my input, thanks for the code that got me started. All code is documented inline

PS> help Test-PortScan -examples

function Test-PortScan
{
<#
    .SYNOPSIS
        Tests to see if a port or range is open
    
    .DESCRIPTION
        A detailed description of the Test-PortScan function.
    
    .PARAMETER Devices
        IP or host name of devices to test.  It will take value from pipleline so you can test multiple devices at the same time.
    
    .PARAMETER StartPort
        Starting port # to test.  This defaults to 1
    
    .PARAMETER EndPort
        Last port of a range to scan.  This defaults to the same as Start port so             if you want to enter only 1 port, you do not have to set this variable.
    
    .PARAMETER Timeout
        Length of the timeout to test the connection in milliseconds.  The default is 100 milliseconds.
    
    .EXAMPLE
        Test-PortScan -devices 192.168.1.1 -StartPort 20 -EndPort 82
        Tests the device at 192.168.1.1 on ports 20 - 82
    
    .EXAMPLE
        Test-PortScan -Devices 192.168.1.1 -StartPort 80
            Tests the device at 192.168.1.1 for port 80 (also Test-PortScan 192.168.1.1 80)
    
    .EXAMPLE
        '192.168.1.1','192.168.1.2','122.168.1.3' | % {Test-PortScan -Devices $_ StartPort 22 25}
        Tests each of those proceeding IP addresses on ports 22 - 25
    
    .NOTES
        # Original code found here:  https://superuser.com/questions/805621/test-network-ports-faster-with-powershell
    #>
    
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true,
                   ValueFromPipeline = $true,
                   ValueFromPipelineByPropertyName = $true,
                   Position = 0,
                   HelpMessage = 'You must supply at least a device name or IP')]
        [Alias('Name')]
        [string]$Devices,
        [Parameter(Position = 1)]
        [int]$StartPort = 1,
        [Parameter(Position = 2)]
        [int]$EndPort = $StartPort,
        [Parameter(Position = 3)]
        [int]$Timeout = 100
    )

    BEGIN
    {
        $PSObject = @()
        $Output = @()
    }
    PROCESS
    {
        foreach ($Device in $Devices)
        {
            Write-Verbose "Not to the port loop yet"
            $Port = $StartPort
            do
            {
                #($Port = $StartPort; $Port -gt $EndPort; $Port++)              
                Write-Verbose "Made it to the port loop"
                $requestCallback = $state = $null
                $client = New-Object System.Net.Sockets.TcpClient
                $beginConnect = $client.BeginConnect($Device, $Port, $requestCallback, $state)
                Start-Sleep -Milliseconds $Timeout
                if ($client.Connected) { $Open = $true }
                else { $Open = $false }
                $client.Close()
                [pscustomobject]@{
                    'Computer' = $Device
                    'Port'     = $Port
                    'Open'     = $Open
                }
                $Port += 1
            }
            until ($Port -gt $EndPort)
        }
        
    }
    END
    {
        
    }
}