Question

I'm parsing simple (no sections) INI file in PowerShell. Here code I've came up with, is there any way to simplify it?

convertfrom-stringdata -StringData ( `
  get-content .\deploy.ini `
  | foreach-object `
    -Begin { $total = "" }  `
    { $total += "`n" + $_.ToString() } `
    -End { $total } `
).Replace("\", "\\")
Was it helpful?

Solution

After searching internet on this topic I've found a handful of solutions. All of them are hand parsing of file data so I gave up trying to make standard cmdlets to do the job. There are fancy solutions as this which support writing scenario.

There are simpler ones and as far as I need no writing support I've chose following very elegant code snippet:

Function Parse-IniFile ($file) {
  $ini = @{}

  # Create a default section if none exist in the file. Like a java prop file.
  $section = "NO_SECTION"
  $ini[$section] = @{}

  switch -regex -file $file {
    "^\[(.+)\]$" {
      $section = $matches[1].Trim()
      $ini[$section] = @{}
    }
    "^\s*([^#].+?)\s*=\s*(.*)" {
      $name,$value = $matches[1..2]
      # skip comments that start with semicolon:
      if (!($name.StartsWith(";"))) {
        $ini[$section][$name] = $value.Trim()
      }
    }
  }
  $ini
}

This one is Jacques Barathon's.

Update Thanks to Aasmund Eldhuset and @msorens for enhancements: whitespace trimming and comment support.

Update 2 Skip any name=value pairs where name starts with a semicolon ; which are comment lines. Replaced $ini [$section] = @{} with $ini[$section] = @{}.

OTHER TIPS

Don Jones almost had it right. Try:

ConvertFrom-StringData((Get-Content .\deploy.ini) -join "`n")

-join converts the Object[] into a single string, with each item in the array separated by a newline char. ConvertFrom-StringData then parses the string into key/value pairs.

This is really an extension to the current answer (couldn't seem to append a comment).

I messed around with this to do rudimentary handling of integers and decimals...

function Parse-IniFile ($file)
{
  $ini = @{}
  switch -regex -file $file
  {
    #Section.
    "^\[(.+)\]$"
    {
      $section = $matches[1].Trim()
      $ini[$section] = @{}
      continue
    }
    #Int.
    "^\s*([^#].+?)\s*=\s*(\d+)\s*$"
    {
      $name,$value = $matches[1..2]
      $ini[$section][$name] = [int]$value
      continue
    }
    #Decimal.
    "^\s*([^#].+?)\s*=\s*(\d+\.\d+)\s*$"
    {
      $name,$value = $matches[1..2]
      $ini[$section][$name] = [decimal]$value
      continue
    }
    #Everything else.
    "^\s*([^#].+?)\s*=\s*(.*)"
    {
      $name,$value = $matches[1..2]
      $ini[$section][$name] = $value.Trim()
    }
  }
  $ini
}

One possibility is to use a .NET ini library. Nini for example.

I've translated the Simple Example from the Nini docs into PowerShell below. You need to put nini.dll into the same directory as the script.

$scriptDir = Split-Path -parent $MyInvocation.MyCommand.Definition
Add-Type -path $scriptDir\nini.dll

$source = New-Object Nini.Config.IniConfigSource("e:\scratch\MyApp.ini")

$fileName = $source.Configs["Logging"].Get("File Name")
$columns = $source.Configs["Logging"].GetInt("MessageColumns")
$fileSize = $source.Configs["Logging"].GetLong("MaxFileSize")

I optimized this solution for my needs adding some things to the function and a new function for writing back the ini file:

  1. I made an ordered dictionary out of the original dictionary (hash table) to be able to preserve the file structure
  2. And to make it possible to preserve the comments and blank lines, I put them in a special key. They can be then ignored, when using data or thrown away when writing to a file as demonstrated below in the function Set-IniFile
  3. In Set-IniFile using the options -PrintNoSection and -PreserveNonData, it can be controlled if NO_SECTION should be used and if the non-data lines (not matching key=value or [section] should be preserved.

Function Get-IniFile ($file)       # Based on "https://stackoverflow.com/a/422529"
 {
    $ini = [ordered]@{}

    # Create a default section if none exist in the file. Like a java prop file.
    $section = "NO_SECTION"
    $ini[$section] = [ordered]@{}

    switch -regex -file $file 
    {    
        "^\[(.+)\]$" 
        {
            $section = $matches[1].Trim()
            $ini[$section] = [ordered]@{}
        }

        "^\s*(.+?)\s*=\s*(.*)" 
        {
            $name,$value = $matches[1..2]
            $ini[$section][$name] = $value.Trim()
        }

        default
        {
            $ini[$section]["<$("{0:d4}" -f $CommentCount++)>"] = $_
        }
    }

    $ini
}

Function Set-IniFile ($iniObject, $Path, $PrintNoSection=$false, $PreserveNonData=$true)
{                                  # Based on "http://www.out-web.net/?p=109"
    $Content = @()
    ForEach ($Category in $iniObject.Keys)
    {
        if ( ($Category -notlike 'NO_SECTION') -or $PrintNoSection )
        {
            # Put a newline before category as seperator, only if there is none 
            $seperator = if ($Content[$Content.Count - 1] -eq "") {} else { "`n" }

            $Content += $seperator + "[$Category]";
        }

        ForEach ($Key in $iniObject.$Category.Keys)
        {           
            if ( $Key.StartsWith('<') )
            {
                if ($PreserveNonData)
                    {
                        $Content += $iniObject.$Category.$Key
                    }
            }
            else
            {
                $Content += "$Key = " + $iniObject.$Category.$Key
            }
        }
    }

    $Content | Set-Content $Path -Force
}


### EXAMPLE
##
## $iniObj = Get-IniFile 'c:\myfile.ini'
##
## $iniObj.existingCategory1.exisitingKey = 'value0'
## $iniObj['newCategory'] = @{
##   'newKey1' = 'value1';
##   'newKey2' = 'value2'
##   }
## $iniObj.existingCategory1.insert(0, 'keyAtFirstPlace', 'value3')
## $iniObj.remove('existingCategory2')
##
## Set-IniFile $iniObj 'c:\myNewfile.ini' -PreserveNonData $false
##

I'm not exactly sure what your source data looks like, or what your goal is. What exactly are you parsing for? Can you post a sample of the file? As-is, it looks like you're just concatenating carriage returns to the existing lines of the file and replacing \ with \.

Nor certain why you're using $_.ToString() since $_ is already a string object output by Get-Content.

Is the goal just to take a file containing a bunch of name=value pairs, and convert that to a hashtable? That's what ConvertFrom-StringData does, although that cmdlet is only available in the preview of PowerShell v2.

If your file looks like...

key1=value1
key2=value2
key3=value3

Then all you should need is

ConvertFrom-StringData (Get-Content .\deploy.ini)

I'm not sure I understand why you're tacking on extra carriage returns. There's also no need to use the -Begin and -End parameters, at least not as far as I can see from what you've posted.

nini looks like a library ... not sure for powershell

powershell crash

first step

[void][system.reflection.assembly]::loadfrom("nini.dll") (refer add-type now in ps2 )

after you can use it

$iniwr = new-object nini.config.iniconfigsource("...\ODBCINST.INI") 

$iniwr.Configs et boom 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top