Writing CMDLETs in PowerShell

One of the many nice features of PowerShell is that you can write a function that behaves like a cmdlet.  That is, it can be written to not only process command line arguments but it can also process pipeline input.  That said, writing a well-rounded cmdlet function can be a bit tricky.  Take the function below, inspired by Scott Hanselman’s post on transforming XML via XSLT with PowerShell.  Here is one way to write this cmdlet such that it handles not only traditional arguments as well as pipeline input but it will also handle both at the same time.  Note that the actual XSLT "transforming" work is done by NXSLT2.EXE written by Oleg Tkachenko.
 
function Transform-Xml {
  param([string]$stylesheetPath=$(throw ‘$stylesheetPath is required’),
        [string[]]$xmlPath)
       
  begin {
    function applyStylesheetToXml([xml]$xml) {
      $result = $xml.get_OuterXml() | nxslt2.exe – $stylesheetPath
      [string]::join([environment]::newline, $result)
    }
    function applyStylesheetToXmlFile($sourcePath) {
      $rpath = resolve-path $sourcePath
      $result = nxslt2.exe $rpath $stylesheetPath
      [string]::join([environment]::newline, $result)
    }
  }
 
  process {
    if ($_) {
      if ($_ -is [xml]) {
        applyStylesheetToXml $_
      }
      elseif ($_ -is [IO.FileInfo]) {
        applyStylesheetToXmlFile $_.FullName
      }
      elseif ($_ -is [string]) {
        if (test-path -type Leaf $_) {
            applyStylesheetToXmlFile $_
        }
        else {
            applyStylesheetToXml $_
        }
      }
      else {
        throw "Pipeline input type must be one of: [xml], [string] or [IO.FileInfo]"
      }
    }
  }
     
  end {
    if ($xmlPath) {
      foreach ($path in $xmlPath) {
        applyStylesheetToXmlFile $path
      }
    }
  }
}
 
Note a couple of things about this function.  It doesn’t convert the output to XML.  That’s because an XSLT transform doesn’t always output XML.  In fact, quite often it outputs HTML and unless that HTML is XHTML then it won’t convert to the [xml] type.  The function also contains three sub-blocks: one for handling initial processing called ‘begin’.  Another block is called ‘process’ and it gets called for every object passed in from the pipeline.  And finally the ‘end’ block gets called when there is no more pipeline input.
 
A general approach to writing these cmdlets is to process your pipeline input in the ‘process’ block and your command line arguments in the ‘end’ block.  However, in both case make sure you have input.  In the pipeline case, you can check to see if you have pipeline input or not by checking if $_ is  equal to $null in the ‘process’ block.  In the ‘end’ block check your arguments and don’t process if they are equal to $null unless $null is a valid input.  As for the ‘begin’ block, it is the place to write initialization code (init a hashtable or counter value) and it is also a convenient place to put helper functions.
 
In the ‘process’ block make sure you can handle the types coming in from the pipeline and error on those that you can’t.  You can use coercion to try to handle more types but I’d rather have it fail for a scenario that I haven’t thought about.  When handling input that corresponds to files be sure to handle both the [string] type for paths specified via a string and the [System.IO.FileInfo] type which gets output by Get-ChildItem.  Finally, with filenames specified via a [string] argument make sure you run Resolve-Path on them to get the fully qualified filename.
 
Update: I have updated this article in several spots however the Transform-Xml function remains the same – so far.  I have noticed that I have a tendency to treat blog posts like PowerPoint slide-decks – you can tweak them both endlessly.  🙂
 
Advertisements
This entry was posted in PowerShell. Bookmark the permalink.

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