{"id":17666,"date":"2007-06-17T12:24:48","date_gmt":"2007-06-17T20:24:48","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/powershell\/?p=17666"},"modified":"2019-05-16T12:26:53","modified_gmt":"2019-05-16T20:26:53","slug":"using-a-dsl-to-generate-xml-in-powershell","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell\/using-a-dsl-to-generate-xml-in-powershell\/","title":{"rendered":"Using a DSL to generate XML in PowerShell"},"content":{"rendered":"<p>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:\nhttp:\/\/blogs.msdn.com\/powershell\/archive\/2007\/05\/29\/using-powershell-to-generate-xml-documents.aspx\nThe 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 \u201choles\u201d 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:\nhttp:\/\/blogs.msdn.com\/wndp\/archive\/2007\/06\/15\/offtopic-another-way-to-generate-xml-in-powershell.aspx\nAs an alternative, he suggests a mixed approach that combines strings and the .NET XmlDocument APIs. The result looks like:\n$doc = [xml] &#8220;<?xml version=\"\"1.0\"\" encoding=\"\"utf-8\"\"?><ns:RoleInstance xmlns:ns=\"\"http:\/\/namespace.microsoft.com\/2007\/Whatever\"\"\/>&#8221; \n$elem = $doc.CreateElement(&#8220;ns:TestBuild&#8221;)\n$elem.SetAttribute(&#8220;Product&#8221;, $Product);\n$elem.SetAttribute(&#8220;Lab&#8221;, $Lab);\n$elem.SetAttribute(&#8220;BuildNumber&#8221;, $OSBuildNumber);\n$elem.SetAttribute(&#8220;SPBuildNumber&#8221;, $SPBuildNumber);\n$elem.SetAttribute(&#8220;TimeStamp&#8221;, $BuildLabString.Split(&#8220;.&#8221;)[4]);\n$elem.SetAttribute(&#8220;SKU&#8221;, $SKU);\n$elem.SetAttribute(&#8220;Language&#8221;, $SystemLocale.Split(&#8220;-&#8220;)[0].Trim());\n$elem.SetAttribute(&#8220;Culture&#8221;, $SystemLocale.Split(&#8220;-&#8220;)[1].Trim());\n$elem.SetAttribute(&#8220;Architecture&#8221;, $Processor);\n$elem.SetAttribute(&#8220;Type&#8221;, $Type);\n$doc.get_ChildNodes().Item(1).AppendChild($elem) | out-null\n$elem = $doc.CreateElement(&#8220;ns:Implementation&#8221;);\n$elem.SetAttribute(&#8220;type&#8221;, &#8220;WTTResource&#8221;);\n$elem.SetAttribute(&#8220;ResourceName&#8221;, $Name );\n$elem.SetAttribute(&#8220;ResourceId&#8221;, $ResourceId );\n$elem.SetAttribute(&#8220;ResourceConfigurationId&#8221;, $Id );                \n$doc.get_ChildNodes().Item(1).AppendChild($elem) | out-null \n$doc.get_ChildNodes().Item(1).SetAttribute(&#8220;GUID&#8221;, [GUID]::NewGuid().ToString() );\n$doc.save((Join-path $RolePath &#8220;RoleInstance.xml&#8221;))  \nNow, while this works fine, it really isn\u2019t much different from what you\u2019d do in C# or VB.Net. In theory, PowerShell is supposed to make things like this easier. \nA general approach that languages like Ruby and PowerShell use to simplify these tasks is to create a  DSL or Domain-Specific Language.  (Obligatory plug \u2013 I cover some of this in Chapter 8 of my book. Plug completed). The idea is to build a Domain-Specific Language (DSL) or \u201clittle language\u201d 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\u2019s example as:\n$doc = .\/New-XmlDocument {\n    @{<\/p>\n<p>        element = &#8220;ns:TestBuild&#8221;<\/p>\n<p>        attributes = @{<\/p>\n<p>            Lab = $lab<\/p>\n<p>            BuildNumber = $buildnumber<\/p>\n<p>             SPBuildNumber = $OSBuildNumber<\/p>\n<p>            TimeStamp = $BuildLabString.Split(&#8220;.&#8221;)[4]<\/p>\n<p>            SKU = $sku<\/p>\n<p>            Language = $SystemLocale.Split(&#8220;-&#8220;)[0].Trim()<\/p>\n<p>            Culture = $SystemLocale.Split(&#8220;-&#8220;)[1].Trim()<\/p>\n<p>            Architecture = $Processor<\/p>\n<p>            Type = $Type<\/p>\n<p>         }<\/p>\n<p>     }<\/p>\n<p>     @{<\/p>\n<p>         element = &#8220;ns:Implementation&#8221;<\/p>\n<p>         attributes = @{<\/p>\n<p>             type = &#8220;WTTResource&#8221;<\/p>\n<p>             ResourceName = $Name<\/p>\n<p>             ResourceId = $ResourceId<\/p>\n<p>             ResourceConfigurationId = $Id<\/p>\n<p>         }<\/p>\n<p>     }<\/p>\n<p>}<\/p>\n<p>$doc.save((Join-path $RolePath &#8220;RoleInstance.xml&#8221;))  <\/p>\n<p>What I\u2019ve 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 \u2013 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:\nparam ([scriptblock] $sb)<\/p>\n<p>  # Hard coded for now &#8211; should be parameterized<\/p>\n<p>$doc = [xml] &#8216;<?xml version=\"1.0\" encoding=\"utf-8\"?><ns:RoleInstance xmlns:ns=\"http:\/\/namespace.microsoft.com\/2007\/Whatever\"\/>&#8216;   #<\/p>\n<p># Execute the scriptblock to get the list of element hashtables<\/p>\n<p>$elements = &#038; $sb <\/p>\n<p># Now iterate over the list construction elements then adding<\/p>\n<p># the specified attributes. <\/p>\n<p>foreach ($e in $elements)<\/p>\n<p>{<\/p>\n<p>    $elem = $doc.CreateElement($e.element)<\/p>\n<p>    foreach ($attr in $e.Attributes.GetEnumerator())<\/p>\n<p>    {<\/p>\n<p>        $elem.SetAttribute($attr.name, $attr.value)<\/p>\n<p>    }<\/p>\n<p>    # The next step would be to recursively construct the<\/p>\n<p>    # children of this node but that&#8217;s not implemented yet&#8230; <\/p>\n<p>    # Finally add this element<\/p>\n<p>    [void] $doc.get_ChildNodes().Item(1).AppendChild($elem)<\/p>\n<p>}<\/p>\n<p>#<\/p>\n<p># Return the constructed document&#8230;<\/p>\n<p># $doc<\/p>\n<p>While this script is not a complete solution (it doesn\u2019t support building child nodes) it does illustrate how, in a few lines of code, you can build a custom \u201clanguage\u201d that lets you express the intent of your solution clearly and concisely. You can also take advantage of PowerShell\u2019s use of dynamic scopes to specify \u201cresources\u201d in the document description by simply defining a variable at one level and then using it in a nested scope. You can create \u201cbase\u201d attribute descriptions as hashtables stored in these resource variables and then derive complete descriptions by using PowerShell\u2019s 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.\nThere is one other very useful extension that could be added to this script and that\u2019s 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 \u201clittle language\u201d solution, however it is left as an exercise to the reader J\n-bruce\n===================================================================\nBruce Payette [MSFT] <\/p>\n<p>Windows PowerShell Tech Lead <\/p>\n<p>Visit the Windows PowerShell Team blog at: http:\/\/blogs.msdn.com\/PowerShell<\/p>\n<p>Visit the Windows PowerShell ScriptCenter at: http:\/\/www.microsoft.com\/technet\/scriptcenter\/hubs\/msh.mspx<\/p>\n<p>Windows PowerShell in Action (book): http:\/\/manning.com\/powershell<\/p>\n<p>XmlBuilder.zip<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 \u201choles\u201d to embed [&hellip;]<\/p>\n","protected":false},"author":685,"featured_media":13641,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[33,11,22],"class_list":["post-17666","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-language","tag-philosophy","tag-typexml"],"acf":[],"blog_post_summary":"<p>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 \u201choles\u201d to embed [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/17666","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/users\/685"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/comments?post=17666"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/17666\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media\/13641"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media?parent=17666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/categories?post=17666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/tags?post=17666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}