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.