Wednesday, February 29, 2012

PowerShell, the ETS and Type XML

One of (the many!) cool features of PowerShell is the ETS, the Extensible Type System. With the ETS, you are able to create new types and, perhaps more importantly, extend existing types simply and easily.  The ETS is one of the key ways that PowerShell provides a level of consistency in the objects you use, despite that consistency being lacking in the underlying object(s).

Take, for example the System.Array type. This type, in .NET, has a Length property, but does not have a Count property. Why not, I hear you ask. The answer is simple: the folks that developed this type did not include it. Sadly, the .NET Framework has lots of little (potential) inconsistencies. In a perfect world, we can fix this – and with PowerShell we can!

Using a bit of XML, you can easily add an alias called Count to the Name property. This XML looks like this:

<Types>
    <Type>
        <Name>System.Array</Name>
        <Members>
            <AliasProperty>
                <Name>Count</Name>
                <ReferencedMemberName>Length</ReferencedMemberName>
            </AliasProperty>
        </Members>
    </Type>
</Types>

To add this alias, you just save this XML (say to My.Types.PS1XML) then import this definition into PowerShell by using the Update-Type cmdlet. Now as it happens you don’t need to do this for System.Array, as Microsoft has already provided this extension (and a bunch more) in $PSHome/Types.PS1XML, which is loaded by default. But I think you get the idea.

If you want to see the ETS at work, take some time to study this Types.PS1XML file – it shows  how PowerShell extends .NET specifically to provide a level of consistency across objects that is missing from the .NET Framework itself. Some cool things this file does include:

  • Provides .ToString() methods for a number of types (these definitions override the default .ToString() method in the type itself.
  • Add new properties implemented as PowerShell script blocks. For example, System.Management.Automation.PSDrive is extended with a script property called Used (but for PSdrive objects that are based on the FileSystem provider only!). 
  • Define default Property Sets for some objects. These then become the properties that PowerShell display by default. For example, the default property set for System.ServiceProcess.ServiceController is defined as; Status, Name and DisplayName. If you run the Get-Service cmdlet, you will see these three properties are displayed by default.

The ETS enables you to adapt ANY .NET type – adding in properties that don’t exist, or overriding the ones that do exist. These new properties can be a new alias (as noted above), a script property, or a script method. One particular place I’ve found recently where this is useful is on the output of Get-ADComputer. If you run Get-ADComputer (Part of the active directory module shipped by Microsoft with Server 2008 R2), the output is objects of the type Microsoft.ActiveDirectory.Management.ADComputer. This type has a Name property for the computer name (which is the host name) as well as a DNSHostName. This is all fine and well, but many of the cmdlets that take a computer name (and send the command to that computer for processing) use the ComputerName property. So how do you use Get-ADComputer to get computer names and then pipe the output to other cmdlets when the property names differ?  For example, suppose you want to find  One way is like this:

Get-ADComputer –filter * | foreach {Get-Service –name ‘DNS Server’ –computername $_.name} …

That’s a bit messy – as we wanted the computer name to be returned from Get-ADComputer and used in the call to Get-Service. By default, this is not possible. However, if we do some magic type adaptation, we could add an alias property to the ServiceController object, like this:

<Types>
<Type>
    <Name>Microsoft.ActiveDirectory.Management.ADComputer</Name>
    <Members>
          <AliasProperty>
                <Name>ComputerName</Name>
                <ReferencedMemberName>Name</ReferencedMemberName>
            </AliasProperty>           
    </Members>
</Type>
</Types>

Once you import this definition (use Update-TypeData specifying the filename including this definition), then, we can do this:

Get-AdComputer -Filter * |
   
Get-Service -Name "DNS Server" -ErrorAction SilentlyContinue |
        Format-Table MachineName, Displayname, Status –Autosize

On my system, this produces the following output:

MachineName DisplayName  Status
----------- -----------  ------
COOKHAM1    DNS Server  Running
TALLGUY2    DNS Server  Running

As you can see, Type XML is the key to exploiting the ETS. With Type XML and the ETS, POwershell and you can find consistency in places where it’s never existed. What a cool feature!

[Later]

Thanks for the comment from JB. If, like JB,  you were wondering why I a using the MachineName property in the call to Format Table. The ComputernName property is initially created on the objects produced by Get-ADComputer. The Get-Service cmdlet takes the objets from the pipeline and gets the ComputerName property the sends the service request to the computer named by computer name. However, Get-Service produces objects that return the ComputerName in a Machinename property (even though the CMDLET parameter is Computername)!  Rather demonstrating my point about consistent inconsistency! I could add another alias property to the ServiceController type, which is not a bad idea!

 

Technorati Tags: ,,

1 comment:

JB said...

Hey I think that you have a mistake here. In the XML example you call the new property ComputerName referencing Name. Then you don't use ComputerName, but rather MachineName, in the usage example.