Speedy Ping using PowerShell

For those that know me, you know how much I love runspaces and preach about their use for their quickness and being lightweight in PowerShell to accomplish your day to day activities where you need to run many things in parallel and at the same time want to throttle how many things run at a time. In fact, I loved them so much that I made a module to make the process easier for everyone else!

Anyways, one of the most common things that a Sysadmin might do is use ping or Test-Connection to check if a system is online (or at least is allowed to send ICMP responses back) so that you can perform additional work or document that the system is still available. This works great, but what if you wanted to ping many systems at a time to figure out their availability? Most likely you would grab a collection of systems and work through each one to check with Test-Connection.

In fact, with Test-Connection you have the –AsJob parameter which pushes all of the work to a single PSJob that checks all of the systems and returns completed when all of the systems have been checked. Note that you need remoting configured on those systems. This is actually a pretty fast approach to performing a large scale ping of your systems. In my testing I found it to be at or near the top on a number of iteration variations.

image

Test-Connection -ComputerName google.com,prox-hyperv -Count 1 -AsJob | 
Wait-Job | Receive-Job

image

Another approach is to use a runspacepool with runspaces to provide some multithreading to attempt to ping more than one system at a time. I won’t cover using Start-Job as history shows that they are heavy and runspaces perform much faster than PSJobs. In my example, I use my module, PoshRSJob with Test-Connection in an attempt to get the most impact from runspaces.

Lastly, and the technique I am starting to use more and more is the SendPingAsync Method of the System.Net.NetworkInformation.Ping class which sends the work to another Task object in which the operation is performed in the background and you can check to see if it is completed after a while or wait on the object using [Threading.Tasks.Task]::WaitAll(). An example of doing this is below:

$Task = (New-Object System.Net.NetworkInformation.Ping).SendPingAsync('prox-hyperv')
$Task

image

Here I am creating the System.Net.NetworkInformation.Ping class and then immediately calling SendPingAsync() while supplying the name of the system that I want ping. The object returned is a

System.Threading.Tasks.Task`1[[System.Net.NetworkInformation.PingReply, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] object. Yea, that is quite the object name and you will notice that it is strongly typed for the particular object (System.Net.NetworkInformation.PingReply) that is returned from the Ping but wrapped in the System.Threading.Tasks.Task.

Digging more into this object, we can look at the results in the result property.

$Task.Result

image

In this case, our result of successful and we even have the returned IP address. Also note that by the time I displayed the $Task after saving the object to it, the command was already done. If there was some sort of failure, it would have looked like this:

image

Definitely a different look here. There is nothing in the Result property and the error provides a lot of data with the most important piece being that “No such host is known”. What is unfortunate here is that there is no record of the system that this ping was attempted with. This can make it more difficult if you decided to use multiple systems with your ping async attempt. But the tradeoff for this is its amazing speed by use of the Async method.

Being the curious person that I am, I wanted to run a performance test using these approaches and see how they stacked up. The methods that I am using are:

  1. SendPingAsync
  2. Test-ConnectionAsync
    1. Wrapper for the SendPingAsync method that provides the computername tested; download here.
  3. PoshRSJob for runspaces
  4. Test-Connection using –AsJob
  5. Test-Connection within a ForEach () {} loop

The code that I am using is below and it covers what I feel are the most likely checks for online systems.

1,10,100,500,1000,5000 | ForEach {
    Write-Verbose "Iteration: $_" -Verbose
    $Computername = (1..$_).ForEach({'prox-hyperv'})
    $Time = (measure-command {
        $Task = forEach ($Computer in $Computername) {
            (New-Object System.Net.NetworkInformation.Ping).SendPingAsync($Computer)
        }
        [Threading.Tasks.Task]::WaitAll($Task)
        $Task.Result
    }).TotalMilliseconds
    [pscustomobject]@{
        Type = 'SendPingAsync'
        Count = $_        
        Time_MS = $Time
    }

    $Time = (Measure-Command {
        $Computername|Start-RSJob {Test-Connection -ComputerName $_ -Count 1} -Throttle 5 |
        Wait-RSJob | Receive-RSJob
    }).TotalMilliseconds
    [pscustomobject]@{
        Type = 'RSJob'
        Count = $_        
        Time_MS = $Time
    }

    $Time = (Measure-Command {
        Test-ConnectionAsync -Computername $Computername
    }).TotalMilliseconds
        [pscustomobject]@{
        Type = 'Test-ConnectionAsync'
        Count = $_        
        Time_MS = $Time
    }

    $Time = (Measure-Command {
        ForEach ($Computer in $Computername) {
            Test-Connection -ComputerName $Computer -Count 1 
        }
    }).TotalMilliseconds
        [pscustomobject]@{
        Type = 'Test-Connection:ForEach'
        Count = $_        
        Time_MS = $Time
    }

    $Time = (Measure-Command {
        $Job = Test-Connection -ComputerName $Computername -AsJob -Count 1 
        $Job | Wait-Job | Receive-Job
    }).TotalMilliseconds
        [pscustomobject]@{
        Type = 'Test-Connection:AsJob'
        Count = $_        
        Time_MS = $Time
    }

    Get-Job|Remove-Job
    Get-RSJob|Remove-RSJob
}

I found that eventually Test-Connection fails me with the –AsJob parameter as the list of machines gets larger (besides slowing down some). Turns out that this was reported out on User Voice as a possible bug, so I encourage you to vote on this if you agree with me. That link is here: https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11087637-test-connection-throws-quota-exception-when-passedSNAGHTML1a343600

Now because of this, the results are going to be off for Test-Connection, but as I mentioned earlier, the results were starting to drift from being the fastest to being more to the middle of the pack.

image

image

image

image

image

I am ignoring Test-Connection:AsJob as it failed with a Quota Violation in the final two sets of results.

image

image

So the results look rather interesting. The smaller batches show that using Test-Connection with the –AsJob parameter performed right along with the SendPingAsync approaches. More interesting is that using runspaces really didn’t compete at all with those approaches. As we got into the 500 count batch, the SendPingAsync began to separate itself from the Test-Connection –AsJob and the final two tests were inconclusive for –AsJob as it met quota violations while the Async approaches kept on marching with some great times compared to everything else. My assumption is that it would have beat Test-Connection –AsJob.

So what does this all mean? Well, it means that most of the time, you could get by with Test-Connection using AsJob assuming that your systems have remoting enabled. But if you wanted to quick way to look at hundreds of IPs or hostnames, then using the Async approach will not fail you.

Feel free to give my code a try and let me know what you find out in the comments below!

This entry was posted in powershell and tagged , , , . Bookmark the permalink.

2 Responses to Speedy Ping using PowerShell

  1. Pingback: Speedy Ping using PowerShell | Learn Powershell | Achieve More | Soyka's Blog

  2. Pingback: Performing an Asynchronous DNS Lookup Using PowerShell | Learn Powershell | Achieve More

Leave a comment