Over the time I’ve spent automating deployment and build processes, I have fallen out with plain command line applications and batch files. In their stead, I favour msbuild and writing custom tasks to wrap what would have been a command line application in the past.
As I have transitioned into a more DevOps-y kind of world, the emphasis is more on running commands to do configuration and admin at runtime. This really isn’t the place for msbuild so we have to take the step into writing custom code for PowerShell!
First, we need to create a new Class Library for the Cmdlets to live in. The version of .Net you target will depend on which version of PowerShell you want to work with.
The next thing to do is add references to PowerShell. This used to be a manual step to root out the correct assemblies on your file system, until Microsoft have made their reference assemblies for v3, 4 and 5 available as NuGet packages.
As an example, I want to build a simple fortune cookie generator, like the unix “fortune” utility. There is a great selection of fortunes collected on github, so I borrowed a few and created a list of them to select from:
To make this class into a Cmdlet, I need to derive the class from
System.Management.Automation.Cmdlet. I also need
to decide what the class name is going to be and what it should be called in PowerShell.
PowerShell convention is to name a cmdlet using <verb>-<noun> form. The noun part is easy - it’s a fortune
cookie generator - and the verb should probably be one of the standard PowerShell verbs -
“Get” seems to fit nicely. Declaring the name to PowerShell is done with by decorating the class
If you have selected a common verb, you can use the
VerbsCommon enum or, if not,
use the two string version of the attribute to provide the verb as text.
Building the project into an assembly will let you write the PowerShell to use it. In a plain vanilla
console, we can run
Get-Module and see this:
We can then run
Import-Module .\ExampleCmdlet.dll, followed by another
Get-Module and see this:
and run it using
Get-FortuneCookie. Granted that doesn’t do much. We need
to actually execute some code…
To execute actual code, we need to override the
ProcessRecord() method. The
simplest thing we could do is write out the first fortune cookie. Like this:
As you would expect, that writes out to the console:
Now that we have some kind of output, it’s not a massive leap to add in some randomness.
Running the command multiple times, behaves as expected.
Debugging and Verbosity
Maybe now is a good time to add some debug logging. The
gives us what we want here. We could also add some verbose information when the user
runs our cmdlet with the
So far we are writing text to the output using the
WriteOutput method. It would be nice if
we could return an object with a bit more context built in. First, we need to define a type
Second we need to refactor the internal code to deal in FortuneCookie objects. Finally, we
need to tell PowerShell that we want to return that type from the cmdlet. Note, we leave the
WriteObject since PowerShell will now understand something about our new type.
Now that we have a functioning cmdlet, it would be nice to add parameters so that we can
change the behaviour at runtime. Perhaps we should change the default behaviour so that running
Get-FortuneCookie will return all the cookies and we can pick a random cookie by passing the
-Random switch? First we create a member property to hold the setting and apply a
attribute to alert PowerShell to it.
So running with no parameters as before we get:
and running it with the -Random switch returns to the original functionality:
Most parameters are native types but the
SwitchParameter type allows us to pass the
name of the option. If we had declared Random to be a
bool we would have been forced to write:
Get-FortuneCookie -Random $true
Which I think we can all agree is not an elegant experience for the user.
Finally, one of the most useful and yet under used arguments to a cmdlet is the WhatIf switch. I like this
because it allows you to try out a dummy run of a scary command like
actually doing anything. A well written but potentially high risk cmdlet will implement this
option to allow someone to make sure all their parameters are correct before running it for real.
And that’s our example cmdlet completed. There are more options to explore - for error reporting, throwing exceptions, mandatory parameters, handling pipeline input - which I may come back to later once I’ve had longer to play around building some real world cmdlets.