PowerShell Foreach vs Foreach-Object Comparison

In the thing called Microsoft PowerShell scripting language, cmdlets are the ones that usually used to perform three operations such as collecting information, setting information, and deleting information. Aside from them, PowerShell also has the ability to process the information more than once.

There are two PowerShell functions built in by default that mostly used by PowerShell. Those are Foreach Loop and Foreach-Object. The first one makes you possible to iterate through a set of items gathered in a PowerShell variable. For instance, by using the Get-ADUser PowerShell cmdlet, you can gather the user information from the Active Directory. You can use Foreach Loop if you have to look at city property of every user before taking an action. As for the second one, it is able to be used to work with the objects directly and mostly used in a pipeline. Here is the comparison of these two:

Actually, Foreach Loop or Statement and Foreach-Object are similar but each of them always behaves differently. On the surface, you might be wondering how could some people be confused. One consists of seven characters while another has 14.

The first intention of the two loops methods might be clear. It is better for you to know that there is enough memory to store the objects in if you use Foreach. Some people might think that the Foreach-Object is always well-loved and preferred and it is not completely wrong. In fact, Foreach-Object with only collecting one object at a time, hence reducing the overall storage requirement makes it more interesting. However, PowerShell is so sure with the Foreach Statement that makes it enabled to perform, a bit faster compared to the Foreach-Object. In this kind of thing, you will need to understand the difference between performance and space.

The second issue addresses the Foreach-Object directly. When it is executed, the pipeline element generating the object is actually interlace with the execution of the Foreach-Object cmdlet itself. In the terms used by layman, for each object that is generated, you can pass it into a Foreach block for processing before generating the next element. The result of the action means that the next pipeline input objects can be affected. For those the users of traditional shell who are reading this, every command would be run as a separate process and it can be executed at the same time. However, it is not the same with PowerShell as they alternate.

If you need a better explanation, you can try to think about the left side runs and creates the object and then the right one runs. Once in the Foreach case, whether through invoking Foreach-Object or Foreach directly, a special variable is also made. The excellent Foreach variable which is explicitly related to the loop enumerator. On the other words, in the practice, the loop enumerator is able to be manipulated and jump forward in the loop. However, now you can take a decision to prefer which method to use less simple again.

To compare the Foreach Statement and Foreach-Object, there is Measure-Command that can measure how long a command or script takes to complete. It is really important for processing the large amount of data.

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

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

In conclusion, Foreach Statement consumes more memory, but it is definitely faster. Each object is stored in the memory. With the thing named Foreach-Object, the objects are processed one after another and the results for each of them, which goes through the pipe is output instantly.

Foreach-Object is best to used when you are sending the data through the pipeline because it will continue streaming the objects to the next command in the pipeline, just like the example below:

ForEach-Object -InputObject (1..1E4) {

$_

} | Measure-Object

 

Count: 10000

Average:

Sum:

Maximum:

Minimum:

Property:

The same thing cannot be applied with Foreach () {} because the pipeline will be broken and there will be some error messages if you try to send that output to another command.

ForEach ($i in (1..1E4)) {

$i

} | Measure-Object

 

At line:3 char:3

+ } | Measure-Object

+   ~

Please take note that the empty pipe element is not allowed.

+ CategoryInfo: ParserError: (:) [], ParentContainsErrorRecordException

+ FullyQualifiedErrorId: EmptyPipeElement

You will need to save all the output that is processed by Foreach to a variable and then pipe it to another cmdlet like:

$Data = ForEach ($i in (1..1E4)) {

$i

}

$Data | Measure-Object

Now you have totally damaged the pipeline which makes it more apparent after this. You should not only stop the pipeline to start processing the data, but you cannot even send the data to the pipeline from the statement without having to gather the output first into a variable and then sending it down the pipeline.

It is really important if you want to use the data in another command via the pipeline. You also have to write down another difference that those options share, which is the performance versus memory consumption.

The Foreach Statement loads all of the items up front into the collection before processing them at the same time. As for the Foreach-Object, it expects the items to be streamed through the pipeline, that is why it is lowering the memory requirements. However, at the same time, it takes a performance hit. Here are a couple of tests to highlight the different between these two:

$time = (Measure-Command {

    1..1E4 | ForEach-Object {

        $_

    }

}).TotalMilliseconds

 [pscustomobject]@{

    Type = ‘ForEach-Object’

    Time_ms = $Time

 }

$Time = (Measure-Command {

    ForEach ($i in (1..1E4)) {

        $i

    }

}).TotalMilliseconds

  [pscustomobject]@{

    Type = ‘ForEach_Statement’

    Time_ms = $Time

 }

ForEach_Statement

Once again, Foreach Statement that allocates everything to memory before processing is faster compared to Foreach-Object. It is true that the larger amount of data, the more risk that you have of running out of memory before you are able to process all of them.

Leave a Reply

Your email address will not be published.