My last blog post covered a simple, four line, way to scan an IP range.  For a small range of IP addresses this solution works well.  For a large IP range…not so much.

One clarification before moving on.  This and the previous post require PowerShell 2.0 (#requires -version 2.0).  Why? The test-connection cmdlet is new to PowerShell 2.0.  While you could write a function using ping.exe or the .NET method of pinging it's much easier to just use test-connection.

http://go.microsoft.com/fwlink/?LinkID=135266

Back to business.

The reason why the simple method does not work well for large ranges would be glaringly obvious if you ever tried to run it on a subnet of say 254 or 510 IP addresses.  Test one IP, respond with results, test second IP, respond with results…etc.  Now imagine there are several hundred IP’s not responding.  It’s going to take a while to scan it all.  Does it work? Yes.  Does it work efficiently.  Not even a little.

Fortunately the less than simple PowerShell IP range scanner is not very complex either, as long as you are familiar with the concept of jobs.

http://technet.microsoft.com/en-us/library/dd315273.aspx

PowerShell jobs allow you to run sub-instances of code asynchronously.  That’s a fancy way of saying you can do lots of things at the same time.  To make test-connection a job all you need to do is add -asJob to the command, like so:

	...test-connection "$firstThree`.$_" -count 2 -quiet -asJob...

 

Or you can use the start-job cmdlet.  For my purposes I use start-job because I need to return an IP with the correct boolean response from test-connection.

Now for some bad news.  Since jobs run in separate background processes on the system the main script body has no idea what’s going on in the background unless you tell it to look.  Now for some good news.  The Microsoft PowerShell team did a great job of making it easy(ish) to collect information from those background jobs.  Let’s take a look at the final code, shall we?

 
  1. # asynchronous IP range scanner  
  2.  
  3. # define the range  
  4. [string]$firstThree = “192.168.1”  
  5. [int]$startRange = 100  
  6. [int]$endRange = 110  
  7. # defines how many IPs to scan at a time. Used to limit the amount of resources used by the scan.  
  8. $groupMax = 50  
  9.  
  10. # start the range scan as jobs  
  11. $count = 1  
  12. $startRange..$endRange | %{  
  13.         # start a test-connection job for each IP in the range, return the IP and boolean result from test-connection  
  14.         start-job -ArgumentList “$firstThree`.$_” -scriptblock { $test = test-connection $args[0] -count 2 -quiet; return $args[0],$test } | out-null  
  15.         # sleep for 3 seconds once groupMax is reached. This code helps prevent security filters from flagging port traffic as malicious for large IP ranges.  
  16.         if ($count -gt $groupMax) {  
  17.             sleep 3  
  18.             $count = 1  
  19.         } else {  
  20.             $count++  
  21.         }  
  22.     }  
  23.  
  24. # wait for all the jobs to finish  
  25. get-job | wait-job  
  26.  
  27. # store the jobs into an array  
  28. $jobs = get-job  
  29. # holds the results of the jobs  
  30. $results = @()  
  31. foreach ($job in $jobs) {  
  32.     # grab the job output  
  33.     $temp = receive-job -id $job.id -keep  
  34.     $results += ,($temp[0],$temp[1])  
  35. }  
  36.  
  37. # stop and remove all jobs  
  38. get-job | stop-job  
  39. get-job | remove-job  
  40.  
  41. # sort the results  
  42. $results = $results | sort @{Expression={$_[0]}; Ascending=$false}  
  43. # report the results  
  44. foreach ($result in $results) {  
  45.     if ($result[1]) {  
  46.         write-host -f Green “$($result[0]) is responding”  
  47.     } else {  
  48.         write-host -f Red “$($result[0]) is not responding”  
  49.     }  

This snippet does the same thing as the simple IP range scanner except it does it *much* faster. How fast? I used measure-command to test the same range of ten(10) IP addresses on my network.  Eight of those IPs do not respond, two do.  The difference?  Drum roll please.

Asynchronous IP range scan:

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 18
Milliseconds      : 199
Ticks             : 181999900
TotalDays         : 0.000210648032407407
TotalHours        : 0.00505555277777778
TotalMinutes      : 0.303333166666667
TotalSeconds      : 18.19999
TotalMilliseconds : 18199.99

Synchronous IP range scan:

Days              : 0
Hours             : 0
Minutes           : 1
Seconds           : 4
Milliseconds      : 667
Ticks             : 646679415
TotalDays         : 0.000748471545138889
TotalHours        : 0.0179633170833333
TotalMinutes      : 1.077799025
TotalSeconds      : 64.6679415
TotalMilliseconds : 64667.9415

Does three and a half times faster sound good?  I thought so.  If you have any questions about the code, or how to convert it to a function or stand alone app, feel free to add a comment below.

Written by James Kehr Employee @ SherWeb