Chapter 1. Introducing MSH

Monad, also known more formally as the MSH Command Shell, is a next generation Windows command shell. Built on top of the .NET Framework, MSH provides a powerful infrastructure for the automation of a wide range of administrative tasks. At last, the command line is a first-class citizen in the world of Windows system management.

It would be unfair to characterize MSH as simply an evolution of the cmd.exe shell, a system whose roots reach back to the days of MS-DOS and before. Indeed, although the standard “host” of MSH is a console application, MSH is designed so that it can be used in other contexts, such as the MMC (Microsoft Management Console). This new shell is built from the ground up with a focus on structured data and today’s administrative challenges.

The pipeline, a mechanism for passing data between different functional units, has long been a feature of many shells, including cmd.exe. MSH goes beyond the traditional notion of using text to pass data between the different stages of the pipeline and all of the “prayer-based parsing” that goes with it by allowing the transfer of structured data in the form of .NET objects between the pipeline elements. This self-describing information can be used at any point in a complex sequence and allows any process to operate on data in an intelligent fashion, even pipeline elements that have never seen a given type of data before.

MSH also uses a provider model so that the many types of hierarchical data stores used with Windows systems can be accessed through a single consistent set of commands. In MSH, the mechanism for retrieving folders, files, content, and the current location applies not only to filesystems but also to other stores, such as the registry.

Repeatability and consistency are the two words that capture some of the real value of using MSH. Administering a single machine today is a simple task; Windows offers a well-organized graphical interface for settings and configuration, and Terminal Services makes it easy to effect changes on a server located on the other side of the world. Unfortunately, in simple terms, this model doesn’t scale well at all; it takes twice as long to manage 2 machines and 10 times as long for 10. Fortunately, there are management tools such as Systems Management Server (SMS) that ease this burden across the enterprise. Also, technologies such as WSH (Windows Scripting Host) and Perl can be used to automate repetitive tasks. By offering a scriptable language, MSH offers yet another alternative to the manual click-by-click sequence where a one-time investment in authoring a configuration script enables quick replay on a potentially large number of machines with predictable results. Add this all up, and there’s a lot of time to be saved.

Let’s take a moment to dispel a few rumors about MSH. The new shell is not a programming language to be compared with C#, C++, or VB.NET, yet it does offer a powerful scripting language. Although MSH relies heavily on the .NET Framework, it is first and foremost an administrative tool. As we’ll see, MSH is a hybrid, taking the idea of a command shell and combining it with a rich scripting language to form something altogether more useful. MSH isn’t going to replace everything in a system manager’s toolbox either; in fact, think of MSH as a conduit that provides easy access to pre-existing components whether they are exposed through the .NET Framework, COM objects, or some other mechanism. cmd.exe isn’t going away either; MSH even offers support for other command-line utilities that process text. The existing command-line toolset will continue to live, and investments made in console-based applications remain valuable. Finally, MSH is not positioned to encroach on the realm of software builds. While it offers a number of features useful in a large build environment, it’s a complement to NMAKE, MSBuild, or Visual Studio Team System, rather than a drop-in replacement for them.

These are just a few of the features of MSH that are causing Windows system administrators to take notice. As a shell, it brings together some powerful concepts by leveraging the .NET Framework and COM, and by allowing existing tools to be reused rather than reinvented. As we’ll soon see, a tool like this has a very wide range of uses.

Get MSH

MSH is supported on a number of currently available Microsoft operating systems. To use MSH, you’ll need to be running on one of the following platforms:

  • Microsoft Windows XP SP2

  • Microsoft Windows Server 2003 SP1

  • Microsoft Windows Vista (formerly Windows Code Name “Longhorn”)

In addition to a supported operating system, MSH requires the .NET Framework 2.0 redistributable, SDK, or Visual Studio 2005.

Downloading MSH

Everything needed to get up and running can be downloaded from the Web. Follow these installation steps and you’ll be ready to go:

  1. Download and install the .NET Framework 2.0 Redistributable from http://msdn.microsoft.com/netframework/downloads/updates/default.aspx. Several versions are available for different machine architectures (32-bit and 64-bit); pick the one suitable for your machine.

  2. Go tohttp://download.microsoft.com and search for “Monad”. Download and install the latest release.

That’s it. Let’s get started!

Get to Know Verb-Noun Syntax and Cmdlets

We’ll begin by getting the shell up and running so that we can start to put MSH through its paces. This section will focus on the time-honored task of inspecting the process list to see what’s currently running on a system. There are some MSH features that may not be immediately familiar to those in other command shells—in particular, the strict command syntax—but we’ll also look at a few of the obvious differences and see how they’re really nothing to fear.

These basics show you how to start using the shell, and they provide the foundation for the rest of the examples we’ll cover.

How Do I Do That?

Most MSH commands are identified by a pair of words, one verb and one noun, separated by a hyphen. The verb describes the action (such as get or set) while the noun represents the target of the action in singular form (such as process or location). There is a standard list of verbs that covers the majority of tasks (including get, set, add, and remove). Although the number of these verbs may seem excessively large, consistent naming does make learning and using MSH easier in the long run.

Let’s begin by starting the shell. From the Start menu, select Run and type MSH. You’ll see a console window open up with a small introduction:

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

    MSH D:\MshScripts>

MSH is waiting for its first command. The MSH shell is in interactive mode with the current directory set to D:\MshScripts. We’ll look at the different modes of operation in more detail later on. For now, the shell will execute commands line-by-line as they are entered.

Running a first command

We’ll use the get-process cmdlet to generate a list of currently active processes within the system:

    MSH D:\MshScripts> get-process

    Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        119       6      996       3336    31     0.22   1844  alg
        602      12    10408      15816    64    18.96   1656  CcmExec
        409       5     1648       3364    22    16.23    464  csrss
        273      11     7376      12696    55   340.16    212  explorer
          0       0        0         16     0               0  Idle
        146      11     3532       7284    61     2.90   1264  InoRpc
        110       5    11136      12404    60     9.33   1316  InoRT
        107       5     2820       6244    53     4.22   1332  InoTask
        405      10     4404        528    41    11.66    544  lsass
        290      12    33948      32208   175    14.90   3088  msh
    ...

You can use a command line or argument to reduce the number of processes get-process returns. Given something to match against, get-process will compare the process name of each process, only allowing matching ones to be displayed. Let’s take a look at all processes beginning with the letter “r” or “s”:

    MSH D:\MshScripts> get-process [rs]*

    Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
         96       4     1772       4972    43    54.60   1200  Realmon
        260       6     1260       2840    24    13.38    532  services
         21       1      168        368     4     0.57    308  smss
         96       4     2428       3208    26     0.32   1088  spoolsv
        207       6     2056       4312    35     4.41    940  svchost
        251      13     1472       3968    34     8.20    756  svchost
       1665      50    14648      22040    98    44.27    824  svchost
        183       5     2280       4440    57     0.64    720  svchost
         85       4     1036       2968    28     2.44    896  svchost
        284       0        0        216     2    64.20      4  System

get-process will accept another parameter called Exclude. This is used to filter certain processes from the results list. This time, we’ll find all processes starting with the letter “w,” except those that start with the three letters “win”:

    MSH D:\MshScripts> get-process w* -Exclude win*

    Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        125       4     1300       3624    23     0.82    320  wmiprvse
        137       4     3388       4156    25     1.36   1920  wmiprvse
        221       7     6352       7868    65     2.60   1708  wuauclt

What Just Happened?

Let’s take a step back and look at what we’ve just witnessed. To better understand how get-process works, let’s start from the top.

What is a cmdlet?

Cmdlets (pronounced “command-lets”) are one of the fundamental parts of the functionality of MSH. Cmdlets range from the very simple to the very complex, but all are designed to do a single task and to do it well. MSH provides a framework within which these cmdlets can be run, effectively providing the plumbing for passing information between different pipeline elements. Cmdlets are not designed to be monolithic giants that completely solve any given problem; instead, their power derives from composition—their use in concert with other cmdlets. A well-designed cmdlet focuses on doing one job in a clear and predictable manner. We’ll be talking a lot more about cmdlets and composition going forward.

A cmdlet is implemented as a managed class (built on the .NET Framework) that implements a well-defined set of methods to process data.

Why the verb-noun model?

Although the verb-noun syntax may seem slightly foreign or even cumbersome, there are rewards in its consistency. Because we already know how to list active processes with the get-process command, it’s only a small jump to manipulate processes by using other verbs, for example stop-process and new-process. The symmetry in cmdlet naming helps to group commands based on either task or target, which makes it easy to use other related cmdlets by picking one of the set of common verbs. It’s worth noting that the verb-noun requirement applies only to cmdlets; as we meet other MSH concepts such as functions, scripts, and aliases, we’ll see that they are not subject to the same strict syntax.

If you’re concerned that get-process requires more keystrokes than tasklist (its nearest cmd.exe equivalent), or that get-childitem is significantly longer than dir, rest assured that there are shortcuts in the form of command aliases . We look into aliases in more detail in Chapter 2. For now, we’ll continue to use the long form as it generally improves the readability of scripts.

Tip

All nouns have a default verb, get, which is assumed if no verb is given. In other words, the command process will behave in exactly the same manner as get-process.

What About...

In itself, generating a process list isn’t rocket science. tlist.exe, part of the Windows Resource Kit, has been offering this functionality for years. However, the MSH version is going to enable us to do a lot more. In the next few examples, we’ll see how this cmdlet can be combined with others to offer some flexible process list reporting.

We’ll look at wildcards in Chapter 4, but it is worth mentioning now that the [rs]* and win* style syntax used here isn’t restricted to the get-process cmdlet. In fact, it is actually MSH that interprets the command-line parameters (not the cmdlets), and the shell extends this kind of wildcard support and parsing consistency throughout.

Where Can I Learn More?

The get-help cmdlet is the portal into the built-in help system for MSH . By simply giving it a cmdlet name, a help page covering the syntax and usage will be shown:

    MSH D:\MshScripts> get-help get-process

    NAME
        get-process

    SYNOPSIS
        Gets a list of processes on a machine.

    DETAILED DESCRIPTION
        The get-process Cmdlet gets a list of the process running on a machine
        and displays it to the console along with the process properties.

        This command also supports the ubiquitous parameters:
        -Debug (-db), -ErrorAction (-ea), -ErrorVariable (-ev)
        -OutBuffer (-ob), -OutVariable (-ov), and -Verbose (-vb)

    SYNTAX
        get-process [-ProcessName] [processName] [-Id processId]
        ...

Calling get-help without any parameters generates an overview of the available help topics. Specific help information is available by supplying a topic or cmdlet name.

The get-command cmdlet provides a mechanism for listing all cmdlets registered with the shell, including their signature, parameter list, and description.

Since we’re on the quick tour, let’s take a look at one of the new aspects of MSH: the ability to treat arbitrary data stores like regular filesystems by way of a provider model.

Access the Registry Like a Filesystem

Historically, command-line shells have been intimately tied to the filesystem. With a well-defined hierarchical structure, commands are provided to move up, down, and around; work on items at some location within; or even to extend or contract the structure. Many years have shown a hierarchy is an effective representation of a data store, even if it does introduce a few problems—for example when trying to manipulate a set of items scattered in different locations throughout the tree.

Of course, it hasn’t always been possible to look at a filesystem in the single consolidated manner that we enjoy today. Even with the variety of stores and protocols now in use—from FAT, NTFS, and CIFS to ISO9660—modern operating systems abstract the differences away from us, leaving a single, simple view of hierarchical folders and files. MSH takes this concept a step further and embraces other hierarchical data stores, such as the registry, to enable us to navigate around them and act within them as if they were a simple folder structure on disk. As we’re about to see, this makes many tasks much easier.

In MSH, a provider forms the abstraction layer that exposes a hierarchical store to the shell as another drive. In addition to the familiar A, C, and D drives, you can now see drives representing environment variables, MSH functions, shortcuts to My Documents, and parts of the registry. This list isn’t fixed and drives can be added, each backed by a different provider.

Let’s take a look at the simple example of Windows Notepad and how to change its configuration via the registry.

How Do I Do That?

The registry is divided into five primary divisions known as hives , two of which we’ll look at here. HKEY_LOCAL_MACHINE (often abbreviated to HKLM) stores system-wide settings shared by all users of the machine. HKEY_CURRENT_USER (HKCU) contains the current user’s settings and user-specific information.

When it starts, MSH installs two additional drives (named hkcu: and hklm:) that map to these two hives. You can start browsing around them right away:

    MSH D:\MshScripts> cd hkcu:
    MSH D:\MshScripts> dir

    SKC  VC ChildName                      Property
    ---  -- ---------                      --------
      2   0 AppEvents                      {}
      0  31 Console                        {ColorTable00, ColorTable01,
                                            ColorTab...
     23   1 Control Panel                  {Opened}
      0   3 Environment                    {MSHCOMMANDPATH, TEMP, TMP}
      1   6 Identities                     {Identity Ordinal, Migrated5, Last
                                            Us...
      2   0 Keyboard Layout                {}
      0   0 Network                        {}
      1   0 Printers                       {}
      5   0 Software                       {}
      0   0 UNICODE Program Groups         {}
      0   1 SessionInformation             {ProgramCount}
      0   7 Volatile Environment           {LOGONSERVER, CLIENTNAME,
                                            SESSIONNAME...

For comparison, it’s easy to see the relationship between this output and the tree structure shown in the Registry Editor tool (regedit.exe), as displayed in Figure 1-1.

Registry Editor
Figure 1.1. Registry Editor

Notepad stores its per-user settings in the HKCU hive under the \Software\Microsoft path. We can navigate to that path with a simple cd and use the get-property cmdlet to view its content:

    MSH D:\MshScripts> cd hkcu:\Software\Microsoft\Notepad
    MSH D:\MshScripts> get-property .

    lfEscapement         : 0
    lfOrientation        : 0
    lfWeight             : 400
    lfItalic             : 0
    lfUnderline          : 0
    lfStrikeOut          : 0
    lfCharSet            : 0
    lfOutPrecision       : 1
    lfClipPrecision      : 2
    lfQuality            : 2
    lfPitchAndFamily     : 49
    iPointSize           : 100
    fWrap                : 0
    StatusBar            : 0
    fSaveWindowPositions : 0
    lfFaceName           : Lucida Console
    szHeader             : &f
    szTrailer            : Page &p
    iMarginTop           : 1000
    iMarginBottom        : 1000
    iMarginLeft          : 750
    iMarginRight         : 750
    fMLE_is_broken       : 0
    iWindowPosX          : 88
    iWindowPosY          : 88
    iWindowPosDX         : 600
    iWindowPosDY         : 411

Return to REGEDIT for a moment and it’s easy to see that this output directly corresponds to the content of the righthand pane when the Notepad node is selected. Notepad uses the lfFaceName key to store the font used for displaying content. We’ll use the set-property cmdlet (notice we’re using the same noun, just a different verb) to change this value to Verdana:

    MSH D:\MshScripts> set-property . -property lfFaceName -value "Verdana"

Now when we start Notepad, it’ll load the new setting from the registry for the content area and display it in a different font, as seen in Figure 1-2.

What About...

...Using this approach to configure any application? It depends. Applications can store configuration settings in many places, such as XML files, INI files, Active Directory, and the registry. Each application may pick a different

Windows Notepad
Figure 1.2. Windows Notepad

approach that works well for its specific scenario, but this doesn’t guarantee any consistency. Even applications that don’t wholly rely on the registry don’t follow one single format or naming convention in use, although typically you’ll find settings under \Software\ [Vendor name] \ [Application name]. You can use the graphical REGEDIT tool or the commands we’ve seen here to explore the registry further.

Although we’ve only looked at the registry provider in this section, the same process works for other stores with providers offered by the shell.

At this point, it’s fair to ask, “Aren’t there other tools for this?” and “What about Group Policy and SMS?” Indeed, there are many tools on the Windows platform for application management that excel at their tasks. MSH isn’t designed to replace those; instead, MSH levels the field, allowing immediate interactive exploration of the system. Group Policy and SMS are invaluable in large automated deployments, and MSH can be considered a complement for day-to-day administration. Use MSH if it makes sense for a task, but remember it supports, rather than replaces, the suite of management tools available today.

Where Can I Learn More?

The Windows registry is described in more detail at http://support.microsoft.com/default.aspx?scid=kb;EN-US;256986. As the article notes, changing values in the registry is not a subject to be taken lightly. Be sure you know which values you’re changing and the effect they’ll have before making any changes to production machines.

The get-provider cmdlet lists the providers currently registered with MSH, along with the drives that use them. Similarly, get-drive will list all physical drives (hard disks, removable media), as well as those relying on provider-based systems.

Moving on, it’s time to take a look at some of the fundamental changes to the pipeline in MSH.

Create a Pipeline to Pass Information

The cmd.exe command shell has always supported the idea of redirecting the output of a process to a different location. The vertical bar, or pipe symbol (|), is used to create this pipeline. When present, it instructs the shell to redirect the output of one command to the input stream of another, effectively chaining the commands together. For example, while the command type win.ini | more is a familiar way of paging through a long text file, what’s really happening is that the output of the type command, which lists the contents of a file in its entirety, is being piped to the more command, which knows how big the screen is and how to pause when it’s full.

The pipeline is not a new concept and is the glue that most command shells use for passing data between different processes. However, MSH takes the concept one step further. Instead of passing simple text streams between different steps (the method used by almost all command shells today), for communication between MSH cmdlets and scripts, strongly typed objects are used to carry both the information and its structure.

Passing strongly typed data has some significant advantages. Flat text files are rarely the best way to represent structured data as there is only so much information that can be captured in a line-to-line text listing. Historically, when information is transferred between two processes in textual format, there will be some mutually agreed upon encoding—maybe the generator will always output information in some sequence that the receiver must then parse to recreate the structure for processing. Authors must either add complexity to their tool by generating both human-readable and machine-readable output, or they end up forcing the script writer to use a parsing tool such as AWK to extract meaning from the textual output. Such parsers can be difficult to write, are prone to failure with minor tool changes, and cannot always handle international characters. Clearly, this tightly binds the two tools together, requiring code in both to handle the interchange. This is often so restrictive that it limits the tools so that they cannot be used for any other purpose. With MSH passing structured data instead of text, much of this encoding and decoding effort disappears and arbitrary pipelines become a reality.

The pipeline empowers MSH through composition. Composition, in this sense, refers to the way in which we can combine small functional units together, creating something altogether more useful. As we discussed earlier, cmdlets are designed to do a simple task well—for example, listing processes, sorting, and filtering. Let’s take a look at how we can pipeline cmdlets together to list, filter, and sort the process list without the get-process cmdlet ever having to know what sorting is.

How Do I Do That?

As we’re already comfortable with the get-process cmdlet, we’ll use that as a starting point. We’ll create a pipeline with the | symbol and introduce the where-object cmdlet to apply a test to each object as it passes through the pipe. If the object satisfies the test criteria, it will continue on, in this case, to be shown in the console:

    MSH D:\MshScripts> get-process | where-object { $_.Handles -gt 200 }

    Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        624      13    10548      15756    65    25.01   1656  CcmExec
        407       5     1684       3420    23    22.71    464  csrss
        274      11     7376      12696    55   565.91    212  explorer
        404      10     4472       2376    42    16.12    544  lsass
        282      12    35028      32416   176    21.93   3088  msh
        260       6     1276       2864    24    14.54    532  services
       1709      52    18092      24888   103    62.37    824  svchost
        209       6     2080       4320    36     4.80    940  svchost
        262      14     1500       3988    34    11.43    756  svchost
        284       0        0        216     2    77.96      4  System
        551      61     7332       4136    51    19.24    488  winlogon
        225       8     6364       7888    66     3.00   1708  wuauclt

Pipelines aren’t limited to two stages. Now that we’ve established the set of processes that have more than 200 open handles, we can pipe that set into another cmdlet that will sort the output based on handle count:

    MSH D:\MshScripts> get-process | where-object { $_.Handles -gt 200 } | sort-object Handles

    Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        209       6     2080       4320    36     4.80    940  svchost
        260       6     1276       2864    24    14.54    532  services
        262      14     1500       3988    34    11.64    756  svchost
        274      11     7376      12696    55   580.04    212  explorer
        284       0        0        216     2    78.89      4  System
        405      10     4472        588    42    16.28    544  lsass
        407      12    34632      33052   175    23.16   3088  msh
        408      12    18432      19221    99    20.01   3089  msh
        414       5     1684       3420    23    24.42    464  csrss
        551      61     7332       4136    51    19.34    488  winlogon
        618      13    10352      15740    64    25.38   1656  CcmExec
       1748      53    18312      24964   105    63.40    824  svchost

Sometimes it’s convenient to group objects by some property after they’ve been sorted. Like sort-object, the group-object cmdlet takes a parameter to organize its output. For example, let’s view the same list, but this time, group together the processes by name:

    MSH D:\MshScripts> get-process | where-object { $_.Handles -gt 200 } | group-object ProcessName

    Count Name                      Group
    ----- ----                      -----
        3 svchost                   {svchost, svchost, svchost}
        2 msh                       {msh, msh}
        1 CcmExec                   {CcmExec}
        1 csrss                     {csrss}
        1 explorer                  {explorer}
        1 lsass                     {lsass}
        1 services                  {services}
        1 System                    {System}
        1 winlogon                  {winlogon}

What Just Happened?

The three new cmdlets we’ve covered here are all similar in their behavior. At a high level, they all examine the objects in the pipeline and put some or all of them back into the pipeline in a different order. The where-object cmdlet is used to control whether an object continues through the pipeline or is dropped. In contrast, the sort-object cmdlet will output every object it sees, but it may do so in a different order after it has had the opportunity to rearrange the objects. Meanwhile, the group-object cmdlet will allow all objects to pass through but will do so after placing the objects in a container related to the grouping property.

The $_ notation can be read as “this.” When used in the script block for the where-object test, it refers to the current object in the pipeline. The dot notation, $_.Handles, is used to access the properties of the object for this test. We’ll look at objects and their properties in more detail shortly.

MSH offers a set of operators for performing comparisons. Several of the common ones are listed in Table 1-1; Appendix A contains the complete list. Note that the < and > symbols are used for redirection in the shell and cannot be used to perform less-than or greater-than comparisons.

Table 1-1. Comparison operators

Operator

Description

-gt

Greater than

-lt

Less than

-eq

Equal to

-ne

Not equal to

The important point to note here is that the get-process has no notion of sorting or filtering. In addition to significantly reducing the complexity of that cmdlet, it also has the overall benefit in that sorting and filtering now use a common syntax anywhere within the shell. Whereas today you have to learn a different syntax for each tool (look at the differences in sorting between DIR and TLIST, for example), now it’s just a case of using where-object and sort-object for everything.

What About...

...Using legacy tools in the pipeline? Is this possible? Yes! MSH allows the use of non-cmdlet applications to form part of a pipeline, even though it is not able to deduce any structure from their output. As such, output from a legacy tool will take the form of a list of strings representing each line of the output. For example, the command ping 127.0.0.1 | sort-object is valid, but it probably won’t yield the results you’re hoping for. MSH will simply perform an alphabetical sort of all the lines of output and kindly return that to the screen.

Given the previous output, it would be hard to claim that the grouped output is easier to read. Fortunately, as we’ll discover, there are several other cmdlets that can be used to tidy this up for display on-screen, in print, or in other applications. As we’re beginning to see, the key factor here is that the data is grouped as we want it and any downstream cmdlets will only have to be concerned with presentation.

...How about sorting on more than one field? Although we only used a single property in this case, sort-object will take a comma-separated list of fields for sorting. If the values of the first are the same for two objects, the values in the second will be compared instead.

Where Can I Learn More?

The built-in help guides for the cmdlets introduced here have more details on their syntax and usage:

    get-help about_pipeline
    get-help where-object
    get-help sort-object
    get-help group-object

All of these scenarios are made possible through the shift toward structured objects instead of text. We’ll take a look at objects and the pipeline in more detail starting in Chapter 3. In the meantime, we’ll look at some of the more immediate benefits of MSH.

Display Data

So far, we’ve been building commands with little attention to the output, other than assuming it will be displayed in the console window. How does MSH know how to neatly display the objects, especially if it’s not familiar with their structure? It turns out that there is a fair bit of plumbing in MSH to make sure that console output is displayed in a useful form for a wide range of types.

The default formatter is a standard part of the shell. If and when an object reaches the end of the pipeline, MSH inspects its type and compares it to a list of known objects. If a match is found, MSH understands how to best format the object for display; if not, MSH displays the .NET properties of the object.

The type of the first object to reach the end of the pipeline generally governs how subsequent objects will be displayed. Alternatively, a command or script can explicitly insert formatting cmdlets (from the format-* family) and/or outputting cmdlets (from the out-* family) in the pipeline for more explicit control over how and where objects should be displayed.

Let’s take a look at some of the different tools available for presenting data.

How Do I Do That?

Armed with the knowledge that there is a default formatter at work, we can look at the output of get-process in a new light. The default formatter knows about Process objects because they are one of the common data types used in shell work. The default formatter is configured to pick out some of the more interesting aspects of a process and tabulate them for display:

    MSH D:\MshScripts> get-process

    Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        123       5     1008       2500    32     1.24   1844  alg
        799      14    14188      16480    71   189.09   1656  CcmExec
         19       1     1444       1516    13     1.61   1052  cmd
        426       5     1796       3148    24   184.88    464  csrss
        361      11     9420      13312    58 2,206.90    212  explorer
    ...

Some of the columns in this output, such as ProcessName and Id, are simple properties of the Process data structure. Others, including the non-paged memory size, paged memory size, and working set, are calculated columns . For each of these columns, the default formatter is running a small fragment of script on each Process object it sees, in this case, to convert a number in bytes to kilobytes for display.

We’ll come back to the default formatter’s behavior shortly. Meanwhile, let’s say we’re just interested in displaying a subset of the properties of the msh process. The format-list cmdlet is one of the simplest formatters. When provided with a list of property names, it will display each in sequence, along with its corresponding value. For now, let’s just use a few properties that are common to all Process types; we’ll see where these property names come from later when we meet the get-member cmdlet in Chapter 3:

    MSH D:\MshScripts> get-process msh | format-list
    ProcessName,PriorityClass,Id,VirtualMemorySize,Handles,StartTime,WorkingSet,Modules

    ProcessName       : msh
    PriorityClass     : Normal
    Id                : 3088
    VirtualMemorySize : 186503168
    Handles           : 451
    StartTime         : 1/22/2005 5:20:51 PM
    WorkingSet        : 37068800
    Modules           : {msh.exe, ntdll.dll, mscoree.dll, KERNEL32.dll,
                        ADVAPI32.dll, RPCRT4.dll, SHLWAPI.dll, GDI32.dll,
                        USER32.dll, msvcrt.dll, mscorwks.dll, MSVCR80.dll,
                         mscorlib.ni.dll, ole32.dll, shell32.dll, comctl32.dll,
                        comctl32.dll, rsaenh.dll, mscorsec.dll, WINTRUST.dll,
                        CRYPT32.dll, MSASN1.dll, IMAGEHLP.dll, SOFTPUB.DLL,
                        xpsp2res.dll, userenv.dll, VERSION.dll, secur32.dll,
                        netapi32.dll, cryptnet.dll, WLDAP32.dll, WINHTTP.dll,
                        SensApi.dll, Cabinet.dll, System.Management.Automation.
    ...

format-table is one of the more versatile formatters. In simple usage it can be provided with a comma-separated list of properties and will automatically tabulate the corresponding values for each object it sees. In many cases, a simple format-table pass will be sufficient for data presentation:

    MSH D:\MshScripts> get-process | format-table Id,ProcessName,StartTime

                       Id ProcessName                StartTime
                       -- -----------                ---------
                     1844 alg                        1/22/2005 5:17:58 PM
                     1656 CcmExec                    1/22/2005 5:17:56 PM
                     1052 cmd                        1/29/2005 5:32:20 PM
                      464 csrss                      1/22/2005 5:17:34 PM
                      212 explorer                   1/22/2005 5:19:06 PM
                        0 Idle
                      544 lsass                      1/22/2005 5:17:36 PM
                     3088 msh                        1/22/2005 5:20:51 PM
    ...

As with most formatting cmdlets, format-table will accept a GroupBy parameter. When displaying results, this is used to separate objects based on some property value. For cleanliness of presentation, it’s common to first sort the input before presentation so that each grouped category appears just once. For example, let’s create a tabulated process listing grouped by process priority:

    MSH D:\MshScripts> get-process | sort-object PriorityClass,ProcessName
    | format-table -GroupBy PriorityClass ProcessName,Id,VirtualMemorySize,PriorityClass


        PriorityClass: Normal

    ProcessName                                   Id         VirtualMemorySize
    -----------                                   --         -----------------
    svchost                                      896                  29769728
    svchost                                      756                  35753984
    svchost                                      824                 110878720
    svchost                                      720                  59514880
    smss                                         308                   3911680
    spoolsv                                     1088                  27459584
    svchost                                      940                  39124992
    wmiprvse                                     320                  23642112
    wmiprvse                                    1920                  26124288
    wuauclt                                     1708                  68648960
    System                                         4                   1941504
    csrss                                        464                  25485312
    explorer                                     212                  60772352
    alg                                         1844                  33259520
    CcmExec                                     1656                  73891840
    cmd                                         1052                  13979648
    notepad                                     1792                  25735168
    services                                     532                  25305088
    msh                                         3088                 186503168
    lsass                                        544                  43470848


        PriorityClass: High

    ProcessName                                   Id         VirtualMemorySize
    -----------                                   --         -----------------
    winlogon                                     488                  53092352
    Idle                                           0                         0

We’ve only scratched the surface of format-table here. It’s possible to be far more expressive in terms of formatting (width and alignment), as well as content (through the use of calculated columns). In the following example, the syntax may seem somewhat obtuse to begin with, but it’s very regular and can be used to create almost any imaginable table output:

    MSH D:\MshScripts> get-process | format-table @{expression="ProcessName";
    width=50; label="Name"}, @{expression="Id"; width=5}, @{expression={$_
    .VirtualMemorySize/1024}; width=30; label="Virtual memory (kb)"}

    Name                                             Id    Virtual memory (kb)
    ----                                             --    -------------------
    alg                                            1844                  32480
    CcmExec                                        1656                  72008
    cmd                                            1052                  13652
    csrss                                           464                  24888
    explorer                                        212                  59348
    Idle                                              0                      0
    lsass                                           544                  42452
    msh                                            3088                 182132
    notepad                                        1792                  25132
    ...

What Just Happened?

We started by looking at how the default formatter operates on known types. It’s important to realize that the formatting process is applied to any object when it has nowhere further to go (i.e., it has reached the end of the pipeline, and the next stop is the console output). The shell does not discriminate between a single command (a pipeline of length 1, if you will) and a complex series of processes all piped together—ultimately, both cases have an end point.

format-list is a simple case, and we’ve already seen the bulk of its functionality here. One thing to remember is that because it generates the same listing for each element, its output can tend to get very large when it’s used for any more than a small number of objects. It has a couple of other interesting usages. For types known to MSH, format-list with no parameter will display the object’s most relevant properties, whereas format-list with a wildcard parameter (*) will display all of them. If the default formatter does not recognize a type it encounters, it always falls back to the list formatter that shows all properties and values of the object.

The complex format-table case uses a construct known as a hashtable, which is a data type we haven’t discussed yet. When we look at hashtables and other data types in Chapter 3, we’ll be able to revisit this aspect of format-table to create some more interesting reports.

What About...

...Are there any other formatters? The format-wide cmdlet can be used to list output in a horizontal order. Its formatting is similar to the effect of the /w switch on the DIR command where items are listed left to right and wrap when a line is complete.

...Combining grouping and the default formatter? It certainly is possible. If a formatting cmdlet is used without parameters and the objects are of a known type, the default formatting rules will be applied. In other words, applying format-table to the output of get-process will result in the familiar tabulated style offered by the default formatter. A pipeline such as get-process | sort-object PriorityClass | format-table -GroupBy PriorityClass can be used to group processes by priority while maintaining the standard column format.

Where Can I Learn More?

You can learn more about how the default formatter works by looking at the *.mshxml file distributed with MSH. This file references a series of similar files, each of which contains configuration that is used by the default formatter.

The get-command cmdlet, as well as the help system, can also be used to find more information about the available formatters and their usage:

    MSH D:\MshScripts> get-command format-*

What’s Next?

By now, you’ve hopefully got a taste for the power of MSH as a tool. In the chapters that follow, we’ll look more deeply into the different facets of the environment, starting with the basics and moving on to talk about the different components in more detail. Welcome aboard!

Get Monad (AKA PowerShell) now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.