Flexible pipelining with ScriptBlock Parameters

PSMDTAG:FAQ: How can I pipeline data to a parameter which does not accept pipeline input?
PSMDTAG:FAQ: What are ScriptBlock Parameters?


One of the foundation concepts of Windows PowerShell is pipelining objects instead of text.  What happens is that when an upstream command generates an object, the PowerShell engine examines the data requirements of the next pipeline element and then binds the pipeline object to it.  That binding can either be:



  • Accepts Pipeline Input – BYVALUE
  • Accepts Pipeline Input – BYPROPERTYNAME

You can see this when you look at the Parameter section of the help for any cmdlet.  Take a look at the Help for Get-Process, you see the following:


-inputObject <Process[]>
    The object on which to act


    Required?                     true
    Position?                     named
    Default value
    Accepts pipeline input?       true (ByValue)
    Accepts wildcard characters?  false



-id <Int32[]>
    The Id number of the process


    Required?                     true
    Position?                     named
    Default value                 null
    Accepts pipeline input?       true (ByPropertyName)
    Accepts wildcard characters?  false


The engine attempts to bind the pipeline object BYVALUE first and if that fails, it tries to bind it BYPROPERTYNAME (which is to say – see if the pipeline object has a property with this name [ID] and if it can be cast to this datatype [INT32]). 


Here is the point:  The cmdlet developer decides which parameters can be pipelined and whether they can be pipelined by value or by propertyname.  They do this based upon their view of the common scenarios and the benefit/risks of this binding.  Imagine the incomprehensibility and potential disasters that would arise if all parameters accepted pipeline input!  The point is that as a Cmdlet developer, caution is your friend.


But here is the point: Cmdlet developers don’t know everything and even if they did, there is not always a clear consensus of what should be pipelinable.  Case in point: take a look at the parameters for Get-WMIObject – none of its parameters can take input from the pipeline.


In a recently newsgroup posting (Suggestion: Get-WmiObject reading computer names from input stream) Alex and MOW tried various things to get this to work.  MOW wanted to have a file (Computers.csv) of the form:


Computername,Class
localhost,win32_operatingsystem
foo,win32_share


and then be able to do the following:
PS> Import-Csv computers.csv |Get-WmiObject


Of course this does not work because Get-WMIObject does not accept pipeline input.  At this point you have 2 solutions: foreach and scriptblock parameters.



FOREACH



This should be pretty obvious:


PS> Import-Csv computers.csv | foreach {
>> Get-Wmiobject -computerName $_.ComputerName `
>> -Class $_.Class  }



ScriptBlock Parameters



This is not so obvious.  If you specify a Scriptblock as the value of a parameter on a commandline you have one of 2 situation – you specified it for a parameter which takes a ScriptBlock or it doesn’t.  If it does, the PowerShell engine passes the ScriptBlock to the parameter.  If it doesn’t, they you’ve just used yourself a SCRIPTBLOCK PARAMETER.  What happens is this case is that this SCRIPTBLOCK will be run for each and every object in the pipeline and the results will be bound to this parameter.  That means the following produces the same results as the foreach example above:


PS> Import-Csv Computers.csv |
>> Get-WmiObject -ComputerName {$_.ComputerName} -Class {$_.Class}


ScriptBlock parameters provide you as simple, powerful mechanism to get pipelining of parameters where the cmdlet author has not already provided this for you.


Enjoy!
Jeffrey Snover
Windows PowerShell Architect


PSMDTAG:INTERNAL: How parameterbinding works
PSMDTAG:PHILOSOPHY: Cmdlets should only allow pipelining of parameters that are safe to pipeline, the engine can allow the user to override that via ScriptBlock parameters.