Effective PowerShell Item 3: Know Your Output Formatters

I have mentioned previously that Windows PowerShell serves up .NET objects for most everything.  Get-ChildItem (alias Dir) outputs a sequence of System.IO.FileInfo and System.IO.DirectoryInfo objects output.  Get-Date outputs a System.DateTime object.  Get-Process outputs System.Diagnostics.Process objects and Get-Content outputs System.String objects (or arrays of them based on how -ReadCount is set).  You get the idea – PowerShell’s currency is .NET objects.  This isn’t always obvious because of the way that PowerShell renders these .NET objects to text for display on the host console.  Let’s imagine for a moment that we had to figure out how to solve this problem ourselves.

Our first approach might be to rely on the ToString() method that is available on every .NET object.  That would work fine for some .NET objects e.g.:

PS C:\> (get-date).ToString()
9/3/2007 10:21:23 PM

But not so well for others:

PS C:\> (Get-Process)[0].ToString()
System.Diagnostics.Process (audiodg)

Hmm, that is certainly less than satisfying.  Guess we need to think a little harder.  OK let’s not strain our brains.  🙂  Let’s just look at how the PowerShell team solved this problem.  They invented the notion of "views" for the common .NET types as well as a default view for any particular .NET type they provide a view for.  You don’t have to use the formatting cmdlets.  If you don’t specify a formatting cmdlet then PowerShell will choose a formatter based on the default view for a .NET type which could be tabular, list, wide or custom. 

Quick defintion break: Types for objects.  The System.DateTime class is a .NET type, there is only one of these.  The Get-Date cmdlet outputs an instance of this type a.k.a an object.  There can be many DateTime objects based off the one definition of System.DateTime.  PowerShell defines a view for the type that gets applied to all instances (objects) of that type.

OK so what if PowerShell doesn’t define a view for a .NET type?  This is a certainty because the possible set of .NET types is infinite.  I could create one right now called Plan9FromOuterSpace, compile it into a .NET assembly and load it into PowerShell.  How’s PowerShell going to deal with the type it isn’t familiar with?  Let’s see:

@’
public class Plan9FromOuterSpace {
    public string Director = "Ed Wood";
    public string Genre = "Science Fiction B Movie";
    public int NumStars = 0;
}
‘@ > C:\temp\Plan9.cs

PS C:\> csc /t:library Plan9.cs
PS C:\> [System.Reflection.Assembly]::LoadFrom(‘c:\temp\Plan9.dll’)
PS C:\> new-object Plan9FromOuterSpace

Director                   Genre                                        NumStars
——–                   —–                                        ——–
Ed Wood                    Science Fiction B Movie                             0

It seems that up to a certain number of public properties (IIRC 5), PowerShell will use a tabular view.  If you more than that number of public properties then PowerShell falls back to a list view.  OK back to the topic of views.  There can be (and often is) multiple views defined for a single .NET type.  These views are defined in XML format files in the PowerShell install directory:

PS C:\> Get-ChildItem $PSHOME\*format*

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Windows\System32\
    WindowsPowerShell\v1.0

Mode                LastWriteTime     Length Name
—-                ————-     —— —-
-a—         1/24/2007  11:23 PM      22120 Certificate.format.ps1xml
-a—         1/24/2007  11:23 PM      60703 DotNetTypes.format.ps1xml
-a—         1/24/2007  11:23 PM      19730 FileSystem.format.ps1xml
-a—         1/24/2007  11:23 PM     250197 Help.format.ps1xml
-a—         1/24/2007  11:23 PM      65283 PowerShellCore.format.ps1xml
-a—         1/24/2007  11:23 PM      13394 PowerShellTrace.format.ps1xml
-a—         1/24/2007  11:23 PM      13540 Registry.format.ps1xml

These views look like this:

<View>

  <Name>process</Name>

  <ViewSelectedBy>

    <TypeName>System.Diagnostics.Process</TypeName>

    <TypeName>Deserialized.System.Diagnostics.Process</TypeName>

  </ViewSelectedBy>

  <TableControl>

    <TableHeaders>

      <TableColumnHeader>

        <Label>Handles</Label>

        <Width>7</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Label>NPM(K)</Label>

        <Width>7</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Label>PM(K)</Label>

        <Width>8</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Label>WS(K)</Label>

        <Width>10</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Label>VM(M)</Label>

        <Width>5</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Label>CPU(s)</Label>

        <Width>8</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Width>6</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader />

    </TableHeaders>

    <TableRowEntries>

      <TableRowEntry>

        <TableColumnItems>

          <TableColumnItem>

            <PropertyName>HandleCount</PropertyName>

          </TableColumnItem>

          <TableColumnItem>

            <ScriptBlock>[int]($_.NPM / 1024)</ScriptBlock>

          </TableColumnItem>

          <TableColumnItem>

            <ScriptBlock>[int]($_.PM / 1024)</ScriptBlock>

          </TableColumnItem>

          <TableColumnItem>

            <ScriptBlock>[int]($_.WS / 1024)</ScriptBlock>

          </TableColumnItem>

          <TableColumnItem>

            <ScriptBlock>[int]($_.VM / 1048576)</ScriptBlock>

          </TableColumnItem>

          <TableColumnItem>

            <ScriptBlock>

              if ($_.CPU -ne $())

              {

              $_.CPU.ToString("N")

              }

            </ScriptBlock>

          </TableColumnItem>

          <TableColumnItem>

            <PropertyName>Id</PropertyName>

          </TableColumnItem>

          <TableColumnItem>

            <PropertyName>ProcessName</PropertyName>

          </TableColumnItem>

        </TableColumnItems>

      </TableRowEntry>

    </TableRowEntries>

  </TableControl>

</View>

The XML definition above is of the "table view" for the Process type.  It defines the column attributes of the view as well as the data that goes into each column, in some cases massaging the data into a more easily consumable value (KB vs bytes or MB vs bytes).   Here is the "wide view" definition for the Process type:

<View>

  <Name>process</Name>

  <ViewSelectedBy>

    <TypeName>System.Diagnostics.Process</TypeName>

  </ViewSelectedBy>

  <WideControl>

    <WideEntries>

      <WideEntry>

        <WideItem>

          <PropertyName>ProcessName</PropertyName>

        </WideItem>

      </WideEntry>

    </WideEntries>

  </WideControl>

</View>

In this "wide view" the only property that PowerShell will display is the ProcessName.  In searching the DotNetTypes.format.ps1xml, we can find more definitions.  The following StartTime "named view" isn’t invoked by default, you have to specify it by name to the Format-Table cmdlet:

<View>

  <Name>StartTime</Name>

  <ViewSelectedBy>

    <TypeName>System.Diagnostics.Process</TypeName>

  </ViewSelectedBy>

  <GroupBy>

    <ScriptBlock>$_.StartTime.ToShortDateString()</ScriptBlock>

    <Label>StartTime.ToShortDateString()</Label>

  </GroupBy>

  <TableControl>

    <TableHeaders>

      <TableColumnHeader>

        <Width>20</Width>

      </TableColumnHeader>

      <TableColumnHeader>

        <Width>10</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Width>13</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

      <TableColumnHeader>

        <Width>12</Width>

        <Alignment>right</Alignment>

      </TableColumnHeader>

    </TableHeaders>

    <TableRowEntries>

      <TableRowEntry>

        <TableColumnItems>

          <TableColumnItem>

            <PropertyName>ProcessName</PropertyName>

          </TableColumnItem>

          <TableColumnItem>

            <PropertyName>Id</PropertyName>

          </TableColumnItem>

          <TableColumnItem>

            <PropertyName>HandleCount</PropertyName>

          </TableColumnItem>

          <TableColumnItem>

            <PropertyName>WorkingSet</PropertyName>

          </TableColumnItem>

        </TableColumnItems>

      </TableRowEntry>

    </TableRowEntries>

  </TableControl>

</View>

Why I am showing you all this?  I think it is important to understand the "magic" behind how a .NET object – this binary entity – gets rendered into text on your host console.  With this knowledge, you should never forget that you are dealing with .NET objects first and foremost.  

You also may be wondering if there is an easier way to figure out what views are available for any particular .NET type.  There is if you have the PowerShell Community Extensions installed.  PSCX provides a handy script provided by Joris van Lier called Get-ViewDefinition and you can use it like so:

PS C:\> get-viewdefinition System.Diagnostics.Process

Name       : process
Path       : C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml
TypeName   : System.Diagnostics.Process
SelectedBy : {System.Diagnostics.Process, Deserialized.System.Diagnostics.Process}
GroupBy    :
Style      : Table

Name       : Priority
Path       : C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml
TypeName   : System.Diagnostics.Process
SelectedBy : System.Diagnostics.Process
GroupBy    : PriorityClass
Style      : Table

Name       : StartTime
Path       : C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml
TypeName   : System.Diagnostics.Process
SelectedBy : System.Diagnostics.Process
GroupBy    :
Style      : Table

Name       : process
Path       : C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml
TypeName   : System.Diagnostics.Process
SelectedBy : System.Diagnostics.Process
GroupBy    :
Style      : Wide

From this output you can see that there are quite a few views that you might not have been aware of related to the System.Diagnostics.Process .NET type that Get-Process outputs.  Let’s check out these alternate views:

PS C:\> Get-Process | Format-Wide

audiodg                              csrss
csrss                                devenv
dexplore                             DPAgnt
DpHost                               dwm
EDICT                                ehmsas
ehtray                               explorer
FlashUtil9d                          Idle
ieuser                               iexplore
iexplore                             iexplore


PS C:\> Get-Process | Format-Table -View Priority

ProcessName                  Id   HandleCount   WorkingSet
———–                  —   ———–   ———-
audiodg                    1276           125      9592832
csrss                       548           775      3440640
csrss                       604           831     14360576
devenv                     2632           974     93655040

   PriorityClass: Normal

ProcessName                  Id   HandleCount   WorkingSet
———–                  —   ———–   ———-
dexplore                   4324           401      4214784
DPAgnt                     3300           133      2674688
DpHost                      352           207     10928128

   PriorityClass: High

ProcessName                  Id   HandleCount   WorkingSet
———–                  —   ———–   ———-
dwm                        4072           235     86724608


PS C:\> Get-Process | Format-Table -View StartTime

ProcessName                  Id   HandleCount   WorkingSet
———–                  —   ———–   ———-
audiodg                    1276           120      9572352
csrss                       548           757      3432448
csrss                       604           834     14360576
devenv                     2632           974     93655040

   StartTime.ToShortDateString(): 8/31/2007

ProcessName                  Id   HandleCount   WorkingSet
———–                  —   ———–   ———-
dexplore                   4324           401      4214784

   StartTime.ToShortDateString(): 8/29/2007

BTW what if you have forgotten what formatters are available to you in PowerShell?  Don’t forget that you can use the first of the "big four" cmdlets, Get-Command like so:

PS C:\> get-command format-*

CommandType     Name                         Definition
———–     —-                         ———-
Cmdlet          Format-Custom                Format-Custom [[-Property…
Cmdlet          Format-List                  Format-List [[-Property] …
Cmdlet          Format-Table                 Format-Table [[-Property]…
Cmdlet          Format-Wide                  Format-Wide [[-Property] …

You are probably already pretty familiar with Format-Table.  It presents data in tabular format.  This is the default format for many views including those for System.Diagnostics.Process and .   Format-Wide is also pretty straight-forward.  PowerShell displays a single property defined by PowerShell (ie the most interesting) in multiple columns.  Format-Custom is interesting but probably not a formatter that you will use that often – it will be implicitly invoked for those .NET types that have custom views like System.DateTime:

<View>

  <Name>DateTime</Name>

  <ViewSelectedBy>

    <TypeName>System.DateTime</TypeName>

  </ViewSelectedBy>

  <CustomControl>

    <CustomEntries>

      <CustomEntry>

        <CustomItem>

          <ExpressionBinding>

            <PropertyName>DateTime</PropertyName>

          </ExpressionBinding>

        </CustomItem>

      </CustomEntry>

    </CustomEntries>

  </CustomControl>

</View>

DateTime is a ScriptProperty that PowerShell that is defined like so:

PS C:\> get-date | Get-Member -Name DateTime

   TypeName: System.DateTime

Name     MemberType     Definition
—-     ———-     ———-
DateTime ScriptProperty System.Object DateTime {get=if ($this.DisplayHint -i…

This brings me to my favorite formatter that I use when I’m spelunking PowerShell output.  Notice that the Definition column above is truncated.  Often when I want to see everything I will use the Format-List cmdlet.  This formatter outputs the various property values on individuals lines so that data is rarely truncated e.g.:

PS C:\> get-date | Get-Member -Name DateTime | Format-List

TypeName   : System.DateTime
Name       : DateTime
MemberType : ScriptProperty
Definition : System.Object DateTime {get=if ($this.DisplayHint -ieq  "Date")
                                 {
                                     "{0}" -f $this.ToLongDateString()
                                 }
                                 elseif ($this.DisplayHint -ieq "Time")
                                 {
                                     "{0}" -f  $this.ToLongTimeString()
                                 }
                                 else
                                 {
                                     "{0} {1}" -f $this.ToLongDateString(), $th
             is.ToLongTimeString()
                                 };}

Now we can see the entire definiton of the DateTime ScriptProperty.  NOTE: PowerShell often defines an abbreviated set of these property values to display by default with the Format-List cmdlet.  It doesn’t want you to be overwhelmed with information.  However, when you’re spelunking you want to see all the gory details.  All you have to do to get all the property values is execute "format-list *".  Check out the default list format for a Process object:

PS C:\> (Get-Process)[0] | Format-List

Id      : 1284
Handles : 103
CPU     :
Name    : audiodg

versus what you get when you ask Format-List to give you everything:

PS C:\> (Get-Process)[0] | Format-List *

__NounName                 : Process
Name                       : audiodg
Handles                    : 99
VM                         : 47075328
WS                         : 9027584
PM                         : 11141120
NPM                        : 3360
Path                       :
Company                    :
CPU                        :
FileVersion                :
ProductVersion             :
Description                :
Product                    :
Id                         : 1284
PriorityClass              :
HandleCount                : 99
WorkingSet                 : 9027584
PagedMemorySize            : 11141120
PrivateMemorySize          : 11141120
VirtualMemorySize          : 47075328

See what I mean?  Look at how much information you would have missed if you forgot that little ‘ol "*"!  In summary, if there is one and only one thing you get out of this long post please let it be this.  "Format-List *" is the formatter to use when you want to look at all the property values of an object.

Advertisements
This entry was posted in Effective PowerShell. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s