ForEach-Object runs a script block once for every object that comes down the pipeline. Inside the block, $_ is the current object:
'Austin', 'Denver', 'Seattle' | ForEach-Object {
"Sending the weekly report to the $_ office"
}
Output:
Sending the weekly report to the Austin office
Sending the weekly report to the Denver office
Sending the weekly report to the Seattle office
$PSItem is another name for $_, if you find it easier to read. I ran every example in PowerShell 7.6 and Windows PowerShell 5.1, and I note where they differ.
Work with each file, user or row
ForEach-Object is most useful at the end of a pipeline, where each object has properties you can use:
Get-ChildItem -Path C:\psfaqs\Reports -File | ForEach-Object {
'{0,-18} {1,6:N1} KB' -f $_.Name, ($_.Length / 1KB)
}
Output:
budget-2026.xlsx 5.0 KB
notes.txt 0.3 KB
q3-sales.csv 2.0 KB
The same pattern works for rows from Import-Csv, services, users or anything else a cmdlet returns.
Run code before and after the loop with -Begin and -End
-Begin runs once before the first object, -Process runs for each object, and -End runs once at the end. That’s handy for totals:
Get-ChildItem -Path C:\psfaqs\Reports -File | ForEach-Object -Begin {
$total = 0
'Checking files...'
} -Process {
$total += $_.Length
} -End {
"Total size: $([int]($total / 1KB)) KB"
}
Output:
Checking files...
Total size: 7 KB

Shortcut: call a property or method on every object
For a single property or method, skip the script block and pass its name:
'austin', 'denver' | ForEach-Object -MemberName ToUpper
Get-ChildItem -Path C:\psfaqs\Reports -File | ForEach-Object -MemberName Name
Output:
AUSTIN
DENVER
budget-2026.xlsx
notes.txt
q3-sales.csv
This is the same as ForEach-Object { $_.ToUpper() }, just shorter.
Skip an item: use return, not continue
Inside ForEach-Object, return moves on to the next object:
1..5 | ForEach-Object {
if ($_ -eq 3) { return }
"Processing order 100$_"
}
'Done'
Output:
Processing order 1001
Processing order 1002
Processing order 1004
Processing order 1005
Done
continue and break don’t work the way they do in a loop here. They stop the whole script, which the Import-Csv guide shows step by step.
ForEach-Object vs the foreach loop
PowerShell also has a foreach statement. It looks similar, but it works on a list that’s already in memory instead of the pipeline:
# ForEach-Object: works in the pipeline, one object at a time
Get-ChildItem -Path C:\Reports | ForEach-Object { $_.Name }
# foreach statement: loops over a list you already have
$files = Get-ChildItem -Path C:\Reports
foreach ($file in $files) { $file.Name }
To compare speed, I doubled 200,000 numbers three ways:
$numbers = 1..200000
$tests = [ordered]@{
'ForEach-Object' = { $numbers | ForEach-Object { $_ * 2 } }
'foreach loop' = { foreach ($n in $numbers) { $n * 2 } }
'.ForEach()' = { $numbers.ForEach({ $_ * 2 }) }
}
foreach ($name in $tests.Keys) {
'{0,-15} {1,6:N0} ms' -f $name, (Measure-Command { $null = & $tests[$name] }).TotalMilliseconds
}
Output in PowerShell 7 (your times will differ):
ForEach-Object 863 ms
foreach loop 16 ms
.ForEach() 363 ms
Output in Windows PowerShell 5.1:
ForEach-Object 873 ms
foreach loop 21 ms
.ForEach() 567 ms

| ForEach-Object | foreach statement | |
|---|---|---|
| Speed on large lists | Slowest | Fastest |
| Memory | One object at a time | Needs the whole list first |
| Works in a pipeline | Yes | No |
| Skip an item | return | continue |
| Parallel option | -Parallel (PowerShell 7) | No |
I use ForEach-Object in pipelines and for huge inputs I don’t want in memory, and foreach for loops over data I already have. Microsoft describes the statement in about_Foreach.
One confusing detail: foreach is also an alias for ForEach-Object. After a pipe, like 1..3 | foreach { $_ }, it’s the cmdlet.
Run items in parallel with -Parallel (PowerShell 7)
When each item waits on something, like a network call or a remote computer, -Parallel runs several at once. Here five one-second tasks run sequentially, then in parallel:
$sequential = Measure-Command {
1..5 | ForEach-Object { Start-Sleep -Seconds 1 }
}
$parallel = Measure-Command {
1..5 | ForEach-Object -Parallel { Start-Sleep -Seconds 1 } -ThrottleLimit 5
}
'One at a time: {0:N1} seconds' -f $sequential.TotalSeconds
'In parallel: {0:N1} seconds' -f $parallel.TotalSeconds
Output in PowerShell 7:
One at a time: 5.1 seconds
In parallel: 1.2 seconds

-ThrottleLimit sets how many run at the same time, and the default is 5. -Parallel doesn’t exist in Windows PowerShell 5.1, where the command fails with “Parameter set cannot be resolved.”
Use outside variables with $using:
Each parallel block runs in its own runspace, so it can’t see your variables. Prefix them with $using::
$prefix = 'SRV'
1..3 | ForEach-Object -Parallel {
"$using:prefix-0$_ checked"
} | Sort-Object
Output in PowerShell 7:
SRV-01 checked
SRV-02 checked
SRV-03 checked
Parallel blocks can finish in any order, which is why I sorted the output.
For quick work, -Parallel is much slower: doubling 200 numbers took 20 ms normally and about 2.5 seconds with -Parallel on my PC, because each runspace takes time to start. The ForEach-Object documentation covers the details.
Frequently Asked Questions
What does ForEach-Object do in PowerShell?
It runs a script block for each object in the pipeline. Inside the block, $_ (or $PSItem) is the current object.
What is the difference between ForEach-Object and foreach?
ForEach-Object works in the pipeline, one object at a time. The foreach statement loops over a list in memory and was many times faster in my test.
How do I skip an item in ForEach-Object?
Use return. continue inside ForEach-Object stops the whole script instead of skipping one item.
Does ForEach-Object -Parallel work in PowerShell 5.1?
No. -Parallel was added in PowerShell 7. In 5.1, use background jobs with Start-Job, or run the items one at a time.
How do I use a variable inside ForEach-Object -Parallel?
Prefix it with $using:, like $using:prefix. Parallel blocks can’t see variables from the outer script otherwise.
Pipelines get more useful with these:
Bijay Kumar is an esteemed author and the mind behind PowerShellFAQs.com, where he shares his extensive knowledge and expertise in PowerShell, with a particular focus on SharePoint projects. Recognized for his contributions to the tech community, Bijay has been honored with the prestigious Microsoft MVP award. With over 15 years of experience in the software industry, he has a rich professional background, having worked with industry giants such as HP and TCS. His insights and guidance have made him a respected figure in the world of software development and administration. Read more.