How does the TFS snap-in handle formatting?

Powershell formatting is pretty flexible, as long as you know how it works.  So how do the version control cmdlets work?  Open up %programfiles(x86)%\Microsoft Team Foundation Server 2008 Power Tools\PowerShell\Microsoft.TeamFoundation.PowerTools.PowerShell.format.ps1xml and have a look for yourself.  Let’s consider the ubiquitous Item class for now:

<View> <Name>Item</Name> <ViewSelectedBy> <TypeName>Microsoft.TeamFoundation.VersionControl.Client.Item</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Width>7</Width> <Alignment>Right</Alignment> </TableColumnHeader> <TableColumnHeader> <Label>CheckinDate</Label> <Width>10</Width> <Alignment>Right</Alignment> </TableColumnHeader> <TableColumnHeader> <Label>ContentLength</Label> <Width>7</Width> <Alignment>Right</Alignment> </TableColumnHeader> <TableColumnHeader> <Label>ServerItem</Label> <Alignment>Left</Alignment> </TableColumnHeader> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>ChangesetId</PropertyName> </TableColumnItem> <TableColumnItem> <ScriptBlock>$_.CheckinDate.ToString('d')</ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock> if ($_.ItemType -eq [Microsoft.TeamFoundation.VersionControl.Client.ItemType]::Folder) { '&lt;DIR&gt;' } else { ConvertTo-FixedByte $_.ContentLength 7 } </ScriptBlock> </TableColumnItem> <TableColumnItem> <ScriptBlock> ConvertTo-FixedPath $_.ServerItem 7,10,6 </ScriptBlock> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View>

And a few examples of what it looks like, taken from an 80- and a 112-character-wide console:

PS> tfdir Changes CheckinDat Content ServerItem etId e Length ------- ---------- ------- ---------- 8224 2/25/2009 <DIR> $/Test-ConchangoV2/super/u...g/directorynamefromhell 8224 2/25/2009 11 KB $/Test-ConchangoV2/super/u...efromhell/devilsurf.jpg 8224 2/25/2009 12 B $/Test-ConchangoV2/super/u...orynamefromhell/foo.txt 8224 2/25/2009 5 MB $/Test-ConchangoV2/super/u.../PowerGUI.1.6.1.639.msi PS> tfdir Changes CheckinDat Content ServerItem etId e Length ------- ---------- ------- ---------- 8224 2/25/2009 <DIR> $/Test-ConchangoV2/super/ultra/very/very/mega/long/directorynamefromhell 8224 2/25/2009 11 KB $/Test-ConchangoV2/super/ultra/very/very/m...ong/directorynamefromhell/devilsurf.jpg 8224 2/25/2009 12 B $/Test-ConchangoV2/super/ultra/very/very/mega/long/directorynamefromhell/foo.txt 8224 2/25/2009 5 MB $/Test-ConchangoV2/super/ultra/very/very/m...torynamefromhell/PowerGUI.1.6.1.639.msi

You can see that the Powershell team’s “expand toward the right to minimize visual variance” concept is used to great effect.  However, we violated their “never provide a label” maxim in 3 out of 4 columns.  Oops!  Seriously though, I don’t think anyone would want to use a ScriptProperty with the exact parameters found in the formatter XML.  What we provided instead is far more useful: a couple of fully generic cmdlets named ConvertTo-FixedByte and ConvertTo-FixedPath.  I’ll cover the former in another post; there’s a fair amount of detail to dig into, but its use here is rather mundane. 

ConvertTo-FixedPath is what really makes this example shine.  If you’re like me, you hate managing long paths in shell environments.  Wrapping onto multiple lines, truncating the front, and (especially) truncating the end of the path are all unacceptable.  Cmdlets to the rescue!  This one has two modes (“parameter sets” in PS-speak).  The first is obvious enough:

PS> convertto-fixedpath 12345678901234567890 -width 16

12345678...67890

The 20-character input string is truncated to 16, but only by omitting things in the middle.  (Note that this mode has an off-by-one bug.  Hint: odd numbers.)  This allows you to answer the important questions: what is the file name? what branch/workspace are they located in?  Usually we don’t care as much about the intermediate directories.  Maybe the same is true for other kinds of strings too – let me know if you find it useful!

The other mode is designed explicitly for use in a ps1xml formatter.  Consider:

PS> convertto-fixedpath 12345678901234567890 -otherwidths 30, 20, 10

12345678...67890

Same output, but what the heck are the “other widths”?  Imagine this string were going into a variable-length field.  We want the string to expand dynamically, showing us as much of the filename as possible.  Yet if it does need to be cropped, we want it done intelligently, as if we’d known the width in advance and passed it as a –width parameter.

As it turns out, we can know the final width, given a little hackery.  The Powershell API lets you access the raw console host details, including its BufferSize.  All you need to do is subtract the widths of the other columns, plus room for the separator character PS places between each column.  So imagine the code above is used in a four-column formatter: 80 - 30 - 20 - 10 - (4*1) = 16.  Sure enough, if you scroll back up to the XML, you’ll see the values passed to –otherwidths are no coincidence.  We hand-tweaked every column width so it fit the data type as closely as possible without wrapping*, then plugged in those widths to the convertto-fixedpath cmdlet driving the final variable-length column.

This hack may not always work as Powershell migrates away from the ancient CSRSS hosting environment.  But in theory, anyone who implements PSHostRawUserInterface should be compatible.  It works in the Powershell v2 ISE environment that ships with Win7, for instance.  Custom-PS-tool-lovers, let me know how it goes.

*Except for column titles, obviously.  They were a necessary casualty of two higher-priority design considerations (a) keeping labels identical to the underlying property name (b) supporting the default 80-character console width without squishing average-sized paths.

Leave a Reply