In my last post Getting a Username/Password in PowerShell (cross posted to PowerShellCommunity.org), I was asked why not just use the Add-Member cmdlet.  Having been doing software development for my entire adult life, this is not the first time this question has come up.  Ok.  Maybe not those exact words, but something very similar - why use code X when code Y does the same thing.  Where I come from there is really only one response to this - look at both code alternatives and determine which one is the most efficient at doing the job without also becoming a maintenance problem.  So lets take a peek under the hood a bit so we can see why I chose PSObject.Members.Add over using the Add-Member cmdlet.

Scripting is code - except on the command line

When working in PowerShell I try to keep a clear understanding of my usage scenario.  If I am writing something intended to be typed on the command line then I tend to favor a more script oriented approach where I make heavy use of piping one command into another and where cmdlets provide all of the heavy lifting.  However, when I sit down to write a function, I am coding and I tend to favor a much more expanded and explicit coding style using variables and objects.  This makes it easier to maintain the code and to understand what exactly I am trying to accomplish.

Know what is happening under the cover

Once I know what coding style I will be using then I need to evaluate the effectiveness and efficiency of the coding alternatives.  Often this means you will need to understand what is happening inside the framework.  One of the reasons that I am excited about the announcement that .Net is going to provide Source code is that I will finally be able to look at the source code with full commenting.  Until that happens though, I still have Reflector and I use it very heavily.

In this case the question is:  Why use Members.Add instead of using Add-Member?  Lets look at what is going on in Add-Member and you'll see why.

Microsoft.PowerShell.Commands.AddMemberCommand.ProcessRecord() is the relevant method that gets executed when we run the Add-Member cmdlet.  There is some basic error checking to make sure that certain cmdlet parameters were set - like membertype.

Next we have a giant case statement: 

Select Case Me.memberType
    Case PSMemberTypes.AliasProperty
        member = Me.GetAliasProperty
    Case PSMemberTypes.CodeProperty
        member = Me.GetCodeProperty
    Case PSMemberTypes.NoteProperty
        member = Me.GetNoteProperty
    Case PSMemberTypes.ScriptProperty
        member = Me.GetScriptProperty
    Case PSMemberTypes.ScriptMethod
        member = Me.GetScriptMethod
    Case PSMemberTypes.MemberSet
        member = Me.GetMemberSet
    Case PSMemberTypes.PropertySet
        member = Me.GetPropertySet
    Case PSMemberTypes.CodeMethod
        member = Me.GetCodeMethod
    Case Else
        'Deal with error...
End Select


This case statement doesn't provide any real value to our function since I already know what type of members I wish to add, I don't need the cmdlet to make a conversion from my "string" to a root class type.  Even once the membertype is determined, the cmdlet calls another function (GetNoteProperty in our case) which we see below: 

Private Function GetNoteProperty() As PSMemberInfo
    Me.EnsureValue1HasBeenSpecified
    Me.EnsureValue2HasNotBeenSpecified
    Return New PSNoteProperty(Me.memberName, Me.value1)
End Function
 

So in essence, this big case statement is doing exactly the same thing as my code, except with a lot more overhead.  It is creating a new PSNoteProperty.  Now lets look at the next part of the ProcessRecord method.

Dim info2 As PSMemberInfo = Me.inputObject.Members.Item(member.Name)
If (Not info2 Is Nothing) Then
    If Not Me.force Then
        MyBase.WriteError("...")
    Else
        If info2.IsInstance Then
            Me.inputObject.Members.Remove(member.Name)
            goto Label_0201
        End If
        MyBase.WriteError("...")
    End If
    Return
End If
Me.inputObject.Members.Add(member)

So we see that beyond a bunch of extra error handling to deal with members that already exist, this code just calls Members.Add().

At this point I think it is safe to say that my original code and the Add-Member cmdlet are performing exactly the same function.  However, with the cmdlet there is a performance penalty to be paid for parsing all of the strings passed to the cmdlet and converting them to types.  Determining how to act based on some of the parameters (like -force).  Also, I think that my code is just as readable as using the cmdlet.  Ultimately though, Add-Member is just sugar coating for adding a PSMemberInfo object to a PSObject.Members collection.

So, the next time you have a question about whether to use a cmdlet or whether to use "code", pull out reflector and see what is really happening under the hood.