Using a DSL to generate XML in PowerShell

Using a DSL to generate XML in PowerShell

  • Comments 3

A while back, Jeffrey posted an article on how to use string expansion and XML casts to build XML documents in-line in a PowerShell script:

http://blogs.msdn.com/powershell/archive/2007/05/29/using-powershell-to-generate-xml-documents.aspx

The overall feel of the approach that Jeffrey described is very much like that of ASP, JSP, PHP on any of the other systems that use “holes” to embed code to dynamically generate document content within the document itself. Ari over at the Windows Core Networking blog had some concerns about this approach for various reasons. You can see his comments at:

http://blogs.msdn.com/wndp/archive/2007/06/15/offtopic-another-way-to-generate-xml-in-powershell.aspx

As an alternative, he suggests a mixed approach that combines strings and the .NET XmlDocument APIs. The result looks like:

$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")) 

Now, while this works fine, it really isn’t much different from what you’d do in C# or VB.Net. In theory, PowerShell is supposed to make things like this easier.

A general approach that languages like Ruby and PowerShell use to simplify these tasks is to create a  DSL or Domain-Specific Language.  (Obligatory plug – I cover some of this in Chapter 8 of my book. Plug completed). The idea is to build a Domain-Specific Language (DSL) or “little language” that is designed to express solutions in a problem domain clearly and concisely. After playing around with notation for an hour or so, I came up with something that let me re-write ARI’s example as:

$doc = ./New-XmlDocument {
    @{
        element = "ns:TestBuild"
        attributes = @{
            Lab = $lab
            BuildNumber = $buildnumber
             SPBuildNumber = $OSBuildNumber
            TimeStamp = $BuildLabString.Split(".")[4]
            SKU = $sku
            Language = $SystemLocale.Split("-")[0].Trim()
            Culture = $SystemLocale.Split("-")[1].Trim()
            Architecture = $Processor
            Type = $Type
         }
     }
 
     @{
         element = "ns:Implementation"
         attributes = @{
             type = "WTTResource"
             ResourceName = $Name
             ResourceId = $ResourceId
             ResourceConfigurationId = $Id
         }
     }
}

$doc.save((Join-path $RolePath "RoleInstance.xml")) 

 

What I’ve done here is create a script New-XmlDocument. This command takes a scriptblock as its only argument. The scriptblock contains a number of hash literals that describe the elements to create. Each hashtable contains two members – the type of element to build and the list of its attributes. The attribute list is, in turn, a hashtable with name/value pairs for each attribute.  A similar approach could be used to add child notes at each level. The script to process this data structure turns out to be quite simple:

param ([scriptblock] $sb)
  # Hard coded for now - should be parameterized 
$doc = [xml] '<?xml version="1.0" encoding="utf-8"?><ns:RoleInstance xmlns:ns="http://namespace.microsoft.com/2007/Whatever"/>'   #
# Execute the scriptblock to get the list of element hashtables
$elements = & $sb 
 
# Now iterate over the list construction elements then adding
# the specified attributes. 
foreach ($e in $elements)
{
    $elem = $doc.CreateElement($e.element)
    foreach ($attr in $e.Attributes.GetEnumerator())
    {
        $elem.SetAttribute($attr.name, $attr.value)
    }
    # The next step would be to recursively construct the
    # children of this node but that's not implemented yet... 
 
    # Finally add this element
    [void] $doc.get_ChildNodes().Item(1).AppendChild($elem)
}
 
 
#
# Return the constructed document...
# $doc

 

While this script is not a complete solution (it doesn’t support building child nodes) it does illustrate how, in a few lines of code, you can build a custom “language” that lets you express the intent of your solution clearly and concisely. You can also take advantage of PowerShell’s use of dynamic scopes to specify “resources” in the document description by simply defining a variable at one level and then using it in a nested scope. You can create “base” attribute descriptions as hashtables stored in these resource variables and then derive complete descriptions by using PowerShell’s ability to add two hashtables together.  Finally, instead of directly encoding all of the literal hashtables, you can could  a function in the body of the document that returns a collection of hashtables simplifying the construction of repeated nodes.

There is one other very useful extension that could be added to this script and that’s schema validation. As each element is constructed, it would be easy to check a hashtable to see if the element is permitted in the document. Once you have the element, additional hashtables can be used to verify that the attributes are correct and are of the correct type, etc. As you might imagine, the schema notation once again lends itself to a “little language” solution, however it is left as an exercise to the reader J

-bruce

===================================================================
Bruce Payette [MSFT] 
Windows PowerShell Tech Lead 
Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx
Windows PowerShell in Action (book): http://manning.com/powershell

 

Attachment: XmlBuilder.zip
Leave a Comment
  • Please add 1 and 6 and type the answer here:
  • Post
  • PingBack from http://blogs.msdn.com/wndp/archive/2007/06/15/offtopic-another-way-to-generate-xml-in-powershell.aspx

  • I cannot find the sample in your book?

  • I'm not sure where to put this request (Maybe there's a Connect forum for PowerShell?):

    I would love to see the following enhancement to PowerShell's XML handling.  Given the following XML (stored in $xml):

    <a>

     <b>

       <c>1</c>

       <c>2</c>

     </b>

     <b>

       <c>3</c>

     </b>

     <b>

       <c>4</c>

       <c>5</c>

       <c>6</c>

     </b>

    </a>

    I would like to be able to do this:

    $xml.a.b.c

    to get a stream of the contents of each "c" node, instead of this:

    $xml.a.b | % { $_.c }

    I'm not asking for full XSLT without using .NET methods or the ability shove ? and % into the middle of the property expressions (although that might be cool), I just want a behavior where . hides the unfiltered / untransformed foreach.

    Is this already possible in 2.0?

Page 1 of 1 (3 items)