Share via


Edit XML with powershell

Question

Wednesday, January 13, 2010 7:46 PM

Hi! I have xml file with data like:
...
<Item>
   <value>aaa</value>
   <value>bbb</value>
</Item>
...

I need change "aaa" and "bbb" to "111" and "222" and add value "333".
In result a want see data like:
...
<Item>
   <value>111</value>
   <value>222</value>
   <value>333</value>
</Item>
...
How i can do it with powershell?

   

All replies (9)

Thursday, January 14, 2010 3:01 AM ✅Answered | 2 votes

I knew I'd get it eventually.... Using get-member wasn't giving me everything available.  I happened to use getenumerator() on the element, and it gave me a whole bunch of other properties.... one of which was innertext.  Anyway, this is a complete script that does everything you requested.  Now don't ask me about xml attributes...  I've had it with this stuff for another two weeks!

[xml] $xml = gc .\file.xml

$newitem = $xml.CreateElement("Value")
$newitem.set_InnerXML("333")

# Write nodes in XML:
$xml.items.AppendChild($newitem)
$nodes = @()
$array = New-Object System.Collections.Arraylist
foreach ($node in $xml.Items.ChildNodes){        
    $array.add($node    )
}
$array |foreach {
    if ($_.innertext -eq "aaa") {
        $newitem = $xml.CreateElement("Value")
        $newitem.set_InnerXML("111")            
        $xml.Items.ReplaceChild($newitem, $_)     
    }
    if ($_.innertext -eq "bbb") {
        $newitem = $xml.CreateElement("Value")
        $newitem.set_InnerXML("222")            
        $xml.Items.ReplaceChild($newitem, $_)     
    }    
}

$xml.save("C:\Users\Tome\scripts\new.xml")

Thursday, January 14, 2010 3:03 AM ✅Answered | 1 vote

Sorry... forgot to mention one other thing.  The reason <Item> doesn't work is because the xmldocument has a property called Item.  It also adds all tags as properties.  Therefore it can't add the xml node Item because there is already a property with that name.


Wednesday, January 13, 2010 8:43 PM

This is one way:

$xml1 = @()
$xml0 = gc test.xml
$xml0 |% {
switch ($_) {
"<value>aaa<\value>" {$xml1 += "<value>111<\value>"}
"<value>bbb<\value>" {$xml1 += "<value>222<\value>";$xml1 += "<value>333<\value>"}
default {$xml1 += $_}
}
}
$xml1


Wednesday, January 13, 2010 9:51 PM

I was looking at the xml implementation in Powershell a few weeks ago. Based on my notes I can tell you how to add the new element, but I'm not sure how to change the existing ones yet.

One thing to note... I ran this with your xml, and had problems because you are using the tag <Item> for some reason.  Powershell does not seem to like that.  I changed Item to Items and used the following:

[xml] $xml = gc .\file.xml

$newitem = $xml.CreateElement("Value")
$newitem.set_InnerXML("333")

# Write nodes in XML:
$xml.items.AppendChild($newitem)

You can then see that $xml.items has the new value by running:

$xml.items

....but I also ran into another snag while trying to export to file.  For some reason xmldocument.save does not work properly for me, and neither does $xml|write-file new.xml

Just for reference this uses the .net implementation of xmldocument/xmlelement/xmlnode

I'm going to try and revisit this some point soon, but hopefully someone else can help us out before then.


Thursday, January 14, 2010 2:39 AM

I got really far on this.... Only one hurdle left.  I can't seem to test for $_ -eq "aaa" or -match "aaa".  If anyone can help me get that piece it would be great.... If you remove that test it does successfully write over all elements.  I had to use the arraylist because of the way powershell was handling the foreach $node loop.  For some reason processing in the loop caused the childnodes to change.  It would only process the first element and then stop.  I tried using a powershell array, but I couldn't add the objects to the array with += so I just went with an arraylist to make it work.

Also to note....  the reason xmldocument.save wasn't working for me was b/c I wasn't putting in the full path:

[xml] $xml = gc .\file.xml

$newitem = $xml.CreateElement("Value")
$newitem.set_InnerXML("333")

# Write nodes in XML:
$xml.items.AppendChild($newitem)
$nodes = @()
$array = New-Object System.Collections.Arraylist
foreach ($node in $xml.Items.ChildNodes){        
    $array.add($node    )
}
$array |foreach {
    if ($_ -eq "aaa") {
        $newitem = $xml.CreateElement("Value")
        $newitem.set_InnerXML("111")            
        $xml.Items.ReplaceChild($newitem, $_)     
    }
    
}

$xml.save("C:\Users\Tome\scripts\new.xml")

Thursday, January 14, 2010 2:44 AM

fyi... the biggest problem I've had is that there is a property called #text that seems to be used in these elements.  That's where the values can be get/set.  My above method to use ReplaceChild is my way of getting around this when replacing, but I'm pretty sure this is why I can't get the value I want to use -match or -eq against.


Thursday, January 14, 2010 4:53 PM

Sorry... forgot to mention one other thing.  The reason <Item> doesn't work is because the xmldocument has a property called Item.  It also adds all tags as properties.  Therefore it can't add the xml node Item because there is already a property with that name.

Thank you!  Item is only sample, in real i have another name and you code work. But, now,  i have another problem. How i can remove old all "Genre"s and replace with mew from array?

<Genres>
   <Genre>111</Genre>
   <Genre>222</Genre>
   <Genre>333</Genre>
</Genres>

$xml = [xml](get-content file)
$root = $xml.get_DocumentElement();
$arr = @("asd","dfg","ert","456")
for ( $i = 0; $i -lt $arr.count; $i+=1 ){
    if ($root.Item.Value[$i] -ne $null){
        #how i can replace element?   
        $newitem = $xml.CreateElement("Genre")
        $newitem.set_InnerXML($arr[$i])           
        $xml.Item.ReplaceChild($newitem, $_)     
    }
    else{
        $newitem = $xml.CreateElement("Genre")
        $newitem.set_InnerXML($arr[$i])
        $root.Item.AppendChild($newitem)
    }
}


Friday, January 15, 2010 3:41 PM

The $xml variable is populated with a properties for each sub element.... Therefore you need to use $xml.genres instead of $xml.items.  There's also a removeall() function that will do what you want to do:

$xml = [xml](get-content file.xml)

$arr = @("asd","dfg","ert","456")
$genres = $xml.Genres
$xml.Genres.RemoveAll()

for ( $i = 0; $i -lt $arr.count; $i+=1 ){
        $newitem = $xml.CreateElement("Genre")
        $newitem.set_InnerXML($arr[$i])
        $genres.AppendChild($newitem)
}

$xml.save('C:\Users\tome\scripts\new.xml')

Thursday, May 9, 2013 4:25 PM

# You can't and shouldn't use the root name Item since it is protected XML property in Powershell.
# Solution: Change Item to for example Items

$file = "C:\temp\Items.xml"
[xml] $xml = Get-Content -Path $file
$xml.PreserveWhitespace = $true
$xpath = "//value"

ForEach($val in $xml.SelectNodes($xpath)){
     switch ($val.InnerXML){
        "aaa" {
            $val.InnerXML = "111"
            break;
            }
        "bbb" {
            $val.InnerXML = "222"
            break;
            }
          default {}
     }
}

$inner=$xml.CreateElement("value");
$inner.InnerXML="333";
$xml.items.AppendChild($inner)

# Note that the $file must be the absolute (including path) name
$xml.Save($file)