How to iterate through a collection of items in a Parallel Foreach loop in PowerShell?

4 min read.

That’s a mouthful, right? But yeah – this article shows and tells how to do a parallel foreach loop in PowerShell, and how to do it properly!

Well, at least probably. This is how I did it anyway. But that’s enough chitchat, let’s get to it!

Background

In PowerShell, you can iterate through objects with a ForEach-Object loop like this:

# Create an array of integers
$items = 1..10
# Iterate through the items in the array
$items | ForEach-Object {
# Perform some operation on each item
$_ * 2
}

This code little script will loop through your integers one by one. And that’s nice and all – but what if you want to be fast, furious and utterly reckless?

Well, in comes the -Parallel switch!

What is a “Parallel” Foreach loop in PowerShell anyway?

By default, Foreach-Object processes items sequentially, meaning it executes the operation on one item at a time, waiting for each item to finish before moving on to the next.

A parallel ForEach-Object loop, on the other hand, enables you to execute the operations on multiple items simultaneously, taking advantage of the available processing power to improve efficiency. It allows for parallel execution of the script blocks within the loop, which can be beneficial when dealing with computationally intensive tasks or when you have independent operations that don’t rely on each other’s results.

PowerShell provides the ForEach-Object -Parallel parameter to enable parallel processing. Here’s an example of how it can be used:

$items = 1..10

$items | ForEach-Object -Parallel {
    # Perform some operation on each item
    $_ * 2
}

In the above example, the ForEach-Object -Parallel loop processes each item in parallel, executing the script block for each item simultaneously. The result is a new collection containing the doubled values of the original items.

It’s important to note that parallel execution gives 0 guarantees the order of the output elements to be the same as the input collection. Additionally, parallel execution introduces concurrency, which can have implications depending on the nature of the operations being performed. Therefore, it’s essential to consider potential race conditions or conflicts that may arise when modifying shared resources within a parallel loop.

Parallel foreach loops can significantly improve performance in certain scenarios, but it’s important to evaluate the specific use case and determine if parallel execution is appropriate and safe for your particular scenario.

Solution

Let’s go through a real-life example of a parallel foreach loop in PowerShell! In my case, I’m getting a bunch of AAD users, iterating through them to perform some magic on each user, and updating progress after each execution.

Time needed: 15 minutes

How to write a working parallel Foreach-Object loop in PowerShell?

  1. Grab a collection of something

    We’re going to iterate through something, so the whole thing starts with having a collection of something. Quite often these are going to be some complex objects that you’re getting back from somewhere.

    In my case – let’s grab some PnPUser objects:

    $users = Get-PnpUser

  2. Create an “index” array

    This is a bit weird – but now we’ll create an array of numbers to work as our indexer, the number by which we access the elements in our array.

    Somewhat like below:
    $total = $users.Count
    $indexArray = 0..$($total-1)

  3. Create your Foreach-Object -Parallel loop

    Now we’ll create the loop – something like below:
    $indexArray | Foreach-Object -Parallel {
    # Do something
    }


    This funny little loop will go through our numbers 0, 1, 2, 3 … all the way to the last index of our array. It might not do it in order, but it’ll do it in SOME order, eventually.

  4. Access the $users inside the loop

    Now comes a tricky part – you’ll need to access the “outside” array from inside your weird parallel loop. And you can’t do it by just doing $users[$_] because that’s forbidden.

    Instead, we’ll first “map” our external array to a new one that we’ll use inside the parallel loop. For this, we’ll need a weird “using” magic word, to tell PowerShell to grab the array from outside the loop:
    $usersInternal = $using:users

  5. NOW grab your item from the array

    Now we can access an item from the array:
    $user = $usersInternal[$_]

    And with this, do whatever we want.

  6. (OPTIONAL): Update progress

    Ah, well, as a nice bonus of the indexer array approach, we also have an easy way of calculating the progress of our script by doing something like below:

    $percentage = $_ / $using:total * 100
    Write-Progress -Activity "Search in Progress" -Status "$($percentage) % Complete:" -PercentComplete $percentage

A copy-pasteable script snippet

You didn’t think I’d only share you the concepts and not the actual code, did you? :)

Anyway – the pattern ends up looking somewhat like this:

# Get a collection of objects - in my case, I'm getting all users on a tenant
# It's actually a bit more complicated than this, but
# that's beyond the scope of this article
$users = Get-PnpUser 

$total = $users.Count

# We then pass the index of items to Foreach-Object
# Otherwise we can't get the "index" of each item in the
# collection

# This is done by instantiating an array of ints between
# "0" and "total number of items -1"
0..$($total-1) | Foreach-Object -Parallel {

  # Then we'll grab the $users from outside the loop
  # by calling it with using -keyword
  $usersInternal = $using:users

  # Then we can grab the user from the internal collection
  # (Because you can't access the "external" array
  # with the loop iterator $_)
  $user = $usersInternal[$_]

  # Now we can do whatever we want with the user

  # And of course, update progress like this:
  $percentage = $_ / $using:total * 100

  Write-Progress -Activity "Search in Progress" -Status "$($percentage) % Complete:" -PercentComplete $percentage
}

And now comes the question – did I have to figure out this convoluted way of looping through items just to update the progress properly? Was that the only reason why I would go through all this?

I guess we’ll never know :)

mm
5 1 vote
Article Rating
Subscribe
Notify of
guest

0 Comments
most voted
newest oldest
Inline Feedbacks
View all comments