I'm trying to do something pretty simple with powershell and xml but haven't a bit of trouble. Basically I'm trying to take the following xml... and sort the machine elements by name. Then put them back into the XML so that I can save back to a file.
Sorting seems to be working if output the $new object however, during the replacechild it's complaining "Cannot convert argument "0", with value: "System.Object[]" for "ReplaceChild" to type "System.Xml.XmlNode" : "Cannot convert the "System.Object[]" to type "System.Xml.XmlNode".
If I do a Get-Member on both $orig and $new they both say they are of type XMLElement which I believe inherits from XMLNode.
What am I missing guys? Driving me nuts. Thanks for your help!
<company>
<stuff>
</stuff>
<machines>
<machine>
<name>ca</name>
<b>123</b>
<c>123</c>
</machine>
<machine>
<name>ad</name>
<b>234</b>
<c>234</c>
</machine>
<machine>
<name>be</name>
<b>345</b>
<c>345</c>
</machine>
</machines>
<otherstuff>
</otherstuff>
</company>
[xml]$xml = (get-content Company.xml)
[XmlNode]$orig = $xml.Company.Machines
[XmlNode]$new = ($orig.Machine | sort Name )
$xml.Company.ReplaceChild($new, $orig)
There are various problems here. One is that your sort
returns a list of xml elements rather than a single xml element. Another problem is that it returns the original xml elements rather than copies, so any manipulation of the xml DOM that you do using them will also affect the result.
Here's a simple way to get what you want. Sort in reverse order, and then insert each node in turn in front of the others. Each time you insert a node from the result of the sort it will automatically remove it from the original set of node:
[xml]$xml = @"
<company>
<stuff>
</stuff>
<machines>
<machine>
<name>ca</name>
<b>123</b>
<c>123</c>
</machine>
<machine>
<name>ad</name>
<b>234</b>
<c>234</c>
</machine>
<machine>
<name>be</name>
<b>345</b>
<c>345</c>
</machine>
</machines>
<otherstuff>
</otherstuff>
</company>
"@
[System.Xml.XmlNode]$orig = $xml.Company.Machines
$orig.Machine | sort Name -Descending |
foreach { [void]$xml.company.machines.PrependChild($_) }
$xml.company.machines.machine
Edit: The pipeline can also be written with an ascending sort (as David Martin pointed out), and you can reduce typing by using the node in the variable:
$orig.Machine | sort Name | % { [void]$orig.AppendChild($_) }
The reason this doesn't work is that $xml.Company.Machines is a single XmlElement. To get the collection of machine elements, you need to use $xml.Company.Machines.Machine, so this is the one you want to sort.
However, the ReplaceChild method doesn't take a collection, so I'm guessing you'll have to remove all children and then append them back in the order you want them. Something like the following should work fine:
[xml]$xml = Get-Content .\Company.xml
$machines = $xml.company.machines
$orderedMachineCollection = $machines.machine | Sort Name
$machines.RemoveAll()
$orderedMachineCollection | foreach { $machines.AppendChild($_) } | Out-Null
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With