IN THIS CHAPTER
This chapter explains how PowerShell can be used as a management interface for applications. With new releases, such as Exchange 2007, Microsoft has built the management interface upon the foundation of PowerShell. For instance, when an administrator makes a change in the Microsoft Management Console (MMC) for Exchange, a PowerShell cmdlet is executed in the background to process the request. For developers who want to manage their own applications through PowerShell, Microsoft has released the PowerShell Software Development Kit (SDK) that can be leveraged to create custom cmdlets—which can then be called from a custom command shell, Windows application, or MMC snap-in.
This chapter covers creating a custom cmdlet, creating a custom snap-in, adding parameters to a custom cmdlet, and several other advanced features. Additionally, the scenario covers creating an MMC 3.0 snap-in that uses Yahoo! Maps geocoding functionality to pull coordinates for a street address.
All the source code referenced in this chapter are available at www.informit.com/title/9789780768687187.
The examples in this chapter are written in C# using Microsoft Visual Studio 2008 Express Edition (Visual Basic .NET is also supported). This is a free development tool from Microsoft and is highly recommended (although not required, one could just as easily write a custom cmdlet in Notepad).
The entire Microsoft Visual Studio Express Edition suite can be downloaded here:
For those new to .NET development, here are some definitions for commonly used words that will be seen throughout this chapter (several of these definitions are also found in Chapter 3, “Advanced PowerShell Concepts”):
• Namespace—A hierarchical grouping of objects. For example: The System.Net.Mail
is the namespace where the mail-related objects are located.
• Class—An object template that contains methods and properties. For example: The SmtpClient
class within the System.Net.Mail
namespace can be used to send an email message.
• Method—A function that can be executed by a class object. For example: The SmtpClient
class exposes a method named Send
.
• Property—A variable that can be set or obtained from a class object. For example: The Body
property in the MailMessage
class contains the text in the body of an email message.
• Library—A .dll file that contains compiled .NET code. For example: The System.Net.dll library contains compiled code pertaining to the System.Net
namespace.
• Global Assembly Cache—A collection of .NET libraries that are built into the .NET framework.
The PowerShell SDK is included in the Microsoft Windows Software Development Kit for Vista and .NET 3.0 Runtime Components. Although the SDK was released for Windows Vista, it is also supported on Windows XP SP2 and Windows 2003 SP1 or later.
To download the Microsoft Windows Software Development Kit for Vista and .NET 3.0 Runtime Components, go to the following location:
Installing the Microsoft Windows Software Development Kit is a straightforward process.
1. Click Next at the Welcome Screen (see Figure 13.1).
2. Accept the license agreement and click Next (see Figure 13.2).
3. Leave the default install location and click Next (see Figure 13.3).
4. Select the .NET Development Tools under Developer Tools / Windows Development Tools (see Figure 13.4).
Creating a custom cmdlet is a snap. The following example demonstrates how to create a simple cmdlet that returns “Hello World!”
The first step in creating a cmdlet is to determine a name. PowerShell cmdlet names are standardized using the verb-noun format. Each cmdlet begins with a verb that describes what type of command the cmdlet is going to execute—and ends with a noun that describes what object the cmdlet is going to execute against. For instance, the commonly used Get-Command
cmdlet begins with the verb “Get,” which is the type of command the cmdlet is going to execute. It ends with the noun “Command,” which is what the cmdlet is going to execute against.
When naming a custom cmdlet, Microsoft has provided several sets of standard verbs to choose from in the System.Management.Automation
namespace. The classes include the following:
• VerbsCommon
• VerbsCommunications
• VerbsData
• VerbsDiagnostic
• VerbsLifecycle
• VerbsOther
• VerbsSecurity
Each of these classes contains commonly used verbs, such as the following:
• Get
• Set
• Start
• Stop
The noun should also be singular, not plural. So, for example, Get-Command
is acceptable, but Get-Commands
is not.
When selecting a name for a namespace or class, Microsoft recommends using the naming convention of ApplicationName.PowerShell.Commands
for a namespace and VerbNounCommand
for a class.
The following is an example of the correct naming convention from one of the SDK libraries, as displayed in the Visual Studio Object Browser (see Figure 13.7).
After the appropriate names are chosen, a new class for a custom cmdlet can be created. If Visual Studio is being used, create a new Class Library project named HelloWorld (see Figure 13.8).
Next, add a reference to the System.Management.Automation.dll by completing the following steps (see Figure 13.9).
1. From the toolbar, select Project >> Add Reference.
2. Click the browse tab. (The libraries from the PowerShell SDK are not loaded in the Global Assembly Cache so they won’t show up on the default list.)
3. Navigate to C:Program FilesReference AssembliesMicrosoft WindowsPowerShellv1.0 and select System.Management.Automation.dll.
4. Click OK.
The required attributes for a new cmdlet class include a verb and a noun, as described previously. The class must also be a public class to be executed as a cmdlet from within PowerShell.
The two cmdlet classes are Cmdlet
and PSCmdlet
. The Cmdlet
class is more flexible and can be called directly from other .NET code, whereas the PSCmdlet
class must execute in a PowerShell runspace (but has the primary benefit of being able to call PowerShell scripts).
In the following example, the Cmdlet
class has been inherited in the HelloWorld. PowerShell.Commands
namespace. For the verb attribute, the VerbsCommon.Get
property is used; for the noun attribute, “HelloWorld
” is used. When executed from within PowerShell, the cmdlet name will be Get-HelloWorld
.
A custom cmdlet must override one of the following methods that are contained in the Cmdlet
and PSCmdlet
classes: BeginProcessing
, ProcessRecord
, or EndProcessing
. Each method is executed in sequence at a different phase of the cmdlet lifecycle, so the particular method that should be selected depends on whether one intends to process input from the pipeline.
If a custom cmdlet needs to perform any pre-processing before receiving pipeline input, you should use the BeginProcesses
method. If the cmdlet must receive pipeline input, you should use the ProcessRecord
method. If the cmdlet will not receive pipeline input, you should use the EndProcessing
method.
To return data to the pipeline, you must use the WriteObject
method and pass the output data.
In the following example, the ProcessRecord
method has been overridden (details on handling input come later in the section “Creating Custom Parameters”), and the simple string “Hello World!” is sent to the pipeline.
A PowerShell snap-in contains cmdlets that can be executed from within PowerShell. To execute a custom cmdlet, you must create a custom snap-in that is registered from within PowerShell.
The first step is to add a reference to the System.Configuration.Install
library that is part of the Global Assembly Cache. This library is required to write a custom installer for our snap-in.
Next, create another public class and inherit PSSnapIn
, which resides in the System.ComponentModel
namespace. Microsoft recommends using the VerbNounPSSnapIn
naming convention for these classes.
Finally, add the RunInstaller
attribute so that installutil.exe can be invoked to register the snap-in after it is compiled.
In the following example, the custom PSSnapIn
class has been named GetHelloWorldPSSnapIn
.
Three required properties must be overridden for a custom snap-in: Name
, Vendor
, and Description
.
The complete code for a custom cmdlet and snap-in displays as follows:
With the code completed, it can now be compiled into a .dll library. If Visual Studio is being used, select Build >> Build Solutions from the toolbar. To compile the code using the command line, use the following commands:
After the code has been compiled, the snap-in must be registered with PowerShell. Be aware that this requires administrator privileges, so do a RunAs
if a low-privilege account is being used.
The following commands are used to register the snap-in:
To confirm that the snap-in has been registered, run the Get-PSSnapIn
cmdlet with the –registered
parameter:
Add the snap-in to PowerShell with Add-PSSnapIn
cmdlet:
To confirm that our cmdlet is now available, run Get-Command Get-HelloWorld
:
Finally, the custom cmdlet Get-HelloWorld
can now be executed in PowerShell!
Cmdlets can accept two types of parameters: command-line parameters and pipeline parameters.
Command-line parameters come after the command and modify how the command operates. For instance, the Get-Command
cmdlet returns a list of all available PowerShell commands. However, if the –name
parameter is used, information on a specific command is returned.
Likewise, many cmdlets also accept parameters from the pipeline. For example, the Format-List
cmdlet accepts objects from an upstream cmdlet and outputs it in a list format, as opposed to the more common table format.
Again, to create a custom parameter, the first step is to come up with a name. PascalCase, or capitalizing the first letter of each word, is the recommended format.
It is also recommended to use common parameter names that are already established, such as Name, ID, Property, and Location. This is particularly important when receiving input from the pipeline, because PowerShell has the functionality to match like-types.
Microsoft maintains a list of recommended parameter names here:
http://msdn2.microsoft.com/en-us/library/ms714468(VS.85).aspx
In the following example, a public string property named Location
has been created that inherits the Parameter
class. Get
and Set
functionality has also been added:
To enable a cmdlet to accept pipeline parameters, set the ValueFromPipeline
attribute to true.
Now that the parameter class is complete, the cmdlet must be modified to properly process any object that is received. In the following example, this has been accomplished by checking to determine whether the private myLocation
string is null. If it is not null, an alternate message is output to the pipeline.
In the following example, the cmdlet is executed using no input, a command-line parameter, and a pipeline parameter, respectively:
In addition to the basic functionality of parameters outlined in this chapter, some advanced options are also useful.
Arrays can be used to accept multiple instances of an object. In the following example, the single Location
string has been changed to accept an array of strings:
The ProcessRecord
override is then modified to output each string in the array:
Now the user can input multiple locations with a single command:
The Position = X
property can be used to identify the order in which a parameter will appear after a command so that the user does not need to use the –Parameter
command-line switch. In the following example, the Position
property is set to zero, so the first object that follows the Get-HelloWorld
command becomes the Location
parameter value:
The Mandatory = true
property can be used to enforce a parameter. If the user does not supply the parameter, PowerShell will prompt for it:
The HelpMessage = string
property can be used to add help to a parameter. In the following example, we set a help message with the value “Sample Help Message:”
Parameters also have excellent built-in support for input validation. This enables cmdlet developers to set limits on exactly what type of input is acceptable.
The ValidateLength
attribute can be used to ensure that an input value meets minimum and maximum length requirements. The first value is the minimum length, and the second value is the maximum length. In the following example, the minimum length is set to 5 characters, and the maximum length is set to 10 characters:
The ValidateRange
attribute can be used to ensure than an input integer is within a predefined numeric range. The first value is the minimum value, and the second value is the maximum value. In the following example, the Location
attribute has been changed to an int,
and the ValidateRange
attribute is set to accept a number between 5 and 10:
The ValidatePattern
attribute can be used to ensure than an input integer meets a specific series of values. This is particularly useful for values, such as license plate numbers, that do not fall between a specific numeric range but still have a strict format that must be followed. In the following example, the ValidatePattern
attribute is looking for a value that matches the California license plate format of number-letter-letter-letter-number-number-number:
The ValidateSet
attribute can be used to accept only a specific value or set of values. There is also an optional parameter IgnoreCase=Boolean
to determine whether the parameter is case sensitive (the default is case insensitive). In the following example, San Bruno and San Jose are the only valid input values that will be accepted:
The ValidateCount
attribute can be used to accept a specific number of input values. This is useful if a parameter is an array that should be only of a specific size. In the following example, only two values can be input:
Lastly, the following common parameter names are reserved and should not be used: Confirm, Debug, ErrorAction, ErrorVariable, OutBuffer, OutVariable, Verbose, and WhatIf. The following aliases are also reserved: db, ea, ev, ov, ob, and vb.
Get-Help
is a commonly used cmdlet, and it is expected that any custom cmdlets support it.
Help for a custom cmdlet is written in a formatted XML file. This provides an easy way for a cmdlet developer to fill in the necessary fields so that the help is output in a standardized format.
The XML file must follow the standardized naming scheme, which is <libraryname>.dll-Help.xml. For the Get-HelloWorld
cmdlet, the XML help file is named HelloWorld.dll-Help.xml.
When Get-Help
is run against a cmdlet, several predefined sections are populated with data from the XML help file. Before proceeding, take a moment to run Get-Help Get-Command –full
to see an example of what a complete help section looks like.
After the XML file is created, the first step is to add the header. This provides the schema information, which sets the rules for how the XML file must be formatted and populated.
The first section of the XML file is the <command:details>
node, which populates the Name and Synopsis sections of Get-Help
.
The <command:name>
node is required and must match the name of the cmdlet; otherwise, Get-Help
returns an error.
The <command:verb>
node is the verb portion of the cmdlet name.
The <command:noun>
node the noun portion of the cmdlet name.
The <maml:description>
node is actually what appears under the Synopsis section.
The next section of the XML file is the <command:syntax>
node, which populates the Syntax sections of Get-Help
. If a cmdlet has multiple parameter sets, multiple syntaxItem
nodes can be added for each set. Because the Get-HelloWorld
cmdlet has only one parameter set, only one syntaxItem
is supplied.
The <maml:name>
node is the name of the cmdlet as it will appear in the Syntax section.
The <command:parameter>
node contains several attributes:
• required=boolean
—Indicates whether the parameter is required
• globbing=boolean
—Indicates whether the parameter supports wildcards
• pipelineInput=boolean
—Indicates whether the parameter supports pipeline input
• position=int
—Indicates if the parameter supports the position attribute and what location it is at
The <command:parameterValue>
node is the object type the parameter accepts.
Multiple <command:parameter>
sub-nodes can be added when a cmdlet accepts multiple parameters.
The next section of the XML file is the <maml:description>
node, which populates the Detailed Description section of Get-Help
.
The next section of the XML file is the <maml:parameters>
node, which populates the Parameters section of Get-Help
. This section is different from the parameters that are displayed under the Syntax section and are intended to be more detailed.
The node structure is also essentially the same, with the exception of the new <maml:description>
node, which contains a description of the parameter.
This section is visible when using the –Full
or –Detailed
command-line parameters of Get-Help
.
The next section of the XML file is the <command:inputTypes>
node, which populates the Input Type section of Get-Help
. The input type should indicate the object type that is accepted as input, such as a string.
Multiple Input Types can be specified using the sub-node <command:inputType>
. The <dev:type>
node contains the actual value that is returned.
The next section of the XML file is the <command:outputTypes>
node, which populates the Return Type section of Get-Help
. This is similar to the previous Input Type section, and it should also indicate the object type that is returned as output, such as a string.
The Notes section consists of both notes and examples.
The notes are populated by the <maml:alertSet>
node. The <maml:title>
node is the header that is displayed above the note, and the <maml:alert>
node is the actual note itself.
The examples are populated by the <command:examples>
node. Multiple examples can be supplied using the sub-node <command:example>
.
The <maml:title>
node should indicate the example number.
The <dev:code>
node should include an example of the syntax to run the command.
The <dev:remarks>
node should include an example of the output from the command.
The <maml:relatedLinks>
node populates the Related Links section of Get-Help
. Typically, this section includes cmdlets that are often used in conjunction with the custom cmdlet.
Note that in this case the <maml:navagationLink>
sub-node is different from the parent node. Multiple instances of <maml:navagationLink>
can be supplied. The actual data is contained in the <maml:linkText>
node.
A runspace can be used to execute PowerShell commands from within a .NET-based application. The following example creates a simple .NET executable that calls the Get-HelloWorld
cmdlet.
As with the previous example that creates a custom cmdlet, you must first add a Reference to the System.Management.Automation.dll
.
At this point, the application looks as follows:
The next step is to create a runspace configuration. This is required to create a runspace and can be used to add a custom snap-in.
The following example adds the GetHelloWorldPSSnapIn
:
Next, the runspace itself can be created using the runspace configuration.
The following example creates a runspace and then opens it:
After the runspace has been initialized, cmdlets can be called.
The following example calls the Get-HelloWorld
cmdlet and sets the Location
parameter:
Next, a pipeline object must be created. The pipeline object in the .NET executable functions just like a pipeline from within PowerShell. Multiple cmdlets can be called, and output can be passed down the chain.
In the following example, the command that was created previously is added to the pipeline. After the pipeline is invoked, it returns a PSObject
collection.
The Collection
class is in the System.Collections.ObjectModel
namespace, and the PSObject
class is in the System.Management.Automation
namespace.
The output is written to the console:
The complete source code looks like this:
After compiling the code into an executable, it can now be run:
As mentioned at the beginning of the chapter, the management interface for new Microsoft products is built upon the foundation of PowerShell. This gives administrators the ability to fully manage their products from the command line.
However, for most administrators, managing a product such as Exchange entirely from the command line is not practical. With MMC 3.0, Microsoft is wrapping the PowerShell cmdlets into a graphical user interface that enables any administrator to easily manage the product with a simple point-and-click.
In this scenario, Company ABC is working on a product to track the location of its customers so that it can determine when it makes sense to open a new shipping depot. Developers at Company ABC must come up with a way to resolve the longitude and latitude of each customer address.
Yahoo! offers an excellent Geocoding API as part of its Yahoo! Maps web application. Developers at Company ABC decided to leverage this API for their Get-Coordinates
cmdlet.
For more information about the Yahoo! Geocoding API, visit this address: http://developer.yahoo.com/maps/rest/V1/geocode.html
The Get-Coordinates
cmdlet must accept parameters for street, city, and state. These parameters will in turn be used to create a full web address from which the cmdlet will then receive a formatted XML file that contains the longitude and latitude.
Here is the complete source code for the cmdlet:
As with all cmdlets, the Get-Coordinates
cmdlet must be compiled and registered with installutil.exe before it can be used in PowerShell.
Here is the output from the cmdlet after it has been run:
MMC 3.0 snap-ins can be created using simple User Controls designed in Visual Studio. The User Control in this scenario creates a runspace and calls the Get-Coordinates
cmdlet.
To create a User Control in Visual Studio, right-click on the project name inside the Solution Explorer and select Add >> User Control (see Figure 13.10).
In the design view, a simple interface was created using the textbox and label controls, along with a button to initialize the search (see Figure 13.11).
The code-behind for the User Control is also straightforward. The cmdlet parameters are taken from their respective TextBox
controls. A pipeline is created, the cmdlet is added to it, and then the pipeline is invoked. The resultant output is then converted into a string, split, and used to populate the result controls.
Here is the complete source code for the User Control code-behind:
The first step to create an MMC 3.0 snap-in is to add a reference to the Microsoft.ManagementConsole.dll library. Fortunately, this is included in the Microsoft Windows Software Development Kit for Vista, so there is nothing additional that needs to be installed.
Each snap-in must have a unique GUID. Microsoft has created a utility named guidgen.exe, which is the simplest way to create a new GUID.
After guidgen.exe is executed (see Figure 13.12), select Registry Format
and click the Copy
button.
Next, add a new class definition to the Visual Studio project. The new class inherits the SnapIn
class. Two attributes are required: a GUID
(which can be pasted in from the guidgen.exe application) and a DisplayName
. The DisplayName
displays under “Available snap-ins” when the snap-in is added to an MMC console.
Additionally, another new class that inherits the SnapInInstaller
class is required so that installutil.exe can be used to register the snap-in.
MMC is designed to support multiple snap-ins, so each snap-in must have its own node. The DisplayName
property of the node appears under “Console Root” after the snap-in is added to an MMC console.
The final code required is to create a FormViewDescription
. Multiple types of FormViews can be used—the FormViewDescription
is the class that is required to create a WinForm with a User Control.
In the following code, a new FormViewDescription
is added, and the ControlType
property is set to the GetCoordinatesControl
that was created previously. The FormViewDescription
is then added to the node.
Finally, the code must be compiled and registered with installutil.exe.
It can then be added to MMC and used as a snap-in (see Figure 13.13)!
In this chapter, you learned how to create a PowerShell cmdlet and how to execute that cmdlet from within an MMC 3.0 snap-in. Advanced functionality, such as creating parameters, supporting Get-Help
, input validation, and creating runspaces in .NET were explained in detail.
PowerShell offers a new standard for command-line interfaces, and MMC 3.0 offers a new standard for GUIs. Now that Microsoft has established a precedent that its applications can be managed by both interfaces, users will begin to expect similar functionality in any third-party applications that they administer.
As a developer, this is both an exciting and challenging turn of events. Although new technologies and coding techniques must be learned, the user experience will be significantly improved as a result.