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.
16 Answers
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 1 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)
0An even quicker way could be :
param($ip,$port)
New-Object System.Net.Sockets.TCPClient -ArgumentList $ip, $portThe 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 : FalseThe 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 :)
2Taking @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() } } 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 #> [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
} 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: #> [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 { }
}