Prof. Powershell

Perfect PowerShell Properties Part 3: Custom Formatting

Here's how to jump through the complicated hoops to update format data in PowerShell.

I hope you've been following along the last few weeks as we've been looking at ways to extend PowerShell's type system. If not, take a few minutes to get caught up (part 1 and part 2). This week we'll look at adding a custom formatting option.

Although PowerShell makes it easy to update type data, updating format data is not as easy. First, let's look at the existing format definitions for file objects. We can see existing format data with the Get-FormatData cmdlet.

PS C:\> get-formatdata -TypeName system.io.fileinfo | select -ExpandProperty FormatViewDefinition

Name                                              Control
----                                              -------
children                                          TableControl
children                                          ListControl
children                                          WideControl

You can interpret this to mean that there are existing definitions whether you use Format-Table, Format-List or Format-Wide. What I want to do is add a custom view. Given the number of properties I want to display and for the sake of simplicity I'm going to create a List view. This is going to require me creating a ps1xml file. You could copy this XML to a file.

$fmtxml = @'
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>MyFileData</Name>
<ViewSelectedBy>
<TypeName>System.IO.FileInfo</TypeName>
</ViewSelectedBy>
<ListControl>
<ListEntries>
<ListEntry>
<ListItems>
<ListItem>
<PropertyName>FullName</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Directory</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Mode</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Name</PropertyName>
</ListItem>
<ListItem>
<PropertyName>Extension</PropertyName>
</ListItem>
<ListItem>
<Label>Size</Label>
<PropertyName>Length</PropertyName>
</ListItem>
<ListItem>
<Label>Modified</Label>
<PropertyName>LastWriteTime</PropertyName>
</ListItem>
<ListItem>
<Label>ModifiedAge</Label>
<ScriptBlock>(Get-Date) - $_.lastWriteTime</ScriptBlock>
</ListItem>
<ListItem>
<Label>Created</Label>
<PropertyName>CreationTime</PropertyName>
</ListItem>
<ListItem>
<Label>CreationAge</Label>
<ScriptBlock>(Get-Date) - $_.CreationTime</ScriptBlock>
</ListItem>
</ListItems>
</ListEntry>
</ListEntries>
</ListControl>
</View>
</ViewDefinitions>
</Configuration>
'@

Or do as I did last time and create the XML file using PowerShell. You only have to create the file once, unless you decide to make changes.

$file = Join-Path -path $home\documents\windowspowershell -child MyFileFormat.ps1xml
$fmtxml | Out-File -FilePath $file

One major item I want to point out in the XML is that because I am defining some properties using script blocks, I need to reference $_. In order for PowerShell to not think I'm trying to use a variable in the here string, I am using single quotes, not double. The XML will use some existing properties, like Name. A few properties I am simply renaming.

<ListItem>
<Label>Size</Label>
<PropertyName>Length</PropertyName>
</ListItem>

Although, if I have my custom type definition that I showed you in an earlier lesson, I could have reference that. I also am creating some calculated properties.

<Label>CreationAge</Label>
<ScriptBlock>(Get-Date) - $_.CreationTime</ScriptBlock>
</ListItem>
All that remains is to tell PowerShell to use these new formatting definitions.
Update-FormatData -AppendPath $file
Re-checking format definitions shows my new view.
PS C:\> Get-FormatData -TypeName system.io.fileinfo | Select -ExpandProperty FormatViewDefinition

Name                                                              Control     
----                                                              -------     
children                                                          TableControl
children                                                          ListControl 
children                                                          WideControl 
MyFileData                                                        ListControl

The view won't be used until I specify it along with the appropriate format cmdlet.

PS C:\> dir c:\work -file | format-list -View MyFileData

Here is a fragment from the result:

[Click on image for larger view.]  Figure 1.

Figure 1
The downside to only having this as a formatting option, is that I can't do anything else with this other than send it to a text file or printer. So while I wanted you to know how this works, personally I would prefer to do everything as a type extension. Then I can format as I want, export, convert or whatever else needs to be done.

So to complete the story, these are the commands I would put into my PowerShell profile.

Update-TypeData -MemberType ScriptProperty -MemberName SizeKB -Value {$this.Length/1KB} -TypeName system.io.fileinfo
Update-TypeData -MemberType ScriptProperty -MemberName SizeMB -Value {$this.Length/1MB} -TypeName system.io.fileinfo
Update-TypeData -MemberType AliasProperty -MemberName Size -Value Length -TypeName system.io.fileinfo
Update-TypeData -MemberType AliasProperty -MemberName Created -Value CreationTime -TypeName system.io.fileinfo
Update-TypeData -MemberType AliasProperty -MemberName Modified -Value LastWriteTime -TypeName system.io.fileinfo
Update-TypeData -MemberType ScriptProperty -MemberName ModifiedAge -Value {(Get-Date) - $this.LastWriteTime} -TypeName system.io.fileinfo
Update-TypeData -MemberType ScriptProperty -MemberName CreationAge -Value {(Get-Date) - $this.CreationTime} -TypeName system.io.fileinfo

And I would create property sets.

$xml=@"
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>System.IO.FileInfo</Name>
<Members>
<PropertySet>
<Name>MyFileData</Name>
<ReferencedProperties>
<Name>Name</Name>
<Name>Size</Name>
<Name>ModifiedAge</Name>
<Name>CreationAge</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>MyFileDataKB</Name>
<ReferencedProperties>
<Name>Name</Name>
<Name>SizeKB</Name>
<Name>ModifiedAge</Name>
<Name>CreationAge</Name>
</ReferencedProperties>
</PropertySet>
<PropertySet>
<Name>MyFileDataMB</Name>
<ReferencedProperties>
<Name>Name</Name>
<Name>SizeMB</Name>
<Name>ModifiedAge</Name>
<Name>CreationAge</Name>
</ReferencedProperties>
</PropertySet>
</Members>
</Type>
</Types>
"@

$file = Join-Path -path $home\documents\windowspowershell -child MyFileType.ps1xml
$xml | Out-File -FilePath $file

Update-TypeData -AppendPath $file

Now I can use these properties any way I like.

[Click on image for larger view.]  Figure 2.

You can download a text file of my extensions here.

About the Author

Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.

comments powered by Disqus
Most   Popular