Writing a powershell cmdlet…

A cmdlet (reads “command-lit”) is a lightweight command used in Powershell environment. The Powershell runtime invokes cmdlets within the context of automation scripts that are provided at the command line.

Cmdlets perform an action. Through Powershell pipiline, they can return a .NET object.

Let’s look at the code.

[Cmdlet(VerbsData.Publish, "Message", DefaultParameterSetName = IdRangeParameterSet, SupportsShouldProcess = true)]
public class EventCmdlet : Cmdlet
{
    internal const string IdRangeParameterSet = "IdRange";
    const string DateRangeParameterSet = "DateRange";

    [Parameter(HelpMessage = "The source for the messages.", Mandatory = true, Position = 0)]
    public string Source { get; set; }

    [Parameter(HelpMessage = "The single audit entry id", Mandatory = true, ParameterSetName = IdParameterSet)]
    public int? SingleId { get; set; }

    //this is to support WhatIf in unit tests. (In the context of the tests ShouldProcess would return always false)
    public Func<bool> ShouldProcessAction = () => true;

    protected override void ProcessRecord()
    {
        var shouldProcess = ShouldProcessAction.Invoke();
        _eventManager.ShouldProcess = shouldProcess;

        var source = _sourcesEvents.FirstOrDefault(s => s.Name.ToLower() == Source.ToLower());

        if(source == null)
        {
            _logger.ErrorFormat("The specified source {0} was not found", Source);
            return;
        }
      
        if(SingleId.HasValue)
        {
            _logger.InfoFormat("processing a message by a single Id of {0} ", SingleId.Value);
            _eventManager.PublishEvent(source, SingleId.Value);
        }
    }
}

Cmdlet attribute

[Cmdlet(VerbsData.Publish, "Message", DefaultParameterSetName = IdRangeParameterSet, SupportsShouldProcess = true)]
public class EventCmdlet : Cmdlet

In order to declare a cmdlet class as a cmdle, Cmdlet attribute is required. So you start your class with this attribute.

Cmdlet parameter

    [Parameter(HelpMessage = "The source for the messages.", Mandatory = true, Position = 0)]
    public string Source { get; set; }

    [Parameter(HelpMessage = "The single audit entry id", Mandatory = true, ParameterSetName = IdParameterSet)]
    public int? SingleId { get; set; }

    [Parameter(HelpMessage = "The starting audit entry id", Mandatory = true, ParameterSetName = IdRangeParameterSet, Position = 1)]
    public int? FromId { get; set; }

    [Parameter(HelpMessage = "The ending audit entry id", Mandatory = true, ParameterSetName = IdRangeParameterSet, Position = 2)]
    public int? ToId { get; set; }

The public properties define the parameters available to the user that is running the application. You can use positional parameters which are handy if you need to put range values. Parameter set can be used to perform a specific action.

ShouldProcess feature

[Cmdlet(VerbsData.Publish, "Message", DefaultParameterSetName = IdRangeParameterSet, SupportsShouldProcess = true)]
public class EventCmdlet : Cmdlet

...

    //this is to support WhatIf in unit tests. (In the context of the tests ShouldProcess would return always false)
    public Func<bool> ShouldProcessAction = () => true;

...

    protected override void ProcessRecord()
    {
        var shouldProcess = ShouldProcessAction.Invoke();
        ....
    }

Powershell allows you to write cmdlets that prompt for user feedback. It should be declared in the Cmdlet attribute and you must call ShouldProcess method. (For more info, Requesting Confirmation from Cmdlets)

Processing method

There are Processing Methods like BeginProcessing, ProcessRecord, and EndProcessing. Typically, you override ProcessRecord, as it is called for every record that the cmdlet processes. BeginProcessing and EndProcessing are called only one time.

Running your cmdlet with F5 / Ctrl F5, visual studio run command

It will be very convenient if you can run your cmdlet without manually opening a shell and importing your module. In your cmdlet project, open property of the project and set the following sections on Build tab.

  • Start external program: c:\windows\syswow64\WindowsPowerShell\v1.0\powershell.exe
  • command line arguments: -noexit -command import-module .\Event.psd1

Resolving dependencies

If your cmdlet performs a rather complex action, it will depend on various other assemblies. You need to inject those dependent binaries into your cmdlet. Also, in order to unit-test your code, you need to mock out those assemblies too.

Unfortunately, powershell cmdlet can only be invoked through the default constructor, and as a result, you cannot pass dependencies through its default constructor.

First, you need psd1, which is a powershell module definition. You can create one by using New-ModuleManifest. In the manifest file, you can specify RequiredAssemblies.

# Assemblies that must be loaded prior to importing this module
RequiredAssemblies = 'Test.Framework.dll',
                     'EventR.Lib.dll',
                     'EventR.Configuration.dll',
                     'RabbitMQ.Client.dll',
                     'Jayrock.Json.dll'

Then use your IoC as service locator in the default constructor.

public EventCmdlet()
{
    Container.InitializeInstance<EventConfigurationContributor>();
    _events = Container.Instance.ResolveAll<IEvents>().ToList();
    _eventManager = Container.Instance.Resolve<EventManager>();
    _logger = Container.Instance.Resolve<ILoggerFactory>().Create(MethodBase.GetCurrentMethod().DeclaringType);
    ShouldProcessAction = () => ShouldProcess("Messages");
}
Writing a powershell cmdlet…

2 thoughts on “Writing a powershell cmdlet…

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