Parse XML with Powershell to get value of .Name property when masked by child node

Viewed 169

In Powershell, how can I get the value of the .Name property of an XML element when it has a child node called 'Name' which masks the property.

# Assign XML that contains an element called 'Name' deliberately
# to mask the .Name property on the parent element.
$xml = [xml]@"
<People>
    <Person>
        <Name>Jo Bloggs</Name>
   </Person>
</People>
"@

Write-Host $xml.People.FirstChild.Name
# > Jo Bloggs
#Expect to get the .Name property of the <Person> element but instead
# we get the 'Name' child node text.

# Use XML that doesn't mask the .Name property
$xml = [xml]@"
<People>
    <Person>
        <FullName>Jo Bloggs</FullName>
   </Person>
</People>
"@

Write-Host $xml.People.FirstChild.Name
# > Person
# We now get what we originally expected, the value of the .Name
# property of the Person element itself.

Of course, I can call the .LocalName property of the Person element and I get the expected result, but that's not a generic solution as if the Person element has a LocalName child element, that too is masked.

UPDATE 2021-08-26 : I have since found this SO question which is very similar to mine, with some detailed answers. Unable to completely parse XML in powershell

2 Answers

I don't think there is a way to tell PowerShell to prioritize shadowed properties in this case, but I'd be happy to be proven wrong.

You could go through an XPathNavigator and evaluate an XPath string. This

# or:  [System.Xml.XPath.XPathDocument]::new("C:\Path\to\the\file.xml")
$doc = [System.Xml.XPath.XPathDocument]::new([System.IO.StringReader]::new(@"
<People>
    <Person>
        <Name>Jo Bloggs</Name>
   </Person>
</People>
"@))

$xpath = $doc.CreateNavigator()
$xpath.Evaluate("name(/People/*[1])")

will print Person.

According to this question and answer

SO Question and Answer

The below line will get the intrinsic .Name property and not the Powershell decorated property.

$xml.People.FirstChild.get_Name()

The more complete solution demonstration is:

# Assign XML that contains an element called 'Name' deliberately
# to have Powershell decorate and thererfore Shadow the intrinsic 
# .Name property on the parent element.
$xml = [xml]@"
<People>
    <Person>
        <Name>Jo Bloggs</Name>
   </Person>
</People>
"@

# Solution to avoid issues with collision between decorated
# properties which shadow intrinsic element properties is to
# use the .get_<property-name>() method.
#
# In this case, .get_Name() should always return the intrinsic
# element property, and never the Powershell decorated property.
Write-Host $xml.People.FirstChild.get_Name()

# > Person
# This works
Related