Sometimes, we can discover name collisions in PowerShell
Consider example
$xml1 = [xml] "<tag />"
$xml2 = [xml] "<tag name='attributeValue' />"
When we call
$xml1.tag.Name
we get tag
but when we call
$xml2.tag.Name
we get attributeValue
That’s happened because of the name collision XmlNode.Name property vs automatic NoteProperty name constructed from the attributes
So the question is how to get tag name from $xml2 as well. It took me about 15 minutes to find the answer when similar issue occurred in one of our deployment scripts.
I don’t want to just show the answer, I want to discuss the way I discovered it. BTW, I could not find anything useful in Google or StackOverflow. You’re more than welcome to point me if you can find anything else instead of this blogpost :)
So let’s start from
$xml2.tag | Get-Member -Force
It has many hidden members that’s visible only because of the -Force parameter
We can see that we have method called get_Name, and as we can easily guess, all the properties always come with get_Property and set_Property methods
So if we call
$xml2.tag.get_Name()
we will get expected result tag
Besides that, when I was looking into output of Get-Member -Force I found some interesting properties like PSBase, according to Get-Member documentation:
PSBase: The original properties of the .NET Framework object without extension or adaptation. These are the properties defined for the object class and listed in MSDN.
So alternative solution will be
$xml2.tag.PSBase.Name
Let’s consider more complex synthetic case
$xml3 = [xml] "<tag psbase='attributeValue' />"
How can we retrieve value for psbase attribute?
Naive approach
$xml3.tag.psbase
won’t work, because of the another collision, PSBase is a special property.
Well, we can use attribute approach
For PowerShell 3 we can use
$xml3.tag.Attributes["psbase"].Value
works as expected
But this won’t work in PowerShell 2
[ : Unable to index into an object of type System.Xml.XmlAttributeCollection.
At line:1 char:22
+ $xml3.tag.Attributes[ <<<< "psbase"].Value
+ CategoryInfo : InvalidOperation: (psbase:String) [], RuntimeException
+ FullyQualifiedErrorId : CannotIndex
and we will do this in more complex way
($xml3.tag.Attributes | Where-Object { $_.Name -eq "psbase" }).Value
Or we can have some tricky code which doesn’t require to know that we deal with XmlNode.
In order to test that I tried different approaches
I tried
New-Object PSObject -Property @{ psbase = "abc" }
but this fails with
New-Object : The member name "psbase" is reserved.
At line:1 char:11
+ New-Object <<<< PSObject -Property @{ psbase = "abc" }
+ CategoryInfo : NotSpecified: (:) [New-Object], ExtendedTypeSystemException
+ FullyQualifiedErrorId : PSObjectMembersMembersAddReservedName,Microsoft.PowerShell.Commands.NewObjectCommand
Then I ended up with another synthetic example
Add-Type -TypeDefinition "public class MyTest { public string psbase = `"abc`"; }"
$x = New-Object MyTest
$x.psbase does not return what we want to get (abc)
Note, that I used public field instead of property. Otherwise we could use get_psbase approach
That’s tricky one.
And the solution I found is
($x.PSObject.Members | Where-Object { $_.Name -eq "psbase" }).Value
returns abc as expected
Similarly, for XmlNode this also works.
($xml3.tag.PSObject.Members | Where-Object { $_.Name -eq "psbase" }).Value
Another approach for $x is to use reflection
$x.GetType().GetField("psbase").GetValue($x)
We could use reflection because it is a fair property of the object.
I could not invent the example which would have only NoteProperty named psbase. For this case reflection won’t work and only approach with PSObject.Members will work there.
I know this all is weird, but if you understand that, you will have better understanding of some PowerShell hidden traps.
Please send me more weird things if you know any…
Stay tuned