powershellxml-parsing

How to append XML elements with Attributes using Powershell


My XML has elements like:

<?xml version="1.0" encoding="utf-8"?>
<UserPreferences Version="3">
  <Record Key="ProfileManager">
    <Path>C:\Users\bob\AppData\Roaming\Software\21\</Path>
  </Record>
  <Record Key="Measurement Creation Context">
    <SnapOrthogonal>False</SnapOrthogonal>
    <SnapContent>True</SnapContent>
   </Record>
  <Record Key="WebFavoritesList" />
  <Record Key="DefaultsManager" />
  <Record Key="ToolSetManager">
    <ToolSet>
      <Path>Tool Set 1.btx</Path>
      <Title>Tool Set 1</Title>
    </ToolSet>
    <ToolSet>
      <Path>My Tool Set.btx</Path>
      <Title>My Tool Set</Title>
    </ToolSet>
  </Record>
  <Record Key="RecentFileList" />
  <Record Key="TemplateData" />

     .
   </Record>
</UserPreferences>

I need to append 3 new ToolSets to the tree.

I can't figure out how to select the element Record Key = "ToolSetManager" to so I can append data to that particular element.

My code:

$xml = [xml](Get-Content "$env:appdata\software\21\UserPreferences.xml")

#Loop to find the Element with the Key "ToolSetManager"
#Hopefully there is a simpler/direct way to get here?
$allRecordKeys = $xml.UserPreferences.Record.key

foreach ($Key in $allRecordKeys) {
 if ($Key -eq "ToolSetManager") {

   $newElement1 = $xml.CreateElement("ToolSet")
   $newElement1.SetAttribute("Path", "Null.btx")
   $newElement1.SetAttribute("Title", "Null")

# This is where I can't figure out what should go before the .appendChild...:
   $xml.RevuUserPreferences.Record.ToolSetManager.AppendChild($newElement1)

   $xml.save("$env:appdata\software\21\UserPreferences.xml")
   }
 }

Solution

  • To select the node having Key equal to ToolSetManager you can use SelectSingleNode with the following XPath:

    $node = $xml.SelectSingleNode("//Record[@Key='ToolSetManager']")
    

    Then $node should look like:

    Key            ToolSet
    ---            -------
    ToolSetManager {ToolSet, ToolSet}
    

    Then if you wanted to add a new <ToolSet> to <Record Key="ToolSetManager">, the code is a bit more annoying, what you currently have will not generate the same <ToolSet> nodes as the ones your XML has, you'd get something like this using your current approach:

    <ToolSet Path="Null.btx" Title="Null" />
    

    When what you want is most likely:

    <ToolSet>
      <Path>Null.btx</Path>
      <Title>Null</Title>
    </ToolSet>
    

    The easiest way could be by cloning one of those elements and then updating its values, i.e.:

    $clone = $node.ToolSet[0].Clone()
    $clone.Path = 'Null.btx'
    $clone.Title = 'Null'
    $null = $node.AppendChild($clone)
    

    Another way, much more manual can be:

    # Create <ToolSet>
    $toolset = $xml.CreateElement('ToolSet')
    # Create <Path>
    $path = $xml.CreateElement('Path')
    # Add Null.txt to <Path>
    $null = $path.AppendChild($xml.CreateTextNode('Null.btx'))
    # Add <Path> to <ToolSet>
    $null = $toolset.AppendChild($path)
    # Create <Title>
    $title = $xml.CreateElement('Title')
    # Add Null to <Title>
    $null = $title.AppendChild($xml.CreateTextNode('Null'))
    # Add <Title> to <ToolSet>
    $null = $toolset.AppendChild($title)
    # Add <ToolSet> to <Record Key="ToolSetManager">
    $null = $node.AppendChild($toolset)
    

    Then if you save the updated $xml (there you can use $xml.Save('path\to\xml')) it should look like:

      <Record Key="ToolSetManager">
        <ToolSet>
          <Path>Tool Set 1.btx</Path>
          <Title>Tool Set 1</Title>
        </ToolSet>
        <ToolSet>
          <Path>My Tool Set.btx</Path>
          <Title>My Tool Set</Title>
        </ToolSet>
        <ToolSet>
          <Path>Null.btx</Path>
          <Title>Null</Title>
        </ToolSet>
      </Record>