Monday, August 25, 2008

Automatically generating PowerShell *.Types.ps1xml and *.Format.ps1xml

I helped a friend of mine a lot with this, so I thought I should share this. He currently works at a customer's site to create a PowerShell snap-in for administrating the customer its home-grown LOB. It's quite an application, used by 5000+ users and supported by about 20 administrators.

So far, the administrators are freaking out because administration this LOB is a snap with the cmdlets he has written. As more and more administrators use the PowerShell to administrate the LOB, more features requests are coming in and a lot of them are about how the objects are displayed inside PowerShell (see http://msdn.microsoft.com/en-us/library/cc136149(VS.85).aspx for more details).

The PowerShell needs two files to format the returned objects proberly: [NAME]. Types.ps1xml (general information) and [NAME].Format.ps1xml (table based output). These are very simple structured XML files but keeping these files manually in sync with your development and the feature requests can be a pain.

Because it was my idea that my friend should design a PowerShell snap-in instead of the originally planned COM interfaces, I searched for a solution. Since I found none, I wrote it myself (surprise!).

The good: The solution works as expected and my friend is happy. The bad: The code has no unit testing, works by using string constants and string.Format() and has some limitations (e.g. only a single Alias per property while the PS could support 100+ alias for the same property). However, you can use the code a starting point for your own solution.

My solution is based around the custom attributes [xPSObjectAttribute], [xPSPropertyAttribute] and [xPSSortPropertyAttribute]. During development, you simply decorate an object that is returned by a cmdlets with [xPSObject]. Oh, BTW: the xPS simple stands for "PowerShell Extension".

Within the object you then decorate a property with the [xPSProperty] attribute and define several properties on it. For example:

[xPSProperty(Default = true, Alias = "FullName")]

public string Name

When you later on run the PSTypeXMLFormatXMLGenerator:

xPSXMLOutput output = PSTypeXMLFormatXMLGenerator.GetPS1XML(typeof(TestClass));

You will get a type XML (some parts have been omitted):

<Type>

<Name>TestApp1.TestClass</Name>

<Members>

<MemberSet>

<Name>PsStandardMembers</Name>

<Members>

<NoteProperty>

<Name>DefaultDisplayProperty</Name>

<Value>FullName</Value>

</NoteProperty>

<PropertySet>

<Name>DefaultDisplayPropertySet</Name>

<ReferencedProperties>

<Name>FullName</Name>

...

<AliasProperty>

<Name>FullName</Name>

<ReferencedMemberName>Name</ReferencedMemberName>

</AliasProperty>

You can also change in which sequence the properties should be listed by PowerShell:

[xPSProperty(Default = true, Sequence = 10, Alias = "FullName")]

public string Name

[xPSProperty(Sequence = 20)]

public int Size

The result XML:

...
<Name>DefaultDisplayPropertySet</Name>

<ReferencedProperties>

<Name>FullName</Name>

<Name>Size</Name>
...

To provide on which properties the PowerShell should sort a list of this object, use the [xPSSortProperty] attribute (can be applied to several properties):

[xPSSortProperty(SortID = 1)]

public string Name

[xPSSortProperty(SortID = 20)]

public int Size

XML:

<Name>DefaultKeyPropertySet</Name>

<ReferencedProperties>

<Name>Name</Name>

<Name>Size</Name>

</ReferencedProperties>

For the table based output, PSTypeXMLFormatXMLGenerator does also generate a format XML.

[xPSProperty(Default = true, Sequence = 1, Alias = "FullName", ColumnWidth = 30)]

public string Name

[xPSProperty(Sequence = 2, ColumnName = "Size (K)", ColumnRightAligned = true, ColumnScript = "[int]($_.Size / 1024)")]

public int Size

Resulting XML:

<View>

<Name>TestClass</Name>

<ViewSelectedBy>

<TypeName>TestApp1.TestClass</TypeName>

</ViewSelectedBy>

<TableControl>

<TableHeaders>

<TableColumnHeader>

<Label>FullName</Label>

<Width>30</Width>

</TableColumnHeader>

<TableColumnHeader>

<Label>Size (K)</Label>

<Alignment>Right</Alignment>

</TableColumnHeader>

...

<TableRowEntry>

<TableColumnItem>

<PropertyName>Name</PropertyName>

</TableColumnItem>

<TableColumnItem>

<ScriptBlock>[int]($_.Size / 1024)</ScriptBlock>

</TableColumnItem>

...

I guess you are getting the basic idea how this class works. For your reference, here's a screen shot what the [xPSProperty] Attribute supports:




Once again, this class is not fully tested and should be seen as a basis for your own solution. If you use it, you would make me happy to leave a comment or provide a back link. Download the source and a sample application here.

Enjoy!

2 comments:

  1. Awesome stuff! I can't wait for a chance to sit down and look at this in detail. I'd like to apply it in posh code itself as I stand on the other side of the developer divide. :)
    We will be mentioning your article on the next show.

    -hal
    Co-Host, PowerScripting Podcast (powerscripting.net)

    ReplyDelete
  2. You rock -- the PowerShell team should have implemented attribute-based generation from the start...

    ReplyDelete