navigation
 Thursday, June 21, 2007

You can write your own cmdlet ("command-let") to extend PowerShell in the .NET language of your choice.  You need to write both the cmdlet and a PowerShell snap-in to help install and register the command.  Here's an example snap-in for a "get-food" command  that lists tasty Mediterranean foods (like Falafels):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System;
using System.Collections.ObjectModel; // supports Collection
using System.Management.Automation; // supports PSSnapIn
using System.Management.Automation.Runspaces; // supports *ConfigurationEntry
using System.ComponentModel; // supports RunInstaller
using Falafel;

// Project also references System.Configuration.Install

public class FalafelSnapIn : CustomPSSnapIn
{
public FalafelSnapIn()
: base()
{
}

public override string Name
{
get
{
return "FalafelSnapIn";
}
}

public override string Vendor
{
get
{
return "Falafel Software ";
}
}

public override string Description
{
get
{
return "Runs custom Falafel commands.";
}
}

/// <summary>
/// Specify the cmdlets that belong to this custom PowerShell snap-in.
/// </summary>
private Collection<CmdletConfigurationEntry> _cmdlets;
public override Collection<CmdletConfigurationEntry> Cmdlets
{
get
{
if (_cmdlets == null)
{
_cmdlets = new Collection<CmdletConfigurationEntry>();
_cmdlets.Add(
new CmdletConfigurationEntry("get-food", typeof(FalafelCmdlet), null));
}

return _cmdlets;
}
}
}

CustomPSSnapIn knows how to be installed via installutil.exe, contains information about name, vendor, description etc., and has collections of types that can be registered with PowerShell such as cmdlets, Types, Formats and Providers.  FalafelSnapIn descends from CustomPSSnapIn, overrides the Cmdlets collection and adds the "get-food" cmdlet to the collection. Notice that much of the PowerShell specific functionality is supported in the System.Management.Automation namespace.

 Next we'll look at FalafelCmdlet, the implementing class for the "get-food" cmdlet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Management.Automation; // supports PSSnapIn, CmdLet, Parameter

namespace Falafel
{
[Cmdlet(VerbsCommon.Get, "Food")]
public class FalafelCmdlet : Cmdlet
{
private string _contains;

[Parameter(Mandatory = false, Position = 0, HelpMessage =
"List item descriptions containing this string")]
public string Contains
{
get { return _contains; }
set { _contains = value; }
}

protected override void ProcessRecord()
{
MediterraneanFoods foods = new MediterraneanFoods();
foreach (MediterraneanFood food in foods.FindFoods(_contains))
{
WriteObject(food);
}
}
}
}

First the Cmdlet attribute marks this class as a cmdlet and helps formalize the naming convention for cmdlets as being verb-noun combinations.  VerbsCommon lists the verbs that may be used:

FalafelCmdlet descends from Cmdlet but you can also use PSCmdlet.  Cmdlet is lighter-weight but PSCmdlet has more access to the PowerShell runtime. For this example the functionality would be the same so I will go with the lighter-weight Cmdlet.  The Contains property in this example holds a string used in searching food descriptions.  The Parameter attribute marks the Contains property as a parameter for the cmdlet, provides a help string and identifies Contains as not being mandatory.

Finally the ProcessRecord() method of Cmdlet is overridden to perform the actually work of the command.  In ProcessRecord() a class called MediterraneanFoods returns a generic list of MediterraneanFood objects based on description.  We won't get into the workings of MediterraneanFoods here because its purpose is to simply provide sample functionality for the command.  Note: Watch this space for a tasty blog by Lino on Anonymous Delegates that gets into how the generic list is searched.

The really cool part of ProcessRecord() is the WriteObject() method of Cmdlet.  Instead of Console.Writeline() text-only output, WriteObject() actually ouputs MediteraneanFood objects into the PowerShell pipeline.  This means that your objects are automatically usable by other commands.  You'll see this in a minute when we register and run the command. 

Here are the PowerShell commands I use to install and register the cmdlet:

cd C:\Clients\Falafel\Projects\FalafelCmdletLibrary\bin\Debug
set-alias iu $Env:windir\Microsoft.NET\Framework\v2.0.50727\installutil.exe
iu FalafelCmdletLibrary.dll
Add-PSSnapin FalafelSnapIn

The first line changes the directory to where the assembly for the FalafelCmdlet is stored.  Then, as a convenience you can use set-alias to make access to InstallUtil.exe easier.  The "IU" alias for InstallUtil installs the assembly.  This step produces a certain amount of logging I won't include here.  Finally the Add-PSSnapin makes the snap-in available to the current PowerShell console session.  You can call Get-PSSnapin to see the description and verify it's there:

Now we can run the "get-food" command, passing the "contains" parameter.  Notice the output by default is in table format.

Remember the call to WriteObject() that makes all our output actual objects instead of text?  Here's an example of piping the output of the one command that will easily work with an existing command without any adapting or parsing necessary to make it all work.  The "|" character is used to pipe the output from get-food to a format-list:

...and we have the output in list, not table, format.  Or we could pipe the output to the get-member command that performs reflection on objects passed to it.  You can see the MediterraneanFood object has Description and Name properties:

This all opens up possibilities for you to wrap existing .NET functionality in command-line form and to use the output in other existing commands.

Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, i, strike, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview