Save hash table in PowerShell object notation (PSON)
Asked Answered
A

4

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'}
    )            
}
Allegorical answered 28/2, 2013 at 15:37 Comment(2)
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.Partridge
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)Beneficent
W
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
Welker answered 28/2, 2013 at 16:0 Comment(5)
I would prefer PSON, as it is easier to read and edit than the XML that is generated by Import-Clixml.Allegorical
If it's a matter of "readability", V3 lets you convert to JSON, which might actually be better.Lipchitz
Yes, I did read that. Unfortunately, my client is still on Windows XP, which doesn't support v2.Allegorical
IIRC the clixml cmdlets introduced in v1 but I might be wrongWelker
@Allegorical 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.)Elis
C
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.)

Conidium answered 20/7, 2014 at 19:49 Comment(4)
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 ...Suitor
[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.Suitor
Also the ReadMe link is incorrect (and suggested edit queue is full). Easy to find: github.com/iRon7/ConvertTo-Expression#readmeSuitor
@john v kumpf, I have changed the embedded function to cmdlet script which makes it easier installable.Conidium
W
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
Welker answered 28/2, 2013 at 16:0 Comment(5)
I would prefer PSON, as it is easier to read and edit than the XML that is generated by Import-Clixml.Allegorical
If it's a matter of "readability", V3 lets you convert to JSON, which might actually be better.Lipchitz
Yes, I did read that. Unfortunately, my client is still on Windows XP, which doesn't support v2.Allegorical
IIRC the clixml cmdlets introduced in v1 but I might be wrongWelker
@Allegorical 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.)Elis
L
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
Lipchitz answered 28/2, 2013 at 16:18 Comment(1)
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.Allegorical
U
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.

Unfledged answered 15/11, 2016 at 10:46 Comment(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}Beneficent

© 2022 - 2024 — McMap. All rights reserved.