ForEach vs. ForEach
I know I cover some advanced topics in this newsletter, which I know for some of you can be a bit overwhelming. I'll admit that I am often uncertain about what topics to cover. I've been using PowerShell for so long that it is difficult to remember what it was like to be a beginner, or at least someone with less experience. Sometimes I'll think a topic is too basic, but then I have to remember there's really no such thing as too basic. We all have to start somewhere.
Today, I want to explore the concept of ForEach
in PowerShell. There is a ForEach-Object
cmdlet and a ForEach
statement. Unfortunately, the cmdlet has an alias of ForEach
, which can be confusing. What are the differences? When should you use one over the other? Why does any of this matter? Let's dive in.
Pipeline First
One of PowerShell's most powerful features is the pipeline. The pipeline allows you to pass objects from one command to another.
PS C:\> Get-Service "bits","winrm","w32time","wsearch" | Restart-Service -Force -PassThru
Status Name DisplayName
------ ---- -----------
Running bits Background Intelligent Transfer Servi...
Running w32time Windows Time
Running winrm Windows Remote Management (WS-Managem...
Running wsearch Windows Search
You don't have manage each service individually. You get the service object with one command and hand it off to another command to do something with it. Before PowerShell, you would have to process each service individually.
dim service, serviceNames
'create an array variable for the service names
serviceNames = Array("bits","winrm","w32time","wsearch")
wscript.echo "Restarting services..."
for each n in serviceNames
query = "select * from Win32_Service where Name='"+n+"'"
set service = GetObject("winmgmts:").ExecQuery(query)
for each s in service
s.StopService()
s.StartService()
wscript.echo s.name
next
next
wscript.echo "Services restarted."
In this VBScript example, I have to process each service name in a for each
loop and then do the same with the WMI results. I'm very happy that I can replace 13 lines of VBScript that have to be saved to a file to a single line of PowerShell that I can run interactively.
However, there are times when you need to process each item in a collection individually. If I want to take the numbers 1 through 5 and multiply each one by two, I have to process each number separately. This is where ForEach
comes in. Or I want to process a collection of names and display each on in all uppercase. This is where PowerShell's enumeration tools come in.
Foreach-Object
The story I heard back in the early days of PowerShell was that Microsoft recognize they needed an enumeration tool so someone wrote the ForEach-Object
cmdlet. Then someone else wrote the ForEach
statement for scripts, not realizing the overlap. I doubt this is 100% true, and as it turns out, both tools have their place.
The ForEach-Object
cmdlet is designed to work with the pipeline. You can pipe objects to ForEach-Object
and process each object individually. You can use the $_
automatic variable to reference the current object in the pipeline.
PS C:\> 1..5 | ForEach-Object {$_ * 2}
2
4
6
8
10
The code in the script block {}
is executed once for each object in the pipeline. The $_
variable is a placeholder for the current object. If you find that confusing, you can use $PSItem
.
1..5 | ForEach-Object {$PSItem * 2}
There is no limit to the code you can run in the script block. Typically, we keep things short when working in the console. But that is not a requirement.
dir c:\work\ -file | Group-Object Extension |
Where Count -ge 5 | Foreach-Object {
$Name = $_.Name.Replace('.','')
$Zip = Join-Path -Path d:\temp -ChildPath "$Name.zip"
Write-Host "Archiving $($_.Count) $($_.Name) files to $zip" -ForegroundColor green
$_.Group | Compress-Archive -DestinationPath $zip -CompressionLevel Optimal -Update
Get-Item $Zip
}
I want to create a zip file for each file extension that has five or more files. I group the files by extension and then filter the groups. I then create a zip file for each group and update the zip file if it already exists. I then return the zip file object. There's no command to do this for me in a seamless pipeline like my script example. I have to process each GroupInfo object individually using the ForEach-Object
cmdlet.

There is more to ForEach-Object
than this simple example. The cmdlet is a way that you can create an ad-hoc PowerShell function that processes pipeline input. Let me explain.
I'm sure many of you know that to create an advanced PowerShell function that accepts pipeline input, you need use three separate and named script blocks.
Function ZipFile {
[CmdletBinding]
Param($object)
Begin {}
Process {}
End {}
}
The code in the Begin
block runs once before the pipeline starts. The code in the Process
block runs once for each object in the pipeline. The code in the End
block runs once after the pipeline completes. The ForEach-Object
cmdlet has similar blocks exposed through parameters.

I'm only focusing on the relevant parameters.