In this tutorial, I would like to show you how to build your first CMDLET (command let) using C# and PowerShell SDK. We will make the simplest one as our HelloWorld, only to learn the skeleton of creating it. Many things we can do with CMDLET. For example, some folks make CMDLET for IIS 7.0 Management, SQL Server 2008, TFS, Sharepoint etc. Maybe myself should release one for MSMPI for example hmmm, spicy!. Ok, let start!
Basically, cmdlets are .NET class, derived from abstract classes:
- System.Management.Automation.Cmdlet or
- System.Management.Automation.PSCmdlet
and what we should do is to override some methods with our needs. The cmdlet assembly then can be installed to the PowerShell and can be used from PowerShell or from other applications which use the PowerShell to Invoke cmdlets.
A cmdlet has a name, that consists of a verb that indicates the action the cmdlet takes and a noun that indicates the items that the cmdlet acts upon. For example Get-Proc, it uses the verb "Get", defined by the VerbsCommon enumeration, and the noun "Service" to indicate that the cmdlet works on service items. When naming cmdlets, do not use any of the following characters: # , () {} [] & - /\ $ ; : ” ’<> | ? @ `. We should choose a noun that is specific. It is best to use a singular noun prefixed with a shortened version of the product name, for example Get-Proc.
Once we have chosen a name for our cmdlet, define a .NET class to implement the cmdlet, here is the class definition for Get-Proc cmdlet:
[Cmdlet(VerbsCommon.Get, "Proc")]
public class GetProcCommand : Cmdlet
The class for a cmdlet must be explicitly marked as public. Classes that are not marked as public will default to internal and will not be found by the Windows PowerShell runtime. CmdletAttribute attribute, with the syntax [Cmdlet(verb, noun, …)], is used to identify this class as a cmdlet. This is the only required attribute for all cmdlets, and it allows the Windows PowerShell runtime to call them correctly. It is a good practice to reflect the cmdlet name in the class name. To do this, use the form "VerbNounCommand" and replace "Verb" and "Noun" with the verb and noun used in the cmdlet name.
Cmdlet class can derived from both Cmdlet and PSCmdlet abstract classes. It depends on deep we would like to integrate with PowerShell runtime, let say access to session state data, call scripts etc. When deriving from Cmdlets, we aren't really depend on Powershell. We are not impacted by any changes in PowerShell runtime. Our cmdlet can invoke directly from any application instead of invoking it through the Windows PowerShell runtime. In most cases, deriving from Cmdlet is the common choice.
The Cmdlet class provides three main input processing methods, at least one of which your cmdlet must override.
BeginProcessing
For all types of input, the Windows PowerShell runtime calls BeginProcessing to enable processing. If our cmdlet must perform some preprocessing or setup, it can do this by overriding this method. Windows PowerShell uses the term "record" to describe the set of parameter values supplied when a cmdlet is called.
ProcessRecord
If your cmdlet accepts pipeline input, it must override the ProcessRecord method. For example, a cmdlet might override both methods if it gathers all input using ProcessRecord and then operates on the input as a whole rather than one element at a time, as the Sort-Object cmdlet does.
EndProcessing
This method is used optionally to end the pipeline input. Means that EndProcessing is optional when if we use ProcessRecord method to accepts pipeline input. If our cmdlet does not take pipeline input, it should override the EndProcessing method. Be aware that this method is frequently used in place of BeginProcessing when the cmdlet cannot operate on one element at a time.
For example, because our sample Get-Proc cmdlet must receive pipeline input, it overrides the ProcessRecord method and uses the default implementations for BeginProcessing and EndProcessing. Some cmdlet might have object variables to clean up when it is finished processing (for example, if it opens a file handle in the BeginProcessing method and keeps the handle open for use by ProcessRecord). It is important to remember that the Windows PowerShell runtime does not always call the EndProcessing method, which should perform object cleanup. For example, EndProcessing might not be called if the cmdlet is canceled midway or if a terminating error occurs in any part of the cmdlet. Therefore, a cmdlet that requires object cleanup should implement the complete IDisposable pattern, including the finalizer, so that the runtime can call both EndProcessing and Dispose at the end of processing.
Here is my simple codes to show OS processes stream :
//This class implements a Get-Proc cmdlet that has no parameters.
[Cmdlet(VerbsCommon.Get, "Proc")]
public class GetProcCommand : Cmdlet
{
protected override void BeginProcessing()
{
base.BeginProcessing();
}
protected override void ProcessRecord()
{
// Get the current processes from Diagnostic Object
Process[] processes = Process.GetProcesses();
WriteObject(processes, true);
}
}
How to Register Cmdlets
There are two mechanisms that can be used to register our Get-Proc cmdlet:
1 - Extending the shell using a snap-in (commonly used)
2 - Registering with a custom shell
I used the first one using a snap-in. This registration mechanism does not require a new shell, but adds the cmdlet to the current session or a saved session. A Windows Powershell snap-in can register all the cmdlets in a single assembly, or it can register a specific list of cmdlets. Note : snap-in assembly should be installed into a protected directory; otherwise, an unauthorized user could replace your assembly with malicious code.
To write a snap-in that register our cmdlet in its assembly, our snap-in class must derive from the PSSnapIn class. Here our simple codes look like:
// Create the PowerShell snap-in used to register Get-Proc CmdLet
[RunInstaller(true)]
public class GetProcPSSnapIn : PSSnapIn
{
//Create an instance
public GetProcPSSnapIn(): base(){}
// Specify the name of the PowerShell snap-in.
public override string Name
{
get
{
return "GetProcPSSnapIn";
}
}
//Specify the vendor for the PowerShell snap-in.
public override string Vendor
{
get
{
return "RAM";
}
}
//Specify the localization resource information for the vendor.
public override string VendorResource
{
get
{
return "GetProcPSSnapIn,RAM";
}
}
//Specify a description of the PowerShell snap-in.
public override string Description
{
get
{
return "This is a PowerShell snap-in that includes the Get-Proc cmdlet.";
}
}
//Specify the localization resource information for the description.
//Use the format: resourceBaseName,Description.
public override string DescriptionResource
{
get
{
return "GetProcPSSnapIn,This is a PowerShell snap-in that includes the Get-Proc";
}
}
}
Using the Windows Powershell snap-in library (.DLL file), register it to GAC using InstallUtil.exe. Remember that we need to sign our cmdlet assembly before register it to GAC. We can use PowerShell to set alias for InstallUtil.exe by the following:
PS > set-alias installutil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
PS > installutil SelectStrCommandSample.dll
Now we can add the Windows Powershell snap-in to the shell using the Add-PSSnapin cmdlet as shown below. Note : make sure you run the PowerShell using Administrator right.
PS > add-pssnapin GetProcPSSnapIn
To check the registration result, we can run:
PS > get-PSsnapin -registered
If the operation is successful, we should be able to run our cmdlet
PS > Get-Proc
DONE. Please download source code here. What about the MSMPI stuff??? Just to inform you as a closing, that PowerShell can use any Windows Objects (.NET, COM, WMI, etc). So MSMPI CMDLET should be possible!
Hope this helps!
Ciao - RAM