PowerShell: ForEach vs ForEach-Object

I was helping a fellow colleague with some PowerShell scripting recently and one concept cropped up that inspired this post. What is the difference between ForEach and ForEach-Object, and when should you use them?

So, fundamentally for the ForEach-Object cmdlet executes the statement body (that is to say, whatever is contained in your curly braces {}) as each object is created. In the case of ForEach, all of the objects are collected prior to the statement body executing. Simple. But lets delve a little deeper into what this actually means in practice.

The first implication of the difference between these two loop methods might be obvious. If you use ForEach, you had better know there is enough memory to store the objects in. Would one assume then, that ForEach-Object is always preferred? Sounds reasonable. After all, ForEach-Object, by virtue of only gathering one object at a time, thus reducing the overall storage requirement makes it appealing. But, why would I be continuing with this post if the answer was as simple as that? PowerShell has optimisations within the ForEach statement that enables it to perform, somewhat significantly faster, than its per-object brother does. In each use case, you should be asking yourself the familiar question of performance versus space.

The second implication addresses the ForEach-Object directly. When ForEach-Object executes, the pipeline element generating the object is actually interwoven with the execution of the ForEach-Object cmdlet itself. In layman’s terms; for each object that is generated, pass it into a ForEach block for processing before generating the next element. The result of this means that subsequent pipeline input objects can be affected. For the traditional shell users reading this, each command would be run as a separate process and thus, can be executed at the same time. Not with PowerShell, they alternate. Think of it like this; the left side runs and creates the object, then the right side runs. Once in this ForEach scenario, be it through invoking ForEach-Object or ForEach directly, a special variable is also created. The magic $foreach variable that is explicitly bound to the loop enumerator. What this means in practice, is that the loop enumerator can be manipulated and jump forward in the loop. Cool. However, it does now make our decision over which method to use less simple again.

Now, I’ve not mentioned anything about SharePoint yet, and being a SharePoint consultant, this leads me nicely to an example of how to choose between our ForEach and ForEach-Object methods.

  • First, let’s grab something interesting from Management Shell. I’m picking the Quick Launch navigation as it will give us some (but not tons) of nice objects:
$web = Get-SPWeb http://sharepoint
$nav = $web.Navigation.QuickLaunch
  • Now, use the Measure-Command cmdlet to loop through each object in $nav, to start I’ll pick ForEach-Object:
Measure-Command { $nav | ForEach-Object { $_.Id } }
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 4
Ticks             : 42197
TotalDays         : 4.88391203703704E-08
TotalHours        : 1.17213888888889E-06
TotalMinutes      : 7.03283333333333E-05
TotalSeconds      : 0.0042197
TotalMilliseconds : 4.2197
  • Next, using the same Measure-Command cmdlet use the ForEach method:
Measure-Command { ForEach ($item in $nav) { $_.Id } }
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 1
Ticks             : 10192
TotalDays         : 1.17962962962963E-08
TotalHours        : 2.83111111111111E-07
TotalMinutes      : 1.69866666666667E-05
TotalSeconds      : 0.0010192
TotalMilliseconds : 1.0192

Wow! So the TotalMilliseconds when using ForEach-Object was 4.2197 while using ForEach came in at a blistering 1.0192. If the objective of this enumeration of objects was to simply audit or record something, without a doubt, I should be using ForEach in my solution.

I hope that you now have some useful reference and understanding about the difference between these two methods. I’ve given you just one way in which you can ascertain which one to use in your solutions, but now you know how they operate, crack open PowerShell and see if you can discover more for yourself!

About the author