Last weekend I decided I needed to improve my Powershell scripting skills for a technical training I’m attending this week and started working on a script that could download, install and configure several applications unattended on demand. More on that script in a later blog post, for now I will focus on a specific feature I needed to script in order to make it all work.

One of the applications was still using a .ini file to configure some parameters of the tool and by nature of the script, I wanted to modify these values. One could use a search & replace method to change values in a .ini file (which is of course a simple text based file). In my search to easily parse a .ini file, I found several functions that could read the file and insert the values into hashtables. This makes values usable in a variable like $inihash[sectionname][keyname] = “value” however due to the nature of hashtables, it will sort the values in it’s hashtable by the hashvalue of it’s content. As most of us know, a hash value is different per object, per runtime, per system and will generate a unsorted mess. Inserting a .ini file with key1, key2, key3 might end up as key2, key1, key3 and there is no real way to circumvent this.

I was trying to work out the solution by working with hashtables, arrays and more .net objects in order to store the .ini values in a neat and sorted way. One could make a array with a hashtable with another nested array and hashtable but this would greatly increase complexity and reduce code friendliness as the $ini[section][key] notation would no longer function.

The other day I was casually discussing the problem with a fellow trainee and he suggested to (ab)use a XML object as a “storage box” as it would neatly allow nested constructions (section.key = value) and will remain sorted as it was imported (1,2,3 -> 1,2,3). First we would have to import a .ini file in to a XML object, later modify values and finally export the XML to a .ini file (sorted like the input .ini file).

Importing a .ini file to a [xml] object

Using the following function we can import a test.ini file and make a XML object. The function builds a string that we can later convert to XML (quick and dirty way to create custom XML objects in Powershell).

  • powershell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function Parse-IniFile ($file)
{
 [String]$iniXMLString = '<?xml version="1.0" ?>&lt;ini>'
 $opentag = ""

 switch -regex -file $file
 {
  "^\[(.+)\]$"
  {
   if ($opentag -ne "") { $iniXMLString += "&lt;/"+$opentag +">" }
   $section = $matches[1].Trim()
   $iniXMLString += "&lt;" + $section + ">"
   $opentag = $section
  }   
  "^\s*([^#].+?)\s*=\s*(.*)"
  {
   $name,$value = $matches[1..2]
   $iniXMLString += "&lt;" + $name + ">" + $value + "&lt;/" + $name + ">"
  }
 }
 if ($tagopen -ne "") { $iniXMLString += "&lt;/"+$opentag+">" }
 $iniXMLString += "&lt;/ini>"
 Return $iniXMLString
}

Use the function with the following commands:

  • powershell
1
2
$iniXMLString = Parse-IniFile ".\Test.ini"
[xml]$iniXML = $iniXMLString

This will create a XML object with all the values in the .ini file. This newly created object can be used to read the data from the .ini file in a easy manner:

  • powershell
1
2
Write-Host $iniXML.Section1.Key1
$iniXML.Section2.Key2 = "my new value"

Using this [xml] object, you can easily read and manipulate data stored in your .ini file. When you are done processing your data, you’ll want to write a new .ini file. To achieve this, we would have to traverse the XML object and write the data in a .ini filetype using the following function:

  • powershell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function Loop-XML($node, $first) {
 $returnValue = ""
 if ($node.HasChildNodes) {
  if ($node.FirstChild.Name -eq "#text") {
   $returnValue += ($node.name + "=" + $node.FirstChild.value) + "`r`n"
  } else {
   if (!$first) {
    $returnValue +=  ("[" + $node.name + "]")  + "`r`n"
   }
   foreach($item in $node.ChildNodes) {
     $returnValue +=  (Loop-XML $item $FALSE)
   }
  }
 }
 Return $returnValue
}

This will return a string with the content of your new .ini. All that’s left is to write back to the new content of the file:

  • powershell
1
2
$newIni = Loop-XML $iniXML.ini $true
Set-Content ".\myNewFile.ini" $newIni

Should you wish to debug your XML object, try using the Format-XML function written by Keith Hill. A special thanks to my brother Floris for helping me with the recursive loop xml function. You can download the functions plus a little example in this zip file: Ini Powershell.