18

The question Loading a PowerShell hashtable from a file? documents how to load a file that contains a hashtable in PSON format into a variable, but how does one save a hashtable to a file in PSON format?

Hashtable:

@{            
 "name" = "report 0"            
 "parameters" = @(
    @{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
    @{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
    )            
}
iRon
  • 20,463
  • 10
  • 53
  • 79
craig
  • 25,664
  • 27
  • 119
  • 205
  • 1
    There is nothing like this built-in. Presumably you have to write your own solution but it's a tough task unless you are going to allow only limited subset of data types, no recursion in nested objects, etc. – Roman Kuzmin Mar 01 '13 at 10:26
  • Excellent question, but I'm curious as to where the term PSON came from; it seems that it never really caught on. More precisely, it is a _hashtable literal_ notation (which since v3 forms _part_ of the syntactic sugar for custom-object construction; e.g. `[pscustomobject] @{name="foo";age=21}`, but you cannot use that in data files loaded with `Import-PowerShellDataFile`, which only support hashtable literals) – mklement0 Aug 30 '22 at 18:22

4 Answers4

14

After 5 years, the cmdlet I had pasted in the original answer has undergone so many updates that it has become completely outdated. Therefore I have replaced the code and the ReadMe with a link to the latest version.

ConvertTo-Expression

The ConvertTo-Expression cmdlet can be download from PowerShell Gallery using the command:

Install-Script -Name ConvertTo-Expression

ReadMe

The full ReadMe (and source code) is available from the GitHub

Answer

Below are some possible options to serialize the specific example (assigned to $Craig) in the question:

ConvertTo-Expression $Craig
@{
    parameters =
        @{
            name = 'parameter 0'
            default = 1
            values =
                1,
                2,
                3,
                4
        },
        @{
            name = 'parameter 1'
            default = 'A'
            values =
                'A',
                'B',
                'C'
        }
    name = 'report 0'
}

To limit the tree view expansion:
(Expand -0 will output a single line and Expand -1 will remove also the unnecessary spaces)

ConvertTo-Expression $Craig -expand 3
@{
    parameters =
        @{name = 'parameter 0'; default = 1; values = 1, 2, 3, 4},
        @{name = 'parameter 1'; default = 'A'; values = 'A', 'B', 'C'}
    name = 'report 0'
}

Preserving the explicit types (strong typed):

ConvertTo-Expression $Craig -expand 3 -Strong
[hashtable]@{
    parameters = [array](
        [hashtable]@{name = [string]'parameter 0'; default = [int]1; values = [array]([int]1, [int]2, [int]3, [int]4)},
        [hashtable]@{name = [string]'parameter 1'; default = [string]'A'; values = [array]([string]'A', [string]'B', [string]'C')}
    )
    name = [string]'report 0'
}

(Note: As per PowerShell design, HashTables are not in order, but if required you might use the [Ordered] type instead.)

iRon
  • 20,463
  • 10
  • 53
  • 79
  • Or got to an elevated powershell and `Install-Script ConvertTo-Expression -Scope AllUsers` which places the script into `$env:ProgramFiles\WindowsPowerShell\Scripts` (which might not exit if this is your first script). That adds `C:\Program Files\WindowsPowerShell\Scripts` (typical path) to your `$env:path` but only elevated. Back to standard ps and do `$env:Path = "$env:Path$env:ProgramFiles\WindowsPowerShell\Scripts;"` then just `. ConvertTo-Expression.ps1` in any prompt or script you want to use it. Call function as `ConvertTo-Expression ...` – john v kumpf Apr 08 '22 at 14:10
  • [sorry editing time ran out]. Better in the elevated powershell, after `Install-Script` to do `[Environment]::SetEnvironmentVariable('path',$env:path,'machine')` to add the `...\WindowsPowerShell\Scripts` dir to the path for the whole system, elevated and non elevated PS and everything else. – john v kumpf Apr 08 '22 at 14:23
  • Also the ReadMe link is incorrect (and suggested edit queue is full). Easy to find: https://github.com/iRon7/ConvertTo-Expression#readme – john v kumpf Apr 08 '22 at 14:31
  • @john v kumpf, I have changed the embedded function to cmdlet script which makes it easier installable. – iRon Aug 31 '22 at 06:38
11

Try the *-CliXml cmdlets. To save the object:

@{            
 "name" = "report 0"            
 "parameters" = @(
    @{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
    @{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
    )            
} | Export-Clixml -Path c:\hash.xml

To read it back:

Import-Clixml c:\hash.xml
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
  • I would prefer PSON, as it is easier to read and edit than the XML that is generated by Import-Clixml. – craig Feb 28 '13 at 16:34
  • 1
    If it's a matter of "readability", V3 lets you convert to JSON, which might actually be better. – mjolinor Feb 28 '13 at 17:32
  • Yes, I did read that. Unfortunately, my client is still on Windows XP, which doesn't support v2. – craig Feb 28 '13 at 19:13
  • IIRC the clixml cmdlets introduced in v1 but I might be wrong – Shay Levy Feb 28 '13 at 19:42
  • @craig This would be the proper, built-in way to do it, if there wasn't anybody like iRon to build a custom solution for you (which might very likely still have a lot of unknown issues.) – marsze Jan 07 '19 at 08:48
3

One way would be to put the hashtable definition in a scriptblock:

$hashtable = {
  @{            
    "name" = "report 0"            
    "parameters" = @(
        @{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
        @{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
        )            
    }
}

$hashtable.tostring()

@{
"name" = "report 0"
"parameters" = @( @{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4}, @{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'} )
}

Within the script, you'd need to invoke the script block to instantiate the hashtable:

$hash = .$hashtable
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • Correct me if I'm wrong, but it doesn't seem like I'd be able to load changes to the hashtable from a file if it is embedded in a script block. – craig Feb 28 '13 at 17:29
0

How to use a shorthand "object notation" to generate an object in PowerShell:

$object = New-Object -TypeName PSObject -Property @{name="foo";age=21}

DISCLAIMER: I know this does not answer OP's question directly but it might help folks like me searching for a very similar issue and landing here.

Marc
  • 13,011
  • 11
  • 78
  • 98
  • 1
    Since v3 there's syntactic sugar that is not only more concise, but also more efficient, in addition to preserving the property order: `[pscustomobject] @{name="foo";age=21}` – mklement0 Aug 30 '22 at 18:18