Last Update: Sep 04, 2024 | Published: May 11, 2015
Over the last few articles on the Petri IT Knowledgebase, I’ve been walking you through the process of creating a PowerShell-based tool that you could use to ping a range of subnet addresses. I hope you’ve been following along and if not, then take a few minutes to get caught up. Hopefully you’ll notice I’ve followed an iterative process, where I started with a basic command and slowly added pieces onto the tool. If I had tried to write everything all at once and then test, then it’s more than likely that something would not work, and it might take longer to figure out why. Today, I want to add the final piece to the tool, where we’ll add a port check to our existing tool.
PowerShell Ping Sweep Tool Article Series
Philosophically, PowerShell tools, that is scripts and functions, should be designed to do one thing and write a single object type to the pipeline. One of the features I want to add is a quick port check. On one hand, this seems like it should be a completely separate tool. But I’m going to limit my port scan to a single user-defined port. My assumption is that you might want to use the sweep tool to not only see what IP addresses are up, but also see which IT addresses that might have a specific port open.
First, let’s test some code to handle the port scan. I want to test a single server and port.
$IP = "172.16.30.11"
$Port = 80
I already know this port is open, so whatever code I come up with needs to reflect that fact. There’s no easy way around this, but we’ll have to use the .NET Framework directly. How did I know what to use? Google.
$tcp = New-Object System.Net.Sockets.TcpClient
But now you know as well. Here’s what we have.
The object has a method, which you could discover by piping to Get-Member or reading the MSDN documentation called ConnectAsync. It needs an IP address and port.
$tcpconnection = $tcp.ConnectAsync($IP,$Port)
This has made a connection. There is also a Connect method, which you could have used to test. But I used ConnectAsync for performance reasons. Here’s what the object looks like.
You might think you could use the IsCompleted property, but as far as I can tell that has to do with whether the connection process is complete. It turns out we need to dig a bit deeper and look at the AsyncWaitHandle property. This property is a nested object that has a method called WaitOne that will wait for a connection to be made. I’m going to give PowerShell 1000 milliseconds to connect to the port.
$wait = $tcpconnection.AsyncWaitHandle.WaitOne(1000,$False)
The value of $wait will be true or false, which is very handy in PowerShell.
Of course, I should verify that with another port that I know is not open.
$port = 22
$tcp = New-Object System.Net.Sockets.TcpClient
$tcpconnection = $tcp.ConnectAsync($IP,$Port)
$wait = $tcpconnection.AsyncWaitHandle.WaitOne(1000,$False)
Just what I expected. While PowerShell will probably clean up for me, it is a good practice to close and dispose of the connection.
$tcpconnection.Dispose()
$tcp.Dispose()
In my function, I’ll add a parameter called Port, but I won’t assign a default value. As the function processes each IP address, if there is a port value, I can test using the code from above.
If you recall, the function is writing a custom object to the pipeline and I’m going to add a property called Port. If the port is open, I’ll include the value in the output. Otherwise I’ll set the value to null. If no port is tested, then the property will also be null. Here is the finished function.
Function Test-Subnet { [cmdletbinding()] Param( [Parameter(Position=0,HelpMessage="Enter an IPv4 subnet ending in 0.")] [ValidatePattern("d{1,3}.d{1,3}.d{1,3}.0")] [string]$Subnet= ((Get-NetIPAddress -AddressFamily IPv4).Where({$_.InterfaceAlias -notmatch "Bluetooth|Loopback"}).IPAddress -replace "d{1,3}$","0"), [ValidateRange(1,255)] [int]$Start = 1, [ValidateRange(1,255)] [int]$End = 254, [ValidateRange(1,10)] [Alias("count")] [int]$Ping = 1, [int]$Port ) Write-Verbose "Pinging $subnet from $start to $end" Write-Verbose "Testing with $ping pings(s)" #a hash table of parameter values to splat to Write-Progress $progHash = @{ Activity = "Ping Sweep" CurrentOperation = "None" Status = "Pinging IP Address" PercentComplete = 0 } #How many addresses need to be pinged? $count = ($end - $start)+1 <# take the subnet and split it into an array then join the first 3 elements back into a string separated by a period. This will be used to construct an IP address. #> $base = $subnet.split(".")[0..2] -join "." #Initialize a counter $i = 0 #loop while the value of $start is <= $end while ($start -le $end) { #increment the counter write-Verbose $start $i++ #calculate % processed for Write-Progress $progHash.PercentComplete = ($i/$count)*100 #define the IP address to be pinged by using the current value of $start $IP = "$base.$start" #Use the value in Write-Progress $proghash.currentoperation = $IP Write-Progress @proghash #get local IP $local = (Get-NetIPAddress -AddressFamily IPv4).Where({$_.InterfaceAlias -notmatch "Bluetooth|Loopback"}) #test the connection if (Test-Connection -ComputerName $IP -Count $ping -Quiet) { #if the IP is not local get the MAC if ($IP -ne $Local.IPAddress) { #get MAC entry from ARP table Try { $arp = (arp -a $IP | where {$_ -match $IP}).trim() -split "s+" $MAC = $arp[1] } Catch { #this should never happen but just in case Write-Warning "Failed to resolve MAC for $IP" $MAC = "unknown" } } else { #get local MAC $MAC = ($local | Get-NetAdapter).MACAddress } #test Port if specified if ($Port) { $tcp = New-Object System.Net.Sockets.TcpClient $tcpconnection = $tcp.ConnectAsync($IP,$Port) $wait = $tcpconnection.AsyncWaitHandle.WaitOne(1000,$False) If ($wait) { #assign port number of it responds $PortTest = $Port $tcpconnection.Dispose() } else { Write-Verbose "Port $port not open" #assign Null if port not open $PortTest = $Null } $tcp.Dispose() } #if $Port else { #if not testing a port set value to Null $PortTest = $Null } #attempt to resolve the hostname Try { $iphost = (Resolve-DNSName -Name $IP -ErrorAction Stop).Namehost } Catch { Write-Verbose "Failed to resolve host name for $IP" #set a value $iphost = "unknown" } Finally { #create a custom object [pscustomobject]@{ IPAddress = $IP Hostname = $iphost MAC = $MAC Port = $PortTest } } } #if test ping #increment the value $start by 1 $start++ } #close while loop } #end function
With the function loaded into my PowerShell session, I can test a range of addresses and see what is listening on port 53.
test-subnet 172.16.30.0 -Start 200 -end 215 -Port 53
Because I have an object, I can use my tool like any other command in a PowerShell expression.
test-subnet 172.16.30.0 -Port 80 | where {$_.port -eq 80} | Select IPAddress,Hostname
The only task that remains is to insert some comment based help, which I will leave for you to write. I’ll be the first to admit this is far from a perfect PowerShell tool, but I hope the process served as a learning platform. If you have any questions on what I did or why, I hope you’ll leave a comment.