Powershell. Difference between piping commands and using Foreach-Object

Issue

Sorry if this question is already been answered, I could find similar questions but not the exact one I need to ask.

Let’s take two examples:

 1. Get-Process -name msedge,putty | Stop-Process
 2. Get-Process -name msedge,putty | Foreach-Object {Stop-Process $_}

Both are doing the same operation. What about the methods used in each one? Are they the same in the sense that the first example just omits the Foreach-Object construction for the sake of code readability/aesthetics?

Solution

The first example requires the Cmdlet to support binding of the relevant parameters via the pipeline. In your case Stop-Process will bind the Process object from the pipeline to it’s -InputObject parameter.

You can check that using get-help stop-process -Parameter * and see which parameters have "Accept pipeline input?" set to true.

In case a Cmdlet does not support the binding of the relevant parameters values you can wrap ForEach-Object around it, like you did in the second example. This way you use the automatic variable $_ to bind the current pipeline object (or information that you derive from it) "manually" to the corresponding parameter.

What approach should you use if a Cmdlet supports the binding of parameter values from the pipeline? That unfortunately depends. It is possible to write a Cmdlet that behaves differently, depending on how the parameter values are bound. Let me illustrate this point:

function Test-BindingFoo {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [string[]]
        $InputParameter
    )
    
    begin {
        Write-Host "[BEGIN]"
    }
    
    process {
        foreach ($value in $InputParameter) {
            Write-Host "The current value is: $value"
        }
    }
    
    end {
        Write-Host "[END]"
    }
}

If you execute this Cmdlet using the pipeline binding the Begin block of the function is executed exactly once:

❯ "foo1", "foo2" | Test-BindingFoo
[BEGIN]
The current value is: foo1
The current value is: foo2
[END]

If you use ForEach-Object the Begin block is executed every time an object passes through the pipeline:

❯ "foo1", "foo2" | ForEach-Object { Test-BindingFoo $_ }
[BEGIN]
The current value is: foo1
[END]
[BEGIN]
The current value is: foo2
[END]

In well implemented Cmdlets the difference here should not matter. But I found it useful to be aware of what happens inside a Cmdlet when parameteres are passed in in the ways that we have discussed here.

Answered By – Manuel Batsching

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published