Home > Back-end >  Powershell Select-XML - Trouble with XPath as absolute Path
Powershell Select-XML - Trouble with XPath as absolute Path

Time:07-28

Hello powershell fellows,

i have another issue with powershell and xml.

What i try to do: I have an XML File (more accurately, it is a xml export of a AD GPO). This XML looks very similar to this:

<?xml version="1.0" encoding="UTF-16"?>
-<GPO xmlns="http://www.microsoft.com/GroupPolicy/Settings" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    -<Identifier>
        <Identifier xmlns="http://www.microsoft.com/GroupPolicy/Types"></Identifier>
        <Domain xmlns="http://www.microsoft.com/GroupPolicy/Types">Testdomain.local</Domain>
    </Identifier>
    <Name>ExampleName</Name>
    <IncludeComments>true</IncludeComments>
    <CreatedTime>2022-07-26T11:12:25</CreatedTime>
    <ModifiedTime>2022-07-26T11:56:44</ModifiedTime>
    <ReadTime>2022-07-26T11:56:52.7975554Z</ReadTime>
    -<Computer>
        <VersionDirectory>1</VersionDirectory>
        <VersionSysvol>1</VersionSysvol>
        <Enabled>true</Enabled>
        -<ExtensionData>
            -<Extension xsi:type="q1:SecuritySettings" xmlns:q1="http://www.microsoft.com/GroupPolicy/Settings/Security">
                -<q1:SecurityOptions>
                    <q1:KeyName>TestKeyName</q1:KeyName>
                    <q1:SettingNumber>5</q1:SettingNumber>
                    -<q1:Display>
                        <q1:Name>TestName</q1:Name>
                        <q1:Units/>
                        <q1:DisplayString>TestValue</q1:DisplayString>
                    </q1:Display>
                </q1:SecurityOptions>
         ...

Now i want to address these xml nodes with Select-XML cmdlet. I need to use a absolute Xpath Path. Lets say i want to get the value of "SecurityOptions/Display/Name".

I tried something like this:

$namespace = @{ns="http://www.microsoft.com/GroupPolicy/Settings";xsi="http://www.w3.org/2001/XMLSchema-instance";xsd="http://www.w3.org/2001/XMLSchema";q1="http://www.microsoft.com/GroupPolicy/Settings/Security"}

$xpath = '/q1:SecurityOptions/q1:Display/q1:Name'
$result = (Select-Xml -Path "C:\examplepath\example.xml" -XPath $xpath -Namespace $namespace).Node.InnerText

But this gets me no value at all :/

What works:

On the other hand, when i do not use an absolute path like:

$xpath = '//q1:Name'
$result = (Select-Xml -Path "C:\examplepath\example.xml" -XPath $xpath -Namespace $namespace).Node.InnerText

It gives me some result. But i want to address the XML Nodes with an absolute path not only with a relative path.

Maybe anyone can tell me what i am doing wrong here. :)

Thank you in advance !

CodePudding user response:

The answer is as simple as adding a missing /. Not sure why you have to use an absolute path, but in your relative path, your expression //q1:Name starts with // which covers all descendants nodes of the root with that name. In your absolute path expression /q1:SecurityOptions... you start with /, which looks only for direct child elements of the root by that name. But the /q1:SecurityOptionselement is burred several layers down the tree.

So long story short, changing your xpath expression to

'//q1:SecurityOptions/q1:Display/q1:Name'

should work.

CodePudding user response:

Why not simply using PowerShell's Dot notation?

$Xml = [Xml]@'
<GPO xmlns="http://www.microsoft.com/GroupPolicy/Settings" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Identifier>
        <Identifier xmlns="http://www.microsoft.com/GroupPolicy/Types"></Identifier>
        <Domain xmlns="http://www.microsoft.com/GroupPolicy/Types">Testdomain.local</Domain>
    </Identifier>
    <Name>ExampleName</Name>
    <IncludeComments>true</IncludeComments>
    <CreatedTime>2022-07-26T11:12:25</CreatedTime>
    <ModifiedTime>2022-07-26T11:56:44</ModifiedTime>
    <ReadTime>2022-07-26T11:56:52.7975554Z</ReadTime>
    <Computer>
        <VersionDirectory>1</VersionDirectory>
        <VersionSysvol>1</VersionSysvol>
        <Enabled>true</Enabled>
        <ExtensionData>
            <Extension xsi:type="q1:SecuritySettings" xmlns:q1="http://www.microsoft.com/GroupPolicy/Settings/Security">
                <q1:SecurityOptions>
                    <q1:KeyName>TestKeyName</q1:KeyName>
                    <q1:SettingNumber>5</q1:SettingNumber>
                    <q1:Display>
                        <q1:Name>TestName</q1:Name>
                        <q1:Units/>
                        <q1:DisplayString>TestValue</q1:DisplayString>
                    </q1:Display>
                </q1:SecurityOptions>
            </Extension>
        </ExtensionData>
    </Computer>
</GPO>
'@
$Xml.GPO.Computer.ExtensionData.Extension.SecurityOptions.Display.Name
TestName

Note: text (leaf) nodes are returned as strings by the PowerShell accelerated [Xml] parser. This implementation is essential incorrect and noted here: #16878 Decorate dot selected Xml strings (leaves) with XmlElement methods. This behavior might cause difficulties if you e.g. want to change the node. To ensure that the (leaf) node you select is always an XmlElement, you might do something like:

$NameTable = @{
    ns="http://www.microsoft.com/GroupPolicy/Settings"
    xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsd="http://www.w3.org/2001/XMLSchema"
    q1="http://www.microsoft.com/GroupPolicy/Settings/Security"
}
$NameSpace = [Xml.XmlNamespaceManager]$Xml.NameTable
$NameTable.Keys.ForEach{ $NameSpace.AddNamespace($_, $NameTable[$_]) }

$Extension = $Xml.GPO.Computer.ExtensionData.Extension
$NameNode = $Extension.SecurityOptions.Display.SelectSingleNode('q1:Name', $NameSpace)
$NameNode.InnerText = 'NewValue'
[System.Xml.Linq.XDocument]::Parse($Xml.GPO.Computer.ExtensionData.Extension.OuterXml).ToString()

<Extension xsi:type="q1:SecuritySettings" xmlns:q1="http://www.microsoft.com/GroupPolicy/Settings/Security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.microsoft.com/GroupPolicy/Settings">
  <q1:SecurityOptions>
    <q1:KeyName>TestKeyName</q1:KeyName>
    <q1:SettingNumber>5</q1:SettingNumber>
    <q1:Display>
      <q1:Name>NewValue</q1:Name>
      <q1:Units />
      <q1:DisplayString>TestValue</q1:DisplayString>
    </q1:Display>
  </q1:SecurityOptions>
</Extension>
  • Related