I recently needed to generate XML from PowerShell and was disappointed to see the PowerShell blog use the old ASP model of doing text insertion into the middle of a big string. It might be the tester in me, or the security training, but with some unexpected input you can easily end up with malformed XML or even worse maliciously malformed XML. The other extreme would be to use .Net XML APIs to build up the XML doc from scratch, but I didn't like how much redundant code I would need to build the xml header and I didn't find any good sample code in C# to create an XML document from scratch, so I ended up developing a middle approach. Start with a string that has nothing but the xml header and an empty top level element, convert that over to an XMLDocument, use the APIs to add data and then write out the doc to the disk.

For example:

$doc = [xml] "<?xml version=""1.0"" encoding=""utf-8""?><ns:RoleInstance xmlns:ns=""http://namespace.microsoft.com/2007/Whatever""/>"

$elem = $doc.CreateElement("ns:TestBuild")
$elem.SetAttribute("Product", $Product);
$elem.SetAttribute("Lab", $Lab);
$elem.SetAttribute("BuildNumber", $OSBuildNumber);
$elem.SetAttribute("SPBuildNumber", $SPBuildNumber);
$elem.SetAttribute("TimeStamp", $BuildLabString.Split(".")[4]);
$elem.SetAttribute("SKU", $SKU);
$elem.SetAttribute("Language", $SystemLocale.Split("-")[0].Trim());
$elem.SetAttribute("Culture", $SystemLocale.Split("-")[1].Trim());
$elem.SetAttribute("Architecture", $Processor);
$elem.SetAttribute("Type", $Type);
$doc.get_ChildNodes().Item(1).AppendChild($elem) | out-null

$elem = $doc.CreateElement("ns:Implementation");
$elem.SetAttribute("type", "WTTResource");
$elem.SetAttribute("ResourceName", $Name );
$elem.SetAttribute("ResourceId", $ResourceId );
$elem.SetAttribute("ResourceConfigurationId", $Id );               
$doc.get_ChildNodes().Item(1).AppendChild($elem) | out-null

$doc.get_ChildNodes().Item(1).SetAttribute("GUID", [GUID]::NewGuid().ToString() );
$doc.save((Join-path $RolePath "RoleInstance.xml")) 

This code uses CreateElement and SetAttribute and then associates as a child to the 2nd item in the doc ($doc.get_ChildNodes().Item(1)) which is my RoleInstance tag. By using the XML APIs I can feel confident that the information in each attribute gets properly encoded and I keep my document well formed. The only down side is that it is slightly harder to visualize the structure of the resulting XML.

  -- Ari

Update: Bruce over at the powershell blog teaches me a new trick using Domain Specific Languages to do a cleaner job of constructing the XML. Thanks Bruce!