Custom object gotchas


When creating custom objects in PowerShell using the Add-Member cmdlet, it is worth noting that there are a few gotchas which can cause a lot of headaches and debugging unless you are aware of them.

Problem

As you probably know, you can extend any object in PowerShell. When you want to create a new object from scratch though, which should you use? If you’re a programmer with a .Net background you would naturally use the System.Object class. However, this can be a problem if you’re passing it through .Net functions. Why? Because of implicit boxing and unboxing of objects.

So let’s check this in PowerShell:

$obj = new-object System.Object
$obj | Add-Member -Name Test -Value "Test" -MemberType NoteProperty
$obj | Get-Member -Force
   TypeName: System.Object
Name        MemberType   Definition                                                          
----        ----------   ----------                                                          
pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorl...
psadapted   MemberSet    psadapted {ToString, Equals, GetHashCode, GetType}                  
psbase      MemberSet    psbase {ToString, Equals, GetHashCode, GetType}                     
psextended  MemberSet    psextended {Test}                                                   
psobject    MemberSet    psobject {Members, Properties, Methods, ImmediateBaseObject, BaseO...
Equals      Method       bool Equals(System.Object obj)                                      
GetHashCode Method       int GetHashCode()                                                   
GetType     Method       type GetType()                                                      
ToString    Method       string ToString()                                                   
Test        NoteProperty System.String Test=Test

The first thing to note here is that this System.Object instance has got some rather PowerShell specific members on it as it is being wrappered to make it PowerShell friendly. The problem is that when you pass it to a .Net function, the object is unboxed, and the underlying object instance is passed through, WITHOUT THE ADDED CUSTOM MEMBERS!

An easy way to check this is to add the object we created earlier to a hashtable using the .Net provided Add function on the hashtable:

$hash = @{}
$hash.Add("Test",$obj)
$hash.Test | Get-Member -Force
   TypeName: System.Object
Name        MemberType   Definition                                                          
----        ----------   ----------                                                          
pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorl...
psadapted   MemberSet    psadapted {ToString, Equals, GetHashCode, GetType}                  
psbase      MemberSet    psbase {ToString, Equals, GetHashCode, GetType}                     
psextended  MemberSet    psextended {}                                                       
psobject    MemberSet    psobject {Members, Properties, Methods, ImmediateBaseObject, BaseO...
Equals      Method       bool Equals(System.Object obj)                                      
GetHashCode Method       int GetHashCode()                                                   
GetType     Method       type GetType()                                                      
ToString    Method       string ToString()  

Note the conspicuous absence of any “Test” NoteProperty!

Solution 1

The quick and dirty solution is, of course, not to use .Net provided functions with custom objects created in PowerShell. So we can solve the hashtable problem very simply by using a named property on the hashtable to instantiate the addition:

$hash = @{}
$hash.Test = $obj
$hash.Test | Get-Member -Force
   TypeName: System.Object
Name        MemberType   Definition                                                          
----        ----------   ----------                                                          
pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorl...
psadapted   MemberSet    psadapted {ToString, Equals, GetHashCode, GetType}                  
psbase      MemberSet    psbase {ToString, Equals, GetHashCode, GetType}                     
psextended  MemberSet    psextended {Test}                                                   
psobject    MemberSet    psobject {Members, Properties, Methods, ImmediateBaseObject, BaseO...
Equals      Method       bool Equals(System.Object obj)                                      
GetHashCode Method       int GetHashCode()                                                   
GetType     Method       type GetType()                                                      
ToString    Method       string ToString()                                                   
Test        NoteProperty System.String Test=Test

Solution 2

The above works, but it’s not ideal. So how do we resolve this? By creating new object based on PSObject instead of System.Object. That way the base object will be a PSCustomObject which when unboxed will still have the custom members attached to it, even when used from .Net functions:

$obj = new-object PSObject
$obj | Add-Member -Name Test -Value "Test" -MemberType NoteProperty
$hash.Add("Test",$obj)
$hash.Test | Get-Member -Force
   TypeName: System.Management.Automation.PSCustomObject
Name        MemberType   Definition                                                          
----        ----------   ----------                                                          
pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorl...
psadapted   MemberSet    psadapted {ToString, Equals, GetHashCode, GetType}                  
psbase      MemberSet    psbase {ToString, Equals, GetHashCode, GetType}                     
psextended  MemberSet    psextended {Test}                                                   
psobject    MemberSet    psobject {Members, Properties, Methods, ImmediateBaseObject, BaseO...
Equals      Method       bool Equals(System.Object obj)                                      
GetHashCode Method       int GetHashCode()                                                   
GetType     Method       type GetType()                                                      
ToString    Method       string ToString()                                                   
Test        NoteProperty System.String Test=Test 

I hope this has gone some way to explaining the pitfalls of custom object creation. Enjoy and please do let me know what you think of my articles!

  1. MikeW
    June 13, 2011 at 1:42 pm

    Thank you very much sir! I was racking my brain on that one. System.Object just was not behaving like I thought it should. Didn’t have any clue as to why.

  2. May 21, 2012 at 12:25 pm

    apparently fixed in V3

  3. fridojet
    June 8, 2012 at 9:37 am

    hi, I read something about PSObject’s `BaseObject` property ( http://msdn.microsoft.com/en-us/library/system.management.automation.psobject_members(v=vs.85).aspx ) and other nice members. But when I use your Solution 2, then `$obj` doesn’t own these members – but why?

  4. Jeff
    October 16, 2012 at 8:48 pm

    Thank you, thank you. PSObject instead of System.Object is exactly what I needed.

  5. dan
    September 11, 2013 at 7:52 pm

    Still does not work in V3 if passed as a argumentlist in Start-Job

  1. February 21, 2012 at 10:39 pm
  2. September 7, 2012 at 5:04 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: