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 (but 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%).
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 } }
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()
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
The module Get-PortState exports two commands:
*Get-PortState
*Get-PortStateLast
*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.
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.
-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. |
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 : OpenPS 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
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
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:\>
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
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:
Download Check-Open-Ports.ps1.txt (right-click and "save as").
Powershell Windows Networking Asynchronous PSnmap TCP All CategoriesMinimum 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.