Save hash table in PowerShell object notation (PSON) - powershell

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'}
)
}

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.)

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

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

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.

Related

How to Pass User Input to a JSON File using powershell

I am creating a PowerShell script using Convert To-JSON cmd and i achieved that using below
$body = #{
devices = #(
#{
credentials = #(
#{
label = 'username'
value = 'myname'
sensitive = 'false'
},
#{
label = 'Password'
value = 'Password#!'
sensitive = 'true'
}
)
services = #(
#{
name = 'RDP'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
},
#{
name = 'HTTPS'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
},
#{
name = 'SSH'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
}
connections = #(
#{
id = 'myname-rdp'
protocol = 'rdp'
hostname = "192.168.1.6"
port ='3389'
}
)
Parameters = #(
#{
name = 'username'
value = 'myname'
},
#{
name = 'password'
value = 'Password#!'
}
)
}
)
}
i am converting the above powershell to JSON File($body | ConvertTo-Json -Depth 4) and i would like to replace pass the arguments for the Username and IP Address and store it with the username.json every time while converting to JSON.
i have tried the Read-host to get the input from the user but i am facing difficulty to pass that input before printing the output.
For the IP address, you can set the new value by defining the parameter using standard dot-notation.
In the case of username, because Parameters is an array with duplicate object names, doing $body.devices.Parameters.name would return both the username and password objects, so you can filter the array to only select the object where the name is username
$inputUsername = Read-Host "Input Username"
$inputIpAddress = Read-Host "Input IP address"
( $body.devices.Parameters | Where-Object { $_.name -eq 'username' } ).value = $inputUsername
$body.devices.connections.hostname = $inputIpAddress
$body | ConvertTo-Json -Depth 4
As an interpreted language, PowerShell allows you to construct literals based on values that are determined at runtime.
Therefore, simply place your hashtable literal (#{ ... }) after your Read-Host calls, and reference the variables in which you store the Read-Host responses in that literal.
A simplified example:
# Simulate two Read-Host calls (hard-code sample values).
$foo = 42
$bar = 'hi'
# Now define the hashtable literal and reference the variables assigned to above.
# Note: An [ordered] hashtable is used in order to retain the entries
# in definition order.
[ordered] #{
devices = #{
foo = $foo
bar = $bar
}
}
Piping the above to ConvertTo-Json -Depth 4 yields:
{
"devices": {
"foo": 42,
"bar": "hi"
}
}
Note how the values of $foo and $bar were incorporated. You could even use the same technique in a loop, provided the hashtable literal is also in the loop body (after setting the variables it references).

How can I run compare content in a Variable against a hashtable on PowerShell

I have a hashtable as below:
$ProjectType = #{
CSharp = 'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC'
Web_Application = '349C5851-65DF-11DA-9384-00065B846F21'
Windows_Communication_Foundation = '3D9AD99F-2412-4246-B90B-4EAA41C64699'
Windows_Presentation_Foundation = '60DC8134-EBA5-43B8-BCC9-BB4BC16C2548'
Test = '3AC096D0-A1C2-E12C-1390-A8335801FDAB'
Silverlight = 'A1591282-1198-4647-A2B1-27E5FF5F6F3B'
}
I want to run the hashtable against contents in a variable $file, and get a return of the projecttype name from the hashtable if the value (guid) is found in $file.
You most likely want to reverse the order of your Keys and Values on your Hash Table:
$ProjectType = #{
'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC' = 'CSharp'
'349C5851-65DF-11DA-9384-00065B846F21' = 'Web_Application'
'3D9AD99F-2412-4246-B90B-4EAA41C64699' = 'Windows_Communication_Foundation'
'60DC8134-EBA5-43B8-BCC9-BB4BC16C2548' = 'Windows_Presentation_Foundation'
'3AC096D0-A1C2-E12C-1390-A8335801FDAB' = 'Test'
'A1591282-1198-4647-A2B1-27E5FF5F6F3B' = 'Silverlight'
}
# using this as example
$exampleFile = #'
349C5851-65DF-11DA-9384-00065B846F21
60DC8134-EBA5-43B8-BCC9-BB4BC16C2548
A1591282-1198-4647-A2B1-27E5FF5F6F3B
00000000-1198-4647-A2B1-27E5FF5F6F3B
'# -split '\r?\n'
foreach($line in $exampleFile)
{
if($val = $ProjectType[$line])
{
"$line => $val"
continue
}
"$line => could not be found on reference table."
}
Flip the keys and values around so that the reference GUIDs are the keys:
$ProjectType = #{
'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC' = 'CSharp'
'349C5851-65DF-11DA-9384-00065B846F21' = 'Web_Application'
'3D9AD99F-2412-4246-B90B-4EAA41C64699' = 'Windows_Communication_Foundation'
'60DC8134-EBA5-43B8-BCC9-BB4BC16C2548' = 'Windows_Presentation_Foundation'
'3AC096D0-A1C2-E12C-1390-A8335801FDAB' = 'Test'
'A1591282-1198-4647-A2B1-27E5FF5F6F3B' = 'Silverlight'
}
Now you can easily look up any value:
$file = '3AC096D0-A1C2-E12C-1390-A8335801FDAB'
if($ProjectType.ContainsKey($file)){
Write-Host "Found project type guid for '$($ProjectType[$file])'"
}

How to convert a collection of hashtables to a table within a file?

We're trying to write an array of ordered hashtables to a file. While doing this we would like to to be aligned properly in a table format. A bit similar to Format-Table but differently presented.
The code below works fine for the first case but not for the second one. In the second one we have a property that is of type array and is incorrectly assessed. It feels a bit like we're reinventing the wheel here, is there a better solution?
Usually the values will be of type string, string[] or boolean, but what if another type is passed? Thanks for your help.
Desired goals
Describe 'ConvertTo-TextTableHC' {
It 'should convert a collection of hashtable to a text table' {
$testParameters = #(
[ordered]#{
Name = 'ScriptName'
Value = 'Get printers'
Type = 'String'
Mandatory = $true
}
[ordered]#{
Name = 'PrinterName'
Value = ''
Type = 'String[]'
Mandatory = $false
}
)
$actual = ConvertTo-TextTableHC -Name $testParameters
$expected = #"
Name Value Type Mandatory
ScriptName Get printers String True
PrinterName String[] False
"#
$actual | Should -Be $expected
}
It 'should convert a collection of hashtable to a text table' {
$testParameters = #(
[ordered]#{
Mandatory = $true
Name = 'ScriptName'
Value = 'Get printers'
Type = 'String'
}
[ordered]#{
Mandatory = $false
Name = 'OU'
Value = #('ou1', 'ou2')
Type = 'String[]'
}
)
$actual = ConvertTo-TextTableHC -Name $testParameters
$expected = #"
Mandatory Name Value Type
True ScriptName Get printers String
False OU ou1, ou2 String[]
"#
$actual | Should -Be $expected
} -Tag test
}
This is the function:
Function ConvertTo-TextTableHC {
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[System.Collections.Specialized.OrderedDictionary[]]$Name,
[int]$Spacing = 1
)
$result = [String[]]::new($Name.Count + 1)
foreach ($prop in $($name[0].Keys)) {
$rows = , $prop + $Name.$prop
$maxChars = ($rows | Measure-Object -Maximum -Property Length).Maximum + $Spacing
for ($i = 0; $i -lt $rows.Count; $i++) {
$spaces = ' ' * (
$maxChars -
($rows[$i] | Measure-Object -Character).Characters
# fails on boolean values:
# ($rows[$i] | Measure-Object -Maximum -Property Length).Maximum
)
$result[$i] += '{0}{1}' -f $rows[$i], $spaces
}
}
# remove spaces from last column
$result = $result.ForEach({$_.Trim()})
$result -join "`r`n"
}

PowerShell: Adding "rows" to custom object

I want to create an object in powershell that stores information about the state of a script. I can do this:
$myScriptObject =
#("status", "Selected Operation(s):", "None"),
("status", "Current Operation:", "None"),
("status", "Current Step:", "Prompting for Script Action" ),
("test", "This is just for testing", "1,2,3") `
| ForEach-Object {[pscustomobject]#{kind = $_[0]; name = $_[1]; value
= $_[2]}}
And that works:
$myScriptObject
kind name value
---- ---- -----
status Selected Operation(s): None
status Current Operation: None
status Current Step: Prompting for Script Action
test This is just for testing 1,2,3
...and I can even do this:
foreach($myObject in $myScriptObject) {
if ($myObject.kind -eq 'status') {
Write-Host $myObject.name $myObject.value
}
}
which outputs this:
Selected Operation(s): None
Current Operation: None
Current Step: Prompting for Script Action
My questions are:
1. how do I add something like the following to $myScriptObject:
-kind "ActionMenuChoice" -Name "Do This" -Value 1
-kind "ActionMenuChoice" -Name "Do That" -Value 2
How do I change items already in the object?
status Current Step: Prompting for Script Action
to
status Current Step: Prompting for Login
Or am I going about it all wrong? The idea came from the difficulty in returning numerous variables back from a function, and I read using objects is much better to pass back and forth in functions, and found using objects to be much easier to keep track of and to a certain extent manipulate.
Cheers!
If we are keeping your current object array structure, you can create $myScriptObject as an generic list type by casting [collections.generic.list[object]]. Then you can use the .Add() method to add items to your collection.
[collections.generic.list[object]]$myScriptObject =
#("status", "Selected Operation(s):", "None"),
("status", "Current Operation:", "None"),
("status", "Current Step:", "Prompting for Script Action" ),
("test", "This is just for testing", "1,2,3") |
ForEach-Object {[pscustomobject]#{kind = $_[0]; name = $_[1]; value = $_[2]}}
[void]$myScriptObject.add([pscustomobject]#{"Kind" = "ActionMenuChoice"; "Name" = "Do This"; "Value" = 1})
[void]$myScriptObject.add([pscustomobject]#{"Kind" = "ActionMenuChoice"; "Name" = "Do That"; "Value" = 2})
If you want to update an item property in that collection, you will first need to find the object/item within the collection and then access the property you want to update.
($myScriptObject | Where-Object {$_.name -eq 'Current Step:'}).value = "Prompting for Login"
Where-Object can provide the condition needed to locate the target object. Then you can use the object.property syntax to access the property. With PowerShell objects, you can do direct assignment syntax (object.property = value) to update the property value.
I would use a datatable instead:
Add-Type -AssemblyName System.Collections
$dt = New-Object system.Data.DataTable
[void]$dt.Columns.Add('kind',[string]::empty.GetType() )
[void]$dt.Columns.Add('name',[string]::empty.GetType() )
[void]$dt.Columns.Add('value',[string]::empty.GetType() )
# Add new rows:
$newRow = $dt.NewRow()
$newRow.kind = 'status'
$newRow.name = 'Selected Operation(s):'
$newRow.value = 'None'
[void]$dt.Rows.Add( $newRow )
$newRow = $dt.NewRow()
$newRow.kind = 'status'
$newRow.name = 'Selected'
$newRow.value = 'None'
[void]$dt.Rows.Add( $newRow )
# Find row(s):
$rows = $dt.Select("kind = 'status'")
"Found:"
$dt
# Change first row by condition:
$rows = $dt.Select("kind = 'status'")
$rows[0].value = 'test'
[void]$dt.AcceptChanges()
"Changed one row:"
$dt
# Change all rows:
$rows = $dt.Select("")
$rows | % { $_.value = 'new' }
[void]$dt.AcceptChanges()
"Changed all:"
$dt
# Change all rows by condition:
$rows = $dt.Select("name = 'Selected'")
$rows | % { $_.value = 'newer' }
[void]$dt.AcceptChanges()
"Changed by condition:"
$dt

Array of hash tables not output

This is a basic question but I'm stuck. I have the below code:
$array = #(
$hashtable1 = #{
Name = "Test1"
Path = "C:\Test1"
}
$hashtable2 = #{
Name = "Test1"
Path = "C:\Test1"
}
)
The array is created but empty. I have tried comma separation:
$hashtable1 = #{}, $hashtable2 = #{}
But this did not work. What is wrong?
You are assigning the hashtables as variables. Take out the variable assignment:
$array = #(
#{
Name = "Test1"
Path = "C:\Test1"
},
#{
Name = "Test1"
Path = "C:\Test1"
}
)
gms0ulman's helpful answer provides an effective solution for constructing your array of hashtables.
To provide some background information:
A variable assignment such as $hashtable1 = ... is not an expression, so it produces no output, which is why your $array = assignment ended up containing an empty array, given that #(...) saw no output.
However, you can make assignment statements produce output simply by enclosing them in (...), which turns them into expressions, which allows you to assign to the variable and output the assigned value.
#(...) is not needed to construct arrays; instead, you can use ,, the array-construction operator.
Even though it may not be needed, the following demonstrates how to both construct the array of hashtables and save the individual hashtables in dedicated variables:
$array =
($hashtable1 = #{
Name = "Test1"
Path = "C:\Test1"
}),
($hashtable2 = #{
Name = "Test1"
Path = "C:\Test1"
})