How to store hashtable in configuration file?

This topic contains 10 replies, has 3 voices, and was last updated by Profile photo of Dave Wyatt Dave Wyatt 2 years, 1 month ago.

  • Author
    Posts
  • #19600
    Profile photo of GS
    GS
    Participant

    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?

  • #19601
    Profile photo of Don Jones
    Don Jones
    Keymaster

    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.

  • #19602
    Profile photo of GS
    GS
    Participant

    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?

  • #19603
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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.

  • #19604
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Ah.

    Have a look at http://stackoverflow.com/questions/10743892/loading-a-powershell-hashtable-from-a-file. 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.

  • #19606
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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 [url="http://msdn.microsoft.com/en-us/library/system.management.automation.scriptblock.checkrestrictedlanguage(v=vs.85).aspx"]ScriptBlock.CheckRestrictedLanguage()[/url] 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
        } 
    }
    
  • #19607
    Profile photo of GS
    GS
    Participant

    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.

  • #19608
    Profile photo of GS
    GS
    Participant

    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)

  • #19609
    Profile photo of Don Jones
    Don Jones
    Keymaster

    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.

  • #19610
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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.

  • #19681
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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.

You must be logged in to reply to this topic.