Foreach -parallel (Part 3: Mass Ping)

by Dec 12, 2019

In PowerShell 7, there is a new parallel ForEach-Object that can execute code in parallel and speed up things considerably. The same technique can be used in Windows PowerShell via modules like this one:

Install-Module -Name PSParallel -Scope CurrentUser -Force 

Let’s take a look at interesting use cases. For example, if you’d like to get a list of computers that respond to ping (ICMP), this typically can take a long time. One way of speeding things up is to ping with a timeout so you don’t have to wait too long for non-responders.

This chunk of code pings powershell.one and waits a maximum of 1000 milliseconds for a response (taken from here: https://powershell.one/tricks/network/ping):

$ComputerName = 'powershell.one'
$timeout = 1000
$ping = New-Object System.Net.NetworkInformation.Ping
    
$ping.Send($ComputerName, $timeout) |
    Select-Object -Property Status, @{N='IP'={$ComputerName}}, Address 

Now let’s speed things up and mass-ping an entire IP range!

Let’s first take a look at how this can be done in PowerShell 7:

#requires -Version 7.0 

# IP range to ping
$IPAddresses = 1..255 | ForEach-Object {"192.168.0.$_"} 

# timeout in milliseconds
$timeout = 1000

# number of simultaneous pings
$throttleLimit = 80

# measure execution time
$start = Get-Date

$result = $IPAddresses | ForEach-Object -ThrottleLimit $throttleLimit -parallel {
    $ComputerName = $_
    $ping = [System.Net.NetworkInformation.Ping]::new()

    $ping.Send($ComputerName, $using:timeout) |
        Select-Object -Property Status, @{N='IP'={$ComputerName}}, Address
    } | Where-Object Status -eq Success
  
$end = Get-Date
$time = ($end - $start).TotalMilliseconds

Write-Warning "Execution Time $time ms"   

$result 

Within 5 seconds or so, the entire IP range is pinged, and the IP addresses answering the ICMP request are returned.

In Windows PowerShell, the code is almost the same (provided you have installed the PSParallel module from PowerShell Gallery):

#requires -Modules PSParallel
#requires -Version 3.0 

# IP range to ping
$IPAddresses = 1..255 | ForEach-Object {"192.168.0.$_"} 

# number of simultaneous pings
$throttleLimit = 80

# measure execution time
$start = Get-Date

$result = $IPAddresses | Invoke-Parallel -ThrottleLimit $throttleLimit -ScriptBlock {
    $ComputerName = $_
    # timeout in milliseconds
    $timeout = 1000

    $ping = New-Object -TypeName System.Net.NetworkInformation.Ping 

    $ping.Send($ComputerName, $timeout) |
        Select-Object -Property Status, @{N='IP'={$ComputerName}}, Address
    } | Where-Object Status -eq Success
  
$end = Get-Date
$time = ($end - $start).TotalMilliseconds

Write-Warning "Execution Time $time ms"   

$result

Instead of ForEach-Object, the code uses Invoke-Parallel, and since Invoke-Parallel does not support the “using:” prefix, all local variables must be included in the script block (in our example the variable $timeout).

Invoke-Parallel sports a fancy progress bar so you always know how many parallel tasks have been executed.


Twitter This Tip! ReTweet this Tip!