PowerShell Pipeline

Port Checking Using PowerShell

Check remote ports quickly by using PowerShell and the .NET Framework.

Sometimes while performing troubleshooting of an application or just testing whether you can access something across the network, being unable to connect to a required port on another system can impact an application or service that needs access. Back in the day we had the Telnet client available on our systems to quickly attempt a connection to the port and determine if it is open, but now that is more of an optional feature that is not installed by default and could actually be disallowed from being installed. This means that another approach is necessary in order to perform a port check.

Fortunately, through PowerShell, we are able to utilize some classes in the .NET Framework that will allow us to build a solution that can be used to check for an open port on a remote system.

The benefit to this also is that we do not need to possess administrator rights on the system that this will be run on. So yes, we can easily run this on a workstation with a standard user account to quickly test a remote port.

I already know what kind of class I need called TCPClient, but am not totally sure what the full name is, so I will do a quick search for it.

[appdomain]::CurrentDomain.GetAssemblies()  | ForEach {
Try {
$_.GetExportedTypes() | Where {
$_.fullname -match 'tcpclient'
}
} Catch {

}
} | Select Fullname
Figure 1. The .NET class that I need for port checking.

Now I know that I need to use System.Net.Sockets.TCPClient to build an object that will be used for my port connection attempt.

The object can be created simply by using New-Object and specifying the class name without any additional arguments.

$TCPClient = New-Object -TypeName  System.Net.Sockets.TCPClient
$TCPClient

Figure 2. The created TCPClient object.

At this point, we are not connected to anything as noted by looking at the Connected property (currently showing False). Our next step is to determine what remote system and what port will be checked to see if it is in fact open accepting connections. In this case, I am going to look at my domain controller and check for port 389, which is the LDAP port.

Knowing what port and system we want to check, the next step is figuring out what method we should be using to check the port.

$TCPClient | Get-Member -Type Method 
[Click on image for larger view.]  Figure 3. Available methods with possible connection approaches.

Here we have 3 potential ways to make a connection to test the port out on a remote system. Connect works fine, but there are potential issues where the attempt may hang for an extended period of time causing performance issues if you only wanted to wait a second or so before determining the connection to be unavailable.

The other two options (ConnectAsync and BeginConnect) give us an opportunity to attempt to make the connection and then wait a period of time before declaring that the connection to be unavailable.

BeginConnect Approach
Typically, the most common approach that I have seen centers around using BeginConnect and supplying the computer name and port and then specifying $Null on the last two parameters which are looking for an AsyncCallback object and a State object.

Once we call that method, we get back an object named System.Net.Sockets.Socket+MultipleAddressConnectAsyncResult which contains a method under its AsyncWaitHandle property called WaitOne, which in turn takes a parameter which is a timeout that is defined in milliseconds. This will then perform a blocking call until the timeout has been reached. It is assumed that if the timeout has been reached (a $False value is returned), then the connection could not complete successfully while a successful return ($True) means that the connection completed. If the result is $True, we can go ahead and call the EndConnect() method of the TCPClient using the AsyncResult object. If errors occurred, they will be displayed as well. We can use the $TCPClient.Connected property to accurately make sure that it did indeed connect if the timeout wasn't reached prior to the connection attempted being made prior to the timeout.

Ok, now that we have covered what is happening here, let's put this into action and see the results!

$Computername = 'DC1'
[int[]]$Port = 111,389
#Timeout in milliseconds
$TCPTimeout = 100

ForEach ($Item in $Port) {
$TCPClient = New-Object -TypeName  System.Net.Sockets.TCPClient
$AsyncResult = $TCPClient.BeginConnect($Computername,$Item,$null,$null)
$Wait = $Socket.AsyncWaitHandle.WaitOne($TCPtimeout)
If ($Wait) {
Try {
$Null = $TCPClient.EndConnect($AsyncResult)
} Catch {
Write-Warning $_
$Issue = $_.ToString()
} Finally {
[pscustomobject]@{
Computername = $Computername
Port = $Item
IsOpen = $TCPClient.Connected
Notes = $Issue
}
}
} Else {
[pscustomobject]@{
Computername = $Computername
Port = $Item
IsOpen = $TCPClient.Connected
Notes = 'Timeout occurred connecting to port'
}   
}
$Issue = $Null
$TCPClient.Dispose()
}

[Click on image for larger view.]  Figure 4. Results of port scan against DC1 on ports 111 and 389.

Here we can see that our connection to port 389 succeeded while a check on port 111 failed. I had the timeout for this connection attempt was set to only 100 milliseconds, but I wanted to have a quick check against the ports.

ConnectAsync Approach
The other approach which I personally haven't seen used in PowerShell scripts is the ConnectAsync approach. This behaves somewhat like BeginConnect with the exception that instead of returning an Async object that you can use later to test for a timeout, it returns a System.Threading.Tasks.Task<TResult> object that we can monitor to see if the task has been completed and if there are any errors. Let's take a look at an example:

$Computername = 'DC1'
[int]$Port = 389

$TCPClient = New-Object -TypeName System.Net.Sockets.TCPClient
$Task = $TCPClient.ConnectAsync($Computername,$Port)
$Task

[Click on image for larger view.]  Figure 5. Task result object being returned.

We can see that the task object shows that it has already been completed (by the IsCompleted property) and that there appear to be no errors (as shown by the Status not showing 'Faulted' and the Exception property is null). From this we can assume that the connection was good, but can easily verify by checking the Connected property of the TCPClient object.

Here is an example of the same object had we tried connecting to a port that isn't open.

[Click on image for larger view.]  Figure 6. Errors during the connection attempt reported in task.

We can see that our attempt did not fare as well as the previous attempt judging not only by the Status, but also the exception property. Because we have a Task object returned, we could set a timeout using Start-Sleep to basically way for a specific amount of time before assuming the connection is hung and most likely timed out and then take the necessary actions to report it. The thing to keep in mind is that disposing of the Task object may fail if the state property does not have one of the following values: RanToCompletion, Faulted or Canceled.
At the end, we continue to output an object that is familiar to our previous example with BeginConnect().

If ($Task.Status -eq 'Faulted') {
$Issue = $Task.Exception.InnerException
Write-Warning $Task.Exception.InnerException
}
[pscustomobject]@{
Computername = $Computername
Port = $Item
IsOpen = $TCPClient.Connected
Notes = $Issue
}
$Issue = $Null
$TCPClient.Dispose()
Figure 7. Output from ConnectAsync example.

The approach of cancelling a task is pretty complex, which is why I will typically go with the BeginConnect() method to handle timeouts.

With that, we have looked at a couple of options to check for an open port on a remote system that not only allow us to see if it is indeed open, but also how to handle connection attempts which may get hung up for one reason or another.

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular