PowerShell Genie

PowerShell ForEach-Object -Parallel

ForEach-Object -Parallel in PowerShell 7 runs each iteration in its own runspace at the same time. One switch turns a serial loop into a concurrent one.

Lesson 2 min read Windows PowerShell 5.1 and PowerShell 7

Documentation: Microsoft Learn

Overview

The rule that trips everyone: each iteration is a separate runspace, so your variables are not there. $prefix from the calling scope is empty inside the block. Prefix it with $using: to bring it in, so the string becomes $using:prefix followed by a dash and $_. Miss that and you get blanks or nulls, not an error.

-ThrottleLimit controls how many run concurrently and defaults to 5. Raise it for network-bound work where you are waiting on responses; leave it low for anything CPU-bound or hitting a single machine.

Parallel is not automatically faster. Each runspace has setup cost, so for a fast operation over a small collection the serial loop wins. It pays off when each iteration takes real time, particularly waiting on the network.

Order is not preserved. Results arrive as they complete, so sort afterwards if order matters.

Inspect this example’s structure

Read the command and its supplied arguments. This example requires real PowerShell or its module; this diagram does not execute it.

$prefix = 'Server' 1 .. 4 | ForEach-Object { [pscustomobject]@{ Index = $_ Name = "$prefix-$_" } }
  1. Stage 1$prefixPositional values or no named parameters
  2. Stage 2ForEach-ObjectPositional values or no named parameters

    An example to inspect

    Preview the example. Sign in for the full lesson and activities.

    $prefix = 'Server'
    1 .. 4 | ForEach-Object {
    [pscustomobject]@{
        Index = $_
        Name = "$prefix-$_"
      } }

    What happens

    This is the serial version, which works because $prefix is in scope. Add -Parallel and the Name column loses the prefix, because $prefix does not exist in the child runspace. The fix is $using:prefix in place of $prefix. That one substitution is the difference between correct output and silently blank output.

    Worth remembering

    • PowerShell 7 only. Each iteration gets its own runspace.
    • Outer variables need $using: or they are empty.
    • -ThrottleLimit defaults to 5.
    • Results come back out of order.

    Primary documentation

    ForEach-Object reference

    Try it

    Illustrative output · values vary by environment

    1PS> $prefix = 'Server'
    2PS>
    3PS> 1 .. 4 | ForEach-Object {
    4PS> [pscustomobject]@{
    5PS> Index = $_
    6PS> Name = "$prefix-$_"
    7PS> } }

    Example reader

    Inspect this page's example

    Fictional browser data when supported

    Run supported commands against the practice data, or inspect the example without executing it.

    Ready to inspect.

      The serial form of a loop, ready to compare against -Parallel.

      Show reference output

      Illustrative output · values vary by environment

      Index  Name
      -----  ----
      1      Server-1
      2      Server-2
      3      Server-3
      4      Server-4

      Keep going

      Next: PowerShell Runspaces
      Genie guidePowerShell ForEach-Object -Parallel

      ForEach-Object -Parallel in PowerShell 7 runs each iteration in its own runspace at the same time. One switch turns a serial loop into a concurrent one.