2

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

Jayden
  • 2,656
  • 2
  • 26
  • 31

2 Answers2

2

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.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • thanks @Tomalak, I can use that as an alternative - I'll leave the question open for a day or two more and if no-one else can come up with an answer, I'll mark this as the accepted answer – Jayden Aug 25 '21 at 18:54
  • see my additional answer. I found reference buried deep in another SO question. Using .get_Name() appears to work. Your answer helped me find that SO question though, so thanks again. – Jayden Aug 25 '21 at 20:07
1

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
Jayden
  • 2,656
  • 2
  • 26
  • 31