Building a Custom PowerShell Cmdlet in C#

30 January 2017

cloudcodepowershell

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!

New Project

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.

new project

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.

nu-get

Fortune!

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.

Names

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 with CmdletAttribute.

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:

get-module

We can then run Import-Module .\ExampleCmdlet.dll, followed by another Get-Module and see this:

get-module

and run it using Get-FortuneCookie. Granted that doesn’t do much. We need to actually execute some code…

Execution

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:

first output

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.

random output

Debugging and Verbosity

Maybe now is a good time to add some debug logging. The WriteDebug() method gives us what we want here. We could also add some verbose information when the user runs our cmdlet with the -Verbose switch.

Returning Data

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 FortuneCookie. 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 call to WriteObject since PowerShell will now understand something about our new type.

output

Parameters

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 Parameter attribute to alert PowerShell to it.

So running with no parameters as before we get:

full output

and running it with the -Random switch returns to the original functionality:

switch

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.

What, If?

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 Delete-TheInternet without 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.

Done

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.

If you liked this post you can tweet it or follow me on Twitter!