1.5. Display DataSo 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.
1.5.1. 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 ... 1.5.2. 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 togetherultimately, 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. 1.5.3. 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. 1.5.4. 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-* |