Flexible pipelining with ScriptBlock Parameters

Flexible pipelining with ScriptBlock Parameters

Rate This
  • Comments 12

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.

 

Leave a Comment
  • Please add 5 and 5 and type the answer here:
  • Post
  • Now that is going to come in very handy as we move along.

    PS amazes me more-an-more eac day.  Keep up the exceellent blogs Jeffery/PS-Team.

  • I think you have forgotten to mention one of the most important(well in my personal opinion that is) that when you use "scriptblock parameter", only one instance of cmdlet is run while one or more instances are run when using "foreach"... Did i get that correct?

    Ah, another one, I am not sure if I am remembering this correct that, there is a gotcha when it comes to passing a script block as a parameter that, you can't use scriptblock parameter on a parameter which accepts an object of type PSObject.(is it also a case for PSCustomObjects?)
  • >  only one instance of cmdlet is run while one or more instances are run when using "foreach"...

    That is absolutely correct and you are also correct that this can be a critical difference.  Imagine the case where a Cmdlet opens a DB connections, puts each object into the DB and then closes the connection.  If this Cmdlet is in a foreach block - it will open and close the connection for every object where as using ScriptBlock parameters, you only have 1 instance of the Cmdlet so it opens and closes the connection once.

    Correct as well on the restriction.  Scriptblocks work on parameters which are not of type SCRIPTBLOCK or OBJECT. I haven't looked at the code but I don't believe that there is a problem with PSObject types.  

    Jeffrey Snover
    Windows PowerShell Architect
  • Maybe not the right place for this question, but this is the most close topic to my current problem.
    I have created own cmdlet, which works fine in the defautl PowerShell host. It accepts various parameters from command line and the "target" item names from the pipeline.
    Now I am trying to create custom PS Host application. And here I cannot supply a list of "target" objects - I always get parameter binding exceptions.
    SDK is almost empty... Could you help me?
  • > Imagine the case where a Cmdlet opens a DB connections, puts each object into the DB and then closes the connection

    Ah, now that's a case I have never though of where I could make use of this feature!

    Thank you for clearing up there and for the post, Jeffrey.
  • Some people have asked the question, "Why Cmdlets?". If you already have a reasonable API, what is the

  • Some people have asked the question, &quot;Why Cmdlets?&quot;. If you already have a reasonable API,

  • PingBack from http://tylerczt.jordano.es/2007/10/22/cmdlets-vs-apis/

  • PingBack from http://smithuriahdcj.breastbondagenewsank.com/2008/01/21/cmdlets-vs-apis/

  • A while ago I blogged about the power of Flexible Pipeling Scriptblock parameters .&#160; The mechanics

  • PingBack from http://www.keyongtech.com/2831428-easiest-way-to-find-aliases

  • Unfortunately the example

     PS> Import-Csv Computers.csv |

     >> Get-WmiObject -ComputerName {$_.ComputerName} -Class {$_.Class}

    does not work for me!

    (WinXP, PowerShell 2.0)

    I always receive the errors "Get-WmiObject: The RPC Server is not available" and "Get-WmiObject: The input object cannot be bound to any parameters for

    the command either because the command does not take pipeline input or the

    input and its properties do not match any of the parameters that take

    pipeline input."

    Does anyone knows how to solve this?

    The foreach-example

     PS> Import-Csv computers.csv | foreach {

     >> Get-Wmiobject -computerName $_.ComputerName `

     >> -Class $_.Class  }

    works without problems!

Page 1 of 1 (12 items)