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!
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. :)
ReplyDeleteWe will be mentioning your article on the next show.
-hal
Co-Host, PowerScripting Podcast (powerscripting.net)
You rock -- the PowerShell team should have implemented attribute-based generation from the start...
ReplyDelete