Fast TCP port check in Powershell#
The Test-NetConnection cmdlet is great and verbose but too slow if the remote port to check is not opened. This is due to its timeout setting and cannot be modified. In this port, I will show you a custom function that leverages the power of System.Net.Sockets.TcpClient to accelerate the port test.
Note
Update 2019-12-31: I didn't mention Test-Connection
previously because although it has the parameter -TimeoutSeconds
, its output only has True
or False
. What a pity. But things are going to be changed, as per this github issue, @jackdcasey is preparing a pull request to make Test-Connection's output verbose enough.
Test-NetConnection is slow if the port is not opened#
If the port is opened, it's OK.
# if the port is opened
6.2.2> Measure-Command {Test-NetConnection www.google.fr -Port 80} | % TotalSeconds
0,2015152
But if the port is not opened, it would be better to take a coffee to wait for the result.
# if the port is not opened
6.2.2> Measure-Command {Test-NetConnection www.google.fr -Port 123} | % TotalSeconds
WARNING: TCP connect to (2a00:1450:4007:805::2003 : 123) failed
WARNING: TCP connect to (172.217.18.195 : 123) failed
42,5026257
For most of the cases, we only need to test a TCP port in a fast network (often LAN), waiting for 42 seconds is ridiculous, but unfortunately, Test-NetConnection doesn't provide a parameter to decrease the timeout.
System.Net.Sockets.TcpClient is fast#
"Talk is cheap. Show me the code."
Test-Port demos#
# if the port is opened
6.2.2> Measure-Command {Test-Port www.google.fr 80} | % TotalSeconds
0,0648323
# if the port is not opened
6.2.2> Measure-Command {Test-Port www.google.fr 123} | % TotalSeconds
1,0072371
# it works with pipeline too
6.2.2> Measure-Command {"www.google.fr:80", "www.orange.fr:123", "www.free.fr" | Test-Port} | % TotalSeconds
2,0201628
# the output of the Test-Port, the default port to check is TCP 5985
6.2.2> "www.google.fr:80", "www.orange.fr:123", "www.free.fr" | Test-Port | ft -a
RemoteHostname RemotePort PortOpened TimeoutInMillisecond SourceHostname OriginalComputerName
-------------- ---------- ---------- -------------------- -------------- --------------------
www.google.fr 80 True 1000 DELL-ZX www.google.fr:80
www.orange.fr 123 False 1000 DELL-ZX www.orange.fr:123
www.free.fr 5985 False 1000 DELL-ZX www.free.fr
Test-Port source code#
The code is still in POC, there're still many parts to improve. For example, validating the given $ComputerName by resolving its IP, and error handling, etc.
function Test-Port {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, HelpMessage = 'Could be suffixed by :Port')]
[String[]]$ComputerName,
[Parameter(HelpMessage = 'Will be ignored if the port is given in the param ComputerName')]
[Int]$Port = 5985,
[Parameter(HelpMessage = 'Timeout in millisecond. Increase the value if you want to test Internet resources.')]
[Int]$Timeout = 1000
)
begin {
$result = [System.Collections.ArrayList]::new()
}
process {
foreach ($originalComputerName in $ComputerName) {
$remoteInfo = $originalComputerName.Split(":")
if ($remoteInfo.count -eq 1) {
# In case $ComputerName in the form of 'host'
$remoteHostname = $originalComputerName
$remotePort = $Port
} elseif ($remoteInfo.count -eq 2) {
# In case $ComputerName in the form of 'host:port',
# we often get host and port to check in this form.
$remoteHostname = $remoteInfo[0]
$remotePort = $remoteInfo[1]
} else {
$msg = "Got unknown format for the parameter ComputerName: " `
+ "[$originalComputerName]. " `
+ "The allowed formats is [hostname] or [hostname:port]."
Write-Error $msg
return
}
$tcpClient = New-Object System.Net.Sockets.TcpClient
$portOpened = $tcpClient.ConnectAsync($remoteHostname, $remotePort).Wait($Timeout)
$null = $result.Add([PSCustomObject]@{
RemoteHostname = $remoteHostname
RemotePort = $remotePort
PortOpened = $portOpened
TimeoutInMillisecond = $Timeout
SourceHostname = $env:COMPUTERNAME
OriginalComputerName = $originalComputerName
})
}
}
end {
return $result
}
}
Test-Port in parallel#
Although the timeout in Test-Port is 1000 milliseconds, if we have 100 hosts to check and if all the ports are not opened, Test-Port will be slow too, because it runs the check in serial.
I don't prefer to implement the parallel inside Test-Port, as we have already some pure powershell parallel solutions by using the RunspacePool (PoshRSJob, Invoke-Parallel, etc.). And Microsoft is releasing its home-born parallel mechanism ForEach-Object -Parallel
for Powershell 7.