Dynamically Build JSON - PowerShell - json

I am writing a PowerShell function to build the JSON that will be passed to an API. I am using PowerShell 5.
The API expects a certain format and an example is below.
{
“task”: {
“status”: {
“name”: “Resolved”
},
“owner”: {
“name”: “User”
},
“comment”: “Test”
}
}
The structure of the JSON is stored in nested hash tables then piped to ConvertTo-Json and I am passing the data values through parameters.
$baseTask = #{
task = #{
comment = $Comment;
status = #{
name = $Status;
owner = #{
name = $Owner;
}
}
}
I want the function to be as dynamic as possible so I have the identifier as mandatory and then all other parameters as optional.
Param(
[Parameter(Mandatory=$true)]
$TaskID,
$Comment,
$Status,
$Owner
)
If the parameter is NULL I want to remove that element from the definition so that it doesn’t pass NULL to the API. I have the logic below.
If([String]::IsNullOrEmpty($Comment)) {
$baseTask.task.Remove(‘comment’)
}
If([String]::IsNullOrEmpty($Status)) {
$baseTask.task.Remove(‘status’)
}
If([String]::IsNullOrEmpty($Owner)) {
$baseTask.task.Remove(‘owner’)
}
This works; however if I have 50 parameters then I am going to have a lot of repetition with the null checks so I feel there must be a more dynamic way to do this ideally with some sort of loop.
I thought about a hash table with mapping between variable name and command to run, but I can’t find a way to get the name of the variable during execution.
Any help would be much appreciated.

You may check to see which parameters were not used by comparing the bound parameters against all available parameters for your command and then use this to remove the different elements from your hash table.
function New-DynamicJson {
Param(
[Parameter(Mandatory = $true)]
$TaskID,
$Comment,
$Status,
$Owner
)
$baseTask = #{
task = [ordered]#{
Id = $TaskID;
comment = $Comment;
status = #{
name = $Status;
}
owner = #{
name = $Owner;
}
}
}
$commonParams = 'Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable',
'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable'
$MyInvocation.MyCommand.Parameters.Keys.Where({ $_ -notin $commonParams }) |
Where-Object { $_ -notin $PSBoundParameters.Keys } |
ForEach-Object { $baseTask.task.Remove($_) }
$baseTask | ConvertTo-Json
}
Output
PS> New-DynamicJson -TaskID 103 -Comment 'some comment'
{
"task": {
"Id": 103,
"comment": "some comment"
}
}
(Thank you mklement0 for the suggestion to use $MyInvocation instead of $PSCmdlet.MyInvocation which also makes the solution work in non-advanced functions)

You can take something like that with care.
The idea is to convert names ('status') to paths where it should be ('status.name') and then get a hashtable like
'id' = 22
'status.name' = 'new'
'comment' = 'text'
'owner.name' = 'user'
and convert it to multi-layered hashtable based on "dot" separator then:
$baseTask = #{
task = #{
comment = $Comment;
status = #{
name = $Status;
owner = #{
name = $Owner;
}
}
}
And then to JSON
The example below is an example and should be used with caution as it HAS some problems.
function test {
[CMDLetBinding()]
Param(
[Parameter(Mandatory=$true)]
$TaskID,
$Comment,
$Status,
$Owner
)
$paramList = #{}
$params = $PSCmdlet.MyInvocation.BoundParameters
foreach ($paramName in $params.Keys) {
switch -Exact ($paramName) {
'TaskID' { $paramList['id'] = $params[$paramName] }
'Comment' { $paramList['comment'] = $params[$paramName] }
'Status' { $paramList['status.name'] = $params[$paramName] }
'Owner' { $paramList['owner.name'] = $params[$paramName] }
}
}
$replaced = 0
do {
$replaced = 0
$keys = #($paramList.Keys)
foreach ($key in $keys) {
$dotPos = $key.LastIndexOf('.')
if ($dotPos -gt 0) {
$value = $paramList[$key]
if (-not $value.GetType().IsValueType) {
if ($value.Clone -ne $null) {
$value = $value.Clone()
}
}
$subKey = $key.Substring($dotPos + 1)
$newKey = $key.Substring(0, $dotPos)
$paramList.Remove($key) | Out-Null
$newVal = #{$subKey=$value}
$paramList.Add($newKey, $newVal)
$replaced++
}
}
} while ($replaced -gt 0)
return $paramList
}
test -TaskID 123 -Status 'New' -Comment 'Test' -Owner 'user' | ConvertTo-Json -Depth 10 -Compress:$false

Related

Powershell Convert to JSON

I want to convert the data in the following script to JSON, can anyone let me know how to include [] in the JSON using powershell.
My script:
$URL = " http://localhost:4200/api/testinglabproject/test-run"
$projectShortName = "NES"
$testPlanName = "TestManager"
$deviceName = "testt"
$nodeManagerResourceName = "nm-4c2f1575-00fd-4363-b88a-2fb3cf587223"
$hostname = "DSTW8Y2.bla.is.abc.com"
$resourceName = "hs-515a8129-1aa0-4a14-847b-210022fe9cd7"
$hardwareSolutionType = "testt"
$emailId = "abc#gmail.com"
$data = #{
projectShortName=$projectShortName
testPlanName=$testPlanName
hardwareSolution=#{
hardwareSolutionItemList =
#{deviceName = $deviceName
nodeManagerResourceName =$nodeManagerResourceName
hostname = $hostname}
hardwareSolutionMetadataList = "[]"
resourceName = $resourceName
hardwareSolutionType = $hardwareSolutionType
}
emailId = $emailId
}
$jsondata = $data | ConvertTo-Json -Depth 3
Write-Host $jsondata
The resulting JSON should be like this:
{
"projectShortName": <projectShortName>,
"testPlanName": <planName>,
"hardwareSolution": {
"hardwareSolutionItemList": [{"deviceName": <deviceName>,
"nodeManagerResourceName": <NodeManagerResourceName>,
"hostname": <hostName>}],
"hardwareSolutionMetadataList": [],
"resourceName": <HardwareSolutionResourceName>,
"hardwareSolutionType": <hwstName>
},
"emailId": <emailID>
}
I stripped all those superfluous variables and replaced the string "[]" with an actual PowerShell array #() for the hardwareSolutionMetadataList key:
$data = #{
testPlanName = "TestManager"
projectShortName = "NES"
hardwareSolution = #{
hardwareSolutionItemList = #{
deviceName = "testt"
nodeManagerResourceName = "nm-4c2f1575-00fd-4363-b88a-2fb3cf587223"
hostname = "DSTW8Y2.bla.is.abc.com"
}
hardwareSolutionMetadataList = #()
resourceName = "hs-515a8129-1aa0-4a14-847b-210022fe9cd7"
hardwareSolutionType = "testt"
}
emailId = "abc#gmail.com"
}
$jsondata = $data | ConvertTo-Json -Depth 3
Write-Host $jsondata
PowerShell's idea of JSON formatting is a bit odd, but structurally it's OK.
{
"hardwareSolution": {
"hardwareSolutionItemList": {
"deviceName": "testt",
"hostname": "DSTW8Y2.bla.is.abc.com",
"nodeManagerResourceName": "nm-4c2f1575-00fd-4363-b88a-2fb3cf587223"
},
"resourceName": "hs-515a8129-1aa0-4a14-847b-210022fe9cd7",
"hardwareSolutionMetadataList": [
],
"hardwareSolutionType": "testt"
},
"testPlanName": "TestManager",
"emailId": "abc#gmail.com",
"projectShortName": "NES"
}
You can use the -Compress parameter to remove the whitespace for uploading the JSON somewhere.
{"hardwareSolution":{"hardwareSolutionItemList":{"deviceName":"testt","hostname":"DSTW8Y2.bla.is.abc.com","nodeManagerResourceName":"nm-4c2f1575-00fd-4363-b88a-2fb3cf587223"},"resourceName":"hs-515a8129-1aa0-4a14-847b-210022fe9cd7","hardwareSolutionMetadataList":[],"hardwareSolutionType":"testt"},"testPlanName":"TestManager","emailId":"abc#gmail.com","projectShortName":"NES"}

Add key/value pair to hashtable (nested in an array, nested in a hashtable)

Forgive me, but I don't know the correct terminology for this.
Assuming the following hashtable:
$ConfigurationData = #{
AllNodes = #(
#{
NodeName="*"
PSDscAllowPlainTextPassword=$True
PsDscAllowDomainUser=$True
}
)
}
How do I make it look like this:
$ConfigurationData = #{
AllNodes = #(
#{
NodeName="*"
PSDscAllowPlainTextPassword=$True
PsDscAllowDomainUser=$True
NewItem = "SomeNewValue"
AnotherNewItem = "Hello"
}
)
}
I can do:
$ConfigurationData.AllNodes += #{NewItem = "SomeNewValue"}
$ConfigurationData.AllNodes += #{AnotherNewItem = "Hello"}
And doing $ConfgurationData.AllNodes looks right:
$ConfigurationData.AllNodes
Name Value
---- -----
NodeName *
PSDscAllowPlainTextPassword True
PsDscAllowDomainUser True
NewItem SomeNewValue
AnotherNewItem Hello
But converting it to JSON tells a different story:
$ConfigurationData | ConvertTo-Json
{
"AllNodes": [
{
"NodeName": "*",
"PSDscAllowPlainTextPassword": true,
"PsDscAllowDomainUser": true
},
{
"NewItem": "SomeNewValue"
},
{
"AnotherNewItem": "Hello"
}
]
}
NewItem and AnotherNewItem are in their own hashtable and not in the first one and this causes DSC to throw a wobbly:
ValidateUpdate-ConfigurationData : all elements of AllNodes need to be hashtable and has a property NodeName.
I can do the following which gives me the right result:
$ConfigurationData = #{
AllNodes = #(
#{
NodeName="*"
PSDscAllowPlainTextPassword=$True
PsDscAllowDomainUser=$True
}
)
}
#$ConfigurationData.AllNodes += #{NewItem = "SomeNewValue"}
#$ConfigurationData.AllNodes += #{AnotherNewItem = "Hello"}
foreach($Node in $ConfigurationData.AllNodes.GetEnumerator() | Where-Object{$_.NodeName -eq "*"})
{
$node.add("NewItem", "SomeNewValue")
$node.add("AnotherNewItem", "Hello")
}
$ConfigurationData | ConvertTo-Json
{
"AllNodes": [
{
"NodeName": "*",
"PSDscAllowPlainTextPassword": true,
"NewItem": "SomeNewValue",
"AnotherNewItem": "Hello",
"PsDscAllowDomainUser": true
}
]
}
But this seems overkill, compared to a line like $ConfigurationData.AllNodes += #{NewItem = "SomeNewValue"}
I've also tried and failed with:
$ConfigurationData.AllNodes.GetEnumerator() += #{"NewItem" = "SomeNewValue"}
Is there a similar way to target the right "element"?
This line is adding an item at the array level.
$ConfigurationData.AllNodes += #{NewItem = "SomeNewValue"}
In actuality, you want to add to the first element of the array, which is your hashtable:
($ConfigurationData.AllNodes)[0] += #{"new item" = "test"}
Your issue occurs because of the #() brackets you've put in your initial declaration of $ConfigurationData around the internal hashtable, which make it an array.
Per the answer from gms0ulman you need to use the array index operator to access an index of this array and then modify the properties there. E.g for the first element:
$ConfigurationData.AllNodes[0].'NewItem' = 'SomeNewValue'
$ConfigurationData.AllNodes[0].'AnotherNewItem' = 'Hello'
Actually, the only thing I didn't try:
$ConfigurationData = #{
AllNodes = #(
#{
NodeName="*"
PSDscAllowPlainTextPassword=$True
PsDscAllowDomainUser=$True
}
)
}
$ConfigurationData.AllNodes.GetEnumerator().Add("NewItem","SomeNewValue")
$ConfigurationData.AllNodes.GetEnumerator().Add("AnotherNewItem","Hello")
$ConfigurationData | ConvertTo-Json
{
"AllNodes": [
{
"NodeName": "*",
"PSDscAllowPlainTextPassword": true,
"NewItem": "SomeNewValue",
"AnotherNewItem": "Hello",
"PsDscAllowDomainUser": true
}
]
}
I kind of understand the GetEnumerator bit. It creates an index - of sorts, so PS can work with the items.
But I don't get why I have to use the .Add() method and the +=#{} didn't work.

Powershell: Hash table containing functions to be called with parameter

I have a dictionary like this:
function HashHandlerSHA256
{
param($Path, $Checksum)
$csp = new-object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
$ComputedHash = $csp.ComputeHash([System.IO.File]::ReadAllBytes($Path))
$ComputedHash = [System.BitConverter]::ToString($ComputedHash).Replace("-", "").ToLower()
$result = $ComputedHash.CompareTo($Checksum)
return $result -eq 0
}
$HashHandler = #{"SHA256" = HashHandlerSHA256}
containing validation algorithms and the functions to be called for validation. The functions should all have the same parameter and return type.
Now when I have:
$Checksums = #{"SHA256" = "..."}
I'd like to call the correct function depending on which algorithms and values I have available. In this case I'd have a valid sha256 hash.
Now I want to do:
function Validate
{
param($Path, $Checksums)
foreach($Hash in $Checksums) {
$Type = $Hash.Name
$Value = $Hash.Value
if ($HashHandler.ContainsKey($Type)) {
$Handler = $HashHandler.Get_Item($Type)
if (-Not ($Handler -Path $Path -Checksum $Value)) {
return $FALSE
}
}
}
return $TRUE
}
The errormessage I get is:
At C:\Users\username\Desktop\hashtest.ps1:27 char:23
+ if (-Not ($Handler -Path $Path -Checksum $Value)) {
+ ~~~~~
Unexpected token '-Path' in expression or statement.
I am relatively new to PowerShell. I know how to call functions with parameters, but when stored in a variable, I didn't manage to solve this and when searching online for this, I didn't get the answers I needed.
Thanks for help.
if i understand you want Something like this
function HashHandlerSHA256
{
param($Path, $Checksum)
$csp = new-object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
$ComputedHash = $csp.ComputeHash([System.IO.File]::ReadAllBytes($Path))
$ComputedHash = [System.BitConverter]::ToString($ComputedHash).Replace("-", "").ToLower()
$result = $ComputedHash.CompareTo($Checksum)
return $result -eq 0
}
function Validate
{
param($Path, $Checksums)
foreach($Hashkey in $Checksums.Keys)
{
$Value = $Checksums[$Hashkey]
if ($script:HashHandler.ContainsKey($Hashkey))
{
if (-Not (&$script:HashHandler[$Hashkey] -Path $Path -Checksum $Value)) { return $false}
}
}
return $TRUE
}
#add here your couples of algo/function
$script:HashHandler = #{"SHA256" = 'HashHandlerSHA256'}
#checksum to test
$Checksums=#{}
$Checksums["SHA256"]= 'd6a0a09fb1a7971b497674675d5b5621d865d6020e384137548de9c4ac6d4994'
$Checksums["MD5"]= 'xxxx'
#test list checksum and algo
Validate -Path "c:\temp\hello.csv" -Checksums $Checksums
an other solution
$file="C:\temp\exludevalue.txt"
$Checksums=#{}
$Checksums["SHA256"]= 'd6a0a09fb1a7971b497674675d5b5621d865d6020e384137548de9c4ac6d4994'
$Checksums["MD5k"]= '11A8D99F80F9B29FCF6A995D2F17B2E3'
$Checksums.Keys |
%{
if ($(gcm Get-FileHash).Parameters.Algorithm.Attributes.ValidValues -contains $_)
{
$algocalc=(Get-FileHash -path $file -Algorithm $_).Hash;
}
else
{
$algocalc='ALGO NOT FOUNDED'
}
new-object psobject -Property #{
Algo=$_
OldValue=$Checksums[$_]
CalculedValue=$algocalc
ResultComparison= $algocalc -eq $Checksums[$_]
}
}

json formatting string to number

My Json output generates;
[
{
"a1_id":"7847TK10",
"output2":"7847TK10",
"output4":"something",
"output5":"3stars.gif",
"output9": "269000",
...
etc. etc.
The google visualization api asks for a number format for the output9 element e.g.:
"output9": 269000 instead of "output9": "269000". How can I achieve this for this element?
My json.php generates the json output like this:
?>
{
"total": <?php echo $total ?>,
"success": true,
"rows": [
// Iterate over the rows
$nextRow= $result->nextRow();
$r = 1;
$info = array();
while ( $nextRow ) {
$nextColumn = $result->nextColumn();
// Has this column been printed already
if ( $unique )
{
$d = $result->getDataForField($unique);
if ( array_key_exists($d, $already) )
{
$nextRow= $result->nextRow();
continue;
}
$already[$d] = true;
}
echo '{';
// Iterate over the columns in each row
while ( $nextColumn )
{
// Get the variable
$variable = $result->getOutputVariable();
$name = $variable->getName(true);
$data = $result->getDataForField();
if ( !isset($info[$name]) ) {
$info[$name]['translate'] = $variable->shouldTranslate();
$info[$name]['type'] = $variable->getDataType();
$info[$name]['linkable'] = $variable->isLinkable();
}
// Translate the data if requested
if ( $info[$name]['translate'] ) {
$data = LQMTemplate::_($data);
}
$data = $variable->format($data, false);
$type = $info[$name]['type'];
if ( ($type == 'bool') or ($type == 'boolean') )
{
$data = $data ? '1' : '0';
echo "'$name':$data";
} elseif ( $encode ) {
// Can we use json_encode ?
// str_replace because some versions of PHP have a bug that will over escape forward slashes
echo "\"$name\":".str_replace('\\/', '/', json_encode($data));
} else {
$data = LQMUtility::jsonEscape($data, '"');
//echo "'$name':\"$data\"";
echo "\"$name\":\"$data\"";
}
// Conditionally print the next column
$nextColumn = $result->nextColumn();
if ( $nextColumn ) echo ",\n ";
}
// Conditionally print the next column
$nextRow = $result->nextRow();
echo $nextRow ? "},\n" : "}\n";
$r++;
}
unset($result);
echo ']}';
}
}
This depends on how you are generating your JSON.
For example, if you were using a Ruby backend, you could call:
"output9" => output9.to_i
There are various helper methods in different languages (e.g. Java and Javascript have parseInt() functions) to change a string into an integer.
Edit:
If your JSON is being generated by PHP, cast the string to an integer:
$json['output9'] = int($output9_value);
That should get rid of the quotation marks.

Returning value from function (setting variable from existing variable name)

I would like to pass two arguments to my function and set a variable (and variable name) based on the results. I've been trying to do this the following way:
$service_to_check_for = "ping"
function check-service-monitored ($check_command,$check_name) {
if($service_to_check_for -eq $check_command)
{
$Value = "Yes"
}
else
{
$Value = "No"
}
$script:check_command = $check_command
$script:check_name = $check_name
$script:value = $Value
}
check-service-monitored "ping" "NagiosPing"
echo "$check_name : $value"
I would like
$NagiosPing = "Yes"
but how?
Just make sure that the only value your function prints out is the result:
$service_to_check_for = "ping"
function check-service-monitored ($check_command,$check_name) {
if($service_to_check_for -eq $check_command)
{
"Yes"
}
else
{
"No"
}
$script:check_command = $check_command
$script:check_name = $check_name
$script:value = $Value
}
$NagiosPing = check-service-monitored "ping" "NagiosPing"
$NagiosPing
The only output your function provides now is Yes or No, just assign it to your variable