« Tacoma Narrows Bridge Work Continues | Main | Let's All Write a Cmdlet, Part 2 »

October 02, 2005

Let's All Write a Cmdlet

Perhaps you've been playing with Monad and you're thinking of writing a cmdlet--but it seems like a big commitment. It turns out it's actually really easy. Just follow along with these simple steps.

First you need to create a C# file (you could write cmdlets in managed C++, VB.Net, etc but we'll use C#). Put the following in a file called (for this example) adam.cs:

using System;
using System.Management.Automation;

namespace MyNamespace

  public sealed class TestWriteCmdlet : Cmdlet
    protected override void BeginProcessing() {
      WriteObject("My cmdlet worked!");

This is the code for a cmdlet called test-write which just prints out the string shown. The filename adam.cs is just an example...I'm not trying to create a legacy of source code named after me, call it whatever you want.

Now you need to compile it. No hiding under the bed saying you don't have Visual Studio installed...we don't need any fancy-pants IDE, a simple command-line compiler will work. And if you have the .Net 2.0 Beta 2 runtime installed (which you do if you're running Monad), then you have the C# compiler. Run the following command (this assumes your adam.cs file is in the directory Monad is installed in, and that is your current directory):

\windows\microsoft.net\framework\v2.0.50215\csc.exe /target:library /reference:.\system.management.automation.dll adam.cs

This builds an assembly called adam.dll. You should see the banner from the C# compiler, and then lickety-split the compile will complete (it doesn't display anything else if it succeeds):

Microsoft (R) Visual C# 2005 Compiler version 8.00.50215.44
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50215
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Now with current Monad builds you need to compile this into a mini-shell to run it--we're improving that so you can load a new cmdlet into an existing shell, but for now run the following:

make-shell -out adamsh.exe -namespace MyNamespace -reference adam.dll

This builds a mini-shell named adamsh.exe from the adam.dll assembly you compiled above. It reflects on the assembly to discover the test-write cmdlet so that part's all $%#ing magic. A couple of things:

  1. You will see some disheartening output:

    Microsoft Command Shell MakeKit
    Copyright (C) 2005 Microsoft Corporation. All rights reserved.

    warning : A 2-way version violation is found for assembly Microsoft.VisualC.
    The list of assemblies referencing Microsoft.VisualC are:
    , adamsh, Version=, Culture=neutral, PublicKeyToken=null\System.Managemen t.Automation, Version=1.0.60319.0, Culture=neutral, PublicKeyToken=31bf3856ad364 e35\System.Data, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e0 89\Microsoft.VisualC, Version=, Culture=neutral, PublicKeyToken=b03f5f7f1 1d50a3a
    adamsh, Version=, Culture=neutral, PublicKeyToken=null\System.Mana gement.Automation, Version=1.0.60319.0, Culture=neutral, PublicKeyToken=31bf3856 ad364e35\System.Data, Version=, Culture=neutral, PublicKeyToken=b77a5c561 934e089\Microsoft.VisualC, Version=8.0.1200.0, Culture=neutral, PublicKeyToken=b 03f5f7f11d50a3a
    Shell adamsh.exe is created successfully.

    Ignore all that "2-way version violation" stuff--that message has scared off even hardened Microsoft developers, but it's a harmless issue with the .Net assemblies. Instead focus on the "Shell adamsh.exe is created successfully." message at the end. That one means "Good times".

  2. DO NOT called the shell adam.exe (the same as the name of your .cs file)--make-shell.exe generates some code for you (which will go in adamsh.cs in the example above) and if you call your shell adam.exe it will generate the code in adam.cs and silently overwrite your source file--this should be fixed soon, but for now be careful.

OK, so now you have adamsh.exe and you can just run it (by typing adamsh.exe), it will give you the MSH prompt, and then inside it run the test-write cmdlet and you see the expected output. No I won't show you, go do it yourself.

You will probably get a message when you start up adamsh.exe about:

Error loading the extended type data file:
Cannot find the registry key: "SOFTWARE\Microsoft\MSH\MyNamespace.adamsh\Path",
using "D:\msh" to load the configuration files.

There were errors in loading the format data file:
Cannot find the registry key: "SOFTWARE\Microsoft\MSH\MyNamespace.adamsh\Path",
using "D:\msh" to load the configuration files.

If you read it closely it does say that it recovered from the missing registry key, but if you want to get rid of the message, then do the following (this is shown from msh.exe, you can do the equivalent from regedt32 if you want but why? You've got your own mini-shell, use it!):

new-item hklm:\software\microsoft\msh\MyNamespace.adamsh

and then

set-property hklm:\software\microsoft\msh\MyNamespace.adamsh -property Path -Value d:\msh\adamsh.exe

You are creating a key called "MyNamespace.adamsh" under "software\microsoft\msh", and then setting a Path value containing the path to your mini-shell. You should adjust this based on the actual mini-shell name, namespace, and path to the one you built. Note that you can get the "shell id" (the namespace/executable combo that is the registry key name) from the $shellid variable inside your new mini-shell. It's based on the -out and -namespace parameters to make-shell.exe.

So that's it--simple source file, compile, build mini-shell, and now you're cooking with Monad.

Posted by AdamBa at October 2, 2005 08:24 AM

Trackback Pings

TrackBack URL for this entry:


I'm one of those folks who believes we need a better console environment and I also think Monad has interesting possibilities, so please don't take this as trolling, but . . .

isn't that an awful lot of work (with a strangely long list of caveats) for a simple "hello world"?

Posted by: Drew at October 2, 2005 01:21 PM

Remember if you just want to write a *script* that prints "Hello world", then put "Hello world" in a file with the extension .msh and run it. And if you want to write a .exe that prints "Hello world" then do it as you would anyway. What I showed here is creating a cmdlet. In my next post I'll show more of what a cmdlet gives you for free.

- adam

Posted by: Adam Barr at October 2, 2005 09:53 PM

Does monad allow you to write cmdlets using just monad? I would think that monad the sufficient infrastructure for just such a feature.

Posted by: zz at October 3, 2005 03:13 PM

Ah. I didn't realize this was the first of a series. This makes more sense now.

Posted by: Drew at October 3, 2005 04:09 PM

zz - you can't compile a cmdlet into an assembly, but you can write a script that behaves somewhat like a cmdlet. For example if you have a script called add-ten.msh that contains:

process {
($_ + 10)

then the process block is called where ProcessRecord() would be for a cmdlet (once per pipeline object) so you can do:

MSH> (3,4,5) | add-ten.msh

(that is pipe the 3-element array in to the cmdlet). Scripts can also have begin and end blocks that are invoked like BeginProcessing() and EndProcessing().

- adam

Posted by: Adam Barr at October 3, 2005 05:03 PM