PowerShell Pipeline Demo

Happy holidays all!  Long ago, an observant co-worker added the Cookie Monster nickname to my office name plate.  Even during off-seasons, this earns me bonus cookies – “hey!  you’re cookie monster, right?  have this cookie!”  As you might imagine, the holidays are worse.  Forgive me if I’m a bit slow this month.

This is a quick hit to cover two topics that often generate confusion; handling pipeline input, and handling the code behind -Confirm and -Whatif.  I often forget what to expect with pipeline input, and use the verbose output from Test-Pipeline to double check.

Pipeline input

Many of your favorite commands support pipeline input.  Get-ADUser | Set-ADUser.  Get-ChildItem | Remove-Item.  The pipeline is an integral part of PowerShell; it’s covered in two sections of (the subjective) best practices for building PowerShell functions, and examples abound online… yet many community based functions don’t support it.

The key bits:

  • Use [parameter()] attributes to add pipeline support for a variable.
  • Use a Process block in your function
  • Reference the pipeline variable in your process block

A function to demonstrate support for pipeline input, on a ComputerName variable. Copy it to the PowerShell ISE for better code highlighting:

Function Test-Pipeline {            
    [cmdletbinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]            
    param(            
        [parameter( Mandatory = $false,            
                    ValueFromPipeline = $True,            
                    ValueFromPipelineByPropertyName = $True)]            
        [string[]]$ComputerName = "$env:computername",            
            
        [switch]$Force            
    )            
                
    Begin            
    {            
        $RejectAll = $false            
        $ConfirmAll = $false            
            
        Write-Verbose "BEGIN Block - `$ComputerName is a $(try{$ComputerName.GetType()} catch{$null}) with value $ComputerName`nPSBoundParameters is `t$($PSBoundParameters |Format-Table -AutoSize | out-string )"            
    }            
    Process            
    {            
        Write-Verbose "PROCESS Block - `$ComputerName is a $(try{$ComputerName.GetType()} catch{$null}) with value $ComputerName`nPSBoundParameters is `t$($PSBoundParameters |Format-Table -AutoSize | out-string )"            
                    
        foreach($Computer in $ComputerName)            
        {            
            if($PSCmdlet.ShouldProcess( "Processed the computer '$Computer'",            
                                        "Process the computer '$Computer'?",            
                                        "Processing computer" ))            
            {            
                if($Force -Or $PSCmdlet.ShouldContinue("Are you REALLY sure you want to process '$Computer'?", "Processing '$Computer'", [ref]$ConfirmAll, [ref]$RejectAll)) {            
                    Write-Verbose "----`tPROCESS Block, FOREACH LOOP - processed item is a $(try{$computer.GetType()} catch{$null}) with value $computer`nPSBoundParameters is `t$($PSBoundParameters |Format-Table -AutoSize | out-string )"            
                }            
            }            
        }            
    }            
    End            
    {            
        Write-Verbose "END Block - `$ComputerName is a $(try{$ComputerName.GetType()} catch{$null}) with value $ComputerName`nPSBoundParameters is `t$($PSBoundParameters |Format-Table -AutoSize | out-string )"            
    }            
}

Piping two computers to this command:

image

Notice the behavior for $ComputerName in the Begin and End block, it might catch you off guard.

SupportsShouldProcess

One of the first things we learn with PowerShell is to look for –Whatif and –Confirm parameters.  We also learn that these are not available everywhere, and that implementing them is up to the author.  This is another common omission in community based functions, despite it being a best practice to provide this support where appropriate.

You might also find inconsistent implementation.  The simplest implementation (seen in the Cmdlet snippet)  leads to reliance on funky looking language like Do-Something -Confirm:$False, and includes no -Force switch.

Joel Bennett provided a great guideline for this.  You can see the implementation of this in the Test-Pipeline code above.

Confirmation:

image

Force parameter confirms all as expected:

image

Wrapping up

If you are submitting production grade functions to the community, or just want to provide a user experience mimicking a Cmdlet, be sure to look into providing support for the pipeline and SupportsShouldProcess.  It looks like a lot of effort, but if you create a snippet or a template for your functions, you can start with code similar to Test-Pipeline and tweak it to meet your needs.

Further reading:

Disclaimer:

I don’t claim to have followed the above for all of my contributions : )  I’m trying to start though, one of my PowerShell resolutions is to start practicing what I preach and follow best practices!

PowerShell Splatting – build parameters dynamically

Have you ever needed to run a command with parameters that depend on the runtime environment?  I often see logic like this:

If($Cred) { Get-WmiObject Win32_OperatingSystem -Credential $Cred }            
Else      { Get-WmiObject Win32_OperatingSystem }

It doesn’t look too terrible if you only have one option, but things get ugly fast.  What if you want the same logic for $Credential, $ComputerName, and $Filter?  You end up with 6 potential combinations and an unreadable mess of code, and this is with only three parameters.

The answer is splatting!

What is splatting?

Splatting is just a way to pass parameters to commands, typically with a hash table.  It was introduced with PowerShell v2, so it is compatible pretty much anywhere.  Here’s a simple example

#Define the hash table            
$GWMIParams = @{            
    Class = "Win32_OperatingSystem"            
    Credential = $Cred            
    ComputerName = 'localhost'            
}            
            
#Splat the hash table.  Notice we put an @ in front of it:            
Get-WmiObject @GWMIParams

Many blog posts focus on the readability splatting offers.  Readability is very important, but splatting gives us the framework needed to build up parameters for a command dynamically.

Building up command parameters

Let’s build a simple example.  The basic steps we take include creating a hash table, adding key-value pairs to that hash table, and splatting the hash table against a command.

#Create the initial hash table            
#You can use @{} to create an empty hash table            
$GWMIParams = @{            
    ErrorAction = "Stop"            
}            
            
#Add parameters depending on the environment            
#We use simple logic here, but you can get creative            
if($Cred)            
{            
    #We can add a key value pair with the Add method            
    $GWMIParams.add("Credential", $Cred)            
}            
if($Computer)            
{            
    #This alternative to the Add method is easier to read            
    $GWMIParams.ComputerName = $Computer            
}            
if($Filter)            
{            
    $GWMIParams.Filter = $Filter            
}            
            
#Splat the hash table.  You can splat multiple hash tables and still use parameters            
Get-WmiObject @GWMIParams -Class Win32_OperatingSystem

When we run this, Get-WMIObject will have different parameters depending on whether we have $Computer, $Filter, or $Cred defined in our session.  We get a side benefit to this:  a hash table with parameters and their values, which you can use for providing verbose or debug output.

If we ran the example code without Computer, Filter, or Credential variables defined, the parameters will look like this:

image

If we set $Computer to ‘localhost’ and run the same code, we now get different parameters:

image

Next steps

That’s about it!  For further reading, check out Get-Help about_Splatting (the about topic was added in PowerShell 3), or search around for numerous blog posts on the topic.

The real fun is figuring out what logic you should use to work with the hash table you are splatting.  If you follow best practices when writing PowerShell functions, you open up access to the PSBoundParameters, conveniently in the form of a hash table!