PowerShell Foreach Loop: Complete Tutorial with Examples

PowerShell Foreach Loop: Complete Tutorial with Examples

PowerShell Tips Editor 4 min read
PowerShell Foreach Loop: Complete Tutorial with Examples

You have a list of 500 servers, and you need to restart a service on each one. You have 200 log files and need to check each for a specific error. You have a CSV of user accounts to create in Active Directory. All of these problems have the same solution: a PowerShell foreach loop. It’s the most-used iteration construct in PowerShell scripting, and this guide covers every form of it — the foreach statement, ForEach-Object in the pipeline, break and continue, nesting, and a real bulk file processing example.

Basic foreach Statement Syntax

The foreach statement iterates over a collection, assigning each element to a named variable in turn:

$servers = @('web01', 'web02', 'db01', 'cache01')

foreach ($server in $servers) {
    Write-Output "Processing: $server"
}

# foreach works on any enumerable — arrays, file lists, ranges
foreach ($n in 1..5) {
    Write-Output "Number: $n"
}
Processing: web01
Processing: web02
Processing: db01
Processing: cache01
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

The loop variable ($server) is yours to name. It’s scoped to the loop but remains accessible after the loop exits — it holds the last value from the iteration. Modify a copy of the collection element if you need the original unchanged.

ForEach-Object in the Pipeline

ForEach-Object is the pipeline cmdlet version. It processes each object that flows through the pipeline, using $_ to reference the current object:

# Pipeline version — data streams from Get-ChildItem
Get-ChildItem C:\Logs -Filter '*.log' | ForEach-Object {
    $size = [math]::Round($_.Length / 1KB, 2)
    Write-Output "$($_.Name): $size KB"
}

# Short alias % does the same thing
Get-Service | Where-Object { $_.Status -eq 'Running' } | % {
    $_.Name
}
app.log: 12.50 KB
error.log: 4.82 KB

Inside ForEach-Object, always use $_ to reference the current pipeline object. Forgetting $_ and using the variable name from an outer loop is one of the most common mistakes.

foreach vs ForEach-Object: When to Use Which

Both loop through collections, but they have important differences:

  • foreach statement — Faster for in-memory collections. Loads the entire collection before iterating. Supports break and continue natively.
  • ForEach-Object — Slower per item due to pipeline overhead. Streams data, so it works with cmdlets that produce output progressively (like reading from a database or large file). Lower peak memory usage.
  • ForEach-Object with -Parallel (PowerShell 7+) — Runs iterations in parallel threads, dramatically faster for I/O-bound operations.
# For large in-memory work: foreach statement is ~3x faster
$items = 1..10000
foreach ($item in $items) { $item * 2 }

# For streaming pipeline data: ForEach-Object
Get-ChildItem C:\Logs -Recurse | ForEach-Object { $_.FullName }

Using Break and Continue

break exits the loop entirely; continue skips to the next iteration:

$servers = @('web01', 'web02', 'MAINTENANCE', 'db01')

foreach ($server in $servers) {
    if ($server -eq 'MAINTENANCE') {
        Write-Warning "Stopping at maintenance server"
        break   # Exit loop
    }
    Write-Output "Pinging: $server"
}

# continue: skip this iteration, proceed to next
foreach ($n in 1..10) {
    if ($n % 2 -eq 0) { continue }   # Skip even numbers
    Write-Output $n
}
Pinging: web01
Pinging: web02
WARNING: Stopping at maintenance server
1
3
5
7
9

Nested Foreach Loops

Loops inside loops handle multi-dimensional data or combinations:

$environments = @('DEV', 'STAGING', 'PROD')
$services     = @('API', 'Worker', 'Scheduler')

foreach ($env in $environments) {
    foreach ($svc in $services) {
        Write-Output "$env-$svc"
    }
}
DEV-API
DEV-Worker
DEV-Scheduler
STAGING-API
STAGING-Worker
STAGING-Scheduler
PROD-API
PROD-Worker
PROD-Scheduler

In nested loops, break exits only the innermost loop. To break out of multiple levels, use a flag variable or restructure into a function that uses return.

Real Example: Bulk Process Files

Rename all .log files in a folder to include today’s date prefix:

$sourceDir = 'C:\Logs\Archive'
$today     = Get-Date -Format 'yyyy-MM-dd'

Get-ChildItem -Path $sourceDir -Filter '*.log' | ForEach-Object {
    $newName = "${today}_$($_.Name)"
    $newPath = Join-Path $_.DirectoryName $newName

    if (-not (Test-Path $newPath)) {
        Rename-Item -Path $_.FullName -NewName $newName
        Write-Output "Renamed: $($_.Name) -> $newName"
    } else {
        Write-Warning "Skipped: $newName already exists"
    }
}
Renamed: app.log -> 2026-05-04_app.log
Renamed: error.log -> 2026-05-04_error.log

Common Errors and Fixes

  • Modifying collection while iterating causes errors: Removing items from an array while looping over it with foreach throws an error. Collect the items to remove in a separate array, then remove them after the loop completes.
  • Forgetting $_ inside ForEach-Object: Inside ForEach-Object { }, the current item is $_ — not the name you might use in a foreach ($item in $collection) statement. Using the wrong variable returns $null silently.

Related Cmdlets / See Also

Wrapping Up

The foreach statement is the workhorse of PowerShell iteration — fast, readable, and flexible. Use it for in-memory collections and the ForEach-Object cmdlet when processing pipeline data. Remember that $_ is the current object in pipeline contexts. Your next step: take a manual, repetitive task you do every week and build a foreach loop that automates it.

Send-Item -To