What is MSH
Microsoft Shell (codename Monad), hereafter abbreviated MSH, is a revolutionary new console shell and environment for Windows. The primary feature that makes it revolutionary is that MSH is designed around the manipulation of objects. While Unix and Linux shells pipe text from one application to another, MSH allows the piping of .NET objects.
The feature is revolutionary because text strings were a limitation. Data had to be represented in text and broken up into units that other programs could understand, and we had to rely on other programs to do this, such as grep. Programs had to know the structure of text in advance in order to parse it, and although there were some conventions, conventions don't solve all problems.
Some common problems with text parsing:
- It is often necessary to do excessive parsing or multiple passes on input.
- Text parsing is often inaccurate. The setup parsing scripts cannot account for changes to the structure.
- It's easy to have incompatibilities between different versions of programs that change output.
- Text formatting needs to be done cautiously. If formatting symbols occur, they need to be escaped, etc.
MSH aims to solve these problems. Addressing each of them:
- MSH pipes data as objects. Multiple passes are often unnecessary with object streams, since they can be avoided or incorporated into one pass. A consumer of text does not know where one textual element begins and another ends, without actually parsing the entire stream up to that point. Object streams are much easier to work with than text streams, because even if multiple passes are required, random access is available.
- Since no parsing happens (programs access object members for information), there can be no inaccuracy. Meaning is encoded unambiguously into the structure of the objects.
- Version control is handled the same way it is anywhere else in .NET. .NET has extensive mechanisms in place to gracefully handle version differences, although they are beyond the scope of this article.
- As information is transmitted as .NET objects, formatting is not an issue.
Introduction to MSH
This article is an introduction to writing cmdlets. Some very good tutorials exist already to basic MSH features, such as its programming language and basic syntax. Rather than duplicate their efforts, I would point readers unfamiliar with these aspects of MSH to a good one: Ars Technica's Guided tour of the Microsoft Command Shell. The rest of this guide assumes the reader is familiar with these basic features.
What are Cmdlets
Cmdlets are the basic units of execution of MSH. Although MSH is able to execute traditional command-line programs (such as, say, ipconfig), such simple programs would communicate with the shell using primitive text streams. In order to get the advantages MSH offers, one must interact with it using its favored execution units, the cmdlet.
A cmdlet is named by two parts: a verb and an action. The cmdlet is written with the noun following the verb, with a dash in between. An example is get-member
, which is a cmdlet that prints out all public members of the object passed to it. verb-noun
is the naming convention that Microsoft developed in order to specify with clarity what cmdlets do. This naming convention is enforced programmatically in the construction of cmdlets.
Building Your First Cmdlet
Following the age-old tradition of programming tutorials, starting with K&R, we will first develop a simple cmdlet that prints "Hello, world!". Following the naming convention, let's name this cmdlet greet-world
.
A cmdlet is specified by the attribute CmdletAttribute
. This attribute resides in the namespace System.Management.Automation
. That namespace is not present in standard .NET libraries, but rather is provided by an assembly in the Microsoft Command Shell folder, wherever you installed it; by default, it is installed in C:\Program Files\Microsoft Command Shell. It is necessary to add an assembly reference in order to access the CmdletAttribute
class attribute.
With the assembly added to the project, you add the Cmdlet
attribute to the class. The attribute's constructor takes two parameters: the verb and noun name mentioned previously. For our first cmdlet, let's name it greet-world
, so the attribute specification will look like this:
[Cmdlet("greet", "world")]
Our class is going to take no parameters from the shell, so the class definition will be very simple. The class contains one method, the ProcessRecord
function, which executes when our cmdlet is being processed. Inside this method, we can write an object to the cmdlet's output stream. In this case, the object is our greeting, "Hello, world!"
. So the class looks like this:
GreetWorld.cs:
[Cmdlet("greet", "world")]
public sealed class GreetWorld : Cmdlet {
protected override void ProcessRecord() {
WriteObject("Hello, world!");
}
}
In order to test the cmdlet, we have to build it. Unfortunately, this is a complex process. The first step is to compile the class library like normal, which can be done with Visual Studio or the C# command line compiler. Once the assembly is compiled, we have to inject it into the MSH environment.
Although Microsoft developers are working on a way to inject cmdlets into MSH at runtime, the most stable current process is to build a new MSH shell that includes the cmdlets. Fortunately, Microsoft has included a shell compiler; it takes cmdlet dependencies and builds a new MSH instance that provides access to them. This shell compiler is make-shell. The make-shell shell compiler also resides in the Microsoft Command Shell directory. To build a new MSH instance, you must specify a few parameters:
- -out <target.exe> specifies the destination executable name to build.
- -namespace <namespace> tells the shell compiler which namespace the cmdlet resides in.
- -reference <target1.dll,target2.dll,...> specifies in which existing assemblies the shell compiler should search for cmdlets. The compiler will look in the namespace specified by -namespace.
Once your cmdlet assembly is built, execute the shell compiler. An example execution is shown below, for the code file, GreetWorld.cs:
make-shell -namespace Sirophix.Articles.Cmdlets
-out myshell.exe -reference Tutorial.dll
If everything goes smoothly, the output from make-shell should look like the following:
Microsoft Command Shell MakeKit
Copyright (C) 2005 Microsoft Corporation. All rights reserved.
Shell myshell.exe is created successfully.
Simply run myshell.exe and you'll have a new shell that includes greet-world. At the time of this writing, newly compiled shells start up with a number of exceptions; these exceptions seem relatively harmless. The shell will still start up successfully. When it's running, try executing your new cmdlet:
MSH C:\> greet-world
Hello, world!
I mentioned earlier in the article that the power of MSH is that it uses .NET objects instead of strings. This is the case for our cmdlet as well: it returns an actual System.String
, not mere text. You can see that this is the case by piping the return from greet-world to get-member.
MSH C:\> greet-world | get-member
TypeName Name MemberType Definition
-------- ---- ---------- ----------
System.String Clone Method System.Object Cl...
System.String CompareTo Method System.Int32 Com...
System.String Contains Method System.Boolean C...
System.String CopyTo Method System.Void Copy...
System.String EndsWith Method System.Boolean E...
[... and more]
As you can see, the return value from greet-world is an actual object with members and methods. In fact, we can execute these members directly, just like we could in a .NET language. For example, we can just print out the type name to verify what it is:
MSH C:\> (greet-world).GetType().ToString()
System.String
This output demonstrates for sure that we're returning a System.String
. We can even perform transformations on this object:
MSH C:\> (greet-world).ToUpper()
HELLO, WORLD!
Implications
It should be apparent to the reader that MSH is an incredibly powerful environment. In addition to its innate features, Microsoft is also committed to exposing every aspect of system management within MSH. This means that any administrative feature available can be controlled with an MSH script.
MSH has transcended the classic notions of what a shell environment is capable of. MSH has the power of a programming environment with the simplicity of other shell scripting languages. From within MSH, one can script many complex actions (such as connecting to a database, manipulating objects) that would otherwise have to be written in a C++ or C# program.
The next article in the series will cover more advanced features of cmdlets, such as passing named parameters, with and without pipes, and the other types of execution notifications cmdlets can receive.
This article was written with much help from two other developers, Mathias Ricken, a Rice University programming languages researcher, and redwyre from #C++ on DALnet.
Justin is currently an undergraduate computer science student at Rice University.
Justin's programming background is extensive, and he has worked for such companies as Amazon.com and R7Solutions.com. He will be working for Microsoft on the C# language team starting summer of 2006.