Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / IIS

Build a PowerShell cmdlet

4.20/5 (10 votes)
20 Oct 2007CPOL6 min read 1   623  
Learn how to build a simple Windows PowerShell cmdlet, use parameters, wildcards, CustomPSSnapin, and the Extended Type System.

What is Windows PowerShell?

Windows PowerShell (a.k.a. Monad) is a new CLI (Command Line Interface) provided by Microsoft. PowerShell is based on .NET Framework 2.0, and passes data as .NET objects.

What are we going to do?

In this article, you'll see how to develop a commandlet (cmdlet, PowerShell commands) which support wildcards, and uses ETS (Extended Type System), and how to use CustomPSSnapIn.

The sample uses IIS 7 and the IIS 7 .NET libraries (Microsoft.Web.Administration) to retrieve the list of websites in the local IIS 7 server.

How to begin?

  1. First, download Windows PowerShell, of course.
  2. Download Windows SDK
  3. Download PowerShell template for Visual Studio (optional).

What are Cmdlets actually?

Cmdlets are tiny .NET classes derived from System.Management.Automation.Cmdlet or from System.Management.Automation.PSCmdlet, and override a few methods with your own logic. The cmdlets are installed to PowerShell, and can be used from PowerShell, or from other applications which use PowerShell to invoke cmdlets.

Cmdlet vs. PSCmelet

A cmdlet class can derived from two different classes: Cmdlet and PSCmdlet. The difference is how much you depend on the Windows PowerShell environment. When deriving from Cmdlet, you aren't really depending on PowerShell. You are not impacted by any changes in the PowerShell runtime. In addition, your cmdlet can be invoked directly from any application instead of invoking it through the Windows PowerShell runtime.

In most cases, deriving from Cmdlet is the best choice, except when you need full integration with the PowerShell runtime, access to session state data, call scripts etc. Then, you'll derive from PSCmdlet.

Cmdlet attribute

Every cmdlet has a name in the same template: verb-noun. The verb (get, set, new, add, etc.) is from a built-in list of verb names. The noun is for your choice.

The first part in the cmdlet class (getWebsites.cs) is:

C#
[Cmdlet(VerbsCommon.Get, "ws" SupportsShouldProcess = true)]

The verb is "Get" (from the enum VerbsCommon), the noun is "ws", and we supports ShouldProcess. It's very important to use the verb from one of the enums.

Note that in the top of the code, I have these using statements:

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Collections;
using Microsoft.Web.Administration;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;

The ones in bolds are "special" namespaces which are relevant for PowerShell, except Microsoft.Web.Administration which is used to manage IIS 7.

Parameters

Almost any PowerShell cmdlet will use parameters to help users get relevant information. The parameters are, actually, properties which have the ParameterAttribute before:

C#
[Parameter(Position = 0,
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Enter filter by site name (support wildcard)")]
[Alias("SiteName")]

public string Name
{
    set { names = value; }
}

Parameters can be accessed using position or property name. This means that if we set the parameter at position 0, you can call the cmdlet like this: get-websites *. The "*" is the parameter, or using property name: get-websites -Name *. Here, we also define the alias: get-websites -SiteName *.

A mandatory parameter means the user must enter a value for the parameter.

The main logic

In our cmdlet, we can override a few methods. We must override at least one from this list:

BeginProcessing

The code here, in most cases, is used to prepare the cmdlet. This code runs only once, when the cmdlet calls.

ProcessRecord

This is the most commonly overridden method. This method includes the main logic. The code can run more than once, as required.

EndProcessing

This overridden method is used to finalize the cmdlet operation.

We can also override the StopProcessing method, which includes code that will run on an unexpected stop of a cmdlet (for example, the user uses Ctrl+c).

I override only the ProcessRecord method. First, I create an instance of the generic System.Collections.ObjectModel.Collection<> collection. This is the collection type PowerShell uses. The type is PSObject, which is the main object PowerShell uses.

Because we want to support wildcards, I use the built-in wildcard classes that comes with PowerShell:

C#
WildcardOptions options = WildcardOptions.IgnoreCase |WildcardOptions.Compiled;
WildcardPattern wildcard = new WildcardPattern(names, options); 

Then, we create an instance of Microsoft.Web.Administration.ServerManager, the object used to manage IIS 7 websites.

In the foreach loop, we check for every site to see if its name matches the wildcard. If it does, we convert it to PSObject.

Extended Type System

Extended Type System is one of the main and most interesting PowerShell concepts. We can extend any type we want, and add members in addition to the built-in ones. The PSObject object is the main object in PowerShell, because it includes the original object and the extended members in the same object, and gives the user who invokes this cmdlet the option to use any member - the original members and the extended ones.

C#
ps.Properties.Add(new PSNoteProperty("MaxBandwidthMB", site.Limits.MaxBandwidth / 1024));

Here, we add a new property called MaxBandwidthMB, and its value is the the original bandwidth value / 1024.

Types can be extended from code, or from XML files, in a specific format. Here, we will see an example to extend a type with a new property - but we can add properties and methods from a lot of types: aliases, scripts, code methods, etc.

Finally, we add the PSObject instance which includes the original (early bound object) and the extended members to the collection, and use the WriteHost method to write it to the host (can be PowerShell command line host, or another application that invoke our cmdlet).

After you'll finish the cmdlet, you can get a list of the members of the object returned from the cmdlet. Here you can see our extended property (marked):

Screenshot - Capture.jpg

In case of an exception...

We use a try...catch statement, and if an exception occurs, we use the WriteError method to write information about the error to the host.

Formats

If we use this cmdlet now from the console, we will get a strange output, which includes the object types and a few values. We have to specify the default output view we want. We do this in the format.ps1xml file. Note that we use the MaxBandwidthMB property, which is an extended one.

This is the output without the format file:

Screenshot - Capture1.jpg

Snap In

The snap-in includes the details PowerShell needs to install the cmdlet. We can derive it from PSSnapIn which is the "default" - install everything you can, or from CustomPSSnapIn, then we set exactly what to do.

Here, we add the cmdlet and the format file.

First, we define a collection for cmdlets, formats, types, and providers. In the constructor, we add the cmdlet and the format file. We also override a few properties to include information about our snap-in. And, we override the properties to return our collection of cmdlets and formats.

Installation

From PowerShell, we install the snap-in with installutil.exe, part of the .NET Framework SDK. I wrote a little function you can add to your profile:

JavaScript
function installutil
{
    param([string]$dllPath=$(throw "Please Enter the DLL path to Install!"))
    set-alias installutil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
    if(test-path $dllPath)
    {
        installutil /u $dllpath
        installutil $dllpath
        write-host "snap-in installed. now, you can add it to your shell instance"
    }
    else{
        write-error "The file does not exist"
    }
}

Now, you just have to enter:

installutil dllPath

Instead of dllPath, enter the full path to the DLL which includes the DLL of the project.

Now, you have to add the snap-in:

add-pssnapin cpdemo

Note that you may have to change the path of the format file in the snapin.cs class file.

And that's all! The cmdlet is ready to use and returns a collection of PSObjects which includes Microsoft.Web.Administration.Site and an extended property.

In PowerShell, you can use the object that the cmdlet returns for more things. This command, for example, will save the output to a CSV file:

get-ws d* | Where{$_.MaxBandwidthMB -gt 4000000} | 
           Select-Object Name,MaxBandwidthMB | out-csv c:\csv.csv

This command will save a new CSV file which includes the list of names and the MaxBandwidthMB property for all sites for which the name begins with "d" and where the value of the property MaxBandwidthMB > 4000000.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)