The Surly Admin

Father, husband, IT Pro, cancer survivor

Functions That Use the Pipeline

Earlier this week I talked about creating HTML reports from within Powershell, using the ConvertTo-HTML cmdlet.  One of the technique’s I used was a custom function I wrote called Set-AlternatingRows which took the table created by ConvertTo-HTML and alternated the row colors.  This was a great exercise in using Functions and the Pipeline and I wanted to talk about that today.

Functions

As I’ve talked about before, Functions are great for doing the same task over and over again within a script but another thing you can do is reuse functions in many scripts.  There are a couple of ways of doing this, one is just copy and paste the code into your new script or load the function into Powershell via your Profile (or a module).  I don’t like using the profile/module way because the script is then reliant on your Powershell enviornment in order to run properly but if you go to a new computer, or someone else tries to run your script it will fail miserably.  The downside with the copy/paste method, of course, is if you change the function you have to go back to all of your scripts and update them.  That sounds like a post all on it’s own!

Requirements

What do we need out of the Set-AlternatingRows function?  It needs to be simple.  Really simple.  We need to be able to pipe to it from ConvertTo-HTML or anything else.  Need to use existing CSS and change each Table Row (the <tr> tag) to a particular class name, then change to the other class name.  Continue to alternate back and forth until done.  Return everything to the pipeline.

The Pipeline

By far the hardest part of this function was getting the Pipeline working properly, if you don’t follow the rules just right you will get some pretty interesting results–not interesting in the way you would like it though!  There are a couple of rules for writing functions that use the pipeline that you need to follow.

  1. Know where your data is coming from
  2. Use Begin/Process/End

First thing we need to know where the data is coming from.  When calling a function, or any script, there are a number of ways to get data into the script and the main way is through parameters.  Typically we use the PARAM statement to define the parameters and their values, but you can also use the $Args variable to pull in information from parameters.  When using the pipeline we have one additional option which is the pipeline variable, our old friend $_.  The problem with $_, of course, is that it’s only good until you go to the next pipeline and you have to remember what the variable is supposed to be.  Come back a year later and you have to reverse engineer the entire script to figure out what the function is doing.

In order to take control of this, we’re going to use the PARAM statement to assign our own variable names and we have to use a specific Parameter clause’s to accept information from the pipeline.  So here’s how the Set-AlternatingRows function does it:

[CmdletBinding()]
Param(
   [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
   [string]$Line,

   [Parameter(Mandatory=$True)]
   [string]$CSSEvenClass,

   [Parameter(Mandatory=$True)]
   [string]$CSSOddClass
)

First, what the heck is CmdletBinding()?  Found a great article from Don Jones again, and he describes it better then I could so read about it here.  In the case of this function we don’t really need it but let’s leave it in so we can increase the ability of this script later, when we’re better at this sort of thing!  Next comes the PARAM section where we define our parameters, and each parameter get’s it’s own [Parameter()] decorators.  Mandatory = $True means we have to have this parameter or we’ll abort the script.  ValueFromPipeline means we’re going to take information from the pipeline and assign it to this variable.  Now, you have to know exactly what’s coming into your function for this to work, pipe a PSObject into this script and it’s likely to go completely wrong.  This script will take a single line of text (string), and nothing more.  We aren’t limited to just this though, we can accept objects using the [object] code, or anything else.  Also, it’s possible to pick and choose which properties we want without having to ingest the entire object.

Param (
   [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)}
   [string]$Name,

   [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)}
   [string]$SamAccountName
)

With the above, we can pipe a user object into the function and the function will automatically assign the Name property to $Name and the SamAccountName property to $SamAccountName.  Notice the variable name matches the property name and we use the ValueFromPipelineByPropertyName clause.

Begin/Process/End

Microsoft best practice is that every script at least have a Process {} scriptblock in it, but it’s mostly unnecessary and you’ll rarely see implemented.  But when using values from the pipeline you really need to use it.  The way the pipeline works is it feeds a value from an object, one at a time, into the following cmdlet or function, but one of the little quirks is if you don’t use a Process block it will feed the entire stream into your function and the only thing you’ll eventually be able to process is the last object.  This is one of those unexpected outcomes I foreshadowed earlier!

With a Process block we’ve now slowed the stream down and can process each individual object, but what about Begin and End?  The Begin and End blocks are processed before the first object is processed in the Process block and after the last object is completed.  So these are great for setting up your script before you you start processing things, and then for cleaning up after you’re all done.

In Set-AlternatingRows I use the Begin and Process blocks, but not the End block.  Let’s take a look at the Begin block.

Begin {
   $ClassName = $CSSEvenClass
}

Not much to it, is there?  In this case we’re going to set our initial $ClassName variable to the name of our even class row.  Next we work on the Process block where the real magic is happening.

Process {
   If ($Line.Contains("
<tr>"))
   {  $Line = $Line.Replace("
<tr>","
<tr class=""$ClassName"">")
      If ($ClassName -eq $CSSEvenClass)
      {  $ClassName = $CSSOddClass
      }
      Else
      {  $ClassName = $CSSEvenClass
      }
   }
   Return $Line
}

Since the value from the pipeline, $Line, is a string we can use our normal string methods on it.  First we check for a table row tag in th string and if it’s there we need to replace it.  Now, if you had 2 table row tags in the line we’d have a problem as they would both get the same $ClassName variable.  Luckily, the output of ConvertTo-HTML puts all of our table rows on different lines, so we don’t have to account for multiple table row tags.

Once we’ve identified that a table row tag exists we simply replace it with a new table row tab with the classname setting to our $ClassName variable.  Then we switch the variable from even to odd (or back again) and then we use the Return statement to send the $Line string back.  The next object in the pipeline is then sent downt he line.  When the second one comes in the Begin block is not processed since we’ve already done that, and the script moves to the Process block and does another table row search and replace.  This is repeated until everything in the pipeline has been processed and returned.

That’s really it.  Combine this with inputing and outputing PSObjects and you begin to see the real potential in Powershell.  If you’ve used a lot of Powershell as a shell language (what I call one-liner’s) then you already know the power of the pipeline–I feel like Darth Vader “Luke, if you only knew the power of the Pipe!”  Now if you begin writing scripts or functions that can accept pipeline information you can start designing your own scripts to do specific functions with all the same flexibility of the built in cmdlets.

Feel I need to cover something in more depth?  Let me know in comments.

Advertisement

January 24, 2013 - Posted by | PowerShell | , ,

7 Comments »

  1. […] parameter has several settings:  Mandatory and two pipeline values which I’ve talked about here.  I also added an alias of -Computers in case you forget to use -Name–which is an odd […]

    Pingback by Set-ShutdownComputers – Without Confirmation « The Surly Admin | February 7, 2013 | Reply

  2. […] -WhatIf, -Verbose and -Confirm.  This took advantage of the techniques I’ve built up here and […]

    Pingback by Setting the Manager Field in Active Directory « The Surly Admin | February 18, 2013 | Reply

  3. […] time you want to find out a user’s group memberships!  So it’s time to break out our Advanced Functions techniques and pretty this script […]

    Pingback by Get a User’s Group Memberships « The Surly Admin | March 21, 2013 | Reply

  4. […] or 2010) and Spiceworks.  We’re also going to give it the ability to accept input from the pipeline in case you wanted to make some kind of regular report out of […]

    Pingback by Getting User Information « The Surly Admin | April 1, 2013 | Reply

  5. […] convention that would lend itself to this kind of query).  To do this, we need to activate the pipeline capabilities.  That meant changing the name of my parameter to Name (so it matches the Computer Object that […]

    Pingback by New Version: Simple Server Status « The Surly Admin | May 7, 2013 | Reply

  6. […] is the base number we’re going to use and the Maximum we want to go up to.  I’ll use Parameters for these numbers so we can change them on the fly.  For my daughter, the maximum is always 12 so […]

    Pingback by Simple Multiplication Table « The Surly Admin | April 4, 2014 | Reply

  7. […] so happens I’ve written about doing this before, so go ahead and read it, I’ll wait.  So we need an argument decorator in our Param section […]

    Pingback by Exporting User Information « The Surly Admin | July 14, 2014 | Reply


Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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

%d bloggers like this: