Monday, September 25, 2006 2:45 PM bart

Creating a Windows PowerShell cmdlet with a scriptblock parameter

Introduction

Everyone who has started with Windows PowerShell should know the where-object (aliased as where) cmdlet by now. A typical example

get-process | where-object { $_.ProcessName.StartsWith("n") }

or abbreviated using aliases

gps | where { $_.ProcessName.StartsWith("n") }

Assuming you have a single notepad process running on your machine, the output will be very similar as the one displayed below:

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     55       3     1416       6764    52     0,08   3436 notepad

The part indicated in red is a so-called script block. Readers of my blog learned about creating cmdlets themselves already (see all my PowerShell posts too). Today, I'm showing you a possible way to consume a scriptblock in your own cmdlet too, by re-creating the where-object cmdlet.

Dissection of Where-Object

So, what does Where-Object do? The answer is really simple: every object that flows through the Where-Object cmdlet is evaluated against the specified script block. If the script block returns true, it's passed on in the pipeline. If not, the object is dropped.

Considering this, what are the parameters? There are clearly two: one to take the script block and one to take the current object that has to be processed by the cmdlet. Let's concretize this using get-help where-object:

PARAMETERS
    -FilterScript <System.Management.Automation.ScriptBlock>
        The script block that is being applied to the object from the pipeline.

        Parameter required?           true
        Parameter position?           1
        Parameter type                System.Management.Automation.ScriptBlock
        Default value
        Accept multiple values?       false
        Accepts pipeline input?       false
        Accepts wildcard characters?  false

    -InputObject <System.Management.Automation.PSObject>
        The pipelined object that is being evaluated by the scriptBlock.

        Parameter required?           false
        Parameter position?           named
        Parameter type                System.Management.Automation.PSObject
        Default value
        Accept multiple values?       false
        Accepts pipeline input?       true (ByValue)
        Accepts wildcard characters?  false

The first parameter is the script block; the second one (which can be accepted from the pipeline) contains the input object.

Coding time

We know enough to create a similar cmdlet right now. Here it is:

[Cmdlet("where", "object2")]
public class WhereObjectCmdlet :
PSCmdlet
{
  
private ScriptBlock
filterScript;

   [
Parameter(Mandatory = true
, Position = 1)]
   public ScriptBlock
FilterScript
   {
      get { return
filterScript; }
      set { filterScript = value
; }
   }

   private PSObject
inputObject;

   [
Parameter(Mandatory = true, Position = 2, ValueFromPipeline = true
)]
   public PSObject
InputObject
   {
      get { return
inputObject; }
      set { inputObject = value
; }
   }

   protected override void
ProcessRecord()
   {
      object
o = filterScript.InvokeReturnAsIs(inputObject);
      if (LanguagePrimitives
.IsTrue(o))
         WriteObject(inputObject);
   }
}

The two properties need no explanation. The ProcessRecord method isn't too difficult as well. First, the inputObject is passed to the script that's called using the InvokeReturnAsIs method. There's another Invoke method too that returns an array of PSObjects as return objects. You can use this one too, but we just need a scalar value indicating the true/false evaluation state of the script block.

Using LanguagePrimitives we check for a true value and in that case, the object is passed on via the pipeline using WriteObject.

Next, wrap the cmdlet in a snap-in as explained in a previous post. I'm not going to duplicate this code over here; I'll assume you "publish" the cmdlet as "where-object2". Don't forget to installutil the snap-in too.

Testing it

Open Windows PowerShell and add your snap-in using add-pssnapin:

PS C:\Users\Bart> add-pssnapin <snapinname>

Next, run the where-object2 cmdlet on the get-process (gps) output as shown below:

PS C:\Users\Bart> gps | where-object2 { $args[0].ProcessName.StartsWith("n") }

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     55       3     1416       6756    52     0,09   3436 notepad

Works fine isn't it? The only caveat is that we have to use the $args script block parameter collection instead of $_. A little workaround can help:

PS C:\Users\Bart> gps | where-object2 { $_ = $args[0]; $_.ProcessName.StartsWith("n") }

However, the goal of this post is not to nag about the $_ assignment, but rather to show you how to create a cmdlet that invokes a scriptblock. And that's what we did :-).

Happy scripting!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# A graphical MessageBox confirmation cmdlet in PowerShell

Thursday, October 05, 2006 12:32 PM by B# .NET Blog

Introduction
I've been posting about Windows PowerShell programmability quite a bit lately. A few interesting...

# Windows PowerShell 2.0 Feature Focus - Script cmdlets

Saturday, March 22, 2008 9:09 AM by B# .NET Blog

Two weeks ago I did a little tour through Europe spreading the word on a couple of our technologies including