Reputation: 1182
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
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
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
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
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