Using a DSL to generate XML in PowerShell

Steve Lee

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] “” $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] ‘‘ #

# 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

XmlBuilder.zip

0 comments

Discussion is closed.

Feedback usabilla icon