发新话题
打印

[转载]A guided tour of the Microsoft Command Shell

[转载]A guided tour of the Microsoft Command Shell

文章作者:Ryan Paul
原始连接:http://arstechnica.com/guides/other/msh.ars

Introduction
The Windows community is a universe of uniformity in which users depend on robust commercial applications and standard graphical utilities. With the Vista release right around the proverbial corner, Microsoft is engaged in much-needed reinvention. Those of you that have kept up with the Redmond renaissance know that Microsoft will soon unleash a number of powerful new developer frameworks and software construction utilities. It is clear that our favorite software giant plans to cultivate a culture of developer empowerment.

On the Linux platform, the text-based shell is the nexus of computational control and the point at which proper articulation of will can transform commands into consequences. The emulated DOS command shell found in Windows is a shallow farce by comparison, and Microsoft has done little over the years to imbue it with greater power. Now Microsoft is prepared to augment its antiquated command line with a revolutionary new shell that will provide Windows users with a whole new level of control. The Microsoft Command Shell (MSH) features a unique object oriented syntax, extensive support for versatile .NET technology, and an adequate assortment of commands. Available for free download from Microsoft's Beta site, the second MSH beta release provides profound insights into the future of the Windows command line.

Since the release of the beta, the features and functions of the Microsoft Command Shell have been a topic of discussion in the Orbiting HQ. How does MSH compare with Linux shell technologies? Does it increase user efficiency or is it more trouble than it is worth? In an attempt to answer those questions and many others, I decided to put it to the test. Now you can learn what the fuss is all about as we explore the myriad mysteries of MSH.

This is a moderately technical overview and it contains content that may be difficult for nonprogrammers to grasp, so those of you that don't have a background in software development may want to skip around and ignore some of the technical details. Rest assured, there is plenty of content here for regular users and system administrators as well as coders. The code samples are instructive by themselves, so if you get really bored, perusal of the examples will illuminate many of the features of MSH and save you some time.

A concise introduction to .NET
From an architectural standpoint, the .NET platform (pronounced "dot net") resembles Parrot and J2EE. The platform consists of a portable development framework and runtime environment that facilitate construction and execution of platform independent applications. With an emphasis on reflection, network transparency, and rapid application development, the .NET API provides a comprehensive set of classes for robust object oriented programming. The Common Language Infrastructure (CLI) is an official ECMA standard that describes the type system, execution environment, instruction set, and base classes of the .NET framework. The machine-independent instruction set, called the Common Intermediate Language (CIL), is a stack-based, low-level, human-readable, object-oriented assembly language that ensures transparent interoperability between the various languages that target the .NET platform. The Common Language Runtime is a virtual machine and runtime environment that effectively implements the CLI standard.

Compilers that target the .NET platform convert source code into CIL, which is then compiled into .NET assembly for deployment in DLL or EXE files that conform with the Portable Executable (PE) format. The .NET assemblies can then be run by the Common Language Runtime (CLR), which translates the .NET assembly into native machine code while execution occurs. Of the approximately 40 languages that target the .NET platform, many are implementations of other languages like Python, Eiffel, and Scheme.

MSH syntax
Although the MSH syntax is unique, it contains elements of several other common languages. Structurally, the MSH language is a great deal like Perl or JavaScript, but it also inherits a number of features from Bash and SQL. The MSH syntax facilitates development with both functional and procedural idioms, but, like Python, it tends to favor the procedural style for conditional expressions and a few other common constructs. In practice, the combinatorial aspect of the interactive command environment suggests a functional style, but for elaborate scripting, MSH seems to enforce a moderately procedural approach.

MSH adheres to a high standard of consistency. Unlike Linux command-line utilities, which contain their own argument parsers and output format mechanisms, MSH commands (called Cmdlets) all inherit a single base class, which ensures that all commands expose the same methods, parse arguments the same way, and output data with the same standard MSH output framework. Cmdlets, which are written either in C# or in native MSH scripts, all use a consistent verb-noun nomenclature which I will cover in greater depth later.

A few Windows administrators have asked me how MSH compares to WSH.

Values and data structures
Treatment of expressions is one major point of divergence between Bash and MSH. In MSH, you can enter programmatic expressions that don't necessarily use or constitute commands. In this respect, the MSH interactive shell is similar to the top-level interactive environments that come with dynamic languages like Python and Ruby. MSH will evaluate values and expressions entered by the user and echo the results at the command line.

MSH features the typical data types found in most other modern languages: strings, integers, arrays, and hash tables. When you enter any of those kinds of values at the command line, MSH will echo them back.

msh> "blah"
blah
msh> 5
5
By comparison, in the Bash shell, expressions are always treated as commands and the echo command must be called explicitly if the user wants to display a value at the command line.

Arrays in MSH are heterogeneous, which means that they can contain mixed types. You can express an array as a sequence of comma separated values or expressions, and you can retrieve array values by index:

msh> 1, 2, 3, "test"
1
2
3
test
msh> (1, 2, 3, "test")[3]
test
Hash tables (also called associative arrays or dictionaries in Python) allow the user to create a collection of keys and associated values. In MSH, the keys can be any type, and the key types don't necessarily have to be consistent within a single hash table. If a key is an expression, it will be completely evaluated and the result of the expression will become the key. If the key is not an expression, it is treated as a string. Values are assigned to keys with the assignment operator and key/value pairs are separated by semi-colons inside a pair of curly braces with an '@' prefix:

msh> @{ key1 = "value1"; key2 = "value2"; 3 = "value3" }
As with arrays, hash table values can be accessed by key index:

msh> @{ key1 = "value1"; key2 = "value2"; 3 = "value3" }["key2"]
value2
msh> @{ key1 = "value1"; key2 = "value2"; 3 = "value3" }[3]
value3
Assignment and mathematical operators
In MSH, assignment is performed with the "=" operator, and the "$" prefix is used to indicate a variable:

msh> $var1 = 5
At the command line, a variable will resolve into its assigned value:

msh> $var1
5
Basic mathematical operators are available, and the MSH interpreter will evaluate simple mathematical expressions:

msh> 5 + 5
10
msh> $var1 + 5
10
Native support for mathematical evaluation makes MSH a convenient calculator and a useful tool for quick computations. In MSH, the mathematical operators also work on other data types for which appropriate methods are defined:

msh> "test1" + "test2"
test1test2
MSH also supports standard incrementation operators like "+=":

msh> $var1 += 5
msh> $var1
10
Cmdlets and object orientation
Cmdlets are shell commands that perform specific operations. Cmdlet names use a verb-noun nomenclature that makes it easier to guess the behavior associated with a particular Cmdlet. The MSH beta ships with over 100 Cmdlets which perform a variety of simple operations. MSH also includes an alias feature that allows users to associate additional names with available Cmdlets. By default, MSH comes with a complete set of aliases that bind typical Linux command names to comparable MSH Cmdlets, and an additional set of aliases that provide simple two or three letter abbreviations for most commands. In this article, I use the complete Cmdlet names rather than the abbreviations for the sake of clarity.

Cmdlet arguments can be specified in a number of ways. Let's experiment with the get-command Cmdlet, which provides information about various available Cmdlets. The command signature of get-command is:

get-command -Name [string] -Type [CommandTypes] -Verb [string] -Noun [string]
In this case, because -Name is the first parameter, you can omit the parameter designation ("-Name") and provide only the value:

msh> get-command get-command
Command Type   Name               Definition
------------   ----               ----------
Cmdlet       get-command          get-command [-Verb String[]] [-Nou...
If you specify a series of values and omit parameter designations, MSH will automatically associate values with the proper designations based on the default order of parameters specified by the command signature, so if you input the following:

msh> get-command A B C
MSH will assume that A is the -Name parameter, B is the -Type parameter, and C is the -Verb parameter. If you want to use the parameters out of order, or you wish to omit irrelevant parameters, you can specify the parameter designation as well as the value:

msh> get-command -Verb get
Command Type   Name               Definition
------------   ----               ----------
Cmdlet       get-command          get-command [-Verb String[]] [-Nou...
Cmdlet       get-help            get-help [[-Name] String] [-Catego...
Cmdlet       get-history          get-history [[-Id] Int64[]] [[-Cou...
Cmdlet       get-eventlog          get-eventlog [-LogName] String [-N...
Cmdlet       get-childitem         get-childitem [[-Path] String[]] [...
Cmdlet       get-content          get-content [-Path] String[] [-Rea...
...
In MSH, command output is typically a textual representation of an instance or collection of instances. In most cases, the text representation will use a table or list format. Get-command yields a table with three columns: command type, name, and definition. Each of those columns represents a variable of the command class and it is possible to extract those variables from individual command instances.

msh> (get-command get-process).name
get-process
msh> (get-command get-process).type
IsPublic IsSerial Name                  BaseType
-------- -------- ----                  --------
True    False   GetProcessCommand         System.Management...
msh> (get-command get-process).type.name
GetProcessCommand
This is where it starts to get funky. As you have probably guessed, it is also possible to call methods of individual instances right from the command line! Let's try it with a string:

msh> "this is a test".split(" ")
this
is
a
test
Keep in mind that methods are not called like commands. If you leave out the parentheses, the MSH interpreter will emit an error message:

msh> "this is a test".split " "
: Unexpected token ' ' in expression or statement
At line:1 char:26
+ "this is a test".split " " <<<<
We will discuss the object-oriented features of MSH in greater detail later, particularly in the section about the .NET API.

Plumbing
Modularity is one of the greatest strengths of the Linux command line environment. MSH definitely inherits that virtue, and even manages to expand on it in some places. Each command is responsible for a single task, and commands are used together in succession to perform elaborate operations. Pipes are one of the principle mechanisms used to compose such operations. Commands emit streams, which can be redirected to files or transmitted into new commands. MSH utilizes the traditional Linux pipe mechanisms, but it transmits objects instead of traditional streams. Like the Linux shell languages, MSH uses the vertical line character to indicate a pipe. Unlike the Linux shell, any instance can be passed through a pipe, including instances of core data types like strings and integers.

The get-member command reveals the public methods and variables associated with an instance. Let&#39;s pipe a string into the get-member command and see what happens:

msh> "this is a test" | get-member
TypeName: System.String

Name         MemberType Definition
----         ---------- ----------
Clone        Method    Object Clone()
CompareTo      Method    Int32 CompareTo(Object value), Int32 CompareTo(S...
Contains      Method    Boolean Contains(String value)
CopyTo        Method    Void CopyTo(Int32 sourceIndex, Char[] destinatio...
EndsWith      Method    Boolean EndsWith(String value), Boolean EndsWith...
Equals        Method    Boolean Equals(Object obj), Boolean Equals(Strin...
get_Chars      Method    Char get_Chars(Int32 index)
get_Length     Method    Int32 get_Length()
...
As you can see, MSH lists the attributes and methods of the string. The get-member Cmdlet makes it easy to learn more about different MSH types and the operations that they support.

MSH facilitates limited redirection. It is possible to redirect a stream into a file, or append the contents of a stream to a file, but there doesn&#39;t currently seem to be a way to redirect error messages. I had initially hoped that MSH would support arbitrary named streams, but it doesn&#39;t look like they are going in that direction. For the sake of completeness, here is an example of redirection in MSH:

msh> get-contents some_file
test
The write-object command displays a text representation of an instance at the command line and the get-contents command displays the contents of a file. Stream data is redirected to a file with the greater-than sign, just as it is done in Linux shells. Keep in mind that redirection just transmits text, it doesn&#39;t actually serialize the instance in the stream, so if you redirect an instance into a file and then try to extract that data from the file later, you will just get a string, not the original instance. MSH does have commands for serialization, which I will cover in detail later.

Conditionals
MSH provides complete support for conditional expressions. Comparisons are performed with infix comparison operators and comparisons return boolean values that can be assigned to variables or passed as arguments to commands and functions. Let&#39;s try a "less than" integer comparison in MSH:

msh> 3 -lt 5
True
Operators are also available for "greater than" comparisons (-gt) and equality comparisons (-eq). Users can perform composite comparisons with and/or operators:

msh> 3 -lt 5 -and 6 -gt 2
True
In MSH, complete conditional expressions use the traditional "if () {} else {}" syntax found in most other C-style languages:

msh> if (3 -lt 5 -and 6 -gt 2) { "It&#39;s true!" } else { "It&#39;s false!" }
It&#39;s true!
Unlike simple comparisons, complete conditional expressions are procedural and cannot be assigned to variables or passed to functions. If you attempt to assign a conditional expression to a variable, MSH will emit an error:

msh> $a = if (3 -lt 5) { "It&#39;s true!" } else { "It&#39;s false!" }
: &#39;if&#39; is not recognized as a Cmdlet, function, operable program, or script file.
At line:1 char:8
+ $a = if  <<<< (3 -lt 5) { "It&#39;s true!" } else { "It&#39;s false!" }
If you want to assign the result of a conditional expression to a variable, you have to do it like this:

msh> if (3 -lt 5) { $a = "It&#39;s true!" } else { $a = "It&#39;s false!" }
msh> $a
It&#39;s true!
MSH also supports switch statements:

msh> $var = 2
msh>  switch ($var) {
   1 { "You selected one!" }
   2 { "You selected two!" }
   3 { "You selected three!" }
   default { "You selected some other number!" }
  }
You selected two!
If you hit enter while the line contains an incomplete statement, MSH will alter the prompt on the next line to indicate that it requires the rest of the statement. You can spread a single expression across as many lines as necessary, and the shell will evaluate the input when it detects that the statement is complete.

Syntax: loops and iteration, SQL-inspired syntax
Loops and iteration
In MSH, there are a number of ways to iterate over sequences. First, we will look at the procedural iteration mechanisms, for and while . MSH while loops use relatively typical syntax:

msh> $f = 1; $i = 10
msh> while ($i -gt 0) { $f *= $i; $i-- }
msh> $f
3628800
In that factorial computation, the loop decrements $i until it reaches zero, and it assigns to $f the cumulative value of $f multiplied by $i. MSH also supports traditional C style for loops:

msh> $f = 1
msh> for ($i = 10; $i -gt 1; $i--) { $f *= $i }
msh> $f
3628800
When users want to iterate over sequences, external variables and exit conditions are unnecessary. MSH provides the foreach keyword, which allows users to loop through every element of an object:

msh> $f = 1
msh> foreach ($i in 1..10) { $f *= $i }
msh> $f
3628800
The foreach keyword is very similar to Python&#39;s for keyword. In MSH, 1..10 creates an array of numbers from one to ten. In the foreach factorial implementation, we iterate over a sequence of ten numbers rather than counting down from ten to one. The foreach keyword also facilitates iteration over the output of various Cmdlets:

msh> foreach ($i in get-childitem) { write-host $i.extension }
.txt
.html
.doc
.html
The get-childitem Cmdlet lists files in the current directory. In the last example, we iterate over each file in the current directory and echo the file extension to the console. It is possible to use entire pipelines and expressions inside the foreach statement:

msh> foreach ($i in get-childitem | sort-object extension) { write-host $i.extension }
.doc
.html
.html
.txt
The sort-object Cmdlet sorts a sequence of instances by the specified attribute. In that example, we sort the sequence of files by extension before we list the extensions to the console.

The procedural nature of the foreach keyword makes it difficult to nest iterative operations. If we want to take that list of extensions and pass it to a function, we would have to first create an array outside of the foreach statement and then use the foreach statement to add each extension to the array. We could then pass that array to another function. That kind of technique certainly works, but it adds unnecessary complexity and verbosity. MSH will allow you to use foreach in a functional manner, so that you can effectively transform one sequence into another much like you can with the map function in traditional functional programming languages:

msh> get-childitem | sort-object extension | foreach { $_.extension }
.doc
.html
.html
.txt
When you pipe a sequence of instances into the foreach statement, the output is actually another sequence of instances. The $_ variable is a place holder that represents the individual list element. The above example produces a sequence of extensions that can piped into other commands or manipulated as a sequence. If we wrap the expression in parentheses, we can use a numerical index to extract a specific element just as we can with any other sequence:

msh> (get-childitem | sort-object extension | foreach { $_.extension })[2]
.html
With a functional foreach statement, the factorial operation is extremely concise:

msh> $f = 1
msh> 1..10 | foreach { $f *= $_ }
msh> $f
3628800
The only problem is that we still have to define the value of $f before the operation and we have to extract the value of $f at the end. In order to simplify iterative computations, MSH lets us associate three blocks with the foreach statement:

msh>  1..10 | foreach { $f = 1 } { $f *= $_ } { $f }
3628800
The first block is performed once before the iteration begins, the second block is performed for each element in the sequence, and the third block is performed after the iteration has been completed.

SQL-inspired syntax
In our iteration examples, we extracted the extension attribute from a sequence of instances. Extraction of a particular attribute is a common maneuver, so MSH provides the select command to simplify such operations:

msh> get-childitem | sort-object extension | select extension
Extension
---------
.doc
.html
.html
.txt
It is also possible to select more than one attribute:

msh> get-childitem | sort-object extension | select name, extension
Name                        Extension
----                        ---------
somefile.doc                   .doc
output2.html                   .html
output1.html                   .html
examples.txt                   .txt
In some cases, you may want to create additional table columns with dynamically computed values. The select Cmdlet allows users to describe additional columns with simple blocks. Let&#39;s look at a trivial example in which we add a table column that contains the length of the file extension:

msh> get-childitem | select name, extension, {$_.extension.length}
Name                Extension        $_.extension.length
----                ---------        -------------------
examples.txt          .txt                        4
output1.html          .html                       5
output2.html          .html                       5
somefile.doc          .doc                        4
You can include virtually any kind of MSH expression inside the select block. You can even use conditional expressions to determine what to display for each individual item in the table:

msh> get-childitem | select name, extension, {
  if ($_.LastWriteTime.year -lt 2004) { "old file" }
  else { "new file" }
}
Name                Extension            if ($_.LastWriteTime.year...
----                ---------            -------------------------
examples.txt          .txt                new file
output1.html          .html               old file
output2.html          .html               old file
somefile.doc          .doc                new file
In many cases, the user will want to filter the output and display only the elements that meet certain requirements. To display only the extensions that are three characters long, an MSH user might do the following:

msh> foreach ($var in get-childitem | sort-object extension) {
  if ($var.extension.length -eq 4) {
   write-host $var.extension
  }
}
.doc
.txt
Notice that the comparison operation specifies a length of four rather than three, because the dot is included in the extension value. Unfortunately, the above example is extremely verbose and the output can&#39;t be passed to additional operations. MSH provides the where command, a functional filter mechanism that simplifies data extraction:

msh> get-childitem | sort-object extension | select extension | where { $_.extension.length -eq 4 }
.doc
.txt
The where statement enables users to filter sequences. Each object in the stream is passed into the where block, and if the block returns true, the object is appended to the stream. If the block returns false, the object is dropped out of the stream.

Is it just me, or do select and where look familiar? A sequence of instances closely resembles a database table, so when you think about it, SQL syntax is really very appropriate for an object-oriented shell. For comparison purposes, lets see how the previous example looks in Ruby:

Dir["*"].map {|x| File.extname(x) }.sort.find_all {|x| x.length == 4 }
In Ruby, the map and filter operations are actually methods of the array instance. In MSH, the same operations are performed with keywords and piping, but brace blocks are used in a very similar way.

Syntax: regular expressions
Regular expressions
MSH provides some unique syntactic sugar that allows you to perform switch comparisons on strings with regular expressions:

msh> switch -regex ($var) {
  ".*[0-9]+" { "the string ends with a number!" }
  default { "the string doesn&#39;t end with a number." }
}
In Bash, the awk utility is often used to compare a string against regular expressions. Unfortunately, the awk syntax is a unique language of its own that doesn&#39;t support normal Bash expressions, so it integrates poorly with the rest of the shell. The MSH regexp switch syntax is a highly effective way to integrate support for regular expression matching into the shell itself without necessitating other syntactic sacrifices.

In languages like Ruby, regular expressions are syntactic elements like strings or integers and they can be used in other kinds of transient expressions. MSH allows you to create a regular expression instance from a string, but regular expressions are not part of the core language.

MSH provides many syntactic mechanisms for regular expression matching and transformation with strings, so arguably, a Ruby-style regular expression type is not needed. MSH provides a -match operator for regular expression comparisons:

msh> "test5" -match "test[3-9]+"
True
msh> "test1" -match "test[3-9]+"
False
The -match operator can also be used in if and where statements:

msh> get-childitem | select extension | where { $_.extension -match ".*t" }
Extension
---------
.txt
MSH also has a -nomatch operator, which will return False if the string matches the regular expression. By default, the -match and -nomatch operators are case-insensitive. To perform case-sensitive regular expression comparisons, you can use the -cmatch and -cnomatch operators. MSH also has operators that enable users to perform glob matches. For those who don&#39;t know, a glob is a pattern specification that uses a simplified regular expression syntax. The name comes from a Unix library function used to expand the patterns into filename matches. Yeah, you can insert the obligatory dyslexic blogger joke here. You can use -like anywhere you can use -match :

msh> get-childitem | select extension | where { $_.extension -like "*t" }
It doesn&#39;t make much sense to use a separate expression for that, since the get-childitem Cmdlet supports glob arguments by default:

msh> get-childitem *t | select extension
There are many cases where you will want to use the -like operator in your functions and Cmdlets to simulate the glob evaluation of the get-childitem Cmdlet.

How to leverage the .NET API in MSH
Integration of the .NET API imbues MSH with a tremendous amount of power. The integration and availability of code libraries for virtually every common task is part of what initially fueled the popularity of the Python programming language. Python developers use the phrase "batteries included" to describe the usability implied by excellent library accessibility. Support for .NET libraries and components ensures that MSH also comes with batteries included.

MSH has a number of unique features that make it easy for users to leverage .NET technology at the command line. Type casting is an easy way to transform a simple core type into a .NET instance or another core type. To cast an instance into another type, simply place the name of the desired type in brackets right in front of the instance. You can change a string into a number with an int cast:

msh> [int]"5" + 5
10
Let&#39;s try a more sophisticated example. Let&#39;s say we want to parse a string that contains XML. If we turn that string into an XML document instance, we can use the object-oriented features of MSH to transform and manipulate individual XML elements. We can use the type casting mechanism to transform our string into an XML document instance:

msh>  $x = [xml]"<zoo><animal kind=&#39;monkey&#39;><name>Albert</name></animal></zoo>"
msh> $x.zoo.animal
kind                        name
----                        ----
monkey                       Albert
We can also create new instances with the new-object Cmdlet. The System.Net.WebClient class has a DownloadString method that will download data from a remote web site. We can use the new-object Cmdlet to create a new instance of Net.WebClient and then we can use that instance to download the Ars Technica RSS feed and display the contents at the console:

msh> (new-object Net.WebClient).DownloadString("http://feeds.feedburner.com/arstechnica/BAaf")
The XML in text form is obviously not particularly useful. We can use the XML type casting mechanism to parse the RSS feed data into an XML document instance that we can manipulate with standard MSH commands:

msh> [xml](new-object Net.WebClient).DownloadString("http://feeds.feedburner.com/arstechnica/BAaf")
xml                xml-stylesheet         rss
---                --------------         ---
                  {, }                rss
Now let&#39;s try to extract the title and author elements from every item in the rss.channel tag:

msh> ([xml](new-object Net.WebClient).DownloadString("http://feeds.feedburner.com/arstechnica/BAaf")
).rss.channel.item | select title, author
title                        author
-----                        ------
Sneak preview of Windows XP SP3 surf... jeremy@arstechnica.com (Jeremy Reimer)
Microsoft&#39;s FAT patents rejected      hannibal@arstechnica.com (Hannibal)
AOL buys Weblogs Inc.             caesar@arstechnica.com (Ken "Caesar"...
Modding PlayStation 2 ok Down Under    eric@arstechnica.com (Eric Bangeman)
Microsoft stepping up corporate secu... caesar@arstechnica.com (Ken "Caesar"...
...
You can instantiate classes from literally any .NET library. Not all libraries are accessible within MSH by default, but you can import .NET assemblies from external DLL files. You can even import third-party assemblies compiled from any CIL compatible language, so it possible to instantiate IronPython classes inside the MSH shell, for instance.

With further experimentation, I figured out how to import the System.Windows.Forms library from the global assembly cache (GAC). With Winforms at my disposal, I can construct native graphical user interfaces from the command line or in MSH scripts!

The trick here is to use the LoadFile function from the Reflection.Assembly module to load the System.Windows.Forms library. Since Reflection.Assembly can&#39;t be instantiated, the new-object Cmdlet won&#39;t work in this situation. In MSH, it is possible to call a function directly from a module:

msh> [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
Now that we have our Winforms DLL loaded into memory, let&#39;s try to instantiate a window and a button:

msh> $window = new-object Windows.Forms.Form
$window.text = "This is a Windows form!"
$button = new-object Windows.Forms.Button
$button.text = "Click me!"
$window.controls.add($button)
$window.showDialog()

Windows GUI objects created via MSH
Linux users often use the Tk library to produce simple graphical utilities. With MSH and the .NET Winforms API, Windows users can also produce graphical interfaces within scripts or from the command line. Unfortunately, it is not yet possible to bind MSH commands to GUI events, so users can&#39;t produce complete graphical utilities. In a Microsft TechNet chat transcript, Microsoft developer Jeffrey Snover says that they have constructed an experimental set of Cmdlets that provide user interface construction capabilities inspired by Tk, but they don&#39;t plan to include those Cmdlets in the first major release.

Import and export data
MSH features a number of Cmdlets designed specifically to facilitate serialization as well as efficient import and export operations that utilize standard formats. In many cases, users will want to preserve instances for future use, or transmit instances across a network. The export-clixml Cmdlet will automatically serialize an instance into human readable XML and store it in a local file. The instance can then be loaded back into memory from the file with the import-clixml command.

To serialize data, all you have to do is pipe an instance or sequence of instances into the export-clixml command and provide it with the name of the file in which it should store the data. Let&#39;s try it with a hash table that contains a sequence of integers, a string, and an XML document:

msh>  @{ item1 = 1..5; item2 = "test"; item3 = [xml]"<item>text</item>" } | export-clixml mydata.xml
Let&#39;s have a look at the XML file produced by the export-clixml Cmdlet:

mydata.xml
<Objs Version="1.1" xmlns="http://schemas.microsoft.com/msh/2004/04">
  <Obj RefId="RefId-0">
   <TN RefId="RefId-0">
    <T>System.Collections.Hashtable</T>
    <T>System.Object</T>
   </TN>
   <DCT>
    <En>
      <S N="Key">item3</S>
      <XD N="Value"><item>text</item></XD>
    </En>
    <En>
      <S N="Key">item2</S>
      <S N="Value">test</S>
    </En>
    <En>
      <S N="Key">item1</S>
      <Obj N="Value" RefId="RefId-1">
       <TN RefId="RefId-1">
        <T>System.Object[]</T><T>System.Array</T><T>System.Object</T>
       </TN>
       <LST>
        <I32>1</I32><I32>2</I32><I32>3</I32><I32>4</I32><I32>5</I32>
       </LST>
      </Obj>
    </En>
   </DCT>
  </Obj>
</Objs>
As you can see, the data is stored in a consistent, human-readable, XML format. The contents of the mydata.xml file can be transformed with a standard XSLT and manipulated with other XML tools. The XML format facilitates a high level of interoperability and rapid manipulation. Let&#39;s alter the file and then load the instance back into memory. Change the numbers in the LST.I32 tags, and then load the instance with the import-clixml Cmdlet:

msh> import-clixml mydata.xml
Key                   Value
---                   -----
item3                  #document
item2                  test
item1                  {11, 12, 13, 14, 15}
The imported instance can be treated exactly like it was before it was exported. If you wrap the import operation in parentheses, you can treat it just like the hash table itself:

msh> (import-clixml mydata.xml).keys
item3
item2
item1
msh> (import-clixml mydata.xml)["item3"]
item
----
text
It is also possible to import XML files as XML document instances. Remember how we used type casting to transform the string into an XML document? We can also use type casting on the contents of a file. The get-content Cmdlet loads data from a file and outputs it to the console. If we load the XML content of the mydata.xml file into a string, we can cast that string into a document instance, and then manipulate the contents like we did with the RSS file:

msh>  ([xml](get-content mydata.xml)).objs.obj.DCT.En
S                          XD
-                          --
S = item3                     XD = <item><subitem>text</subitem></...
{S = item2, S = test}
S = item1
XML isn&#39;t the only format supported by MSH. The human-readable, comma delimited, CSV format utilized by many spreadsheet applications is extensively supported as well. The import-csv command loads CSV values into a table. For our example, we will use stock quotes. First let&#39;s see what the CSV file looks like as plain text:

msh> get-content quotes.csv
"Symbol","Value","Date","Change"
MSFT,24.73,10/6/2005,0.06
RHAT,20.3,10/6/2005,-0.18
AAPL,51.7,10/6/2005,-1.0801
Now let&#39;s use the import-csv Cmdlet to import the file and load the data into an MSH table:

msh> import-csv quotes.csv
Symbol          Value          Date           Change
------          -----          ----           ------
MSFT           24.73          10/6/2005        0.06
RHAT           20.3           10/6/2005        -0.18
AAPL           51.7           10/6/2005        -1.0801

Using the import-csv Cmdlet
As you can see, the values of the first row became the attribute names, and values in the subsequent rows became attribute values. The imported CSV table can be treated exactly like any other table in MSH. To list only the stocks that have gone up, you can use a simple where statement:

msh> import-csv quotes.csv | where { [float]$_.change -gt 0 }
Symbol          Value          Date           Change
------          -----          ----           ------
MSFT           24.73          10/6/2005        0.06
Notice that we have to manually convert the change value into a float before we perform the comparison. By default, the import-csv command loads all the values as strings. Now let&#39;s use a glob to list the symbol and value of all the stocks whose symbols contain the letter "a" at least once. We will also use the sort Cmdlet to order the stocks by value:

msh> import-csv quotes.csv | select symbol, value | where { $_.symbol -like "*a*" } | sort value
Symbol                       Value
------                       -----
RHAT                        20.3
AAPL                        51.7
Now let&#39;s try a new trick. Say that we have one share of each of those stocks, and we want to determine the total worth of our little investment portfolio. Let&#39;s add up all of the numbers in the Value field and display a total. The obvious solution is a foreach statement that iterates over each element and adds each stock value to a running total:

msh> import-csv quotes.csv | foreach { $v = 0.0 } { $v += $_.value } { $v }
96.73
There is a more effective way to perform the same operation. MSH provides the measure-object Cmdlet to help perform calculations that involve entire fields. In this case, we want to use it to calculate the sum of the value field, but we will also use it to calculate a few other things at the same time:

msh> import-csv quotes.csv | measure-object -property value -sum -min -max -average
Count   : 3
Average  : 32.2433333333333
Sum    : 96.73
Max    : 51.7
Min    : 20.3
Property : Value
It is also possible to export instance values as a CSV file with the export-csv Cmdlet. Let&#39;s export a directory listing as a CSV file and open it in Excel:

msh> get-childitem | export-csv listing.csv

I tested the CSV import and export features extensively with both Excel and OpenOffice.org Calc, both of which can manipulate and generate CSV content that is fully compatible with MSH. Unfortunately, the CSV files don&#39;t preserve data type information, so a great deal of type casting is required after import. Ideally, I would like to see clixml support in Excel so that users can manipulate their tables in a spreadsheet program and still preserve type information.

On my Linux system, I often make it possible for my scripts to generate HTML instead of plain text, because if I need to print the output for whatever reason, HTML is the best way to describe the formatting. MSH supports HTML output, but not with a typical export command. This is one of the few contexts in which MSH fails to behave in a consistent and predictable manner. One would simply assume that there is an export-html Cmdlet, but there isn&#39;t. Let&#39;s see if we can use the get-command Cmdlet to figure out how to transform command output into HTML. We want to find all Cmdlets that contain the letters "html" somewhere in their name:

msh> get-command *html* -type cmdlet
CommandType    Name                   Definition
-----------    ----                   ----------
Cmdlet       convert-HTML              convert-HTML [[-Property] Ob...
The convert-html command transforms an instance or set of instances into printable HTML content. Unlike the export commands which can only output directly to a file, the convert-html command echoes the HTML output to the terminal. I honestly don&#39;t understand why they would make the HTML conversion Cmdlet output to the terminal while all the rest of the format conversion tools work with files; it seems terribly inconsistent. Ideally, all the commands should send their output directly to the terminal and users could then choose to redirect the output to a file, that way the user can still pipe the output directly to another program.

One of the major frustrations of MSH is the lack of support for piped input in other Windows applications. I can&#39;t pipe my HTML content into a new instance of Internet Explorer and I can&#39;t pipe my CSV content directly into a new instance of Excel. One of the early prereleases contained a few additional output Cmdlets that aren&#39;t currently available in the betas, including one that outputs content directly to a new instance of Excel. I sincerely hope that the final release of MSH features an entire collection of convert and export commands for a broader variety of formats. Sources inside Microsoft tell us that MSH users will probably use COM and ActiveX to interface with major Windows applications

Output formats
In the default table output, MSH truncates values in order to avoid wrapping. To the average Bash user, that may seem like a strange choice, but within the context of MSH, it makes a lot of sense. MSH users typically don&#39;t need to do raw text processing on the tables themselves because virtually all the Cmdlets that take piped input operate on instance values rather than the actual table text. Truncation of long values enables MSH to provide clean and legible data that the user can parse visually without a great deal of effort. By comparison, Linux commands that using a lot of text wrapping often produces unwieldy and intimidating output.

The MSH table views provide a graceful and intuitive overview of the relevant data, but there are definitely circumstances in which users will want to turn off truncation and see complete values. Consider the get-member Cmdlet which provides information about methods. If we pipe an integer into get-member , we get the following:

msh> 1 | get-member
TypeName: System.Int32

Name      MemberType Definition
----      ---------- ----------
CompareTo  Method    System.Int32 CompareTo(Int32 value), System.Int32 Com...
Equals    Method    System.Boolean Equals(Object obj), System.Boolean Equ...
GetHashCode Method    System.Int32 GetHashCode()
GetType    Method    System.Type GetType()
GetTypeCode Method    System.TypeCode GetTypeCode()
ToString   Method    System.String ToString(), System.String ToString(IFor...
What if we want to see the complete value of the CompareTo method definition? We could certainly extract it by itself:

msh>  1 | get-member | where {$_.name -eq "CompareTo" } | select definition
Definition
----------
System.Int32 CompareTo(Int32 value), System.Int32 CompareTo(Object value)
But that seems like a lot of extra effort that shouldn&#39;t be necessary, and it could get frustrating very quickly if you want to see the complete text of several values in the table. MSH provides several alternative output formats that don&#39;t truncate text. If you pipe a stream into the format-list Cmdlet, it will be rendered with wrapping and complete text values:

msh>  1 | get-member | format-list
TypeName  : System.Int32
Name     : CompareTo
MemberType : Method
Definition : System.Int32 CompareTo(Int32 value), System.Int32 CompareTo(Object
          value)

TypeName  : System.Int32
Name     : Equals
MemberType : Method
Definition : System.Boolean Equals(Object obj), System.Boolean Equals(Int32 obj
         )

TypeName  : System.Int32
Name     : GetHashCode
MemberType : Method
Definition : System.Int32 GetHashCode()

TypeName  : System.Int32
Name     : GetType
MemberType : Method
Definition : System.Type GetType()

TypeName  : System.Int32
Name     : GetTypeCode
MemberType : Method
Definition : System.TypeCode GetTypeCode()

TypeName  : System.Int32
Name     : ToString
MemberType : Method
Definition : System.String ToString(), System.String ToString(IFormatProvider p
         rovider), System.String ToString(String format, IFormatProvider pr
         ovider), System.String ToString(String format)
Notice that it uses indentation to ensure that the wrapping doesn&#39;t decrease the readability of the output.

Functions
In MSH, functions are reusable script components that contain operations. Functions can be defined interactively at the command line as well as in independent MSH scripts, and they are invoked the same way that Cmdlets are called. Let&#39;s construct a factorial function that uses one of our previous examples:

msh> function factorial {
  param([int]$n)
  1..$n | foreach { $f = 1 } { $f *= $_ } { $f }
}
msh> factorial 10
3628800
The param statement describes the parameters expected by the function. Each comma-separated value in the param statement describes an individual parameter. The variable name indicates the parameter designation and the cast that precedes the variable describes the cast operation to perform on the parameter value when the function is called. Our factorial function takes a single value and returns the factorial. The function invocation can be assigned to a variable or passed as a parameter to another Cmdlet or function. It is also possible to omit the parameter specification, in which case, the parameter values are all available in an $args array available within the function:

msh> function factorial {
  1..[int]$args[0] | foreach { $f = 1 } { $f *= $_ } { $f }
}
You can also specify default argument values within the param statement, and use parameter designations to specify parameter values when you call the function:

msh> function testfn {
  param([int]$arg1 = 10, [int]$arg2 = 20, [str]$arg3 = "test")
  ...
}
msh> testfn -arg2 50 -arg3 "test2"
First class functions
The MSH language provides extensive support for first class functions. The brace blocks passed as arguments to MSH Cmdlets like foreach are actually anonymous functions. You can include these anonymous functions in virtually any kind of MSH expression, and you can assign them to variables or pass them to functions. The flexibility of proper first class functions is an immense asset for experienced developers, and their availability in MSH substantially increases the elegance of the language. Let&#39;s assign a simple function to a variable:

msh> $fn = { 2 * 2 }
msh> $fn
2 * 2
Note that when MSH evaluates the variable, it just displays the contents of the function rather than the value. To actually invoke the function inside of the variable, you have to prefix the variable with an ampersand:

msh> &$fn
4
An ampersand in front of an anonymous function invokes the function and evaluates it down to its output in place, so if you assign &$fn to a variable, the variable will be populated with the value that the $fn function returns:

msh> $output = &$fn
msh> $output
4
Anonymous functions can also utilize variables, either with the param statement or with the $args array:

msh> $fn = { $args[0] * 2 }
msh> &$fn 6
12
You can assign anonymous functions to variables for later use in functions that require command blocks:

msh> $fac = { $f *= $_ }
msh> 1..10 | foreach { $f = 1 } $fac { $f }
3628800
And you can also do all sorts of esoteric experiments with lambda calculus and functional programming theory:

msh> &{&$args[0] $args[2] (&$args[1] $args[2] $args[3])} {&$args[0] (&$args[0] $args[1])}
  {&$args[0] (&$args[0] (&$args[0] (&$args[0] $args[1])))} {$args[0] + 1 } 0
6
The most important benefit of first class functions is the ability to create a function that takes an anonymous function as a parameter. All you have to do is define a parameter with a scriptblock cast and MSH will assume that the parameter is a function. With this technique, you can define the fold function:

msh> function fold {
  param([scriptblock]$fn, $value, $seq)
  if ($seq.length -eq 0) {
   $value
  }
  else {
   &$fn $seq[0] (fold $fn $value $seq[1..$seq.length])
  }
}
We can test our fold function with another form of the factorial computation:

msh> fold { $args[0] * $args[1] } 1 (1..10)
3628800
You can also define functions that manipulate piped input with a user-provided scriptblock. In order to do this, you have to take advantage of the MSH process statement. Each Cmdlet has a begin block which is evaluated when the Cmdlet is first called, a process block which performs the bulk of the work and operates on the provided input, and an end block which is evaluated when the Cmdlet has finished with the input. Functions can also optionally have these blocks, and you can use the processs block to manipulate content passed into the function with a pipe. We can use this technique to define the map function, which is a lot like a simplified foreach function:

msh> function map {
  param([scriptblock]$fn)
  process {
   &$fn
  }
}
msh>  1..5 | map {$_ * 2}
2
4
6
8
10
Notice that the user-provided function can take advantage of the $_ variable when passed to our map function. The $_ variable is only populated and usable when the user-provided function is passed into a function or Cmdlet with a process block. When a sequence of objects is piped into a function, the process block is evaluated once for each object in the sequence.

msh> function mytest {
  begin {
   $x = 0
  }
  
  process {
   $x += 1
   write-host "Item number" $x ":" $_
  }
  
  end {
   write-host "Total:" $x
  }
}
msh> get-childitem | mytest
Item number 1 : listing.csv
Item number 2 : monad_b2_50215_x86.zip
Item number 3 : mydata.xml
Item number 4 : quotes.csv
Total: 4
Defining Cmdlets
Cmdlets can be constructed in either C# or native MSH scripts. For information about C# Cmdlet construction and the MSH SDK, you can refer to the MSDN documentation for MSH. Native script Cmdlet construction is relatively simple. To create a new Cmdlet, create a file and define the contents of a function inside of it. We can define a factorial Cmdlet like this:

compute-fac.msh
param([int]$var)

process {
  if ($var) { $n = $var } else { $n = $_ }

  if ($n) {
   1..$n | foreach { $f = 1 } { $f *= $_ } { $f }
  }
  else { 1 }
}
If a Cmdlet script is in the active directory, you can use the .\ prefix to call it from the command line:

msh> .\compute-fac 10
3628800
Note that our compute-fac Cmdlet will process values from the pipeline if it is not passed any parameter. I also added an extra step to make sure that 1 is returned if the user passes our factorial Cmdlet a 0 . The process block will be evaluated once for every element in the pipeline, so you can pass a sequence of values into the compute-fac Cmdlet to generate a sequence of factorials:

msh> 0..5 | .\compute-fac
1
1
2
6
24
120
Now that we have our handy factorial Cmdlet, let&#39;s use it to generate the approximate value of the mathematical constant known as e:

msh> 0..25 | .\compute-fac | foreach { 1.0 / $_ } | measure-object -sum | select sum
Sum
---
2.71828182845905

Providers
Many operating systems feature a virtual filesystem (VFS) layer, which allows applications to interact with various filesystems through a consistent and universal API. Virtual filesystem layers can also be used to expose other kinds of data structures through the filesystem interface. The VFS layer is often used to provide support for network transparent file access. On Linux, the VFS layer can be extended with kernel modules or userspace filesystem components like FUSE and LUFS. Various Linux graphical environments like GNOME and KDE also feature their own VFS engines.

Providers are the MSH answer to the assorted userspace VFS solutions available for other platforms. MSH providers expose particular C# data constructs to the filesystem, and allow the user to navigate those constructs with the provided filesystem navigation commands. The prerelease ships with a number of very useful and interesting providers, including one that facilitates registry navigation and manipulation.

Let&#39;s have a look at the Registry Provider. To switch the active Provider, use the set-location command to change from the current directory to a directory inside of another provider. The top level of a Provider directory structure is a simulated drive. To find out which Providers are available on the system and what drives are associated with them, use the get-provider Cmdlet:

msh> get-provider
Name            Capabilities            Drives
----            ------------            ------
Alias           ShouldProcess            {Alias}
Environment       ShouldProcess            {Env}
FileSystem        Filter, ShouldProcess      {C, D}
Function         ShouldProcess            {function}
Registry         ShouldProcess            {HKLM, HKCU}
Variable         ShouldProcess            {Variable}
Certificate       ShouldProcess            {cert}
The HKLM and HKCU drives represent two of the top level registry structures (HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER). To test the registry-navigation features, we will tweak a well-known registry security setting.

The RestrictAnonymous key controls availability of system information. With its default setting of 0 , the RestrictAnonymous key makes it possible for remote, anonymous users to acquire a list of system user account names and information about shared resources. Prudent system administrators change this value to 1, which makes that information inaccessible. With MSH and the Registry Provider, an administrator can change that value without regedit.

The registry path of the RestrictAnonymous key is:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\RestrictAnonymous
To start with, we will use the set-location Cmdlet to move to the HKLM drive:

msh> set-location HKLM:\
To see the contents of the drive, we will use the get-childitem Cmdlet:

msh> get-childitem
   Hive: Registry::HKEY_LOCAL_MACHINE

SKC  VC Name                  Property
---  -- ----                  --------
  4  0 HARDWARE                {}
  1  0 SAM                   {}
70  0 SOFTWARE                {}
  8  0 SYSTEM                 {}
We can now use the set-location Cmdlet to traverse the registry:

msh> set-location SYSTEM\CurrentControlSet\Control\Lsa
msh> get-childitem
   Hive: Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

   SKC  VC Name                  Property
   ---  -- ----                  --------
    1  1 AccessProviders           {ProviderOrder}
    1  0 Audit                  {}
    0  1 Data                  {Pattern}
    0  1 GBG                   {GrafBlumGroup}
    0  1 JD                    {Lookup}
    2  0 Kerberos                {}
    0  2 msv1_0                 {ntlmminclientsec}
    0  1 Skew1                  {SkewMatrix}
    1  0 SSO                   {}
    3  1 SspiCache               {Time}
In the Registry Provider directory structure, the get-childitem Cmdlet only lists the directories, it doesn&#39;t show individual registry values. In order to list the keys and values associated with the current location context, the user must employ the get-property Cmdlet. The first parameter of the get-property Cmdlet is the context for which properties should be listed. In this case, we want to list all the properties of the active location, so we will use a single dot as the parameter:

msh> get-property .
MshPath        : Registry::HKLM\SYSTEM\CurrentControlSet\Control\lsa
MshParentPath    : Registry::HKLM\SYSTEM\CurrentControlSet\Control
MshChildName    : lsa
MshDrive       : HKLM
MshProvider     : System.Management.Automation.ProviderInfo
Bounds        : {0, 48, 0, 0, 0, 32, 0, 0}
Security Packages : {kerberos, msv1_0, schannel, wdigest}
restrictanonymous : 0
...
As you can see, the value of the RestrictAnonymous key is 0. It is also possible to use the get-property Cmdlet to acquire information about a specific property, if you pass the property name as the second argument:

msh> get-property . restrictanonymous
MshPath        : Registry::HKLM\SYSTEM\CurrentControlSet\Control\lsa
MshParentPath    : Registry::HKLM\SYSTEM\CurrentControlSet\Control
MshChildName    : lsa
MshDrive       : HKLM
MshProvider     : System.Management.Automation.ProviderInfo
restrictanonymous : 0
The set-property Cmdlet is used to associate values with individual keys. The value 1 can be assigned to the RestrictAnonymous with a single command:

msh> set-property -path . -property restrictanonymous -value 1
MSH also comes with a few other providers for other tasks. The Function Provider enables users to access and manipulate functions defined in the global MSH namespace. The get-childitem Cmdlet can provide information about the previously defined factorial function:

msh> get-childitem function:\factorial
CommandType    Name                   Definition
-----------    ----                   ----------
Function      factorial                1..[int]$args[0] | foreach {...
The code associated with the function is stored in the Definition attribute, which can be accessed like any other public instance property:

msh> (get-childitem function:\factorial).definition
1..[int]$args[0] | foreach { $f = 1 } {$f *= $_} {$f}
As you can see, MSH Providers allow users to traverse and manipulate virtually any kind of data structure with only a few simple commands. The Providers that ship with this prerelease have limited capabilities, but I expect to see many new Providers emerge when the .NET developer community begins to experiment with MSH technology. Like Cmdlets, individual Providers can be written in native C# with the SDK provided by Microsoft.

Providers could potentially add network transparent file management functionality to MSH. If it is possible to wrap a Provider around the registry, it is safe to assume that it is also possible to build Providers that add support for network file protocols like CIFS and FTP. I could also see Provider functionality extended to provide native command line access to data structures within running instances of individual Microsoft applications.

Experimentation with Provider technology illuminates some unique possibilities. It would definitely be nice to be able to extract information from active Internet Explorer instances, or import a data table directly from an active instance of Excel without custom Cmdlets. Integration with MSN Messenger would also open up a world of possibilities. I would certainly welcome the ability to redirect command line output into a chat conversation. It would also be nice to have a Provider that offers simple access to system logs.

One of the problems with Providers is the absence of system-wide integration. Although Providers currently provide skilled users and administrators with a tremendous amount of power at the command line, regular users would be able to benefit from them as well if Providers could be used to extend the URI and protocol support of Explorer. I would definitely like to see better integration of the Provider system in Windows Vista.

Security issues
Much has been said about the potential security risks created by MSH. Rest assured that much of that dialog is mere media hyperbole. On Linux systems, applications that run with normal user privileges don&#39;t have the ability to alter the files of other users or system level files utilized by other applications. Although Windows can also supposedly provide a comparable level of security, many Windows users run in administrative mode, which imposes no such restriction by default.

Windows administrators tell me that it is possible to enable much stronger security mechanisms and there are some third-party technologies that can automate that process for nonadministrators, but on many Windows XP systems, MSH scripts will have just as much access (and consequently, just as much destructive potential) as any other application on the system. MSH does not pose a greater security risk than any other application, nor does it inherently represent an additional security weakness of any kind, but MSH scripts can and will be used for malicious purposes.

Don&#39;t let the potential for malicious use give you the wrong impression about MSH. Scripting languages will always be capable of doing damage, regardless of the platform. The Linux security model is not without its own fallibilities as well, and a malicious Bash script can still do quite a bit of damage to individual user files. With the risks in mind, Microsoft included a number of security features in MSH that help to minimize the potential for destructive employment.

Personally, I think that the security features of MSH are more than adequate for the task, and I applaud Microsoft for their foresight in this regard. Possibly the most thoughtful and effective security feature is the absence of an MSH file association in Explorer. MSH scripts and Cmdlets will not run when double clicked, they must all be manually invoked at the command line. This feature will prevent users from unintentionally activating a malicious MSH script attached to an e-mail.

MSH can also be configured to restrict execution of unsigned scripts. Script certificates are managed by a Certificate Provider mechanism, and in the absence of the relevant certificate, untrusted MSH scripts will not execute. Additionally, the certificates store hash values so that the shell can notify you if a particular script has been altered since the moment it was signed.

As a Linux system administrator, I often use scripts that I downloaded from the Internet, or inherited from a colleague. I always thoroughly read through a script before I use it, but in many cases it is difficult to discern exactly what the consequences will be before I run the script. Even when I know with relative certainty that a script is not malicious, I still want to inspect it in great detail because scripts designed for a particular system can have unexpected side affects on other systems. In MSH, the -whatif parameter can be used to determine exactly what operations will be performed by any command or script. The -whatif parameter is defined by the Cmdlet base class, so it is supported by every available Cmdlet on the system.

The -whatif parameter can be used to determine which files will be deleted when a particular glob is passed to the remove-item command:

msh> remove-item *[1-3] -whatif
What if: Performing operation "Remove File" on Target "C:\segphault\Desktop\testdir\file1".
What if: Performing operation "Remove File" on Target "C:\segphault\Desktop\testdir\file2".
What if: Performing operation "Remove File" on Target "C:\segphault\Desktop\testdir\file3".
MSH also provides a -confirm parameter, which will request user authorization for each individual operation:

msh> remove-item *[1-3] -confirm
Confirm
Are you sure you want to perform this action?
Performing operation "Remove File" on Target "C:\segphault\Desktop\testdir\file1".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help
(default is "Y"):
Although the certificate security feature seems excessive, many of the other MSH security features are excellent and innovative. Microsoft clearly got it right this time, and they deserve some credit for their ingenuity. I sincerely hope that the folks in Redmond apply a comparably high standard to security in Vista.

Windows administration with MSH
According to Microsoft developer Bruce Payette, the next generation of graphical administrative utilities will be Microsoft Management Console (MMC) snap-ins built on top of MSH. Microsoft&#39;s goal is to provide complete MSH command line access to all administrative functionality and make MSH the premier mechanism for Windows scripting and administration.

So, what makes MSH different from the Windows Scripting Host (WSH)? Extensive support for .NET integration, the elegance of the MSH syntax, and the availability of a complete interactive environment to facilitate faster and more effective scripting and efficient administration. In a TechNet interview, MSH developer Jeffrey Snover provides an excellent description of the differences between the two scripting technologies:

"Monad is trying to solve a much larger problem and is based upon an entirely different technology base than WSH. Whereas WSH is COM-oriented Scripting, MONAD is command-oriented and addresses the needs of interactivity as well as .NET scripting. That said, weíve done quite a bit to provide a nice glide path from WSH/VBSCRIPT to MONAD to leverage existing skill."

Although there are currently only a limited number of Cmdlets available for administrative tasks and the documentation only addresses administrative issues at a superficial level, MSH does include a get-wmiobject Cmdlet that provides extensive access to the Windows Management Instrumentation (WMI) framework, which is widely used by Windows administrators for system monitoring and other administrative tasks. In a properly configured environment, WMI provides secure administrative access to other systems on the network as well as the local computer. Although it is traditionally utilized through WSH, the WMI framework is accessible through a number of scripting interfaces, including MSH.

MSH is an excellent language for WMI interaction, and the unique, interactive features of MSH particularly enhance the ease with which administrators can leverage WMI. Observe this blog entry, for example, in which the writer manages to cut the size of his WSH scripts in half by converting them to MSH.

The get-wmiobject Cmdlet allows you to list and instantiate various available WMI objects. To start with, let&#39;s try to determine the MAC addresses and IP address of all of the local network adapters. You can technically still do this with the ipconfig command in MSH, but it&#39;s a good WMI demonstration, and the WMI version will allow you to acquire the information from other computers on the local network rather than just the local system. First, we have to figure out the name of the relevant class, which is relatively easy with the select and where Cmdlets:

msh> get-WMIObject -list | select __class | where { $_.__class -match "NetworkAdapter" }
__class
-------
Win32_TSNetworkAdapterSettingError
CIM_NetworkAdapter
Win32_NetworkAdapter
Win32_TSNetworkAdapterSetting
Win32_TSNetworkAdapterListSetting
Win32_NetworkAdapterConfiguration
Win32_NetworkAdapterSetting
In this case we probably want the Win32_NetworkAdapterConfiguration class, but a quick examination of its available instance values will verify that assumption. You can use the get-member Cmdlet to list its instance values. Now that we know which class to use, we will pass the class name to get-wmiobject to instantiate it, then we will pipe the instance into select and tell it to list the IP address, MAC address, and description of each network interface. We also pipe the instances through to filter out the adapters that don&#39;t have MAC addresses.

msh> get-wmiobject win32_networkadapterconfiguration | select ipaddress, macaddress, description | where { $_.macaddress.length -gt 0 }
ipaddress      macaddress            description
---------      ----------            -----------
{0.0.0.0}      00:12:3F:09:CF:46       Broadcom 440x 10/100 I...
            00:12:3F:09:CF:46       Packet Scheduler Miniport
            50:50:54:50:30:30       WAN Miniport (PPTP)
            33:50:6F:45:30:30       WAN Miniport (PPPOE)
            6A:F1:20:52:41:53       Packet Scheduler Miniport
{1.1.2.8}      00:12:F0:83:0F:F0       Intel(R) PRO/Wireless ...
With the win32_printer WMI object, you can extract information about printers associated with the system:

msh> get-wmiobject win32_printer | select name
name
----
Microsoft Office Document Image Writer
Intuit Internal Printer
Auto Epson Color on PAUL-XP
Auto HP Laser on PAUL-XP
Adobe PDF
It is also possible to extract information about installed software with the win32_product class. This is particularly useful if you want to check to make sure that systems on your network have the latest version of a particular application. Let&#39;s find out what versions of the .NET framework are installed on my laptop:

msh>  get-wmiobject win32_product | where {$_.name -match ".NET Framework"} | select name, version
name                        version
----                        -------
Microsoft .NET Framework 2.0 SDK Beta 2 2.0.50215
Microsoft .NET Framework SDK (Englis... 1.1.4322
Microsoft .NET Framework 2.0 Beta 2    2.0.50215
Microsoft .NET Framework 1.1        1.1.4322
Of course, those are just a few simple examples. You can use the -list parameter to explore other WMI classes, many of which expose useful information. You can also use the -computername parameter to access the WMI service on other local network systems.

Conclusion
Despite my initial skepticism, I am deeply impressed with MSH technology, and I am legitimately excited about the future of the Windows command line. The versatility of the syntax, the broad support for open data formats, the emphasis on security, the capacity for automation, and the extensive support for .NET integration all make MSH an excellent utility that will dramatically increase the productivity of system administrators, software developers, Windows enthusiasts, and computing professionals.

There are still a number of subtle deficiencies, but the current prerelease is so stable and effective, that I can already recommend it for use in production environments. MSH is a work in progress, and Microsoft will probably implement a number of additional features, Cmdlets, and Providers before the official release, but there are a few absent features to which I would particularly like to draw attention.

My biggest frustration with MSH is the low quality of the actual shell interface. On my Linux system, I am extremely dependent on line editing keyboard shortcuts that simplify manipulation and alteration of command line input. MSH has very few line editing shortcuts, and extremely limited support for tab completion. I would like to see an MSH command line interface that provides the code completion and syntax highlighting features found in Visual Studio. Code completion support and parameter tooltips would make the shell much easier to use. Apparently, the SDK will eventually enable users to build their own host applications with MSH functionality, so a third-party or independent developer could potentially construct her own enhanced shell interface.

The other tremendous disappointment is the lack of support for native-class construction. Although users can define functions and Cmdlets with native MSH syntax, there is no way to define new classes. MSH has the potential to compete with mainstream, general-purpose scripting languages like Python and Ruby, but the absence of class construction support will make it significantly less useful to advanced script authors. The ability to define new classes in MSH scripts would make the MSH language an ideal platform for rapid prototyping of C# applications, and it would facilitate greater modularity and code reuse.

The procedural nature of complete conditional expressions also aggravates me. I would like to see the syntax extended to add support for something like the "x ? y : z" syntax found in C and Ruby. Additionally, I would like to see more consistency in output behavior. The disparate collection of convert and export Cmdlets should be streamlined into a single set of Cmdlets with consistent behavior that can export to a file and output to the console.

I would also like to see Microsoft add additional support for graphical application construction with .NET Winforms inside of MSH. Users definitely need the ability to associate scriptblocks with individual GUI events, and there needs to be better syntax for importing existing script components and .NET assemblies.

Mild grievances aside, MSH is the kind of technology that I like the most: it empowers users and diminishes dependence on commercial, third-party applications. Regardless of your interests or ability level, if you use Windows, you can benefit from MSH. This is truly a rare victory for Microsoft, and I implore my colleagues in the Linux community to watch closely and keep an eye on the Microsoft prompt. I look forward to the official release, and I expect to see more excellent command line innovation from Microsoft in the future.
曾几何时,有人对我说:装B遭雷劈。我说:去你妈的。于是,这个人又对我说:如果再说脏话,上帝会惩罚你的。我说:我操上帝。结论:彪悍的人生不需要上帝。

TOP

发新话题