32

With PowerShell, I want to add several sub-elements into an XML tree.
I know to ADD ONE element, I know to add one or several attributes, but I don't understand how to ADD SEVERAL elements.

One way whould be to write a sub-XML tree as text
But I can't use this method because the elements are not added at once.

To add one element, I do that:

[xml]$xml = get-content $nomfichier
$newEl = $xml.CreateElement('my_element')
[void]$xml.root.AppendChild($newEl)

Works fine. This give me this XML tree:

$xml | fc
class XmlDocument
{
  root =
    class XmlElement
    {
      datas =
        class XmlElement
        {
          array1 =
            [
              value1
              value2
              value3
            ]
        }
      my_element =     <-- the element I just added
    }
}

Now I want to add a sub element to 'my_element'. I use a similar method:

$anotherEl = $xml.CreateElement('my_sub_element')
[void]$xml.root.my_element.AppendChild($anotherEl) <-- error because $xml.root.my_element is a string
[void]$newEl.AppendChild($anotherEl)               <-- ok
$again = $xml.CreateElement('another_one')
[void]$newEl.AppendChild($again)

This give this XML tree (partialy displayed):

my_element =
  class XmlElement
  {
    my_sub_element =
    another_one =
  }

Those are attributes, not sub-elements.
Sub-elements would be displayed as this:

my_element =
  [
    my_sub_element
    another_one
  ]

Question: How do I add several sub-elements, one at a time?

Community
  • 1
  • 1
Gregory MOUSSAT
  • 832
  • 4
  • 13
  • 22

4 Answers4

49

Have a look to the following example :

# Document creation
[xml]$xmlDoc = New-Object system.Xml.XmlDocument
$xmlDoc.LoadXml("<?xml version=`"1.0`" encoding=`"utf-8`"?><Racine></Racine>")

# Creation of a node and its text
$xmlElt = $xmlDoc.CreateElement("Machine")
$xmlText = $xmlDoc.CreateTextNode("Mach1")
$xmlElt.AppendChild($xmlText)

# Creation of a sub node
$xmlSubElt = $xmlDoc.CreateElement("Adapters")
$xmlSubText = $xmlDoc.CreateTextNode("Network")
$xmlSubElt.AppendChild($xmlSubText)
$xmlElt.AppendChild($xmlSubElt)

# Creation of an attribute in the principal node
$xmlAtt = $xmlDoc.CreateAttribute("IP")
$xmlAtt.Value = "128.200.1.1"
$xmlElt.Attributes.Append($xmlAtt)

# Add the node to the document
$xmlDoc.LastChild.AppendChild($xmlElt);

# Store to a file 
$xmlDoc.Save("c:\Temp\Temp\Fic.xml")

Edited

Remark : Using a relative path in Save will not do what you expect.

JPBlanc
  • 70,406
  • 17
  • 130
  • 175
  • 1
    Way much better solution than what I found until now ! And cleaner. Thanks. – Gregory MOUSSAT Jun 12 '12 at 09:12
  • This works nice. My problem is, that it writes many thing onto the console. Debug shows it happens at this line: $xmlSubElt.AppendChild($xmlSubText) if I write "> $null" to the end. Something else is written out Can I block it? – Tomi Apr 11 '15 at 12:10
  • Replace `> $null` with `| out-null`. – JPBlanc Apr 11 '15 at 12:19
  • to suppress output to the console, you can also simply prefix all the function invocations with `[void]`, like this : `[void]$xmlDoc.LoadXml(...)`. The assignations `$var = something` don't output anything, but function invocations do actually generate output (their return value) by default. – Pac0 Jun 06 '18 at 22:30
39

I prefer creating xml by hand, instead of using API to construct it node by node, as imho by hand it will be much more readable and more maintable.

Here is an example:

$pathToConfig = $env:windir + "\Microsoft.NET\Framework64\v4.0.30319\Config\web.config"

$xml = [xml] (type $pathToConfig)

[xml]$appSettingsXml = @"
<appSettings>
    <add key="WebMachineIdentifier" value="$webIdentifier" />
</appSettings>
"@


$xml.configuration.AppendChild($xml.ImportNode($appSettingsXml.appSettings, $true))
$xml.Save($pathToConfig)
Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
5

Check this code-sample. It has everything you need to create XML from scratch:

function addElement($e1, $name2, $value2, $attr2)
{
    if ($e1.gettype().name -eq "XmlDocument") {$e2 = $e1.CreateElement($name2)}
    else {$e2 = $e1.ownerDocument.CreateElement($name2)}
    if ($attr2) {$e2.setAttribute($value2,$attr2)}
    elseif ($value2) {$e2.InnerText = "$value2"}
    return $e1.AppendChild($e2)
}

function formatXML([xml]$xml)
{
    $sb = New-Object System.Text.StringBuilder
    $sw = New-Object System.IO.StringWriter($sb)
    $wr = New-Object System.Xml.XmlTextWriter($sw)
    $wr.Formatting = [System.Xml.Formatting]::Indented
    $xml.Save($wr)
    return $sb.ToString()
}

...now let's use both functions to create and display a new XML-object:

$xml = New-Object system.Xml.XmlDocument
$xml1 = addElement $xml "a"
$xml2 = addElement $xml1 "b"
$xml3 = addElement $xml2 "c" "value"
$xml3 = addElement $xml2 "d" "attrib" "attrib_value"

write-host `nFormatted XML:`r`n`n(formatXML $xml.OuterXml)

the result looks like this:

Formatted XML:

 <?xml version="1.0" encoding="utf-16"?>
<a>
  <b>
    <c>value</c>
    <d attrib="attrib_value" />
  </b>
</a>
Carsten
  • 1,612
  • 14
  • 21
2

For anyone else visiting this.

I had issues because my parent document had a namespace, and the ImportNode was adding an empty xmnls="" element to the imported xml, causing issues with my app

Extending on answer above. To get around this, wrap it in a dummy node, with namespace set from parent doc

$pathToConfig = $env:windir + "\Microsoft.NET\Framework64\v4.0.30319\Config\web.config"

$xml = [xml] (type $pathToConfig)
$root = $xml.get_DocumentElement()
$namespaceuri = $root.NamespaceURI

[xml]$appSettingsXml = @"
<Dummy xmlns="$namespaceuri">
    <appSettings>
        <add key="WebMachineIdentifier" value="$webIdentifier" />
    </appSettings>
</Dummy>
"@


$xml.configuration.AppendChild($xml.ImportNode($appSettingsXml.Dummy.appSettings, $true))
$xml.Save($pathToConfig)
Thumpers
  • 111
  • 1
  • 9