0

I'm trying to improve on the original solution (INI file parsing in PowerShell) so I can parse an INI file with entries like the example below.

[proxy]
; IP address and port number
  server = 192.168.0.253
  port = 8080 
  logfile=session.log ; log session 

[user]
; default username and settings
name=J. Doe ;name
address="377 Sunrise Way;Santa Monica;CA" ; address

[program files]
root="C:\Program Files\Windows  " ; path name
path="C:\Program Files\Windows;%windir" ; path name
;
[program]
root=C:\Program Files\Windows ; path name
  path=C:\Program Files\Windows;%windir ; path name

I'm using the following powershell code to populate a nested hash table (if that is the right description) containing the name/value pairs for each section.

I have no problem dealing with the first section where I have lines ending in a comment, or with the value in the second section which contains spaces, but things go wrong when I try to mix quoted strings and comments.

Given that a string begins and ends with a double quote I think it should be possible to get the results I want but I am obviously missing something somewhere (I am a little new to this).

function Parse-INI-File() {
  Param ([parameter()][string]$_file = '')

  # Don't prompt to continue if '-Debug' is specified.
  If ($DebugPreference -eq "Inquire") {$DebugPreference = "Continue"}

  $_settings=@{}
  switch -Regex -file $_file {
    '(?:^ ?\[\s*(?<section>[^\s]+[^#;\r\n\[\]]+)\s*\])' {
      $_section = $Matches.section.trim()
      $_settings[$_section] = @{}
    }
    '(?:^\s*?(?<name>[^\[\]\r\n=#;]+))(?: ?=\s*"?(?<value>[^;#\\\r\n]*(?:\\.[^"#\\\r\n]*)*))' {
      $_name, $_value = $Matches.name.trim(), $matches.value.trim()
      $_settings[$_section][$_name] = $_value
      Write-Debug "/$_section/ /$_name//$_value/" # Debug
    }
  }
  $_settings
}

$_file='./ini-example.ini'
$_output=Parse-INI-File -Debug ($_file)

What I'd like is for the parsing of the sample ini file to result in the following name/value pairs:

DEBUG: /proxy/ /server//192.168.0.253/
DEBUG: /proxy/ /port//8080/
DEBUG: /proxy/ /logfile//session.log/
DEBUG: /user/ /name//J. Doe/
DEBUG: /user/ /address//377 Sunrise Way;Santa Monica;CA/
DEBUG: /program files/ /root//C:\Program Files\Windows/
DEBUG: /program files/ /path//C:\Program Files\Windows;%windir/
DEBUG: /program/ /root//C:\Program Files\Windows/
DEBUG: /program/ /path//C:\Program Files\Windows/

I don't mind if quoted strings include the original quotes or not.

Thank you.

Updated 10 Sep 19 - I have tried the Get-IniContent function in the psini module, but it doesn't ignore comments at the end of a line.

PS C:\> $_output = Get-IniContent (".\ini-example.ini")
PS C:\> $_output["program files"]

Name                           Value
----                           -----
root                           "C:\Program Files\Windows  "' ; path name
path                           "C:\Program Files\Windows;;%windir" ; path name
Comment1                       ;


PS C:\> 
Mike T.
  • 165
  • 1
  • 12

1 Answers1

0

Think I've solved it, there is probably a better solution but I solved the problem by using a separate regex for quoted strings - this complicates the logic a bit but seems to solve the problem reliably.

function Parse-INI-File() {
  Param ([parameter()][string]$_file = '')

  # Don't prompt to continue if '-Debug' is specified.
  If ($DebugPreference -eq "Inquire") {$DebugPreference = "Continue"}

  $_settings=@{}
  switch -Regex -file $_file {
    '(?:^ ?\[\s*(?<section>[^\s]+[^\r\n\[\]]+)\s*\])' {
      $_section = $Matches.section.trim()
      $_settings[$_section] = @{}
      #Write-Debug "1/$_section/" # Debug
    }
    '(?:^\s*?(?<name>[^\[\]\r\n]+))(?: ?=\s*(?<value>[^";#\\\r\n]*(?:\\.[^";#\\\r\n]*)*))' {
      If ($matches.value -ne '' ) {
        $_name, $_value = $Matches.name.trim(), $matches.value.trim()
        $_settings[$_section][$_name] = $_value
        Write-Debug "2/$_section//$_name//$_value/" # Debug
      }
    }
    '(?:^\s*?(?<name>[^\[\]\r\n]+))(?: ?=\s*(?<value>\"+[^\"\r\n]*\")*)' {
      #If ($matches.value -ne $null ) {
      If (-not [string]::IsNullOrEmpty($matches.value)) {
        $_name, $_value = $Matches.name.trim(), $matches.value.trim()
        $_settings[$_section][$_name] = $_value
        Write-Debug "3/$_section//$_name//$_value/" # Debug
      }
    }
  }
  $_settings
}

This seems to produces the results I'd expect

PS C:\> $_output = Parse-INI-File -Debug (".\ini-example.ini")
DEBUG: 2/proxy//server//192.168.0.253/
DEBUG: 2/proxy//port//8080/
DEBUG: 2/proxy//logfile//session.log/
DEBUG: 2/user//name//J. Doe/
DEBUG: 3/user//address//"377 Sunrise Way;Santa Monica;CA"/
DEBUG: 3/program files//root//"C:\Program Files\Windows  "/
DEBUG: 3/program files//path//"C:\Program Files\Windows;%windir"/
DEBUG: 2/program//root//C:\Program Files\Windows/
DEBUG: 2/program//path//C:\Program Files\Windows/

PS C:\> $_output["user"]

Name                           Value                                                                                                                                       
----                           -----                                                                                                                                       
name                           J. Doe                                                                                                                                      
address                        "377 Sunrise Way;Santa Monica;CA"                                                                                                           

PS C:\> 

Note that if there are multiple values with the same name in a section then only the last value is returned (try parsing system.ini to see what I mean)

Mike T.
  • 165
  • 1
  • 12