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:
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.
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.
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.
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
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.
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.