Function Read-ConfigFile2 { <# .SYNOPSIS Function will read the given ConfigFile, and returns a HashTable with all Keys and Values. .DESCRIPTION This Function will get Script configurations from a ASCII/ANSI based ConfigFile (INI). The important criterion is, that every Value the should be read, has to look like this: ConfigLabel = ConfigValue The ConfigFile should be formated within UTF8 and every Line in a ConfigFile which is beginning with a "#", will be ignored! The Return Value of this Function will be a HashTable Object, with two columns (key column and value/values column). Every Value in the Value column will be saved as Array value, even there is only one value. For every line in the ConfigFile, there will be an additional line in HashTable, which says how many line (profiles) where read. .REQUIREMENT General PowerShell V3 .REQUIREMENT Assembly .REQUIREMENT Variables Counter1, Counter2, ConfigFile, ConfigLabels, ConfigFileLine, ConfigFileLines, ConfigValue, ConfigValues, ConfigFileLineKey, ConfigLinesValue, ConfigLinesValues, ConfigLinesValueSeparator, ConfigFileLineValuesTEMP, HashTable_MetaData .REQUIREMENT Variables preSet .REQUIREMENT Functions .VERSION Number: 1.2.0.0 / Date: 23.02.2024 .PARAMETER ConfigFile Optional Parameter. Give the full path to the ConfigFile (eg. _Settings.ini). (Default: If you dont give it, Function will try the retrieve ConfigFile (Path and Name) from the Global Variables, set by the calling Script.) .PARAMETER ConfigLabels Optional Parameter. Give the ConfigLabels (Seperate with a ",") which are very mandatory for a working Script. Script will exit, if one or more are missing or have no Values. .PARAMETER ConfigLinesDelimiter Optional Parameter. Set the Delimiter which is in between the ConfigLabel and ConfigValue(s) in the ConfigFile (Default: "="). .PARAMETER ConfigLinesValueSeparator Optional Parameter. Set the symbol or character for Value separation (Default: ";"). Please Mask with a "\", if you want that Separator ignored (e.g in a RegEx). .PARAMETER RemoveMaskingOfConfigLinesValueSeparator Optional Parameter. Enable or disable, if the masking ("\") mark for ConfigLinesValueSeparator, should be removed after reading it (Default: $False). .EXAMPLE Read-ConfigFile2 -ConfigFile \_Settings.ini .EXAMPLE Read-ConfigFile2 -ConfigFile \_Settings.ini -ConfigLabels LogPath .EXAMPLE Read-ConfigFile2 -ConfigFile \_Settings.ini -ConfigLabels LogPath, LogKeepTime -RemoveMaskingOfConfigLinesValueSeparator #> Param ( [Parameter(Position=0,Mandatory=$False,HelpMessage='Optional Parameter. Give the full path to the ConfigFile (eg. _Settings.ini). (Default: If you dont give it, Function will try the retrieve ConfigFile (Path and Name) from the Global Variables, set by the calling Script.)')] [ValidateNotNullOrEmpty()] [STRING]$ConfigFile=(Get-Variable -Name ConfigFile -Scope Global -ValueOnly), [Parameter(Position=1,Mandatory=$False,HelpMessage='Optional Parameter. Give the ConfigLabels (Seperate with a ",") which are very mandatory for a working Script. Script will exit, if one or more are missing or have no Values.')] [ValidateNotNullOrEmpty()] [ARRAY]$ConfigLabels=@(), [Parameter(Position=2,Mandatory=$False,HelpMessage='Optional Parameter. Set the Delimiter which is in between the ConfigLabel and ConfigValue(s) in the ConfigFile (Default: "=").')] [ValidateNotNullOrEmpty()] [STRING]$ConfigLinesDelimiter='=', [Parameter(Position=3,Mandatory=$False,HelpMessage='Optional Parameter. Set the symbol or character for Value separation (Default: ";"). Please Mask with a "\", if you want that Separator ignored (e.g in a RegEx).')] [ValidateNotNullOrEmpty()] [STRING]$ConfigLinesValueSeparator=';', [Parameter(Position=4,Mandatory=$False,HelpMessage='Optional Parameter. Enable or disable, if the masking ("\") mark for ConfigLinesValueSeparator, should be removed after reading it (Default: $False).')] [ValidateNotNullOrEmpty()] [SWITCH]$RemoveMaskingOfConfigLinesValueSeparator=$False ) #end param #Clear Error Variable $error.clear() #Check if ConfigFile exists IF ((Test-Path -Path $ConfigFile -PathType Leaf -ErrorAction Stop) -eq $true) { Write-Host "DEBUG Info - Read-ConfigFile2: Module begins reading of ConfigFile:" Write-Host "DEBUG Info - Read-ConfigFile2: $ConfigFile" Try { #Prepare Variables [INT]$Counter1 = 0 [INT]$Counter2 = 0 [ARRAY]$ConfigFileLine = @() [ARRAY]$ConfigFileLines = (Select-String -Path $ConfigFile -Pattern "$ConfigLinesDelimiter" -ErrorAction Stop | Where-Object {-not($_-match ":[0-9]{1,}:#")}) [STRING]$ConfigFileLineKey = $NULL [STRING]$ConfigFileLineValue = $NULL [ARRAY]$ConfigFileLineValues = @() [System.Collections.ArrayList]$ConfigFileLineValuesTEMP = @() [ARRAY]$HashTable_MetaData = @($($ConfigFile),$(Get-Date -Format 'ddMMyyyy_HHmmssffff')) Write-Host "DEBUG Info - Read-ConfigFile2: Found $($ConfigFileLines.count) relevant Lines in ConfigFile!" #Prepare HashTable incl. Fallback to PowerShell Version 2 compatibility Try { $ConfigFileTable = [ordered]@{} } #end try Catch { $ConfigFileTable = New-Object [System.Collections.Specialized.OrderedDictionary]([System.StringComparer]::OrdinalIgnoreCase) -ErrorAction Stop Try { $ConfigFileTable = New-Object [System.Collections.Specialized.OrderedDictionary] -ErrorAction Stop } #end try Catch { Write-Host "DEBUG Info - Read-ConfigFile2: Cannot create HashTable for Config Values!" Write-Host "DEBUG Info - Read-ConfigFile2: Exiting, because of this Issue." Write-Host $Error EXIT } #end catch } #end catch #First Key and Value in HashTable was about its own Metadata Informations $ConfigFileTable.Add("HashTable_MetaData",$HashTable_MetaData) Write-Host "" Write-Host "DEBUG Info - Read-ConfigFile2: HashTable Key with name: HashTable_MetaData," Write-Host "DEBUG Info - Read-ConfigFile2: got this Value: $HashTable_MetaData)" } #end try Catch { Write-Host "DEBUG Info - Read-ConfigFile2: Module cannot read ConfigFile:" Write-Host "DEBUG Info - Read-ConfigFile2: $ConfigFile" Write-Host "DEBUG Info - Read-ConfigFile2: Exiting, because of this Issue." Write-Host $Error EXIT } #end catch DO { #Loop for each Line in ConfigFile $ConfigFileLine = ($ConfigFileLines.SyncRoot[$Counter1]).line IF ([INT](([REGEX]::Matches($ConfigFileLine,"$ConfigLinesDelimiter")).count) -gt 1) { Write-Host "DEBUG Info - Read-ConfigFile2: Multiple ConfigLinesDelimiters found $((([REGEX]::Matches($ConfigFileLine,"$ConfigLinesDelimiter")).count)) " $ConfigFileLine = ($ConfigFileLine -split "$ConfigLinesDelimiter",2) } #end elseif ELSE { $ConfigFileLine = ($ConfigFileLine -split "$ConfigLinesDelimiter") } #end else $Counter1++ | Out-Null Write-Host "" #The Key is easy to determ ($ConfigFileLine[0]) = ($ConfigFileLine[0].TrimStart()) ($ConfigFileLine[0]) = ($ConfigFileLine[0].TrimEnd()) ($ConfigFileLineKey) = ($ConfigFileLine[0]) Write-Host "DEBUG Info - Read-ConfigFile2: HashTable Key with name: $($ConfigFileLine[0])," #The Value is more work - first we have to check if Value is empty and if Failsave Value is available IF (([String]::IsNullOrEmpty($ConfigFileLine[1])) -or ([String]::IsNullOrWhiteSpace($ConfigFileLine[1]))) { Try { Write-Host "DEBUG Info - Read-ConfigFile2: $($ConfigFileLine[0]) has no Value, trying to get FailSafe Value from main Script!" ($ConfigFileLine[1]) = (Get-Variable -Name $($ConfigFileLine[0]) -Scope Global -ValueOnly -ErrorAction Stop) #If a object returns, set Array value back to an empty string, #because this happens if a config line in the ini file is empty (like: Profile = ) If ($ConfigFileLine[1].gettype() -is [System.Object]) { $ConfigFileLine[1] = [String]$Null } #end if Write-Host "DEBUG Info - Read-ConfigFile2: FailSafe Value returnd and set!" Write-Host "DEBUG Info - Read-ConfigFile2: Got this Value: $($ConfigFileLine[1])" } #end try Catch { Write-Host "DEBUG Info - Read-ConfigFile2: FailSafe cannot be found, leaving empty!" Write-Host "DEBUG Info - Read-ConfigFile2: ... could be a Issue, if the ConfigLabel was mandatory set by function call!" } #end catch } #end if ELSE { Write-Host "DEBUG Info - Read-ConfigFile2: got this Value: $($ConfigFileLine[1])" } #end else #If the Value line includes the ValueSeperator, split the Value! Except ValueSeperator is escaped/masked by a "\", like its standard in a RegEx IF ($($ConfigFileLine[1]) | Where-Object {($_-match "$ConfigLinesValueSeparator")} | Where-Object {-not($_-match "\\$ConfigLinesValueSeparator")}) { Write-Host "DEBUG Info - Read-ConfigFile2: Found ConfigLinesValueSeparator in Value from $($ConfigFileLine[0])" ($ConfigFileLineValues) = ($ConfigFileLine[1] -split "$ConfigLinesValueSeparator") FOREACH ($ConfigFileLineValue in $ConfigFileLineValues) { $ConfigFileLineValue = ($ConfigFileLineValue.TrimStart()) $ConfigFileLineValue = ($ConfigFileLineValue.TrimEnd()) $ConfigFileLineValuesTEMP.Add($ConfigFileLineValue) | Out-Null } #end foreach $ConfigFileLineValues = @($ConfigFileLineValuesTEMP) $ConfigFileLineValuesTEMP = @() } #end if ELSE { #Maybe you want the remove the "\", because ConfigLinesValueSeparator was set but for one line or case not used IF ($RemoveMaskingOfConfigLinesValueSeparator -eq $True) { ($ConfigFileLine[1]) = ($ConfigFileLine[1].Replace("\$ConfigLinesValueSeparator","$ConfigLinesValueSeparator")) } #end if ($ConfigFileLine[1]) = ($ConfigFileLine[1].TrimStart()) ($ConfigFileLine[1]) = ($ConfigFileLine[1].TrimEnd()) ($ConfigFileLineValues) = ($ConfigFileLine[1]) } #end else #Finally fill keys and values into the HashTable Try { #If the Key is first time inserting IF (($ConfigFileTable.Contains($ConfigFileLineKey)) -eq $False) { Write-Host "DEBUG Info - Read-ConfigFile2: Inserting a single line Value into the HashTable!" $Counter2++ | Out-Null $ConfigFileTable.Add($ConfigFileLineKey,$($Counter2,"Profile(s)")) $ConfigFileTable.Add(($ConfigFileLineKey+'_'+$Counter2),$ConfigFileLineValues) $Counter2=0 | Out-Null } #end if #If the Key is second and more time(s) inserting, like its used in profiles ELSE { Write-Host "DEBUG Info - Read-ConfigFile2: Inserting multi line Values into the HashTable!" DO { $Counter2++ | Out-Null } #end do UNTIL (($ConfigFileTable.Contains($ConfigFileLineKey+'_'+$Counter2)) -eq $False) $ConfigFileTable.$ConfigFileLineKey = @($Counter2,"Profile(s)") $ConfigFileTable.Add(($ConfigFileLineKey+'_'+$Counter2),$ConfigFileLineValues) $Counter2=0 | Out-Null } #end elseif } #end try #Getting last Exeption Message or Code: $Error[0] | fl * -Force Catch { Write-Host "DEBUG Info - Read-ConfigFile2: Error inserting into HashTable!" Write-Host $Error } #end catch } #end do UNTIL ($Counter1 -eq ($ConfigFileLines.Count)) Write-Host "" Write-Host "DEBUG Info - Read-ConfigFile2: Set $($ConfigFileTable.Count) lines in HashTable!" #Check for mandatory Keys and Values! Exit whole Script if something is missing! IF ($ConfigLabels.count -gt 0) { FOREACH ($ConfigLabel in $ConfigLabels) { Write-Host "" Write-Host "DEBUG Info - Read-ConfigFile2: Checking if mandatory ConfigLabel: $ConfigLabel - exists in HashTable" IF ($ConfigFileTable.Contains($ConfigLabel) -eq $True) { Write-Host "DEBUG Info - Read-ConfigFile2: Yes, ConfigLabel exisits!" Write-Host "DEBUG Info - Read-ConfigFile2: Checking if mandatory ConfigLabel: $ConfigLabel - has a Value" IF (!([String]::IsNullOrEmpty($ConfigFileTable.Get_Item($ConfigLabel))) -or (![String]::IsNullOrWhiteSpace($ConfigFileTable.Get_Item($ConfigLabel)))) { Write-Host "DEBUG Info - Read-ConfigFile2: Yes, value for ConfigLabel exisits!" } #end if ELSE { Write-Host "DEBUG Info - Read-ConfigFile2: No, value for ConfigLabel does exisit!" Write-Host "DEBUG Info - Read-ConfigFile2: Exiting, because of this Issue." Write-Host $Error EXIT } #end else } #end if ELSE { Write-Host "DEBUG Info - Read-ConfigFile2: No, ConfigLabel does not exisit!" Write-Host "DEBUG Info - Read-ConfigFile2: Exiting, because of this Issue." Write-Host $Error EXIT } #end else } #end foreach } #end if ELSE { Write-Host "" Write-Host "DEBUG Info - Read-ConfigFile2: No mandatory ConfigLabels are set!" } #end else #Show the whole HashTable in GUI - for Debug reasons #$ConfigFileTable | Out-GridView #Return the whole HashTable Return $ConfigFileTable } #end try ELSE { Write-Host "DEBUG Info - Read-ConfigFile2: Module cannot get content of ConfigFile:" Write-Host "DEBUG Info - Read-ConfigFile2: $ConfigFile" Write-Host "DEBUG Info - Read-ConfigFile2: Exiting, because of this Issue." Write-Host $Error EXIT } #end catch } #end function