TheGeekery

The Usual Tech Ramblings

PowerShell, XML, and Nested Elements

While working on a small PowerShell script to create Windows Media Player playlist files, I stumbled across an odd occurence when working with nested elements. Using the syntax $xml.childnode.childnode2 you cannot add additional child nodes as it treats childnode2 as a string. Using the SelectSingleNode function, it’s possible to work around this.

For a bit of background, a Windows Media Player playlist file (wpl) is really XML, and looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?wpl version="1.0"?>
<smil>
  <head>
      <meta name="Generator" content="Microsoft Windows Media Player -- 12.0.7601.17514" />
      <meta name="ItemCount" content="1" />
      <author />
      <title>PowerScripting Podcast</title>
  </head>
  <body>
      <seq>
          <media src="..\PowerScripting Podcast\PSPodcast-138.mp3" />
      </seq>
  </body>
</smil>

In this case ‘smil’ is an XML Element, ‘head’ and ‘body’ is are also XML elements, but they are children of smil (or child nodes). In most cases, you don’t have to go multiple levels deep, but it can entirely depend on the data set being represented by the XML. In this case there is multiple levels, so we stumble across this odd issue.

If I wanted to add a child node to the ‘smil’ node, the code might look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
$xml = @'
<?wpl version="1.0"?>
<smil>
  <head />
  <body />
</smil>
'@

$body = [xml]$xml

$newnode = $body.CreateElement('title')
$newnode.InnerText = 'PowerScripting Podcast'
$body.smil.AppendChild($newnode)

I’ve truncated some of the information out of the XML to make the code a little shorter, but the output of the above would end up with something that looks like this:

1
2
3
4
5
6
<?wpl version="1.0"?>
<smil>
  <head />
  <body />
  <title>PowerScripting Postcast</title>
</smil>

The title is in the wrong place, I want it inside head, so we should, by assumption, be able to change line 13 to read:

start:13
1
$body.smil.head.AppendChild($newnode)

However this returns an error that $body.smil.head is actually a string, and that AppendChild is an invalid method of System.String.

1
Method invocation failed because [System.String] doesn't contain a method named 'AppendChild'.

This is where the SelectSingleNode function comes in handy. It allows you to, using XML select statements, find an individual node, in this case we’re going for the ‘head’ node. So we’ll change line 13 to read this instead:

start:5
1
$body.SelectSingleNode("//head").AppendChild($newnode)

Now our XML looks like this:

1
2
3
4
5
6
7
<?wpl version="1.0"?>
<smil>
  <head>
      <title>PowerScripting Postcast</title>
  </head>
  <body />
</smil>

The whole reason for this exercise? I used to use Juice, but for some reason it kept downloading the same podcasts over and over again, stuffing a GUID on the filename. One cool feature of Juice was that it created WPL files as it downloaded the podcasts. This caused headaches when it duplicated all the downloads as one of my podcasts had 3 original files in it, but duplicated them about 9 times. I switched to gPodder, which is written in Python but doesn’t have the ability to create playlists1. So I set about writing a PowerShell script to read the SQLite database, and creating one. I’ll write another post later about using PowerShell to talk to SQLite databases.


  1. At least not that I’ve found yet.

Comments