I am dealing with some XML and PowerShell for this thing I am working on. Thought it would be worth giving a brief intro to XML so the concepts are clear to myself and anyone else.
From this site, a simple XML based food menu (which I’ve slightly modified):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="ISO8859-1" ?> <breakfast-menu> <food offer="Today's Special"> <name>Belgian Waffles</name> <price>$5.95</price> <description>two of our famous Belgian Waffles with plenty of real maple syrup</description> <calories>650</calories> </food> <food> <name>Strawberry Belgian Waffles</name> <price>$7.95</price> <description>light Belgian waffles covered with strawberrys and whipped cream</description> <calories>900</calories> </food> <food> <name>Berry-Berry Belgian Waffles</name> <price>$8.95</price> <description>light Belgian waffles covered with an assortment of fresh berries and whipped cream</description> <calories>900</calories> </food> <food> <name>French Toast</name> <price>$4.50</price> <description>thick slices made from our homemade sourdough bread</description> <calories>600</calories> </food> <food> <name>Homestyle Breakfast</name> <price>$6.95</price> <description>two eggs, bacon or sausage, toast, and our ever-popular hash browns</description> <calories>950</calories> </food> </breakfast-menu> |
It’s obvious what the food menu is about. It’s a breakfast menu. It consists of food entries. Each food entry consists of the name of the food, its price, a description, and calories. One of these food items is today’s special, and is marked accordingly. Straightforward.
Items such as <name>
and </name>
are called tags. You have an opening tag <name>
and a closing tag
</name>
. Between these tags you have an some content (e.g. “French Toast”). Tags can have attributes (e.g. offer = “Today’s Special!”). The entirety of an opening and ending tag, their attributes, and the content enclosed by these tags is called an element.
In the example above, the element breakfast-menu
is the the root element. If you visualize the listing above as a tree, you can see it all starts from breakfast-menu
. This root element has 5 children elements, each of which is a food
element. These children elements are also sibling elements to each other. Each food element in turn has 4 different children elements (name
, price
, etc), who are themselves sibling elements to each other.
This site has a really good intro to XML. And this site is is a good reference on the various types such as elements, attributes, CDATA, etc.
XML Notepad is a good way to view and edit/ create XML documents. It gives a hierarchical structure too that’s easy to understand. Here’s the above XML viewed through XML Notepad.
Notice how XML Notepad puts some of the elements as folders. To create a new sibling element to the food
element you would right click on the parent element breakfast-menu
and create a new child element.
This will create an element that does not look like a folder. But if you right click this element and create a new child, then XML Notepad changes the icon to look like a folder.
Just thought I’d point this out so it’s clear. An element containing another element has nothing special to it. In XML Notepad or when viewing the XML through a browser such as Internet Explorer, Firefox, etc they might be formatted differently, but there’s nothing special about them. Everyone’s an element just that some have children and so appear different.
In PowerShell you can read an XML file by casting it into an [xml]
accelerator thus:
1 |
[xml]$SomeVariable = Get-Content .\path\to\file.xml |
Using the above XML, for instance, I can then do things like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
PS> [xml]$temp = Get-Content .\tmp.xml PS> $temp.'breakfast-menu' food ---- {Belgian Waffles, Strawberry Belgian Waffles, Berry-Berry Belgian Waffles, French Toast...} PS> $temp.'breakfast-menu'.food name price description calories ---- ----- ----------- -------- Belgian Waffles $5.95 two of our famous Belgian Waffles with... 650 Strawberry Belgian Waffles $7.95 light Belgian waffles covered with str... 900 Berry-Berry Belgian Waffles $8.95 light Belgian waffles covered with an ... 900 French Toast $4.50 thick slices made from our homemade so... 600 Homestyle Breakfast $6.95 two eggs, bacon or sausage, toast, and... 950 |
Here’s a list of methods available to this object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
PS> $temp | Get-Member | ?{ $_.MemberType -eq "Method" } | Format-Wide -Column 3 AppendChild Clone CloneNode CreateAttribute CreateCDataSection CreateComment CreateDocumentFragment CreateDocumentType CreateElement CreateEntityReference CreateNavigator CreateNode CreateProcessingInstruction CreateSignificantWhitespace CreateTextNode CreateWhitespace CreateXmlDeclaration Equals GetElementById GetElementsByTagName GetEnumerator GetHashCode GetNamespaceOfPrefix GetPrefixOfNamespace GetType ImportNode InsertAfter InsertBefore Load LoadXml Normalize PrependChild ReadNode RemoveAll RemoveChild ReplaceChild Save SelectNodes SelectSingleNode Supports Validate WriteContentTo WriteTo |
The methods vary if you are looking at a specific element:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
PS> $temp.'breakfast-menu'.food | Get-Member | ?{ $_.MemberType -eq "Method" } | Format-Wide -Column 3 AppendChild Clone CloneNode CreateNavigator Equals GetAttribute GetAttributeNode GetElementsByTagName GetEnumerator GetHashCode GetNamespaceOfPrefix GetPrefixOfNamespace GetType HasAttribute InsertAfter InsertBefore Normalize PrependChild RemoveAll RemoveAllAttributes RemoveAttribute RemoveAttributeAt RemoveAttributeNode RemoveChild ReplaceChild SelectNodes SelectSingleNode SetAttribute SetAttributeNode Supports WriteContentTo WriteTo |
Say I want to add a new food
element to the breakfast-menu
element. The AppendChild()
method looks interesting.
You can’t simply add a child by giving a name because it expects as input an object of type Xml.XmlNode
.
1 2 3 4 5 |
PS> $temp.'breakfast-menu'.AppendChild OverloadDefinitions ------------------- System.Xml.XmlNode AppendChild(System.Xml.XmlNode newChild) |
So you have to first create the element separately and then pass that to the AppendChild()
method.
Only the XML root object has methods to create new elements none of the elements below it have (notice the $temp
output above). So I start from there:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# create variables that hold new elements called food, name, and price PS> $newFoodElem = $temp.CreateElement("food") PS> $newNameElem = $temp.CreateElement("name") PS> $newPriceElem = $temp.CreateElement("price") # create variables holding content for name and price PS> $newNameContent = $temp.CreateTextNode("Italian Waffles") PS> $newPriceContent = $temp.CreateTextNode("USD 16.00") # beware of using $ as it gets treated as a variable! # update, I learnt later that it's also possible to add content to an element this way: # $newNameElem.InnerText = "Italian Waffles" # $newPriceElem.InnterText = "USD 16.00" # If I do this way I don't have to append as below either. # append the name & price content to the elements themselves PS> $newNameElem.AppendChild($newNameContent) Name : #text LocalName : #text NodeType : Text ParentNode : name Value : Italian Waffles InnerText : Italian Waffles Data : Italian Waffles Length : 15 PreviousSibling : NextSibling : ChildNodes : {} Attributes : OwnerDocument : #document FirstChild : LastChild : HasChildNodes : False NamespaceURI : Prefix : IsReadOnly : False OuterXml : Italian Waffles InnerXml : SchemaInfo : System.Xml.Schema.XmlSchemaInfo BaseURI : PS> $newPriceElem.AppendChild($newPriceContent) Name : #text LocalName : #text NodeType : Text ParentNode : price Value : USD 16.00 InnerText : USD 16.00 Data : USD 16.00 Length : 3 PreviousSibling : NextSibling : ChildNodes : {} Attributes : OwnerDocument : #document FirstChild : LastChild : HasChildNodes : False NamespaceURI : Prefix : IsReadOnly : False OuterXml : USD 16.00 InnerXml : SchemaInfo : System.Xml.Schema.XmlSchemaInfo BaseURI : # now append these two elements to the parent food element PS> $newFoodElem.AppendChild($newNameElem) #text ----- Italian Waffles PS> $newFoodElem.AppendChild($newPriceElem) #text ----- USD 16.00 # confirm PS> $newFoodElem name price ---- ----- Italian Waffles USD 16.00 # and finally append this food element to breakfast-menu! PS> $temp.'breakfast-menu'.AppendChild($newFoodElem) name price ---- ----- Italian Waffles USD 16.00 # confirm all is well PS> $temp.'breakfast-menu'.food offer : Today's Special name : Belgian Waffles price : $5.95 description : two of our famous Belgian Waffles with plenty of real maple syrup calories : 650 name : Strawberry Belgian Waffles price : $7.95 description : light Belgian waffles covered with strawberrys and whipped cream calories : 900 name : Berry-Berry Belgian Waffles price : $8.95 description : light Belgian waffles covered with an assortment of fresh berries and whipped cream calories : 900 name : French Toast price : $4.50 description : thick slices made from our homemade sourdough bread calories : 600 name : Homestyle Breakfast price : $6.95 description : two eggs, bacon or sausage, toast, and our ever-popular hash browns calories : 950 name : Italian Waffles price : USD 16.00 |
Just for kicks here’s a snippet of the last two entries from the XML file itself:
1 2 |
PS> $temp.Save("C:\Users\Rakhesh\Desktop\blah.xml") PS> Get-Content .\blah.xml |
1 2 3 4 5 6 7 8 9 10 |
<food> <name>Homestyle Breakfast</name> <price>$6.95</price> <description>two eggs, bacon or sausage, toast, and our ever-popular hash browns</description > <calories>950</calories> </food> <food> <name>Italian Waffles</name> <price>USD 16.00</price> </food> |
Yay! That’s a crazy amount of work though just to get a new element added!
Before I forget, while writing this post I came across the following links. Good stuff:
- A Stack Overflow post on pretty much what I described above (but in a more concise form, so easier to read)
- Posts 1, 2, and 3 on XML and PowerShell from the The Scripting Guys
- A post by Jeffrey Snover on generating XML documents from PowerShell
- Yet another Stack Overflow post with an answer that’s good to keep in mind
Removing an element seems to be easier. Each element has a RemoveAll()
method that removes itself. So I get the element I want and invoke the method on itself:
1 |
PS> ($temp.'breakfast-menu'.food | ?{ $_.name -eq "Italian Waffles" }).RemoveAll() |
Or since the result of the $temp.'breakfast-menu.food'
element is an array of child elements, I can directly reference the one I want and do RemoveAll()
.
1 |
PS> $temp.'breakfast-menu'.food[2].RemoveAll() |
Or I can assign a variable to the child I want to remove and then use the RemoveChild()
method.
1 2 3 4 5 6 |
PS> $delElem = $temp.'breakfast-menu'.food[1] PS> $temp.'breakfast-menu'.RemoveChild($delElem) name price description calories ---- ----- ----------- -------- Strawberry Belgian Wa... $7.95 light Belgian waffle... 900 |
That’s all for now!