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!
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.
apparently fixed in V3
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?
Thank you, thank you. PSObject instead of System.Object is exactly what I needed.
Still does not work in V3 if passed as a argumentlist in Start-Job