New information has been added to this article since publication.
Refer to the Editor's Update below.

ScriptingMonad: The Future of Windows Scripting

Thomas Lee

Download the code for this article: Scripting.exe (119KB)

Managing an office full of Windows®-based servers and workstations has been challenging ever since the early days of Windows networking. While administrators have been using a combination of GUI-based applications, batch files, scripts, and a host of other utilities to get their jobs done, the GUI-based applications have tended to be the de-facto standard on the Windows platform.

Unfortunately, the nature of the GUI itself presents an inherent difficulty. A GUI is great for doing one operation on one system, but slow and tedious if you need to perform that operation on 1000 systems. In addition, GUIs are effectively hardcoded for a particular scenario, and typically cannot be used to solve novel problems. In many cases, the command-line approach can be more flexible and scalable, but Windows tools for the command line have, until recently, lacked the functionality and scope of their graphical brethren.

Microsoft® Command Shell (MSH), codenamed "Monad," is the answer to those who are looking for a more flexible and practical scripting environment for Windows. The goal of Monad is to provide a shell environment similar to the Korn, Bourne, or other shells on UNIX and Linux, and a rich programming language like Perl or Ruby, combined with the functionality of the Microsoft .NET Framework. Monad can do much of the heavy lifting, pulling information from Windows Management Instrumentation (WMI), for example, to vastly simplify systems management.

The possibilities offered by Monad offer a large and growing subject to study, and I can only scratch the surface in this column. I’ll cover the basics of Monad as it exists today, including an overview of the system and an introduction to Cmdlets and the pipeline. I’ll also give you instructions on how to get your hands on Monad and install it on your system.

What is Monad?

The name Monad comes from a philosophy called Monadism, a form of pluralistic idealism created by the 18th-century philosopher G. W. Leibniz, who also co-invented calculus. The idea behind Monadism is that the world is an aggregation of simple substances, the smallest of which is called the monad. The Monad product team is focused on this concept of composition, which drives the syntax, naming conventions, and many other aspects of this product.

In the context of Windows, Monad is a new shell and scripting environment. It makes use of small commands, known as Cmdlets (pronounced command-lets), which can be combined in rich and powerful ways to create a first class administration environment.

Monad itself is an environment that can be hosted within other applications, such as the new command shell, msh.exe. Monad is implemented as a DLL that you can host within your own applications, as well. You can create new administration tools on top of Monad (MMC snap-ins, for example, may eventually be layered on top of Monad), allowing you to use a GUI application to perform an operation, then use the script generated by this action to repeat the operation across multiple systems.

Installing Monad

[Editor's Update - 5/22/2006: Monad has been renamed Windows PowerShell. Click here for more information, and to download a copy of Windows PowerShell. ]

Monad is currently in beta testing. Microsoft has released Monad both to a technical beta program and to the wider public as a part of the WinFx™ SDK beta. To install the Monad beta, you must first download and install the WinFx SDK itself. You can download the full WinFx SDK at Microsoft WinFX Software Development Kit for Microsoft Pre-Release Windows Operating System Code-Named "Longhorn"(Out of date link removed). This is a 442MB ISO image of the full SDK.

The Monad download includes a great getting started document, which I strongly recommend you read and work through. The learning curve is greatly assisted by the fantastic support on the Monad beta newsgroup.

Installing Monad is straightforward, although it must be installed on top of the .NET Framework 2.0 Beta 2. Once you have the .NET Framework installed, you can run the Monad installation package, msh_setup-i386.msi (you’ll need a different version for 64-bit computers). This setup package checks that you have the right version of the .NET Framework and completes the installation without requiring a restart. The application folder is, by default, "%programfiles%\Microsoft Command Shell".

Shell Basics

Once you have Monad installed, you can start it by running msh.exe. At launch, the MSH shell looks very similar to the cmd.exe command prompt. You can edit the msh.exe properties, turn on QuickEdit, change fonts and layout, and adjust the screen foreground and background colors, just as you can with cmd.exe.

Inside the Monad window, you use MSH by typing commands. For example, to list the contents of a folder, simply type the familiar DIR command, as shown in Figure 1. As you can see, the basic paradigm is the same as cmd.exe (type commands and get output), and most of the familiar commands are still supported. Nevertheless, this is a very different shell.

Figure 1 Running DIR in Monad

Figure 1** Running DIR in Monad **

Cmdlets

Cmdlets are at the heart of Monad—Monad’s monad, if you will. Cmdlets are small executables, written in C# or any other .NET-compliant language, that implement a standard interface and are derived from a base CMDLET class. With the version of Monad available when writing this, you can even write Cmdlets within the interface itself.

Each Cmdlet has a name based on a verb-noun syntax. Examples include Cmdlets such as Get-Help, Clear-Host, Export-Csv, and so on. Each of these Cmdlets can take parameters as well as input from the pipeline (which I will explain later in this column). Cmdlets should be task oriented as well as self-documenting.

Cmdlets can also be aliased. The GetChildItem Cmdlet, for example, is by default aliased to both the dir and ls commands. Thus, an admin most familiar with the UNIX environment can type ls, while the Windows admin can type dir. Both result in the same output. This should make transitioning to Monad easier for many users.

When you start msh.exe, the engine runs two profile scripts called profile.msh. The first is a systemwide profile found at %allusersprofile%\documents\msh. The second profile is user specific, and is found at %userprofile%\my documents\msh. You can add alias statements to your profile.msh to customize your shell experience. I’ve created a number of customizations for my own profile, which you can download from the TechNet Magazine Web site.

Variables

As with most programming languages, Monad supports the concept of a variable to which you can assign a value. Variables in Monad are named starting with a $ character. For example, $date and $files.Variables can optionally have a data type formally defined. To display the value of a variable, just type the variable name (including the $). You can also do normal variable manipulation as follows:

$[C:\monad]> $one=1
$[C:\monad]> $one
1
$[C:\monad]> $two=[int] 2
$[C:\monad]>  $two
2
$[C:\monad]>  $one+$two
3

Variables can be of any .NET type, including scalars and arrays. An array can be of simple types (such as a set of numbers or strings) or an array of objects (each of which has properties, methods to call, and so on). You can also assign the output of a Cmdlet to a variable, and then use the variable to access the underlying object’s properties and methods. Figure 2 shows an example of assigning the DIR command output to an array of file objects, and then using properties of the objects in the array.

Figure 2Using an Array of File Objects

$[C:\test]> dir
    Directory: FileSystem::C:\test
Mode    LastWriteTime            Length Name
----    -------------            ------ ----
-a---   Jul 13 17:13                  3 test.txt
-a---   Jul 13 17:13                  3 test2.txt
$[C:\test]> $dir=dir
$[C:\test]> $dir[1]
    Directory: FileSystem::C:\test
Mode    LastWriteTime            Length Name
----    -------------            ------ ----
-a---   Jul 13 17:13                  3 test2.txt

$[C:\test]> $dir[1].name, $dir[1].length
test2.txt
3
$[C:\test]> $dir[1].name + " is "+ $dir[1].length 
+ " bytes long"
test2.txt is 3 bytes long
    

Providers

Computer systems support a variety of data stores, and each store has its own concepts and (often non-portable) tools to manage the store. In Windows, you have data stores such as the file system (containing files, folders, and volumes) and the registry (containing keys and values), to name a few.

Providers in Monad present data from different data stores in a consistent fashion to existing Cmdlets. There are seven providers included with the most recent Monad beta. These default providers include: Alias, Certificate, Environment, Filesystem, Function, Registry, and Variable.

Using the Filesystem provider, the Get-ChildItem Cmdlet for that provider would get you the children of the current node, meaning the files and folders in the current working directory. Using the Registry provider, the same Cmdlet would allow you to retrieve the child registry keys and values of the current node.

The Pipeline

UNIX scripting shells have traditionally used the concept of a pipeline, whereby you string commands together and make the output of one command the input of the next command. The pipeline can be extremely powerful, but in the past has suffered one major disadvantage: the pipeline usually only passes raw text between commands. This means each command has to parse text output into a form it can use. That may mean, for example, dropping the first two lines of the text, moving over 23 characters, hoping that blanks are spaces rather than tabs, grabbing the next six characters, and hoping they’re a number. This is often referred to as "prayer-based parsing."

Figure 3Piping to Format-Table

$[C:\test]> $dir=dir
$[C:\test]> $dir | format-table Name,
Length, Mode, LastWriteTime -auto
Name Length Mode LastWriteTime
---- ------ ---- -------------
test.txt 3 -a--- 7/13/2005 5:13:24 PM
test2.txt 3 -a--- 7/13/2005 5:13:33 PM
$[C:\test]> $dir[1.. 0] | format-table Name,
Length, Mode, LastWriteTime -auto
Name Length Mode LastWriteTime
---- ------ ---- -------------
test2.txt 3 -a--- 7/13/2005 5:13:33 PM
test.txt 3 -a--- 7/13/2005 5:13:24 PM

Monad provides a pipeline, but instead of passing raw text, it passes managed objects. That means a Cmdlet can use .NET reflection to look inside the objects being passed and see that the length attribute is an integer, and the name attribute is a string.

As an example, consider the Format-Table Cmdlet. It receives input and formats that input as a table, as shown in Figure 3. You should note that this example shows the range operator working in reverse.

You can also use the Where-Object and Sort-Object Cmdlets to filter and sort the objects in the pipeline based on one or more properties of the objects.

The Get-Process Cmdlet returns a list of all the processes active on your system. To look at just certain processes (say, with more than 500 handles) sorted by handle count, issue the command shown in Figure 4.

$[C:\test]> get-process | where {$_.handles -gt  500}| sort {$_.handles}
Handles NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
------- ------    -----      ----- -----   ------     -- ----------
    516     13     1676       1476    23    68.61   1088 svchost
    546     10     4464       2760    44 7,772.72    832 lsass
    582     24    27148       7708   129 1,777.33   1440 msnmsgr
    599     60     7868       4644    56   110.34    776 winlogon
    844      8    12484       5364    42   544.14    752 csrss
   1117     26    38320      26612   270 5,672.92   2432 explorer
   1954      0        0        116     2 4,420.50      4 System
   2155      5     2292       1084    40 1,992.89    568 DLService
   2325     70   162808     101028   401 4,425.33   1584 svchost

Reflection

Reflection, a feature of .NET, allows you to look inside an object to determine what it is, what its methods are, and so on. A simple way to use reflection in Monad is to use the Get-Member Cmdlet. This Cmdlet takes each object passed in the pipeline and uses reflection to retrieve the object’s properties and methods. You could use this when debugging a script to work out what is actually being passed in the pipeline. Reflection is also a great tool to help you learn Monad. Figure 5 shows some examples of using Get-Member. Notice that gm is a predefined alias for Get-Member.

Figure 5 Reflection with Get-Member

$[C:\monad]> $dir=dir
$[C:\monad]> $dir| gm -MemberType Property 

TypeName: System.IO.FileInfo

Name              MemberType Definition
----              ---------- ----------
Attributes        Property   System.IO.FileAttributes Attributes {get;set;}
CreationTime      Property   System.DateTime CreationTime {get;set;}
CreationTimeUtc   Property   System.DateTime CreationTimeUtc {get;set;}
Directory         Property   System.IO.DirectoryInfo Directory {get;}
DirectoryName     Property   System.String DirectoryName {get;}
Exists            Property   System.Boolean Exists {get;}
Extension         Property   System.String Extension {get;}
FullName          Property   System.String FullName {get;}
IsReadOnly        Property   System.Boolean IsReadOnly {get;set;}
LastAccessTime    Property   System.DateTime LastAccessTime {get;set;}
LastAccessTimeUtc Property   System.DateTime LastAccessTimeUtc {get;set;}
LastWriteTime     Property   System.DateTime LastWriteTime {get;set;}
LastWriteTimeUtc  Property   System.DateTime LastWriteTimeUtc {get;set;}
Length            Property   System.Int64 Length {get;}
Name              Property   System.String Name {get;}

$[C:\monad]> $date = [datetime]"10/1/2005" 
$[C:\monad]> $date | gm

TypeName: System.DateTime

Name                 MemberType     Definition
----                 ----------     ----------
Add                  Method         System.DateTime Add(TimeSpan value)
AddDays              Method         System.DateTime AddDays(Double value)
AddHours             Method         System.DateTime AddHours(Double value)
AddMilliseconds      Method         System.DateTime AddMilliseconds(Double value)
AddMinutes           Method         System.DateTime AddMinutes(Double value)
AddMonths            Method         System.DateTime AddMonths(Int32 months)
AddSeconds           Method         System.DateTime AddSeconds(Double value)
...
Date                 Property       System.DateTime Date {get;}
Day                  Property       System.Int32 Day {get;}
DayOfWeek            Property       System.DayOfWeek DayOfWeek {get;}
DayOfYear            Property       System.Int32 DayOfYear {get;}
Hour                 Property       System.Int32 Hour {get;}
Kind                 Property       System.DateTimeKind Kind {get;}
Millisecond          Property       System.Int32 Millisecond {get;}
Minute               Property       System.Int32 Minute {get;}
Month                Property       System.Int32 Month {get;}
...
#Now that you know what methods this supports, let’s invoke one:
$[C:\monad]> $date.AddMonths(1)

Tuesday, November 01, 2005 12:00:00 AM

Looking Forward

With Monad, I find myself constantly saying "Oh, and then there’s this other really cool feature." I’ve barely scratched the surface of the possibilities it offers. I haven’t even started to discuss the WMI interface, managing COM objects, or any of the more advanced administration topics, but I encourage you to explore further on your own.

Monad is slated to be released as part of the next version of Microsoft Exchange Server, currently planned for release sometime in 2006. Additional avenues of distribution are also being considered. Monad currently runs on Windows XP as well as current builds of Windows Vista.

Prerelease info in this article is subject to change.

Thomas Lee is Chief Technologist at QA plc, the UK's largest independent IT training organization. Thomas is a Microsoft Regional Director, an MVP, and MCT. He lives in the English countryside with his wife Susan and daughter Rebecca.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.