How to use InsertAfter with PowerShell

Keith picture Keith · Jun 17, 2011 · Viewed 8.2k times · Source

I have some xml files where I want to insert the contents of one xml file into another. I thought I'd use LastChild and the InsertAfter method to accomplish this. So far it's not working for me.

Here is the parent.xml file:

<manifest>
  <manifestExecution>
    <assetDetail>
      <fileAsset fileAssetGuid="parentguid1">
    <parentfile1 />
      </fileAsset>
      <fileAsset fileAssetGuid="parentguid2">
    <parentfile2 />
      </fileAsset>
    </assetDetail>
  </manifestExecution>
</manifest>

And here is the child.xml file:

<manifest>
  <manifestExecution>
    <assetDetail>
     <fileAsset fileAssetGuid="childguid1">
    <childfile1 />
     </fileAsset>
    </assetDetail>
  </manifestExecution>
</manifest>

What I want to do is select the fileAsset node(s) from child.xml and insert into parent.xml after the last fileAsset node in parent.xml.

Here is my test code:

$parent = [xml] (Get-Content d:\temp\parent.xml)
$parentnode = $parent.manifest.manifestExecution.assetDetail
$child = [xml] (Get-Content d:\temp\child.xml)
$childnode = $child.manifest.manifestExecution.assetDetail.InnerXml
$parentnode.InsertAfter($childnode, $parentnode.LastChild)

Here is the error msg I'm getting:

Cannot convert argument "0", with value: "<fileAsset fileAssetGuid="childguid1"> <childfile1 /></fileAsset>", for "InsertAfter" to type "System.Xml.XmlNode": "Cannot conver t the "<fileAsset fileAssetGuid="childguid1"><childfile1 /></fileAsset>" value of type "System.String" to type "System.Xml.XmlNode"." At line:5 char:24 + $parentnode.InsertAfter <<<< ($childnode, $parentnode.LastChild) + CategoryInfo : NotSpecified: (:) [], MethodException + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

What am I doing wrong?

Answer

Joel B Fant picture Joel B Fant · Jun 17, 2011

You need to iterate through $childnode's children, remove them from their parent, and import them into the new document context ($child and $parent are different XmlDocument instances) before appending to $parentnode.

This will append all fileAsset nodes from $childnode into $parentnode.

$parent = [xml](get-content d:\temp\parent.xml)
$parentnode = $parent.manifest.manifestexecution.assetdetail
$child = [xml](get-content d:\temp\child.xml)
$childnode = $child.manifest.manifestexecution.assetdetail

while ($childnode.haschildnodes) {
  $cn = $childnode.firstchild
  $cn = $childnode.removechild($cn)
  $cn = $parentnode.ownerdocument.importnode($cn, $true)
  $parentnode.appendchild($cn)
}

Fortunately, most of these methods return the same XmlNode or a new version of it, so the body of the while loop could chained together like this:

$parentnode.appendchild( $parentnode.ownerdocument.importnode( $childnode.removechild( $childnode.firstchild ), $true ))

InsertAfter(newChild,referenceChild) could also work, but would be done a little differently since it also needs a reference to the the node that it will be inserted after.