Custom formatting views - PowerShell scripting and automation - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 3. PowerShell scripting and automation

Chapter 26. Custom formatting views

This chapter covers

· Changing object type names

· Creating view types

· Loading view types

· Using named views

One of the biggest features of PowerShell’s formatting system is its ability to use predefined layouts, or views, to display specific types of objects. When you run a cmdlet like Get-Process or Get-Service, the display you see by default is controlled by a set of predefined views that Microsoft provides along with PowerShell. The default display is a best guess at what you’re most likely to want to see. Those guesses don’t always work out:

PS C:\> Get-WmiObject -Class Win32_OperatingSystem

SystemDirectory : C:\windows\system32

Organization :

BuildNumber : 9600

RegisteredUser : richard_siddaway@hotmail.com

SerialNumber : 00258-70157-84935-AAOEM

Version : 6.3.9600

What you need to do is select the data you want:

PS C:\> Get-WmiObject -Class Win32_OperatingSystem |

select PSComputerName, Caption, ServicePackMajorVersion,

SystemDirectory, OSArchitecture

PSComputerName : RSSURFACEPRO2

Caption : Microsoft Windows 8.1 Pro

ServicePackMajorVersion : 0

SystemDirectory : C:\windows\system32

OSArchitecture : 64-bit

You can also create your own predefined views, either to override the ones Microsoft provides or to provide default formatting for custom objects that you create.

Tip

If you don’t see the data you want, or expect, when running a new PowerShell cmdlet, always try piping into Format-List * to see if there’s more.

26.1. Object type names

The first thing to remember is that PowerShell applies views based on the type name of the object being formatted. You can see an object’s type name by piping one or more instances of the object to Get-Member:

PS C:\> Get-Process | Get-Member

TypeName: System.Diagnostics.Process

...

Or try this to get just the type name:

PS C:\> get-process | get-member| select TypeName -unique

TypeName

--------

System.Diagnostics.Process

You’ll find that most objects produced by a PowerShell Get cmdlet have a unique type name. But objects you produce yourself by running New-Object –TypeName PSObject don’t have a unique type name—they’re a generic PSObject, so you can’t apply a formatting view to them.

PSObject and PSCustomObject

When you use PSObject to create a new object, for instance:

$obj = New-Object -TypeName PSObject -Property @{a=1; c=2}

the .NET class you’re using to give the object’s full type name is System.Management .Automation.PSObject. Because PowerShell loads the System.Management .Automation namespace, you can abbreviate to just PSObject. You may see references to the full name in scripts from the internet, but many people just use PSObject as the type name.

You’ll notice the following when you pass an object you’ve created through Get-Member:

PS C:\> $c | Get-Member

TypeName: System.Management.Automation.PSCustomObject

Don’t panic—nothing’s gone wrong. PSCustomObject is a placeholder, or base type, that’s used when you create a PSObject using the constructor (which has no parameters).

Some people prefer to use PSCustomObject directly and you may see code like this:

$obj2 = New-Object -TypeName PSCustomObject -Property @{a=1; c=2}

In reality it doesn’t matter whether you use PSObject or PSCustomObject, but we recommend PSObject because doing so involves less typing.

If your goal is to apply a view to an object you’ve created in that fashion, you must first give the object a unique type name:

PS C:\> $obj = New-Object -TypeName PSObject

PS C:\> $obj.PSObject.TypeNames.Insert(0,'My.Custom.Object')

In this example, My.Custom.Object is the custom type name. The TypeNames property is an array of names, and your command says to insert a new value at the beginning of the array:

PS C:\> $obj.PSObject.TypeNames

My.Custom.Object

System.Management.Automation.PSCustomObject

System.Object

You may also see scripts that define the custom type like this:

PS C:\> $obj.PSObject.TypeNames.[0]='My.Custom.Object'

Either is perfectly valid. But in any case, before you proceed make sure you’re clear on the object type name that you want to apply formatting to!

26.2. Getting view templates

The syntax of the XML files that define views isn’t well documented, so it’s often easiest to use one of Microsoft’s files as a starting point. You’ll find them in PowerShell’s installation folder:

PS C:\> cd $pshome

PS C:\Windows\System32\WindowsPowerShell\v1.0> dir *.format.ps1xml

Directory: C:\Windows\System32\WindowsPowerShell\v1.0

Mode LastWriteTime Length Name

---- ------------- ------ ----

-a--- 18/06/2013 15:50 27338 Certificate.format.ps1xml

-a--- 18/06/2013 15:50 27106 Diagnostics.Format.ps1xml

-a--- 18/06/2013 15:50 147702 DotNetTypes.format.ps1xml

-a--- 18/06/2013 15:50 14502 Event.Format.ps1xml

-a--- 18/06/2013 15:50 21293 FileSystem.format.ps1xml

-a--- 18/06/2013 15:50 287938 Help.format.ps1xml

-a--- 18/06/2013 15:50 97880 HelpV3.format.ps1xml

-a--- 18/06/2013 19:30 105230 PowerShellCore.format.ps1xml

-a--- 18/06/2013 15:50 18612 PowerShellTrace.format.ps1xml

-a--- 18/06/2013 15:50 13659 Registry.format.ps1xml

-a--- 18/06/2013 15:50 17731 WSMan.Format.ps1xml

Warning

Do not, under any circumstances, modify these files in any way. They’re signed using a Microsoft digital signature and must remain unchanged. Even an extra space, tab, or carriage return can render them useless. Be careful to open them, copy what you need to the Clipboard, and then close the file without saving any changes.

For the examples in this chapter, we used DotNetTypes.format.ps1xml as our starting point, copying and pasting the appropriate parts. An alternative approach is to use Export-FormatData:

Get-FormatData -TypeName System.Diagnostics.Process |

Export-FormatData -Path f1.xml

You’ll need to run the resultant XML through a routine to pretty print, but it’s safe and removes the risk of damaging the format files.

26.3. Starting a view file

View files can be edited in any text editor, including the PowerShell ISE, Notepad, or whatever you like. XML-specific editors can make the files easier to work with, but keep in mind that there are currently no public XML schema definitions (XSD or DTD files) that a dedicated XML editor can use to validate your file.

The following listing shows the content that an empty view file will have. Save this as C:\Test.format.ps1xml; you’ll find it easier to keep files in progress in a folder that has a short path, because you’ll be typing it a lot.

Listing 26.1. Starting Test.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>

<Configuration>

<ViewDefinitions>

</ViewDefinitions>

</Configuration>

Warning

Unlike most of PowerShell, these XML files are case sensitive. That’s one reason we rely so heavily on copying and pasting—it’s easier to not make mistakes that way.

Once you have that starting point, you can add specific view definitions between the <ViewDefinitions> and </ViewDefinitions> XML tags. In other words, the three examples we’re about to walk through can all be pasted in between those two tags.

26.4. Adding view types

PowerShell supports four distinct view types: Table, List, Wide, and Custom. We won’t be covering the Custom type; it’s extremely complex and not publicly documented. You can see an example in the Microsoft-provided Help.format.ps1xml file, and you’re welcome to play with it on your own if you like. Please share any discoveries relating to Custom type with the wider PowerShell community.

For the following examples, you’re going to create a custom object having five properties. The next listing shows the code that creates that object.

Listing 26.2. FormatTest.ps1

function Get-Info {

param([string]$computername=$env:COMPUTERNAME)

$os = Get-WmiObject -Class Win32_OperatingSystem -comp $computername

$cs = Get-WmiObject -Class Win32_ComputerSystem -comp $computername

$props = @{'ComputerName'=$os.csname;

'OSVersion'=$os.version;

'SPVersion'=$os.servicepackmajorversion;

'Model'=$cs.model;

'Mfgr'=$cs.manufacturer}

$obj = New-Object -TypeName PSObject -Property $props

$obj.PSObject.TypeNames.Insert(0,'Custom.Info')

Write-Output $obj

}

Get-Info | Format-Table -Autosize

Get-Info | Format-List

Get-Info | Format-Wide

When you run the script in listing 26.2, the Get-Info function is run each time, and each time it’s passed to a different formatting cmdlet. In the following examples, you’ll develop the Table, List, and Wide views that you want to be displayed when those formatting cmdlets run.

Table views

We’ll start with Table views, because they’re the most complex. Listing 26.3 shows our example. Take a look, and then we’ll explain what’s going on. Remember that this entire block of XML represents a single view definition and would be pasted between the <ViewDefinitions> and</ViewDefinitions> XML tags in your view file.

Listing 26.3. Example Table view

Pay attention to the following specifics:

· The entire section of column header definitions is optional. You can omit <TableHeaders> and </TableHeaders> and everything in between them. If you do, the names of the properties you choose will be used as column headers.

· If you decide to define column headers, you must have the same number of headers as you’ll have columns in the table. But you can have an empty column header tag of just <TableColumnHeader />, which will force that column to just use its property name.

· The data definitions indicate which properties’ values will be displayed on the rows of the table. The number of properties defines the number of columns in the table.

List views

List views are simpler than Table views. The following listing is an example of a List view. Remember that this entire block of XML represents a single view definition and would be pasted between the <ViewDefinitions> and </ViewDefinitions> XML tags in your view file.

Listing 26.4. Example List view

Listing 26.4 includes only three properties in its list: OSVersion, SPVersion, and Mfgr. You refer to these properties by the same names you gave them when you created the custom object. Your own List views can have as many properties as you like.

Wide views

Wide views are unique in that they only display one property, so they’re simple to create, as the following listing demonstrates. Again, this entire block of XML represents a single view definition and would be pasted between the <ViewDefinitions> and </ViewDefinitions> XML tags in your view file.

Listing 26.5. Example Wide view

26.5. Importing view data

Before we continue, run the test script and see what its normal output—without the custom views you’ve created—would look like:

Mfgr ComputerName SPVersion Model OSVersion

---- ------------ --------- ----- ---------

Microsoft Corporation RSSURFACEPRO2 0 Surface Pro 2 6.3.9600

Mfgr : Microsoft Corporation

ComputerName : RSSURFACEPRO2

SPVersion : 0

Model : Surface Pro 2

OSVersion : 6.3.9600

RSSURFACEPRO2

There you can see the default Table, List, and Wide views. Our next listing shows the completed formatting file, with all three of the custom views pasted in.

Listing 26.6. Final Test.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>

<Configuration>

<ViewDefinitions>

<View>

<Name>MyCustomInfoTable</Name>

<ViewSelectedBy>

<TypeName>Custom.Info</TypeName>

</ViewSelectedBy>

<TableControl>

<TableHeaders>

<TableColumnHeader>

<Label>OS Ver</Label>

<Width>9</Width>

</TableColumnHeader>

<TableColumnHeader>

<Label>SP Ver</Label>

<Width>9</Width>

<Alignment>Right</Alignment>

</TableColumnHeader>

<TableColumnHeader/>

</TableHeaders>

<TableRowEntries>

<TableRowEntry>

<TableColumnItems>

<TableColumnItem>

<PropertyName>OSVersion</PropertyName>

</TableColumnItem>

<TableColumnItem>

<PropertyName>SPVersion</PropertyName>

</TableColumnItem>

<TableColumnItem>

<PropertyName>ComputerName</PropertyName>

</TableColumnItem>

</TableColumnItems>

</TableRowEntry>

</TableRowEntries>

</TableControl>

</View>

<View>

<Name>MyCustomInfoList</Name>

<ViewSelectedBy>

<TypeName>Custom.Info</TypeName>

</ViewSelectedBy>

<ListControl>

<ListEntries>

<ListEntry>

<ListItems>

<ListItem>

<PropertyName>OSVersion</PropertyName>

</ListItem>

<ListItem>

<PropertyName>SPVersion</PropertyName>

</ListItem>

<ListItem>

<PropertyName>Mfgr</PropertyName>

</ListItem>

</ListItems>

</ListEntry>

</ListEntries>

</ListControl>

</View>

<View>

<Name>MyCustomInfoWide</Name>

<ViewSelectedBy>

<TypeName>Custom.Info</TypeName>

</ViewSelectedBy>

<WideControl>

<WideEntries>

<WideEntry>

<WideItem>

<PropertyName>ComputerName</PropertyName>

</WideItem>

</WideEntry>

</WideEntries>

</WideControl>

</View>

</ViewDefinitions>

</Configuration>

You’ll need to load that view file into each new shell session in which you want those views to be displayed. To do that, run the Update-FormatData cmdlet. It has two parameters to specify a file path, and you must specify one of them but not both. Which you use makes a difference only if you’re providing a view for an object type that already has a view loaded into memory, such as one of the object types for which Microsoft provides a default view. Here are your choices:

· -AppendPath loads the specified view file into the end of memory. That means it won’t override any views that are already in memory for the same object type.

· -PrependPath loads the specified view file into the beginning of memory. That means it’ll override any existing views for the same object type.

Here’s what happens when you load the custom view file and rerun your test script:

PS C:\> Update-FormatData -AppendPath .\test.format.ps1xml

PS C:\> ./formattest

OS Ver SP Ver ComputerName

------ ------ ------------

6.3.9600 0 RSSURFACEPRO2

OSVersion : 6.3.9600

SPVersion : 0

Mfgr : Microsoft Corporation

RSSURFACEPRO2

You can see that all three views—Table, List, and Wide—were used. The Wide view isn’t any different than when you ran this earlier, because the Wide view was already selecting, on its own, the same property, ComputerName, that you specified in your XML file.

26.6. Using named views

So far you’ve given each of your views a name in their XML definitions. Although there’s no easy way to get PowerShell to list all of the available views by name for a given object type, you can open the XML files and figure out what view names are available. The difficulty level is raised because views for a particular type aren’t necessarily grouped.

If you’re prepared to experiment a little, you can use something like this to match the view names against the type:

$data = @()

$file = "DotNetTypes.format.ps1xml"

$names = Select-Xml -Path $pshome\$file -XPath "Configuration/ViewDefinitions/View/Name" |

select -expand Node |

select -ExpandProperty "#text"

$types = Select-Xml -Path $pshome\$file -XPath "Configuration/ViewDefinitions/View/ViewSelectedBy" |

select -expand Node |

select -ExpandProperty TypeName

If ($names.Count -eq $types.Count){

for ($i=0; $i -le ($names.Count -1); $i++){

$data += New-Object -TypeName PSobject -Property @{

Name = $($names[$i])

TypeName = $($types[$i])

}

}

}

else {

Throw "Error - mismatch between number of names and types"

}

$data | sort TypeName | select Name, TypeName

Use Select-XML to find the Name and TypeName nodes. Loop through the results and combine them into a single object for each view, sort, and output. The (abridged) results look like this:

Name TypeName

---- --------

process System.Diagnostics.Process

StartTime System.Diagnostics.Process

Priority System.Diagnostics.Process

process System.Diagnostics.Process

ProcessWithUserName System.Diagnostics.Process#IncludeUserName

The last one in the list is for the –IncludeUserName parameter that PowerShell v4 introduced. To see this parameter in action, compare the following code and the change to the output:

PS C:\> Get-Process powershell

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName

------- ------ ----- ----- ----- ------ -- -----------

662 28 85832 94488 626 0.89 3216 powershell

PS> Get-Process powershell -IncludeUserName

Handles WS(K) VM(M) CPU(s) Id UserName ProcessName

------- ----- ----- ------ -- -------- -----------

428 96204 626 1.11 3216 RSSURFACEPRO2\... powershell

When multiple views are available for an object type, PowerShell displays the first one in memory, which is why the –AppendPath and –PrependPath parameters of Update-FormatData are so important. But all the formatting cmdlets have a –View parameter that accepts the name of a specific view. Provided the view type matches the formatting cmdlet you used and the type of object being displayed, this parameter will force the shell to use the named view instead of the first one in memory.

For example, Microsoft provides multiple views for the System.Diagnostics .Process objects produced by Get-Process. You’d never know it unless you explored the DotNetTypes.format.ps1xml file, as shown earlier, but it’s true! The second one, which you’d normally never see because it isn’t the first one, is named Priority. Take a look at the output of the normal view:

PS C:\> Get-Process

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName

------- ------ ----- ----- ----- ------ -- -----------

39 6 2012 4348 57 7.69 928 conhost

30 4 844 1400 41 0.00 2176 conhost

33 5 968 3164 46 0.02 2964 conhost

558 11 2072 2796 43 0.44 328 csrss

221 14 10020 8428 54 16.07 368 csrss

307 30 17804 17012 351 28.97 1292 dfsrs

Now compare the output of the normal view with the output displayed by this alternate, named view:

PS C:\> Get-Process | Format-table -view priority

PriorityClass: Normal

ProcessName Id HandleCount WorkingSet

----------- -- ----------- ----------

conhost 928 39 4452352

conhost 2176 30 1433600

conhost 2964 33 3239936

csrss 328 562 2863104

csrss 368 226 8630272

dfsrs 1292 307 17420288

dfssvc 1464 122 4354048

dllhost 1096 197 6701056

dns 1328 5163 58880000

PriorityClass: High

ProcessName Id HandleCount WorkingSet

----------- -- ----------- ----------

vmtoolsd 1540 259 12238848

PriorityClass: Normal

ProcessName Id HandleCount WorkingSet

----------- -- ----------- ----------

VMwareTray 1784 64 5619712

PriorityClass: High

ProcessName Id HandleCount WorkingSet

----------- -- ----------- ----------

wininit 376 77 2289664

winlogon 412 98 2527232

We’ll leave exploring the StartTime view to you. Because PowerShell doesn’t provide a way of discovering these alternate, named views, they’re of limited use. But the functionality is there if you choose to use it. Obviously, if you choose to create multiple named views yourself, then you’ll at least be aware they exist and can use them!

26.7. Going further

In this chapter, we’ve focused on views that display specific object properties. It’s also possible to create view definitions that execute code, such as a table column that provides specific formatting for a numeric value. We find that administrators don’t need to do that all too often; that capability is primarily useful when you’re displaying an object created by someone else’s code, such as a .NET Framework class. In those cases, you can’t modify the object itself; you can modify only what’s displayed, and so things like script properties become useful. But with a custom object like the one you created for this chapter’s examples, you can put whatever you want into the properties, so it’s usually better to initially populate your object properties with exactly what you want and then let the view definition control things like column headers and widths.

Note that you can also define dynamic properties using a type extension file, which we cover in a chapter 27. Our preference is to let views worry solely about visual formatting and to keep any code—like script properties—contained within type files. With that in mind, we cover script properties in chapter 27.

26.8. Summary

Custom views are a powerful technique for creating great-looking output by default. Because you have to load the view files manually in each new shell session, their use might seem limited to you. But once we start talking more about advanced functions in chapter 32, you’ll see how useful these can be together.