A tool for table formatting   3 comments

As I was working on the SMLets CodePlex project, I created a bunch of new cmdlets and I wanted to make sure that the output looked good and was useful. In order to do that I needed to sling a bunch of XML and create my own formatting file. If you’ve ever looked at the format files in PowerShell (to see what’s shipped, type get-childitem “$pshome/*.format.ps1xml”) it can be pretty daunting, so most folks (me included) start with something that works an then modify it. After I did that a few times, I reckoned that I had better build a tool to make life easier. My requirements were pretty straight-forward; build a tool that emitted the XML that I needed to put in the formatting file. Second, commit no unnatural acts with regard to parameters to create the XML. What I really wanted, is to use Format-Table to get the output exactly how I wanted and then just substitute my tool for Format-Table. Thus, New-TableFormat is born.

The following is how I use it, for this example, I’m just using a process object in this example, but the concept applies to any object. First I get the output looking exactly like I want, so in this case, I show the processID, the handles, the name and then the handles in KB. In order to do that last bit, I need to use a script block. After that, it’s simply replace format-table with new-tableformat and hey! presto! I’ve got my XML!

PS> get-process lsass|format-table id,handles,name,@{L="HandlesKB";E={$_.Handles/1KB};A="Right";F="{0:N2}"} -au

 Id Handles Name  HandlesKB
 -- ------- ----  ---------
552    1274 lsass      1.24

PS> get-process lsass|new-tableformat id,handles,name,@{L="HandlesKB";E={$_.Handles/1KB};A="Right";F="{0:N2}"} -au
   <View>
    <Name>ProcessTable</Name>
    <ViewSelectedBy>
     <TypeName>System.Diagnostics.Process</TypeName>
    </ViewSelectedBy>
    <TableControl>
     <AutoSize />
     <TableHeaders>
      <TableColumnHeader><Label>id</Label></TableColumnHeader>
      <TableColumnHeader><Label>handles</Label></TableColumnHeader>
      <TableColumnHeader><Label>name</Label></TableColumnHeader>
      <TableColumnHeader>
        <Label>HandlesKB</Label>
        <Alignment>Right</Alignment>
      </TableColumnHeader>
     </TableHeaders>
     <TableRowEntries>
      <TableRowEntry>
       <TableColumnItems>
        <TableColumnItem><PropertyName>id</PropertyName></TableColumnItem>
        <TableColumnItem><PropertyName>handles</PropertyName></TableColumnItem>
        <TableColumnItem><PropertyName>name</PropertyName></TableColumnItem>
        <TableColumnItem><ScriptBlock>$_.Handles/1KB</ScriptBlock><FormatString>{0:N2}</FormatString></TableColumnItem>
       </TableColumnItems>
      </TableRowEntry>
     </TableRowEntries>
    </TableControl>
   </View>

In order for me to take advantage of this, I need to save it to a file and then call update-formatdata on the new file. You might notice that this is not quite complete as it doesn’t have the required elements for the beginning and end of the file, so I’ve got another parameter –complete which emits complete, standalone XML which I can dump into a file and then import. Once imported I can just use format-table as usual (in this case since there’s already a table format for process objects, I need to include the view name, which is “ProcessTable”, in this case. If you’re building formatting for an object of your own creation or doesn’t already exist, this last step is not necessary. Just output the object and the formatter takes care of everything automatically.

PS> get-process lsass|new-tableformat id,handles,name,@{L="HandlesKB";E={$_.Handles/1KB};A="Right";F="{0:N2}"} -au -comp
 > handleKB.format.ps1xml
PS> update-formatdata handleKB.format.ps1xml
PS> get-process lsass | format-table -view ProcessTable

id  handles name  HandlesKB
--  ------- ----  ---------
552 1263    lsass      1.23

Here’s the script, it’s only 125 lines or so!

param (
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]$object,
    [Parameter(Mandatory=$true,Position=0)][object[]]$property,
    [Parameter()][switch]$Complete,
    [Parameter()][switch]$auto,
    [Parameter()][string]$name,
    [Parameter()][switch]$force
    )
End
{
if ( $object )
{
    if ( $object -is "PSObject")
    {
        $TN = $object.psobject.typenames[0]
    }
    else
    {
        $TN = $object.gettype().fullname
    }
}
elseif ( $name )
{
    $TN = $name
}
$NAME = $TN.split(".")[-1]
$sb = new-object System.Text.StringBuilder
if ( $complete )
{
    [void]$sb.Append("<Configuration>`n")
    [void]$sb.Append(" <ViewDefinitions>`n")
}
[void]$sb.Append(" <View>`n")
[void]$sb.Append(" <Name>${Name}Table</Name>`n")
[void]$sb.Append(" <ViewSelectedBy>`n")
[void]$sb.Append(" <TypeName>${TN}</TypeName>`n")
[void]$sb.Append(" </ViewSelectedBy>`n")
[void]$sb.Append(" <TableControl>`n")
if ( $auto )
{
    [void]$sb.Append(" <AutoSize />`n")
}
[void]$sb.Append(" <TableHeaders>`n")
# 
# Now loop through the properties, creating a header for each 
# provided property
#
foreach($p in $property)
{
    if ( $p -is "string" )
    {
        [void]$sb.Append(" <TableColumnHeader><Label>${p}</Label></TableColumnHeader>`n")
    }
    elseif ( $p -is "hashtable" )
    {
        $Label = $p.keys | ?{$_ -match "^L|^N" }
        if ( ! $Label )
        {
            throw "need Name or Label Key"
        }
        [void]$sb.Append(" <TableColumnHeader>`n")
        [void]$sb.Append(" <Label>" + $p.$label + "</Label>`n")
        $Width = $p.Keys |?{$_ -match "^W"}|select -first 1
        if ( $Width )
        {
            [void]$sb.Append(" <Width>" + $p.$Width + "</Width>`n")
        }
        $Align = $p.Keys |?{$_ -match "^A"}|select -first 1
        if ( $Align )
        {
            [void]$sb.Append(" <Alignment>" + $p.$align + "</Alignment>`n")
        }
        [void]$sb.Append(" </TableColumnHeader>`n")
        # write-host -for red ("skipping " + $p.Name + " for now")
    }
}
[void]$sb.Append(" </TableHeaders>`n")
[void]$sb.Append(" <TableRowEntries>`n")
[void]$sb.Append(" <TableRowEntry>`n")
[void]$sb.Append(" <TableColumnItems>`n")
foreach($p in $property)
{
    if ( $p -is "string" )
    {
        [void]$sb.Append(" <TableColumnItem><PropertyName>${p}</PropertyName></TableColumnItem>`n")
    }
    elseif ( $p -is "hashtable" )
    {
        [void]$sb.Append(" <TableColumnItem>")
        $Name = $p.Keys | ?{ $_ -match "^N" }|select -first 1
        if ( $Name )
        {
            $v = $p.$Name
            [void]$sb.Append("<PropertyName>$v</PropertyName>")
        }
        $Expression = $p.Keys | ?{ $_ -match "^E" }|select -first 1
        if ( $Expression )
        {
            $v = $p.$Expression
            [void]$sb.Append("<ScriptBlock>$v</ScriptBlock>")
        }
        $Format = $p.Keys | ?{$_ -match "^F" }|select -first 1
        if ( $Format )
        {
            $v = $p.$Format
            [void]$sb.Append("<FormatString>$v</FormatString>")
        }
        [void]$Sb.Append("</TableColumnItem>`n")
    }
}
[void]$sb.Append(" </TableColumnItems>`n")
[void]$sb.Append(" </TableRowEntry>`n")
[void]$sb.Append(" </TableRowEntries>`n")
[void]$sb.Append(" </TableControl>`n")
[void]$sb.Append(" </View>`n")
if ( $complete )
{
    [void]$sb.Append(" </ViewDefinitions>`n")
    [void]$sb.Append("</Configuration>`n")
}
$sb.ToString()
}

the thing to note here is that I’m just building the XML string and then emitting it, I’m not using all the XML programming goo, which would probably be better practice, but I didn’t want to be bothered (clearly, that could be a future enhancement). You’ll notice that it handles pretty much everything that format-table does.

I hope this is as useful for you as it is for me

About these ads

Posted April 19, 2011 by jtruher3 in PowerShell

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

Follow

Get every new post delivered to your Inbox.

Join 116 other followers

%d bloggers like this: