Powershell Variables for XML node/path addressing
Asked Answered
R

1

3

Hello dear Powershell experts,

I need some advice. I try to use Powershell variables (which i get from parameters) to address XML paths/nodes.

My Scenario

The code below shows a short cutout of my code. With this cutout i will try to explain my issue as detailed as possible.

foreach ($Gpo in $AllGpos) {
    [xml]$selected_Gpo = Get-GPOReport -ReportType Xml -Guid $Gpo.Id
    $policy_object = $selected_Gpo.GPO.Computer.ExtensionData.Extension
        if ($policy_object.$Policy_Identity_XMLPath_Name -eq $Policy_Identity_Name) {
                if ($policy_object.$Setting_XMLPath_Name -eq $Policy_Identity_Setting_Name -and $policy_object.$Setting_XMLPath_Value -eq $Policy_Identity_Setting_Value) {
                }
                ...

I iterate trough all Group Policy Objects (in an Active Directory Environment), I generate an xml Report of the GPO and then search for specific Group Policy settings in this xml file. This method is working perfectly fine as long as i use hardcoded xml node paths. For example: Instead of using "$policy_object.$Policy_Identity_XMLPath_Name -eq $Policy_Identity_Name" I could use "$policy_object.Policy.DropDownList.Name -eq "Example Setting". But I am unable to use these hardcoded xml node paths and wanted to use variables to address these paths in the xml file.

Short cutout of an example XML

<q1:Policy>
  <q1:Name>TestName</q1:Name>
  <q1:State>Enabled</q1:State>
  <q1:Explain>Test Explaination </q1:Explain>
  <q1:Supported>Testsupported version</q1:Supported>
  <q1:Category>Test Category</q1:Category>
  <q1:DropDownList>
      <q1:Name>Test Name DropDownList</q1:Name>
      <q1:State>Enabled</q1:State>
      <q1:Value>
          <q1:Name>TestName Value</q1:Name>
      </q1:Value>
  </q1:DropDownList>
   </q1:Policy>

My Problem

The problem is, that I am unable to address the xml paths with powershell variables. When i use variables like in the code sample above ($policy_object.$Policy_Identity_XMLPath_Name) I get null back, for which reason my if statements do not work anymore.

What i did so far

Of course I did a lot trial and error things, to get powershell address these xml node paths with variables. I tried to put ($policy_object.$Policy_Identity_XMLPath_Name) in different types of quotes. I also already have seen the stack overflow post with a pretty similar problem (Parsing an XML file with PowerShell with node from variable) But those solutions are basically not that bad, but aren't useful in my concrete scenario. Let me explain:

$Policy_Identity_Setting_XMLPath_Name = 'GPO/Computer/ExtensionData/Extension/SecurityOptions/Display/DisplayFields/Field/Name'
$test = policy_object.SelectNodes($Policy_Identity_Setting_XMLPath_Name)

=> results in null. Don't know why honestly

$ExecutionContext.InvokeCommand.ExpandString("`$(`policy_object.$Policy_Identity_Setting_XMLPath_Name)")

This would result in an "acceptable" outcome. But the outcome is mostly multiple values (GPO Settings) in my scenario and it stores them all in one giant string. I am unable to access a specific value in this string afterwards. So this method is also not ideal, because I am unable to process the outcome further.

I hope you fellows have some good ideas. Maybe I'm making it more complicated than necessary in my brain logic. If you need any more information, let me know.

Radiculitis answered 13/7, 2022 at 7:17 Comment(0)
W
1

If you want to use PowerShell's adaptation of the XML DOM, which allows property-like access to the elements and attributes using the . operator, you can drill down into your XML document iteratively by splitting a .-separated path string into its components with ., taking advantage of the fact that you can use expressions to dynamically specify a (single) property name:

# Sample input
[xml] $xml = @'
<q1:Policy xmlns:q1="http://example.org">
  <q1:Name>TestName</q1:Name>
  <q1:State>Enabled</q1:State>
  <q1:Explain>Test Explaination </q1:Explain>
  <q1:Supported>Testsupported version</q1:Supported>
  <q1:Category>Test Category</q1:Category>
  <q1:DropDownList>
    <q1:Name>Test Name DropDownList</q1:Name>
    <q1:State>Enabled</q1:State>
    <q1:Value>
      <q1:Name>TestName Value</q1:Name>
    </q1:Value>
  </q1:DropDownList>
</q1:Policy>
'@

# Define the path as a single, dotted string; e.g.:
$dottedPath = 'Policy.DropDownList.State'

# Drill down into the XML, component by component.
$result = $xml
foreach ($prop in $dottedPath -split '\.') { $result = $result.$prop }

$result # Output the result: -> 'Enabled'

Note: PowerShell's adaptation of the XML DOM is convenient - notably not needing to specify namespaces (see below) - but has its limitations; see this answer.


By contrast, in order to use XPath queries such as with .SelectNodes():

  • the element names in paths must be /-separated
  • and, since your document uses XML namespaces, you must pass a namespace manager instance as the second argument and namespace-qualify the element names in the path

If you neglect to do the latter, you will indeed get $null as the result.

# Sample input
[xml] $xml = @'
<q1:Policy xmlns:q1="http://example.org">
  <q1:Name>TestName</q1:Name>
  <q1:State>Enabled</q1:State>
  <q1:Explain>Test Explaination </q1:Explain>
  <q1:Supported>Testsupported version</q1:Supported>
  <q1:Category>Test Category</q1:Category>
  <q1:DropDownList>
    <q1:Name>Test Name DropDownList</q1:Name>
    <q1:State>Enabled</q1:State>
    <q1:Value>
      <q1:Name>TestName Value</q1:Name>
    </q1:Value>
  </q1:DropDownList>
</q1:Policy>
'@

# Create a namespace manager and associate it with the document.
$namespaceMgr = [System.Xml.XmlNamespaceManager]::new($xml.NameTable)

# Add an entry for the 'q1' namespace prefix.
# Note:
#  * The *prefix* (entry *name*) need not match the prefix in the document,
#    but whatever name you choose must be used in your XPath query.
#  * The namespace *URL* must match the one in the document, however.
$namespaceMgr.AddNameSpace('q1', 'http://example.org')

# Construct the XPath path, using the appropriate namespace prefixes; e.g.:
$xpath = 'q1:Policy/q1:DropDownList/q1:State'

# Now call .SelectNodes() with the query and the namespace manager.
# Note: This outputs the targeted XML element(s) as a whole, 
#       not their inner text, as dot notation would do.
#       Append .InnerText, if needed.
$xml.SelectNodes($xpath, $namespaceMgr)

Note: Alternatively, consider use of the Select-Xml cmdlet, which provides a higher-level interface to the .NET APIs used above; see this answer for an example.

Worldwide answered 18/7, 2022 at 18:37 Comment(2)
Thank you very much mklement0 also for this very helpful and detailed answer ! Indeed i implemented a really simelar solution to your first suggestion # Drill down into the XML, component by component. $result = $xml foreach ($prop in $dottedPath -split '\.') { $result = $result.$prop } $result # Output the result: -> 'Enabled' => But i will definitly check the XPath and Select-XML solution as well!Radiculitis
Glad to hear the answer helped, @LKo.exp. Yes, the XPath solutions are worth considering, because they are not only faster, but offer sophisticated query capabilities.Worldwide

© 2022 - 2024 — McMap. All rights reserved.