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!

Advertisements

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 )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s