Jump to page sections

Last edited: 2023-05-18.

To check a remote computer for open/closed TCP ports using PowerShell version 2 (and up), you can use the Net.Sockets.TcpClient class from the .NET libraries. There's a few examples below if you just want something simple to use in your script - and also a full-fledged, rather polished port scanner (PSnmap).

There is also an old "Get-PortState" module here, but use PSnmap instead, or Test-NetConnection -Port N -ComputerName serverY in later PowerShell versions. This article was originally written sometime before PowerShell version 3, around 2010-2012. I went over it now and rewrote parts on 2023-05-18.

You are free to review the examples below in case you just want something simple in your own script, or to even use this script module (not really recommended!), but this PowerShell nmap-like script module (PSnmap) that I wrote years later beats this script module hands down in just about every imaginable way (except for how well it's documented). It's orders of magnitude faster, uses less resources, and is overall a more polished product. Subnets can also be specified using CIDR notation (e.g. 10.0.0.0/24) in that new script. PSnmap is also on GitHub here, with some stars even.

With PowerShell version 5 came "Test-Port" as well, for single ports (it is inferior in some ways to PSnmap):

Test-Port -ComputerName 192.168.0.1 -Port 80

Test-Port does not appear to exist in PowerShell version 7 as of 2021-10-11 - on Linux. PSnmap works fine in PowerShell 7 on Linux.

NB! I strongly advise you to use PSnmap instead!

I wrote a module called Get-PortState which produces custom PowerShell objects that can be assigned to a variable, sent to Format-Table for console display, exported to CSV, or whatever you might want to do with them. However, for the sake of convenience, there's an -ExportToCsv parameter that allows you to specify a CSV output file name directly when you run the Get-PortState command. You also have the data/objects from the last Get-PortState run available with the command Get-PortStateLast and can pass that to Export-Csv (or ConvertTo-Csv and Set-Content).

One thing I noticed in my lab environment, is that the timeout for closed and filtered ports against some computers seems to be pretty long. Due to this, I added an -AsJob parameter and a -Timeout parameter (default of 3000 ms / 3 seconds if omitted; only in use together with -AsJob). This will speed up processing in many scenarios, but at the expense of using more resources as it spawns a powershell.exe process for each port connection attempt.

The old script I initially put up is still available at the bottom of this article, in case someone wants it, but I do recommend getting the new one. The old one only works against a single target host and is mostly a telnet replacement for checking for open ports a bit more gracefully, as I mention there. The newer script does that, and more, and is more consistent with recommended PowerShell cmdlet design (but not 100%).

Example Code

Let's start with a simple code example in case you want to incorporate some basic code into a script you're writing:

foreach ($Computer in $ComputerName) {
    
    foreach ($Port in $Ports) {
        
        # Create a Net.Sockets.TcpClient object to use for
        # checking for open TCP ports.
        $Socket = New-Object Net.Sockets.TcpClient
        
        # Suppress error messages
        $ErrorActionPreference = 'SilentlyContinue'
        
        # Try to connect
        $Socket.Connect($Computer, $Port)
        
        # Make error messages visible again
        $ErrorActionPreference = 'Continue'
        
        # Determine if we are connected.
        if ($Socket.Connected) {
            "${Computer}: Port $Port is open"
            $Socket.Close()
        }
        else {
            "${Computer}: Port $Port is closed or filtered"
        }
        
        # Apparently resetting the variable between iterations is necessary.
        $Socket.Dispose()
        $Socket = $null
        
    }
    
}

Asynchronus port check with a timeout in milliseconds

To just check for an open port on a single host, you can use something like the code below. This also has the added bonus of using an asynchronous call with a specified timeout in milliseconds ("3000" here).

#$computer, $port = $args[0,1] # assign values to these
$mysock = new-object net.sockets.tcpclient
$IAsyncResult = [IAsyncResult] $mysock.BeginConnect($computer, $port, $null, $null)
measure-command { $succ = $iasyncresult.AsyncWaitHandle.WaitOne(3000, $true) } | % totalseconds
$succ
$mysock.Connected
#$mysock.Close()
$mysock.Dispose()

Reverse, check locally open ports, emulating netstat

To emulate "netstat" and show locally listening ports, established connections, and remote ports that one single, local server is connected to, you can use this code:

$NetworkProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()

$TcpConnections = $NetworkProperties.GetActiveTcpConnections()

$TcpConnections | Format-Table -AutoSize State LocalEndPoint RemoteEndPoint ----- ------------- -------------- Established 127.0.0.1:49156 127.0.0.1:49157 Established 127.0.0.1:49157 127.0.0.1:49156 Established 127.0.0.1:49158 127.0.0.1:49159 Established 127.0.0.1:49159 127.0.0.1:49158

$TcpConnections | Select -Exp LocalEndpoint -First 3 | ft -AutoSize

AddressFamily Address Port ------------- ------- ---- InterNetwork 127.0.0.1 49156 InterNetwork 127.0.0.1 49157 InterNetwork 127.0.0.1 49158

Get-PortState

The module Get-PortState exports two commands:
*Get-PortState
*Get-PortStateLast

Download of obsolete Get-PortState

*Get-PortState.zip - Download, remember to unblock, and unzip to a PowerShell modules folder. See the variable $env:PSModulePath. Or rename the .psm1 file to .ps1 and dot-source it. You can also run Import-Module against a (relative) arbitrary path containing the module.

But you do want PSnmap instead. It's so much better, and so much faster. Just have a look at that screenshot example.


Screenshot Example

Deprecated module example screenshot

NB! If you use the variable name $Data and dot-source the module/script rather than using it as a module, Get-PortStateLast will fail as the variable names collide. I'm using $Data in the script (I probably should change the variable name). The variable $Properties is also used.

However, if you use it as a module and import it with the Import-Module command, the variables will be correctly scoped to the module, and you won't have this issue.

Get-PortState Parameters

-ComputerName Required
-Port Required
-ExportToCsv Optional. Create a CSV report and save it to the specified file name, using UTF-8 encoding. The file will be overwritten without you being prompted, if it exists.
-Timeout Optional. Timeout in milliseconds before the script considers a port closed. Default 3000 ms (3 seconds). For speeding things up. Only in effect when used with the -AsJob parameter.
-AsJob Optional. Use one job for each port connection attempt. This allows you to override the possibly lengthy timeout from the connecting socket, and a port is considered closed if we haven't been able to connect within the allowed time. Default 3000 ms. See the -Timeout parameter. This may be quite resource-consuming! Ports that are determined to be closed via timeout will be tagged with a "(t)" for timeout.
-Dns Optional. Try to determine IP if given a host name or host name if given an IP. Multiple values are joined with semicolons: ";".
-NoPing Optional. Do not try to ping the target computer if this is specified. By default, the script skips the port checks on targets that do not respond to ICMP ping and populate the fields with hyphens for these hosts. Be aware that computers that do not resolve via DNS/WINS/NetBIOS will also be reported as having failed the ping check.
-ContinueOnPingFail Optional. Try to check the target computer for open ports even if it does not respond to ping. Be aware that computers that do not resolve via DNS/WINS/NetBIOS will also be processed like this (and it should report them as "closed").
-Quiet Optional. Do not display results with Write-Host directly as the script progresses.
-NoSummary Optional. Do not display a summary at the end. The summary includes start and end time of the script, and the output file name, if -ExportToCsv was specified.

A Few Simple Get-PortState Examples

Here is a simple example that just gets two open ports from one target, without using jobs. When ports are open, it usually goes fast. As you can see in the summary, it finishes in less than a second. However, if you do not use the -AsJob parameter, you can experience "hangs" of 20-30 seconds when ports are closed or filtered on some target hosts.

PS C:\PowerShell> Import-Module Get-PortState
PS C:\PowerShell> Get-PortState -comp 2008r2esxi -Port 3389,80 -Dns -Quiet

Start time:  07/29/2012 10:51:14
End time:    07/29/2012 10:51:14

ComputerName : 2008r2esxi
Ping         : Yes
IP/DNS       : 192.168.1.131
Port 80      : Open
Port 3389    : Open

PS C:\PowerShell> Get-PortStateLast | ft -a

ComputerName Ping IP/DNS Port 80 Port 3389 ------------ ---- ------ ------- --------- 2008r2esxi Yes 192.168.1.131 Open Open

To also suppress the summary, use the -NoSummary parameter along with -Quiet. Here I also pipe to Format-Table -Autosize (ft -a) for a neater display of the information.

PS C:\PowerShell> Import-Module Get-PortState
PS C:\PowerShell> Get-PortState -comp 2008r2esxi -Port 3389,80 -Dns -Quiet -NoSummary | ft -a

ComputerName Ping IP/DNS        Port 80 Port 3389
------------ ---- ------        ------- ---------
2008r2esxi   Yes  192.168.1.131 Open    Open

Using Get-PortStateLast

So you might find yourself forgetting to assign the results to a variable, and also forgetting to use the -ExportToCsv parameter. With this in mind, I implemented a Get-PortStateLast command that'll give you the custom PowerShell objects from the last Get-PortState command run (unless you've dot-sourced the script and have later changed the $Data variable).

PS C:\PowerShell> Import-Module Get-PortState
PS C:\PowerShell> Get-PortState -Comp 192.168.1.131,vista64esxi,ubuntu64esxi,192.168.1.153
-Port 80,22,3389,445,20000 -AsJob -Dns -quiet | ft -a

Start time:  07/29/2012 11:16:08
End time:    07/29/2012 11:17:03



ComputerName  Ping IP/DNS                    Port 22    Port 80    Port 445   Port 3389  Port 20000
------------  ---- ------                    -------    -------    --------   ---------  ----------
192.168.1.131 Yes  2008r2esxi.svendsen.local Closed (t) Open       Open       Open       Closed (t)
192.168.1.153 Yes  192.168.1.153             Open       Closed (t) Closed (t) Closed (t) Closed (t)
ubuntu64esxi  Yes  192.168.1.102             Open       Open       Open       Closed     Closed
vista64esxi   Yes  192.168.1.172             Closed (t) Closed (t) Open       Open       Closed (t)


PS C:\PowerShell> # I wanted a CSV file and don't want to start over. Now what?
PS C:\PowerShell> Get-PortStateLast | Export-Csv -NoType -Enc utf8 port-report.csv
PS C:\PowerShell> Import-Csv .\port-report.csv | ft -a

ComputerName  Ping IP/DNS                    Port 22    Port 80    Port 445   Port 3389  Port 20000
------------  ---- ------                    -------    -------    --------   ---------  ----------
192.168.1.131 Yes  2008r2esxi.svendsen.local Closed (t) Open       Open       Open       Closed (t)
192.168.1.153 Yes  192.168.1.153             Open       Closed (t) Closed (t) Closed (t) Closed (t)
ubuntu64esxi  Yes  192.168.1.102             Open       Open       Open       Closed     Closed
vista64esxi   Yes  192.168.1.172             Closed (t) Closed (t) Open       Open       Closed (t)
PS C:\PowerShell> # voila

Another Get-PortState Example

Here I get the IPv6 and IPv4 addresses for a Windows server connected to from a Windows 7 client, as well as whether the default RDP port is available:

PS C:\> Import-Module Get-PortState
PS C:\> Get-PortState -comp 2008r2esxi -port 3389 -Dns -quiet -nosum | ft -a

ComputerName Ping IP/DNS                                     Port 3389
------------ ---- ------                                     ---------
2008r2esxi   Yes  fe80::b434:bc55:a730:d672%10;192.168.1.131 Open


PS C:\> Get-PortStateLast | select -expand 'IP/DNS' | %{ $_ -split ';' }
fe80::b434:bc55:a730:d672%10
192.168.1.131
PS C:\>

Using Get-PortState From Another Script

I don't know how likely it is, but in case someone wants to use the Get-PortState command from another script, or for other purposes than making a report, I should briefly address how you would use the script/module in this regard.

Something like this is the least you can get away with:

PS C:\> if ( (Get-PortState ubuntu64esxi 22 -nos -q).'Port 22' -eq 'Open' ) { 'Do stuff' } else { 'Do other stuff' }
Do stuff

PS C:\> if ( (Get-PortState ubuntu64esxi 23 -nos -q).'Port 23' -eq 'Open' ) { 'Do stuff' } else { 'Do other stuff' }
Do other stuff

Old Check-Open-Ports.ps1 Script

Initially I wrote a simpler version of the script that's now featured on this page. I'm keeping it along with the original description for the time being:

This PowerShell script checks for open ports, or an open port, using the .NET class Net.Sockets.TcpClient. Why didn't I think of this sooner? Essentially it's a replacement for using telnet for checking for open ports. I use this script now, rather than having to start a separate cmd window for each port to test with telnet, and then having the useless cursor in the top left corner, and usually having to close the window or wait for a timeout, if the port is open.

It works like this:

Screenshot of super simple, deprecated (in favor of PSnmap) script to simply check for open ports against a remote computer

Download Old Check-Open-Ports.ps1

Download Check-Open-Ports.ps1.txt (right-click and "save as").

Powershell      Windows      Networking      Asynchronous      PSnmap      TCP          All Categories

Google custom search of this website only

Minimum cookies is the standard setting. This website uses Google Analytics and Google Ads, and these products may set cookies. By continuing to use this website, you accept this.

If you want to reward my efforts