Search code examples
windowspowershellpipelineforeach-object

Powershell. Difference between piping commands and using Foreach-Object


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.