1

I'm trying to use Powershell to read a file which is in a pseudo toml format:

[paths]
path_one="\root\project=six"
path_two="\root\project=wonderland"

[creds]
username_one="slipperyfish"
username_two="bigkahuna"

[vals]
min_level=40
min_level=90

So nothing too complicated or special.

I start by creating a new [PSCustomObject] $config variable.

What I have then been trying to do - and this might be a sub-optimal approach, is to do a switch -regex through the file content, line by line.

When I find a [section] line I initialise a new hashtable for the subsequent values to be broken into key value pairs and stored in.

Then, when I come across a '^$', or an empty line, I then want to do something like:

add-member -inputobject $config -notepropertyname $<hashtable_name> -notepropertyvalue $<hashtable_name> -force

This way all the hashtables that get built up from each [section] and its key/value pairs then get pushed into the pscustomobject which I can then use later in the script to access those values when needed.

The problem I have is that I need the new hashtables to be the name of the [section] so that when I then work with the PSCustomObject at the end it actually contains the hierarchy/properties as per the file.

How can I create a hashtables with the dynamic, only know when read, names of the sections?

Would greatly appreciate any assistance.

PS - I realise that for config/ini files in Powershell using psd1 files is a safer bet - it just happens that for this example I kind of need to use what's being supplied.

::Additional content to show where I am having the problem:

$file = "$($PSScriptRoot)\config.ini"

# Initialise the base config object:
$config = [pscustomobject]::new()

# iterate over the config file:
switch -regex ( get-content -path $file ) {

    # [section] line found
    '^\[.*\]$'
        {
            $category = $_ -replace "(\[|\])"
            <#
                how to create a hashtable here with NAME contained in $category variable?
                This is needed so that later I can use dot notation to drill through the hashtable to
                get to the values.
            #>
        }

}

$config.creds.username_one
<#
    Should return "slipperyfish"
#>

In a nutshell, the new hashtables must have the name of the [section] to which they relate. How can I do that?

Thanks!

4
  • 1
    It's not clear to me what the problem is. What stops you from using the section name for $<hashtable_name>? I suggest to edit the question to show in code what you have tried so far. Commented Dec 26, 2020 at 20:33
  • Your approach sounds fine, but maybe create $config as a hashtable/dictionary and then convert it to [PSCustomObject] after you're done adding each section Commented Dec 26, 2020 at 20:43
  • Thanks! I have updated the question to show where I am having problems, Essentially I am asking how to create a hashtable with the name of the [section]. If I don't do that then there is no way to traverse the $config option later. How to create a hashtable with a variable name from the dynamic value grokked from the [section] text? Hope this makes more sense. Commented Dec 26, 2020 at 20:52
  • 1
    There’s a Get-IniContent function at devblogs.microsoft.com/scripting/… which might be of use... Commented Dec 26, 2020 at 23:34

4 Answers 4

4

Note: As an alternative to parsing such files yourself, consider a third-party module such as PsIni (see this answer for examples); also, adding INI/TOML-file support to PowerShell itself is being discussed in GitHub issue #9035


I suggest creating your $config variable as a nested ordered hashtable, which simplifies your approach:

# Initialize $config as an ordered hashtable.
$config = [ordered] @{}

# Parse the lines of input file "file.ini"
switch -Regex -File file.ini {

  '^\[(.+?)\]\s*$' { # section header
    $config[$Matches[1]] = [ordered] @{} # initialize nested ordered hash for section
  }

  '^\s*([^=]+)=\s*(.*)$' {  # property-value pair

    # Simple support for string and integer values.
    $key, $val = $Matches[1].Trim(), $Matches[2].Trim()
    if ($val -like '"*"') { $val = $val -replace '"' }
    else                  { $val = [int] $val }

    # Add new entry, to the most recently added ([-1]) section hashtable.
    $config[-1].Add($key, $val)
  }

}

Running the above with your sample input, $config.creds then yields:

Name                           Value
----                           -----
username_one                   slipperyfish
username_two                   bigkahuna

To get a specific entry's value, use $config.creds.username_two, for instance.

As you can see, PowerShell allows accessing hashtable entries via dot notation, as with objects, as an alternative to index-based notation; that is, $config.creds.username_two is the more convenient alternative to $config['creds']['username_two']

Sign up to request clarification or add additional context in comments.

Comments

2

Your approach - parsing the input section-by-section using a switch statement - sounds like an excellent strategy!

In terms of creating the resulting object that you want to return to the caller, you'll want to use a hashtable or similar dictionary type until you've collected all the relevant properties, and only then, as the last thing you do, should you convert the dictionary to [PSCustomObject]:

$Path = ".\config.toml"

# Create an ordered dictionary, NOT a PSCustomObject
$config = [ordered]@{}

switch -Regex -File ($Path) {
  '^\[(?<sectionName>.*)\]$' {
    # We encountered a new section, 
    # create a new dictionary to hold the 
    # associated settings and grab the name
    $section = [ordered]@{}
    $sectionName = $Matches['sectionName']
  }
  '^$' {
    # Empty line, add section if not present
    if($sectionName -and -not $config.Contains($sectionName)){
      $config[$sectionName] = [pscustomobject]$section
    }
  }
  '^(?<key>[^=]+)=(?<value>.*)$' {
    # Add key value-pair to latest section
    $section[$Matches['key']] = $Matches['value']
  }
}

# Add any trailing section to the config
if($sectionName -and -not $config.Contains($sectionName)){
  $config[$sectionName] = [pscustomobject]$section
}

# Now we convert to [PSCustomObject]
return [pscustomobject]$config

Comments

2

To create a hashtable with the category name use $_ (or $category in your code) in direct dot-notation:

$config | Add-Member -MemberType NoteProperty -Name $_ -Value @{}

Updated to reflect mklement0's comment

1 Comment

Yes, but just to spell it out (given the edited question): That only works if [pscustomobject] $config already has a property named for the value of $_. Also note that $(...) (the subexpression operator) is not needed in this case; try $o = [pscustomobject] @{ foo = 1 }; $category = 'foo'; $o.$category. And if you do need an actual expression, (...) will do: $o.('fo' + 'o'). You'd only need $(...) if the property name needed to be determined by a (or multiple) statement(s).
0

you can use code already exist founded here

function Get-IniContent ($filePath)
{
    $ini = @{}
    switch -regex -file $FilePath
    {
        “^\[(.+)\]” # Section
        {
            $section = $matches[1]
            $ini[$section] = @{}
            $CommentCount = 0
        }
        “^(;.*)$” # Comment
        {
            $value = $matches[1]
            $CommentCount = $CommentCount + 1
            $name = “Comment” + $CommentCount
            $ini[$section][$name] = $value
        }
        “(.+?)\s*=(.*)” # Key
        {
            $name,$value = $matches[1..2]
            $ini[$section][$name] = $value
        }
    }
    return $ini
}

#for get value to section/key
$res=Get-IniContent "C:\temp\test.ini"
$res["paths"]["path_one"] 

#for get values to section
$res=Get-IniContent "C:\temp\test.ini"
$res["paths"]

#for add section
$res.Add("mysection", @{})

#for add key/value to exist section
$res["mysection"].Add("mynewkey", "mynewvalue")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.