PowerShell ForEach Loop (With Examples)

The PowerShell foreach loop runs a block of code once for every item in a collection, such as an array, a list of files or the output of a command. The current item is stored in the variable you name before in:

foreach ($item in $collection) {
    # do something with $item
}

Here’s a real one that walks through an array of office names:

$cities = 'Seattle', 'Denver', 'Austin', 'Boston'

foreach ($city in $cities) {
    "Checking the $city office"
}

Output:

Checking the Seattle office
Checking the Denver office
Checking the Austin office
Checking the Boston office
PowerShell foreach loop through an array example
A foreach loop over an array in PowerShell 7

In this guide, I’ll loop through arrays, files and hashtables, show how break and continue work, and explain the difference between foreach, ForEach-Object and the .ForEach() method.

I tested everything in PowerShell 7.6 and Windows PowerShell 5.1. The two traps near the end caught me more than once, so don’t skip them.

Loop through an array with foreach

Arrays are the most common use. You don’t need an index or a counter, and the loop stops on its own after the last item.

This one adds up a list of order amounts:

$orders = 120.50, 89.99, 240.00, 15.25
$total = 0

foreach ($amount in $orders) {
    $total += $amount
}

"Orders: $($orders.Count)  Total: {0:C}" -f $total

Output:

Orders: 4  Total: $465.74

The {0:C} format shows the total as currency. On a US system, that’s dollars with two decimals.

Loop through files in a folder

Anything that returns objects can go after in, including a command. Here’s every log file in a folder with its size:

foreach ($file in Get-ChildItem -Path C:\psfaqs\Logs -Filter *.log) {
    '{0,-8} {1,8:N1} KB' -f $file.Name, ($file.Length / 1KB)
}

Output:

app.log       2.3 KB
sql.log      93.8 KB
web.log      17.6 KB
PowerShell foreach loop through files in a folder with their sizes
Looping through files with foreach

For more file examples, see how to loop through files in a folder with PowerShell.

Loop through a hashtable

A hashtable isn’t enumerated item by item on its own. Call GetEnumerator() so each pass gets one key/value pair:

$offices = [ordered]@{ Seattle = 'WA'; Austin = 'TX'; Boston = 'MA' }

foreach ($entry in $offices.GetEnumerator()) {
    "$($entry.Key) is in $($entry.Value)"
}

Output:

Seattle is in WA
Austin is in TX
Boston is in MA

I used [ordered] so the output keeps the order I typed. A plain hashtable doesn’t guarantee any order.

How do I get the index in a foreach loop?

The foreach statement doesn’t track the position for you. Keep your own counter:

$tasks = 'Back up the database', 'Patch the servers', 'Rotate the logs'
$i = 0

foreach ($task in $tasks) {
    $i++
    "Step ${i}: $task"
}

Output:

Step 1: Back up the database
Step 2: Patch the servers
Step 3: Rotate the logs

Note the ${i} braces. Without them, PowerShell reads $i: as a drive-qualified variable and the line breaks. If you need the index a lot, a for loop is usually cleaner.

Break and continue in a foreach loop

continue skips to the next item, and break ends the loop:

$servers = 'WEB01', 'OFFLINE02', 'WEB03', 'STOP', 'WEB05'

foreach ($server in $servers) {
    if ($server -like 'OFFLINE*') { continue }   # skip this one
    if ($server -eq 'STOP')      { break }      # leave the loop
    "Patching $server"
}

Output:

Patching WEB01
Patching WEB03

Break out of nested loops with a label

In nested loops, break and continue only affect the inner loop. Add a label to the outer loop and name it:

:outer foreach ($region in 'East', 'West', 'Central') {
    foreach ($server in 1..3) {
        if ($server -eq 2)          { continue outer }   # jump to the next region
        if ($region -eq 'Central')  { break outer }      # stop both loops
        "$region-SRV$server"
    }
}

Output:

East-SRV1
West-SRV1

I go deeper into this in how to break out of a foreach loop.

foreach vs. ForEach-Object vs. .ForEach()

PowerShell has three ways to do “for each”, and they aren’t interchangeable:

FormWhere it’s usedCurrent item
foreach ($x in $list) { }A statement in a script. Fastest for data already in memory.$x
... | ForEach-Object { }Inside a pipeline. Streams items one at a time.$_ or $PSItem
$list.ForEach({ })A method on a collection (PowerShell 4 and later).$_

Here’s ForEach-Object in a pipeline:

Get-ChildItem -Path C:\psfaqs\Logs -Filter *.log |
    ForEach-Object { '{0} is {1:N0} bytes' -f $_.Name, $_.Length }

Output:

app.log is 2,400 bytes
sql.log is 96,000 bytes
web.log is 18,000 bytes

And the .ForEach() method:

(1..5).ForEach({ $_ * 10 })

Output:

10
20
30
40
50

One thing that confuses people: foreach typed after a pipe (1..3 | foreach { $_ }) is an alias for ForEach-Object, not the foreach statement.

In my timing test with 100,000 items, the foreach statement was several times faster than ForEach-Object. I show the numbers in the for loop article.

Trap 1: continue inside ForEach-Object stops the whole script

This is the one that bites most people. ForEach-Object runs a script block, not a loop, so continue looks for an enclosing loop. When it doesn’t find one, it stops the script:

1..5 | ForEach-Object {
    if ($_ -eq 3) { continue }
    $_
}
'This line never runs'

Output:

1
2
continue inside ForEach-Object stops the whole PowerShell script
The last line never prints (Windows PowerShell 5.1)

It behaves the same in PowerShell 7. To skip one item inside ForEach-Object, use return instead:

1..5 | ForEach-Object {
    if ($_ -eq 3) { return }    # skip 3, keep going
    $_
}
'Done'

Output:

1
2
4
5
Done

Trap 2: changing the collection while you loop

You can’t add to or remove from a list while a foreach loop is walking it:

$queue = [System.Collections.Generic.List[string]]@('job1', 'job2', 'job3')

foreach ($job in $queue) {
    if ($job -eq 'job2') { $queue.Add('job4') }
}

Output:

OperationStopped: C:\psfaqs\modify.ps1:3
Line |
   3 |  foreach ($job in $queue) {
     |           ~~~~
     | Collection was modified; enumeration operation may not execute.

Loop over a copy instead. @($queue) makes a new array, so changing the original is safe:

$queue = [System.Collections.Generic.List[string]]@('job1', 'job2', 'job3')

foreach ($job in @($queue)) {          # loop over a copy
    if ($job -eq 'job2') { $queue.Add('job4') }
}

$queue -join ', '

Output:

job1, job2, job3, job4

What happens when the collection is empty or $null?

Nothing, which is what you want. Since PowerShell 3.0, a foreach over $null runs zero times:

$servers = $null

foreach ($server in $servers) {
    "Restarting $server"
}

'No servers, and no error'

Output:

No servers, and no error

Run a foreach in parallel (PowerShell 7)

PowerShell 7 added ForEach-Object -Parallel, which runs items at the same time. Four one-second tasks finish in about one second instead of four:

1..4 | ForEach-Object -Parallel {
    Start-Sleep -Seconds 1
    "Finished item $_"
} -ThrottleLimit 4 | Sort-Object

Output in PowerShell 7:

Finished item 1
Finished item 2
Finished item 3
Finished item 4

Windows PowerShell 5.1 doesn’t have the parameter:

1..4 | ForEach-Object -Parallel { "Finished item $_" }

Output in Windows PowerShell 5.1:

ForEach-Object : Parameter set cannot be resolved using the specified named parameters.
At C:\psfaqs\parallel51.ps1:1 char:8
+ 1..4 | ForEach-Object -Parallel { "Finished item $_" }
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [ForEach-Object], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.ForEachObjectCommand

Parallel script blocks run in separate runspaces, so use $using:variable to read variables from your script. Microsoft explains the details in the ForEach-Object documentation.

Frequently Asked Questions

What is the syntax of a foreach loop in PowerShell?

foreach ($item in $collection) { <statements> }. $item holds the current element on each pass.

What is the difference between foreach and ForEach-Object?

foreach is a statement that works on a collection already in memory and is faster. ForEach-Object is a cmdlet that processes pipeline input one item at a time, using $_.

How do I skip an item in ForEach-Object?

Use return. A continue inside ForEach-Object stops the whole script unless there’s an enclosing loop.

How do I loop through a hashtable in PowerShell?

Use foreach ($entry in $hash.GetEnumerator()) { $entry.Key; $entry.Value }, or loop over $hash.Keys.

Can a PowerShell foreach loop run in parallel?

Yes, in PowerShell 7 with ForEach-Object -Parallel. Windows PowerShell 5.1 doesn’t support it. See Microsoft’s about_Foreach for the statement itself.

Related loop tutorials: