An introduction to PowerShell Cmdlets

From time to time, I write a useful piece of code without any UI to interact with it. Typical examples are utility libraries, common in many code bases. One way to invoke such code, without an entire application around it, is through unit tests. In addition to unit tests, I often wish I had a way to invoke code directly and on-demand, especially when doing exploratory testing.

A lightweight approach to this task is to expose PowerShell Cmdlets from your library. PowerShell is available for all Windows versions since XP, and version 4 (which this post is based on) is available for Windows 7 and newer.

The starting point for this guide is a simple Messenger class, with two methods:

public static class Messenger
{
    public static void Configure(string configFile)
    {
        // ...
    }

    public static void SendMessage(string receiver, string message)
    {
        // ...
    }
}

The API requires you to pass a configuration file before sending messages. Before we can add any PowerShell Cmdlets, we must reference the System.Management.Automation assembly. The easiest way to accomplish this is through NuGet:

Install-Package System.Management.Automation

Our first task will be to wrap the Configure method in a Cmdlet. To expose a Cmdlet, simply create a class that inherits Cmdlet and decorate it with the CmdletAttribute.

using System.Management.Automation;

[Cmdlet(VerbsCommon.Set, "MessengerConfiguration")]
public class SetMessengerConfigurationCommand : Cmdlet
{
    [Parameter(Mandatory = true, Position = 0)]
    public string ConfigurationFile { get; set; }

    protected override void ProcessRecord()
    {
        Messenger.Configure(ConfigurationFile);
    }
}

The parameters to CmdletAttribute dictates how it is invoked from PowerShell, in this case: Set-MessageConfiguration. The ParameterAttribute on a property adds it as a command line parameter. In our case, it is a mandatory parameter, and we also specify that the parameter is positional, allowing us to pass the argument immediately after the command (position 0), without specifying the parameter name.

Finally, we override the ProcessRecord method to handle the actual invocation.

With the Configure method wrapped, it is time to tackle the SendMessage method. This will require us to handle multiple parameters:

using System.Management.Automation;

[Cmdlet(VerbsCommunications.Send, "Message")]
public class SendMessageCommand : Cmdlet
{
    [Parameter(Mandatory = true, Position = 0)]
    public string Message { get; set; }

    [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
    public string Receiver { get; set; }

    protected override void ProcessRecord()
    {
        Messenger.SendMessage(Receiver, Message);
    }
}

This Cmdlet, invokable with the command Send-Message, is very similar to the previous. Key differences are that we now have two parameters and that one ParameterAttribute specifies ValueFromPipeline = true. The latter means that any value that is piped to the Cmdlet will be stored in the Receiver property. This facilitates scripting, as we will see later.

But first, let us take a look at how we can load and invoke our two Cmdlets. Assuming our library resides in an assembly named Messenger.dll, we load the Cmdlets with the Import-Module command.

Import-Module .\Messenger.dll
Set-MessengerConfiguration .\messenger.config
Send-Message "Test message" "Some receiver"

Now, let us look at how we can exploit the ValueFromPipeline specified on the Receiver parameter. Imagine a text file named receivers.txt with multiple receivers, one on each line:

Get-Content .\receivers.txt | Send-Message "Some alert...."

In this post, I have demonstrated how to wrap a simple API in Cmdlets, exposing a command line interface. The reasons for wanting a CLI can vary, but I tend to use it in exploratory testing. The fact that your API is suddenly available to scripting, further enhances its value.

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