.NET to the rescue: Fast Port Test Command

Listen to this blog post!

Table of contents:

The built-in cmdlet Test-NetConnection can test ports; however, it is complex to use and slow:

1..254 | ForEach-Object { "192.168.2.$_" } | ForEach-Object {
  Test-NetConnection -Port 445 -ComputerName $_
}

The cmdlet is especially slow for any address that isn’t responding because it uses a relatively long timeout value that cannot be changed.

A better Port Scanning Tool

If you just want to test a port on an IP range or a list of servers, using .NET directly and creating your own function is a far more efficient approach:

function Test-Port
{
  [CmdletBinding(DefaultParameterSetName='multipleComputers')]
  param
  (
    [Parameter(Mandatory, Position=1, ParameterSetName='multiplePorts')]
    [Parameter(Mandatory, ValueFromPipeline, Position=0, ParameterSetName='multipleComputers')]
    [string]
    $ComputerName,
    
    [Parameter(Mandatory, Position=1, ParameterSetName='multipleComputers')]
    [Parameter(Mandatory, ValueFromPipeline, Position=0, ParameterSetName='multiplePorts')]
    [int]
    [ValidateRange(1,65535)]
    $Port,
    
    [int]
    [ValidateRange(100,100000)]
    $TimeoutMilliSec = 1000
  )
  
  begin
  {
    
  }
  process
  {
    try
    {
      $client = [System.Net.Sockets.TcpClient]::new()
      $task = $client.ConnectAsync($ComputerName, $Port)
      if ($task.Wait($TimeoutMilliSec))
      {
        $success = $client.Connected
      }
      else
      {
        $success = $false
      }
    }
    catch
    {
      $success = $false
    }
    finally
    {
      $client.Close()
      $client.Dispose()
      [PSCustomObject]@{
        ComputerName = $ComputerName
        Port = $Port
        Success = $success
      }
    }
  }
} 

You can now quickly test a port on an individual server:

PS C:\> Test-Port -Port 445 -ComputerName $env:COMPUTERNAME

ComputerName Port Success
------------ ---- -------
DELL7390      445    True



PS C:\> Test-Port -Port 80 -ComputerName microsoft.com

ComputerName  Port Success
------------  ---- -------
microsoft.com   80    True 

However, you can now test entire server lists or IP ranges as well. Thanks to the configurable timeout, this is much faster than before. The following line finds all computers in the specified IP range that have SMB enabled—including those with ICMP/ping disabled:

1..254 | ForEach-Object { "192.168.2.$_" } | Test-Port -Port 445 

Similarly, if you want to find network printers, use port 9100 instead. This line returns all printers and ignores any non-responding addresses:

1..254 | ForEach-Object { "192.168.2.$_" } | Test-Port -Port 9100 | Where-Object Success 

You can even pipe in a range of port numbers instead of IP addresses or server names. Test-Port automatically selects the appropriate parameter set and tests the range of ports on a single computer:

PS C:\> 80, 445, 5985, 5986 | Test-Port -Computername $env:computername

ComputerName Port Success
------------ ---- -------
DELL7390       80   False
DELL7390      445    True
DELL7390     5985    True
DELL7390     5986    True 

Related links