Indrid
Indrid

Reputation: 1182

Powershell adding dynamic hashtables to pscustomobject

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!

Upvotes: 1

Views: 1114

Answers (4)

mklement0
mklement0

Reputation: 437568

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']

Upvotes: 4

Esperento57
Esperento57

Reputation: 17462

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")

Upvotes: 0

Smorkster
Smorkster

Reputation: 357

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

Upvotes: 2

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174485

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

Upvotes: 2

Related Questions