How to store hashtable in configuration file?

Hello,

My cmdlet accepts parameter -File which will specify folder names and integer value associated with each of those like @{“c:\test” = 5; “d:\temp” = 9}.
What is the best way to feed it to cmdlet? Do I just use Get-Content of that file and assign to hashtable in a script or there is more elegant way to accomplish this?

How are you creating the hash table in the first place?

I don’t understand “use Get-Content of that file.” I don’t understand what file that refers to.

I want my cmdlet to perform certain actions on folders specified in configuration file with associated integer value (like delete files older then X number of days in that specific folder). So I assume I need to have a hash table with foldernames and longevity of files for each of those. How do I properly feed this information to a script from a file?

You’re describing a configuration file. Personally, I would probably lean toward using either XML or JSON for that, so I could just use the [xml] class or ConvertFrom-Json to turn that into objects. However, there is a precedent for using the hashtable syntax: psd1 files (module manifests, localized string tables, etc) do that. You could potentially just use the Import-LocalizedData cmdlet to import such a data file, if you wanted the contents of the file to look like a PowerShell hashtable. This would enforce all the same rules that normally apply to PowerShell data files (restricted language mode, safe to execute), but there’s one little quirk: Import-LocalizedData is intended for loading up string tables, so it looks for the psd1 file in culture-specific subfolders first. Depending on your folder structure, that might occasionally cause a problem, but it’s pretty unlikely.

Ah.

Have a look at Loading a PowerShell hashtable from a file? - Stack Overflow. The answer shows an example of how to load a hash table from a file and have it become an actual hash table, not just a text string.

If you wanted something similar to Import-LocalizedData, but which didn’t have the culture subfolder behavior, you could write your own version taking advantage of the ScriptBlock.CheckRestrictedLanguage() method. Something like this:

function Import-DataFile
{
    param (
        [Parameter(Mandatory)]
        [string] $Path
    )

    try
    {
        $content = Get-Content -Path $path -Raw -ErrorAction Stop
        $scriptBlock = [scriptblock]::Create($content)

        # This list of approved cmdlets and variables is what is used when you import a module manifest
        [string[]] $allowedCommands = @(
            'Import-LocalizedData', 'ConvertFrom-StringData', 'Write-Host', 'Out-Host', 'Join-Path'
        )

        [string[]] $allowedVariables = @('PSScriptRoot')

        # This is the important line; it makes sure that your file is safe to run before you invoke it.
        # This protects you from injection attacks / etc, if someone has placed malicious content into
        # the data file.
        $scriptBlock.CheckRestrictedLanguage($allowedCommands, $allowedVariables, $true)

        return & $scriptBlock
    }
    catch
    {
        throw
    } 
}

Wow, thanks. This is way over my head. I need just input couple of lines of text into cmdlet and cast it as hashtable.
I tried ConvertFrom-Json and it works fine but it does not return hashtable , saving as XML works but it’s difficult to edit by hand since person needs to know how XML is structured to add new elements.
Still looking through trying to find out how to cast imported object as hashtable instead of System.Management.Automation.PSCustomObject which is being returned now.

Actually I don’t even know how I missed it but ConvertFrom-StringData is all is needed and it’s automatically casted into hashtable

ConvertFrom-StringData (gc c:\test\config.txt | out-string)

You can’t cast something into a hash table; they don’t work that way.

But if JSON is easy to read, then all you need to do is change your command to accept an object, instead of a hash table. Assume the object has a Path property and a Days (or whatever) property. Code up your JSON to provide objects having those properties, and ConvertFrom-JSON will do what you want.

Objects are a lot easier to work with when it comes to reading your code, too, since the property names intrinsically describe what the properties are for.

Just occurred to me that this is also being done in the DscConfiguration module, and I think I like Steve’s approach even more:

invoke-expression "DATA { $(get-content -raw -path $path) }"

By wrapping the content of the file in a Data block, you get all the same benefits without having to do the work yourself. PowerShell will make sure that the script inside that data block is safe before it’s allowed to execute.

OK, brain fart here. The Invoke-Expression approach is still open to code injection, and I’ll be updating that function in the DSC tooling modules soon. For example, a psd1’s contents could be replaced with:

} | Out-Null

Do-SomethingEvilHere

$null = {

This would be based on knowledge that the function loading up the PSD1 file was embedding its contents inside some curly braces, but you get the idea. The code passed to Invoke-Expression looks perfectly valid, and Do-SomethingEvilHere would be executed outside of the Data block.