Creating Custom XML from .NET and PowerShell

In my previous article, I walked through piecing together a custom XML file using ConvertTo-Xml. Obviously, this technique works, but it involves a lot of string parsing and manipulation and maybe even some regular expressions. In some ways, I find it to be too “busy.” So let me demonstrate another technique to assemble an XML document from scratch using the .NET Framework.

Before I jump in, I want to head off any comments from readers who are just jumping into the series — if you are intending to save output from a PowerShell expression to XML so that you can re-use it in another PowerShell session, then Export-CliXml is all you need. I covered that in previous articles.

Instead, let’s create an XML file with information pulled from PowerShell that you might need to use outside of PowerShell. You could still bring it back in a PowerShell session, it just requires a few more steps, which I’ll cover in another article. My goal in this article is to create the same type of XML file as I did in the last article. I’m going to get some system information from WMI using Get-CimInstance on a collection of servers.

The XML file will eventually contain this information in a structured format. The first step is to create a blank XML document object. The .NET Framework has a complete XML library under System.XML. Here’s how to create the document.

PowerShell

1

[xml]$Doc=New-ObjectSystem.Xml.XmlDocument

This is now just another object, which means you can pipe it to Get-Member to learn more about it.

If you’ve been paying attention, you’ll notice that I keep referring to XML as being hierarchical. In other words, the document has layers of information. These “layers” are referred to Nodes. Some nodes can be annotated with attributes that provide additional information or context. Within a node you might have one or more elements.

PowerShell

1

2

3

4

5

6

7

<?xml version="1.0"encoding="UTF-8"?>

<Root>

<Node attribute1="value"attribute2="value">

<Element>SomeValue</Element>

<Element2>SomeOtherValue</Element2>

</Node>

</Root>

When creating an XML document from scratch, you almost do it from the inside out. You create child and parent “containers” and then add each child to the parent as you move up the hierarchy. Let me show you.

First, I want to add an XML declaration to the document. This will show the XML version number and encoding.

PowerShell

1

$dec=$Doc.CreateXmlDeclaration("1.0","UTF-8",$null)

This object needs to be added to the document.

PowerShell

1

$doc.AppendChild($dec)

I thought I would also add a comment to the document, which I’ll define in a here string.

PowerShell

1

2

3

4

5

6

7

$text=@"

Server Inventory Report

Generated $(Get-Date)

v1.0

"@

I could use the CreateComment() method from $Doc, save the result to a variable and then append it, or I can do it all in one line.

PowerShell

1

$doc.AppendChild($doc.CreateComment($text))

Here’s what it looks like thus far.

An XML Document (Image Credit: Jeff Hicks)

What’s missing is a root node. I’ll create a node with the name of Computers.

PowerShell

1

$root=$doc.CreateNode("element","Computers",$null)

Now I need to go through my data and create the necessary child nodes and elements. My goal is to have a Computer node for each server, so I’ll use a ForEach loop to process my list of servers. The first things I’ll do is create the child node.

PowerShell

1

2

foreach($computerin$Computers){

$c=$doc.CreateNode("element","Computer",$null)

For the sake of education, I’m also going to create an attribute called Name, which will have the computername as a value.

PowerShell

1

$c.SetAttribute("Name",$computer)

Next, I will create a node for operating system information.

PowerShell

1

$osnode=$doc.CreateNode("element","OperatingSystem",$null)

Obviously, I need data, so I’ll get the related information from computername as the loop is currently processing.

PowerShell

1

$data=$os.where({$_.computername-eq$Computer})

I’m hoping that even though I’m using some new objects and methods, the syntax is familiar to you. Now we get to change gears a bit.

In the <OperatingSystem></OperatingSystem> node that I’ve defined, I want to create a set of elements based on the properties from my WMI query. I’ll do this by creating an element.

PowerShell

1

$e=$doc.CreateElement("Name")

The element can be called whatever you want. In my case, I am using Name and I intend the Caption property from WMI to be the value.

PowerShell

1

$e.InnerText=$data.Caption

Once created, I need to add it to the parent.

PowerShell

1

$osnode.AppendChild($e)

The remaining WMI properties don’t need any transformation. The property name can be the element name so I’ll loop through the properties and repeat the previous steps.

PowerShell

1

2

3

4

5

"Version","InstallDate","OSArchitecture"|foreach{

$e=$doc.CreateElement($_)

$e.InnerText=$data.$_

$osnode.AppendChild($e)

}

At this point the $osnode is complete and I need to add it to its parent.

PowerShell

1

$c.AppendChild($osnode)

The resulting XML will look like this:

the XML result (Image Credit: Jeff Hicks)

Once you understand the process, doing the same thing for computer system information is almost the same.

But, each server has a different set of services. That’s what is in $data. Each item in $data needs to be its own element and I want each WMI property to be the value.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

foreach($itemin$data){

#create a service node

$s=$doc.CreateNode("element","Service",$null)

#create elements for each property

$props|Foreach{

$e=$doc.CreateElement($_)

$e.InnerText=$item.$_

$s.AppendChild($e)

}

#add to parent

$svcnode.AppendChild($s)

}

This process will build up the $svcnode variable to contain elements describing each service. Of course, this node also needs to be appended to its parent, and then I need to append the entire computer node to the root.

PowerShell

1

2

3

4

5

#add to grandparent

$c.AppendChild($svcnode)

#append to root

$root.AppendChild($c)

Almost done. I still need to append the root node to the document itself after processing all the computer names.

PowerShell

1

2

#add root to the document

$doc.AppendChild($root)|Out-Null

Keeping track of parent and child objects is the hardest part of creating an XML document from scratch.

The final step is to save the document.

PowerShell

1

$doc.save("d:\temp\custom.xml")

This looks like a lot of work, and frankly, some of it is. But the more you work with this, the easier it will become. Let me give you the final and complete code that I saved in a script file.

As you look through this code, you’ll notice I’ve used Out-Null in many places. Many of the XML methods will write something to the pipeline. I didn’t want that output, so I’m sending the results to null.

When I run the script, I’ll get a final XML file like this:

The final XML (Image Credit: Jeff Hicks)

My intention in this article isn’t to demonstrate how to create an inventory report, but rather how to use the different parts of the XML library from the .NET Framework. Use what I’ve demonstrated here as a starting point for your own scripting projects.

In the next article, we’ll look at how to bring all of this XML data back to life in PowerShell.

MEMBER LOGIN:

BECOME A PETRI MEMBER:

About the Contributor

Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, teacher and consultant. Jeff has written for numerous online sites and print publications and is a frequent speaker at technology conferences and user groups. His latest book is PowerShell Scripting and Toolmaking.