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.