powershellpingjobs

PowerShell quickly Ping Subnet with Jobs


The following function will ping my subnet with PingRange 1 254 to check IP's:

function PingRange ($from, $to) {
    $from..$to | % {"192.168.0.$($_): $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$($_ ) -quiet -count 1)"}
}

However, this is slow, so I was wondering if it's possible to ping them all concurrently, then collect the results. I guess that this would mean:

  1. Using Start-Job on each Test-Connection (which I can do, that part is easy).

  2. Waiting for all to complete.

  3. Collecting only the ping success results and sorting them.

    function PingRange $from $to { $from..$to | % {Start-Job { "192.168.0.$($_): $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$($_ ) -quiet -count 1)"} } Wait-Job *some test to check if all jobs are complete* Receive-Job some way to get each result, discard all failures, then sort and output to screen }

Is there a shorthand way to do a Wait-Job that will just wait for all spawned jobs to complete?

Receiving the information also seems tricky, and when I try it, I invariable get nothing back from Receive-Job (or a nasty error usually). Hopefully someone more expert on PowerShell Jobs knows how to grab these results easily?


Solution

  • Preface:

    The answer below focuses on command-agnostic ways to achieve concurrency (not just with jobs).


    In PowerShell 7, you can use ForEach-Object -Parallel, which can greatly simplify your function, by running your commands in parallel, using different threads:

    function PingRange ($from, $to) {
      $from..$to | ForEach-Object -Parallel {
        "192.168.0.$_`: $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$_ -quiet -count 1)"
      } -ThrottleLimit ($to - $from + 1) 2>$null -ErrorVariable err | Sort-Object
    }
    

    In Windows PowerShell, you do need jobs for concurrency, but it's better to use Start-ThreadJob than Start-Job, because thread jobs have much less overheads than the standard background jobs, which are child-process-based.
    Jobs of either type can be managed with the same set of other job-related cmdlets, such as Receive-Job, shown below.

    Note: The implementing ThreadJob module ships with PowerShell 7; in Windows PowerShell you can install it on demand; e.g.: Install-Module ThreadJob -Scope CurrentUser.

    function PingRange ($from, $to) {
      $from..$to | ForEach-Object {
        Start-ThreadJob -ThrottleLimit ($to - $from + 1) { 
          "192.168.0.$using:_`: $(Test-Connection -BufferSize 2 -TTL 5 -ComputerName 192.168.0.$using:_ -quiet -count 1)" 
        }
      } | Receive-Job -Wait -AutoRemove -ErrorAction SilentlyContinue -ErrorVariable err |
          Sort-Object 
    }
    

    Note the need for $using:_ in order to reference the enclosing ForEach-Object script block's $_ variable.

    While Start-ThreadJob uses threads (runspaces) to run its jobs, the resulting job objects can be managed with the standard job cmdlets, namely Wait-Job, Receive-Job and Remove-Job.


    Advantages of using Start-ThreadJob over Start-Job:

    The only - largely hypothetical - downside of Start-ThreadJob is that a crashing thread could crash the entire process, but note even a script-terminating error created with Throw only terminates the thread (runspace) at hand, not the caller.

    In short: use Start-Job only if you need full process isolation; that is, if you need to ensure the following:

    Note that in both Start-ThreadJob and Start-Job jobs, the jobs do not see the caller's state in terms of: