Hate Add-Member? (PowerShell's Adaptive Type System to the Rescue)

Hate Add-Member? (PowerShell's Adaptive Type System to the Rescue)

  • Comments 7

Do you hate Add-Member as much as I do?

Wait - maybe you aren't familiar with Add-Member or the glory of PowerShell's Adaptive Type System.  (ATS).  When I looked at the .NET type system, my reaction was "....almost".  I'm not trying to throw a rock at .NET - anyone that knows me knows how much I love .NET but the reality is that it's type system "almost" meets the needs of a management system ... but doesn't.  That is why we invented the Adaptive Type System. 

There are 4 main components of ATS:

Type Adapters. 

A lot of technologies implement their own type systems within .NET.  Most of these technologies are critical to Management scenarios (WMI, ADSI, XML, ADO, etc)  so we needed to do something about this.  What I mean by "implement their own type system" is that they have a small number of .NET types which then map to zillions of their own types.  There are a zillion WMI objects but they are all represented by a 2 or 3 .NET classes.  Type adapters allow us to adapt those technologies so they look like what users what out of a type - namely it's properties and methods.  A normal WMIObject would look like this:

Scope            : System.Management.ManagementScope
Path             :
\\JPSLAP14\root\cimv2:Win32_Service.Name="AeLookupSvc"
Options          : System.Management.ObjectGetOptions
ClassPath        :
\\JPSLAP14\root\cimv2:Win32_Service
Properties       : {AcceptPause, AcceptStop, Caption, CheckPoint...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers       : {dynamic, Locale, provider, UUID}
Site             :
Container        :

We adapt that object so it looks like this:

ExitCode  : 0
Name      : AeLookupSvc
ProcessId : 1068
StartMode : Auto
State     : Running
Status    : OK

Extended Metadata System

.NET gives you a meat-and-potatoes type system giving you properties, fields, methods, interfaces and events.  That is awesome but in management scenarios, we need more.  PowerShell extends the typesystem with PropertySets, Aliases, MemberSets and then provides richer set of Properties and methods (e.g. Script Properties, CodeProperties, ParameterizedProperties, etc).

Type MashUps

One of the most powerful and least used aspects of PowerShell is it's type mashup system. What this does is allow anyone to extend the type system with their own properties, methods, metadata, etc.  You can extend the types or the instances of the types.  This is CRAZY useful when you have one command which outputs objects with a property called SERVER and you want to pipeline it to a command which accepts any object in the pipeline that has a property called COMPUTERNAME.  All you have to do is to modify that definition of that type to ALIAS the property COMPUTERNAME to SERVER and now the pipelining happens with no additional code. This is freakishly powerful stuff.

Dynamic Types

In addition to extending a TYPE, you can dynamically extend any instance of a type.  Take ANY object.  You can add ANY additional property, alias, method, etc to THAT instance of the object and it won't affect any other instance.  I find that I want to do this all the time.  The mechanism for doing this is Add-Member.  Here is what Add-Member looks like:

Adds a user-defined custom member to an instance of a Windows PowerShell object.
Add-Member
        (0)-MemberType | -Type <AliasProperty | All | CodeMethod | CodeProperty | Event | MemberSet | Method |
                                             Methods | NoteProperty | Parameterty | Properties | Property | PropertySet | 
                                             ScriptMethod | ScriptProperty>
        (1)-Name <String>
           -InputObject <PSObject> (ByValue)
        (2)[-Value <Object>]
        (3)[-SecondValue <Object>]
           [-Force ]
           [-PassThru ]

Here is an example of it in use:

PS>$l = gps lsass
PS>Add-Member -InputObject $l -MemberType NoteProperty -Name test -value "PowerShell ROCKS!"
PS>$l.test
PowerShell ROCKS!
PS>$l |fl t*


test               : PowerShell ROCKS!
Threads            : {684, 688, 692, 700...}
TotalProcessorTime : 00:01:48.9354983

SOOOO - why do I hate Add-Member?

I love that Add-Member lets me do a zillion things but in reality, 95% of the time, I just want to add a bunch of NOTEPROPERTIES to an object and Add-Member feels way to heavy to accomplish that.  I've complained about this a number of times and tried to shame Bruce into fixing it but "to ship is to choose" and it has not popped up as the next best use of available calories.  After trying and failing once again, I realized that I was being stupid - that ATS allowed me to control my own destiny. 

What I decided to do was to add a new script method PSAddMember() to EVERY object in the system by attaching it to System.Object.  This method would have 3 signatures. It would take a name/value pair, it would take a name/value/membertype tuple and it would take a hashtable which is a set of name/value pairs.  Here is the file that does that and a demonstration of it in action:

<?xml version="1.0" encoding="utf-8" ?>
<Types>
    <Type>
        <Name>System.Object</Name>
        <Members>
            <ScriptMethod>
                <Name>PSAddMember</Name>
                <Script>
switch ($args.count)
{
1   {
    $hash = $args[0] -as [HashTable]
    foreach ($key in $hash.keys)
    {
        Add-Member -InputObject $this -Name $key -value $hash.$key -MemberType Noteproperty -Force
    }
    }

2   {
    $name,$value = $args
    Add-Member -InputObject $this -Name $name -value $value -MemberType Noteproperty -Force
    }

3   {
    $name,$value,$MemberType = $args
    Add-Member -InputObject $this -Name $name -value $value -MemberType $MemberType -Force
    }
}
                </Script>
            </ScriptMethod>
        </Members>
    </Type>
</Types>

----------------------------------------------------------------

PS> $p = gps lsass
PS> $p.PSAddMember("q1","Value1")
PS> $p.PSAddMember("q3",{"*"* $this.Threads.Count},"ScriptProperty")
PS> $p.PSAddMember("q2",{"*"* $this.Threads.Count},"ScriptProperty")
PS> $hash = @{q3=4; q4="Q4"; q5=(gsv alg)}
PS> $p.PSAddMember($hash)
PS> $p

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
   1721      19    12688      19756    70   110.45    652 lsass

PS> $p.q1
Value1
PS> $p.q2
*****************************
PS> $p |fl q*


q1 : Value1
q2 : *****************************
q4 : Q4
q5 : System.ServiceProcess.ServiceController
q3 : 4

 

PS> $p

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
   1721      19    12688      19756    70   110.45    652 lsass

Lessons learned/relearned:

  1. PowerShell's Adaptive Type System (ATS) is awesome.
  2. With ATS you are in control of your own destiny.  (If you don't like the world - change it yourself. [then share to help others])

Enjoy!

Jeffrey Snover [MSFT]
Windows Management Partner Architect
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx


Leave a Comment
  • Please add 1 and 1 and type the answer here:
  • Post
  • Interesting article, I would never have thought of modifying System.Object itself!

    But any script that used this would no longer be portable across machines, correct?  You couldn't just copy the script, you'd have to copy/edit the appropriate XML file too.

    Will you add the PSAddMember() method to the XML files that come with PowerShell 2.0 by default?

    Also, in other PowerShell blogs, performance tests have shown that add-member is terribly slow, so I hope some calories are being spent in PowerShell 2.0 to make that cmdlet faster (I have a script which parses log data which uses add-member, and it's as slow as a sick dog on a hot day...).

    Thank You!

  • > Will you add the PSAddMember() method to the XML files that come with PowerShell 2.0 by default?

    Watch that space.  I put it on the list of things to consider.

    > Also, in other PowerShell blogs, performance tests have shown that add-member is terribly slow, so I hope some calories are being spent in PowerShell 2.0 to make that cmdlet faster (I have a script which parses log data which uses add-member, and it's as slow as a sick dog on a hot day...).

    We've done a bunch of perf work in general.  I don't know whether this was one of the scenarios that was optimized for.

    jps

  • I for one have numerous scripts lying round my hard drive, that use add-member to tack lots of random things onto objects - and this example will help cut the code no end.... specialy when I want to tag objects with note-properties as they flow through the pipeline.

    However, I'd love to be able to construct a powershell object from scratch using a "create-object" like command...

    This would save me having to use add-type and writing every thing in C# !

    Keep up the good work.

  • Wow. This is really cool--almost. Man, as Shin mentions, the need to ship the XML around with the script really kills it. :(

    I *really* hope you guys can improve this scenario for PS2. I would really love for add-member to be more useful.

  • cool to see some discussion on that, i really need to blog my libraries about this stuff. i've put a lot of time into object synethsis and transformation in PS

  • Did you guys see this news from the Mono project? http://tirania.org/blog/archive/2008/Sep-08.html

  • When I need to construct an object, I simply use this approach:

    $obj="" | select Name,Address,Phone

    $obj.name="blah"

    And as I normally just want to add additional properties, I find this must easier or perhaps more intuitive than using add-member:

    $obj=$obj| select *,NewProperty

    $obj.NewProperty=456

    I can even get rid of existing Properties -

    $obj=$obj | select * -exclude Phone

    $obj

Page 1 of 1 (7 items)