PowerShell Foreach Loops: What You Should Know
By Adam Bertram on March 18, 2015
To complicate matters, PowerShell has two distinct foreach constructs; the foreach statement and the ForEach-Object cmdlet. While they are similar, each behaves very differently. On the surface, you might wonder how someone could mix them up. One is seven characters and the other is 14-but check this out:
As an alias, foreach actually equals ForEach-Object! Now, do you see where the confusion comes from? In reality, foreach is an alias to the ForEach-Object cmdlet, and foreach (not the alias) is a statement. Each has its purpose.
The ForEach-Object (foreach alias) cmdlet is used in the pipeline. It is used to iterate through a collection of items, while being respectful of the PowerShell pipeline-which gets input, processes it, and passes it down the pipeline as objects are done processing. It consumes minimal memory because it's consuming memory for each object and releasing it as it touches each object. Think of ForEach-Object like a conveyor belt. It picks up each “package” as it goes along the conveyor belt, inspects it, sets it back down and repeats the process as more “packages” are sent down the line.
Here's an example of the behavior of a ForEach-Object:
You'll notice that Measure-Object received 10,001 objects. This is because ForEach-Object processed each object and sent it down the pipeline immediately.
Let's try to make it confusing:
This is exactly the same one-liner we saw earlier. But rather than use the full ForEach-Object syntax, I chose to use the foreach alias and pass the -InputObject parameter through the pipeline to ForEach-Object.
Now let's see how much time that took:
163 milliseconds. Not bad for iterating through 10,000 items.
The foreach statement (like ForEach-Object) iterates through a collection of items, but it allocates all the memory it needs ahead of time and ignores the pipeline altogether. To me, the foreach statement is comparable to foreach constructs in other programming languages. An easy way to differentiate the foreach statement from the ForEach-Object cmdlet (even if it's being masked as an alias) is that the alias of ForEach-Object can never be specified at the start of a line.
You can see that this script bombs. But if you look at our first example of ForEach-Object, it worked just fine. This is because we're not using the ForEach-Object alias here. We're using the foreach statement.
Let's try the foreach statement using the same code we tried previously with ForEach-Object:
We receive the error message “An empty pipe element is not allowed.” This is because after the pipe character, there is no pipeline! It's gone! The foreach statement couldn't care less about the PowerShell pipeline-it proceeds to hog all of the objects and completely breaks the pipeline.
Let's time the foreach statement the same way we did with ForEach-Object:
You'll recall it took 163 milliseconds to iterate through 10,000 items with ForEach-Object. The foreach statement took 17 milliseconds. That's nearly 10 times as fast! Although the foreach statement was definitely faster, it consumed more memory while processing the result.
Now that you see the differences, which construct are you most likely to use? As with any technological phenomenon, the best choice depends on your situation, the kind of problem you're trying to solve and the goal of the script. I always use the foreach statement. You can see it's much faster and iterates through items just the same. The only downside is that it consumes more memory. But with systems these days, that's typically not a problem.
If you don't need to save memory, and you don't have a niche reason why you must use the pipeline, I recommend using the foreach statement. However, if you're processing huge datasets that may consume gigabytes of memory, you'll be forced to use ForEach-Object. Otherwise, just stick to the foreach statement.