Magic of Foreach-Object.

It’s Sunday morning and I should probably do something else than playing with PowerShell. But Chad Miller and Oisin Grehan made me dig more into way Foreach-Object behaves. I was always sure that construct like that:

1..10 | ForEach-Object { '=' * 80 } { $_ } { '-' * 80 }

work because documentation is wrong about the way –Begin and –End behave. Oisin comment forced me to actually check it… And because there is no better way to check how things actually  work than by using Trace-Command cmdlet – I did so. And it proven Oisin (and documentation) to be right about parameters binding rules:

Trace-Command -Name ParameterBinding -PSHost -Expression {            
    1..10 | ForEach-Object { '=' * 80 } { $_ } { '-' * 80 }            
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [ForEach-Object]            
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [ForEach-Object]            
DEBUG: ParameterBinding Information: 0 : BIND REMAININGARGUMENTS cmd line args to param: [Process]            

As you can see It’s picking up value from remaining arguments and does magic with it. It’s not hard to prove it right if you intentionally bind array of scriptblocks to –Process:

1..10 | ForEach-Object -Process { '=' * 80 }, { $_ }, { '*' * $_ }, { '-' * 80 }

If you would trace this expression – the only difference you will notice is that now it’s bound during binding named parameters. And as you can see if there is only –Process specified there are few possible scenarios, depending on number of Scriptblocks passed to it:

  • 1 –> used for Process block
  • 2 –> 1st used in Begin, 2nd in Process
  • 3+ –> 1st used in Begin, last in End, what remains in Process

If you specify one (or both) of the named parameters that accept scriptblock – Begin or End – than $Process scriptblock array is not bound to this parameters any more:

1..10 | ForEach-Object { '=' * 80 }{ $_ }{ '*' * $_ }{ '-' * 80 } -Begin { 'Foo' } -End { 'Bar' }

All scriptblocks will be used for process block, because both begin and end are happy with what we passed to it. So if you actually want to pass an array of scriptblocks to process block – you need to either pass empty scriptblock to –Begin and –End, or start and finish your array with empty scriptblock:

1..10 | foreach {}{'='*80}{$_}{'*' * $_}{[char](100 + $_)}{'-'*80}{}

For some reason I could not find documentation that would describe this neat behaviour of –Process parameter. It’s not mentioned in this parameter description and there are no examples of this kind of behaviour, neither in offline nor in online version of help for it. Luckily PowerShell gives us tools (like Trace-Command) that allow us to look inside the way things work. 🙂 I’m almost sure someone blogged about it already (got used to breaking out doors that were open wide in PowerShell world 😉 ) – I was just not aware of that, so maybe there are others who had the same problem. And can either find solution here, or do as I did… break out the open doors. 😉


5 thoughts on “Magic of Foreach-Object.

  1. Good detective work. One way to tell if the docs are wrong about a parameter attribute is to run Get-Command, which examine the code, unlike Get-Help, which displays the help topics.

    function Get-Position
    param ($Cmdlet, $Parameter)
    $p = ((Get-Command $cmdlet).parametersets | foreach {$_.parameters} | where {$ -like $Parameter}).position
    if ($p -eq “-2147483648”) { return “Named” }
    else {return $p}

    Lee Holmes (PowerShell Cookbook) confirmed the “intelligent” behavior of the Process parameter. It wasn’t doc’d because I didn’t know about it, but I’ll add it ASAP, or as soon as I can think of a reasonable way to explain it.

    Thanks for raising the issue.


    June Blender
    Windows PowerShell Documentation

  2. Pingback: PowerShell Magazine » PowerShell Detective

  3. Pingback: Writing Powershell Cmdlets | Tony's Tech Blog

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s