How do I expand variables in a JSON string in Powershell? - json

Goal: Get information from one API request, store those into variables and pass into a new API request to make a JIRA issue.
Env:
Powershell 5.1
Windows 10
Problem: I have no issues with the first two steps in the goal. But I'm not sure how to properly construct the last step.
$summary = 'summary'
$desc = 'desc'
$Body = '{
"fields": {
"project": {"key": "ABC"},
"summary": "${summary}",
"description": ${desc},
"issuetype": {"name": "Story"},
"assignee" : {"name":"bob"}
}
}'
Actual Results: The variables are interpreted literally.
Expected Results: The variables should be expanded in the string.
What I've tried:
I am aware the variable is currently wrapped in single quotes. But using double quotes is not ideal either. There are too many other double quotes to escape. What if I have a massive JSON body? I have to manually put a backtick for each double quote inside. What is a more elegant solution?
Referred to https://adamtheautomator.com/powershell-escape-double-quotes/#Using_PowerShell_to_Escape_Double_Quotes
Many thanks in advance.

There are more ways than one to do this, depending on how large the body json needs to be, you can choose to do that using
Method 1, create a double-quoted Here-String (no need to escape the other double-quotes):
$summary = 'summary'
$desc = 'desc'
$Body = #"
{
"fields": {
"project": {"key": "ABC"},
"summary": "$summary",
"description": "$desc",
"issuetype": {"name": "Story"},
"assignee" : {"name":"bob"}
}
}
"#
Method 2: create the body as template with self-defined placeholders to be used multiple times if needed
$summary = 'summary'
$desc = 'desc'
$template = #'
{
"fields": {
"project": {"key": "ABC"},
"summary": "##SUMMARY##",
"description": "##DESCRIPTION##",
"issuetype": {"name": "Story"},
"assignee" : {"name":"bob"}
}
}
'#
$Body = $template -replace '##SUMMARY##', $summary -replace '##DESCRIPTION##', $desc
Method 3: create numbered placeholders for use with the -f Format operator.
This has the disadvantage that ALL other existing curly brackets { and } have to be doubled..
$summary = 'summary'
$desc = 'desc'
$template = #'
{{
"fields": {{
"project": {{"key": "ABC"}},
"summary": "{0}",
"description": "{1}",
"issuetype": {{"name": "Story"}},
"assignee" : {{"name":"bob"}}
}}
}}
'#
$Body = $template -f $summary, $desc
Method 4, create the body as nested PsCustomObject and convert that to Json
$summary = 'summary'
$desc = 'desc'
$Body = [PsCustomObject]#{
fields = [PsCustomObject]#{
project = [PsCustomObject]#{key = 'ABC'}
summary = $summary
description = $desc
issuetype = [PsCustomObject]#{name = 'Story'}
assignee = [PsCustomObject]#{name = 'bob'}
}
} | ConvertTo-Json -Depth 3 # you can also set this way higher to be on the safe side

You can build a PsObject and convert it to Json format with ConvertTo-Json.
Something like this
$firstname = "myFirstName"
$lastname = "myLastName"
$properties = [ordered]#{
firstname = $firstname
lastname = $lastname
}
$obj = New-Object psobject -Property $properties;
$body = convertto-json $obj
PS >$body
{
"firstname": "myFirstName",
"lastname": "myLastName"
}

Related

Powershell - Sending an array to JSON

I have been having a problem while automating a task using API. The final JSON output of the Powershell script should look like this:
{
"scopes": [
{
"description": "someDescription",
"scopePath": "somePath"
}
],
"sources": [
{
"eventSource": "SecureScores"
},
{
"eventSource": "SecureScoreControls"
},
{
"eventSource": "SecureScoreControlsSnapshot"
},
{
"eventSource": "SecureScoresSnapshot"
}
],
"actions": [
{
"workspaceResourceId": "someWorkspace",
"actionType": "Workspace"
}
]
}
}
While using [] brackets after "actions" and "sources". However no matter what I try I always end up with something like this
"location": "Test",
"etag": "etag",
"properties": {
"Description": "some description",
"IsEnabled": "true",
"scopes": {
"scopePath": "scopePath",
"description": "scopeDescription",
"sources": {
"eventSource": "SecureScores",
"eventSource3": "SecureScoresSnapshot",
"eventSource2": "SecureScoreControlsSnapshot",
"eventSource1": "SecureScoreControls"
},
"actions": {
"actionType": "Workspace",
"workspaceResourceId": "someID"
}
}
}
}
With the wrong {} brackets. I need this to invoke a REST API code correctly. Any idea how to pass the variables so the JSON would then define those values in an array? I use something like
$RESTjson = $RESTdata | ConvertTo-Json while in $RESTData is a hashtable. Example:
$RESTdata = #{
location = $Tags.Region
etag = "etag"
properties = #{
Description = "some description"
IsEnabled = "true"
scopes = #{
description = "scopeDescription"
scopePath = "scopePath"
}
sources = #{
eventSource = "SecureScores"
eventSource1 = "SecureScoreControls"
eventSource2 = "SecureScoreControlsSnapshot"
eventSource3 = "SecureScoresSnapshot"
}
actions = #{
workspaceResourceId = "someID"
actionType = "Workspace"
}
}
}
Tried messing with -Depth parameter of ConverTo-Json command but without any luck. Various definitons of input values but havenĀ“t found the proper way yet.
Use #(...), the array-subexpression operator, around property values that must become arrays.
If you want to retain the definition order, use [ordered] hashtables (not strictly needed).
Use nested hashtables for those properties that must become their own JSON objects, such as #{ eventSource = "SecureScores" } to become { "eventSource": "SecureScores" } in JSON.
$RESTdata = [ordered] #{
location = $Tags.Region
etag = "etag"
properties = [ordered] #{
Description = "some description"
IsEnabled = "true"
scopes = #(
[ordered] #{
description = "scopeDescription"
scopePath = "scopePath"
}
)
sources = #(
#{ eventSource = "SecureScores" }
#{ eventSource = "SecureScoreControls" }
#{ eventSource = "SecureScoreControlsSnapshot" }
#{ eventSource = "SecureScoresSnapshot" }
)
actions = #(
[ordered] #{
workspaceResourceId = "someID"
actionType = "Workspace"
}
)
}
}
# This yields the desired output as shown in your question.
$RESTData | ConvertTo-Json -Depth 3
Note the unfortunate need to pass a -Depth argument to ConvertTo-Json to avoid data truncation - see this post.
Wrap the lone hashtables in the #(...) array subexpression operator to force PowerShell to wrap and store it in an array:
$RESTdata = #{
location = $Tags.Region
etag = "etag"
properties = #{
Description = "some description"
IsEnabled = "true"
scopes = #{
description = "scopeDescription"
scopePath = "scopePath"
}
sources = #(#{
eventSource = "SecureScores"
eventSource1 = "SecureScoreControls"
eventSource2 = "SecureScoreControlsSnapshot"
eventSource3 = "SecureScoresSnapshot"
})
actions = #(#{
workspaceResourceId = "someID"
actionType = "Workspace"
})
}
}

Convert list of values to a key value json

I'm trying to convert a list comma separated IP values (192.168.1.1,192.168.1.2,192.168.1.3, xxxx,xxxx) to a key value json so that at the end of all I can have a file with the structure below.
{
"ips": [
{
"ip": "192.168.1.1"
},
{
"ip": "192.*.*.*"
},
{
"ip": "192.168.1.3/32"
}
]
}
I was trying to user the command ConvertTo-Json command form powershell but really don't manage how to get it.
Home anyone can give me a hand and guide me how to handle this issue
Thanks you very much
Create objects or hashtables with the same contents and structure as your desired JSON, then pipe to ConvertTo-Json:
#{
'ips' = #(
#{ "ip" = "192.168.1.1" },
#{ "ip" = "192.*.*.*" },
#{ "ip" = "192.168.1.3/32" },
)
} |ConvertTo-Json |Set-Content path\to\output.json
Now you just need to generate the inner hashtables from a list instead of hardcoding them:
$listOfIPs = '192.168.1.1,192.*.*.*,192.168.1.3/32' -split ','
#{
'ips' = #(
$listOfIPs |ForEach-Object { #{ "ip" = $_ } }
)
} |ConvertTo-Json |Set-Content path\to\output.json

Appending to an array in json file using powershell script is giving error

{
"$schema": "a",
"contentVersion": "b",
"extensions": [
{
"Name": "c",
"Type": "d",
"Version": "e",
"ConnectionProperties": {
"MaxExecutionTime": "f",
"Authentication": {
"Type": "g",
"Reference": {
"Provider": "h",
"Parameters": {
"SecretId": "i"
}
},
"Properties": {
"ApplicationId": "j",
"TenantId": "k"
}
}
},
"payloadProperties": {
"ConnectionString": {
"Value": "l"
},
"KqlSasUri": {
"reference": {
"path": "m",
"enableScopeTagBindings": "n"
}
},
"DatabaseName": {
"Value": "o"
}
},
"repeatOver": [
{
"name": "p",
"file": "q",
"database": "r"
}
]
}
]
}
Above is the json file, I wish to append to repeatOver array using powershell script
$getContentfromDestinationJson = Get-Content $pathToDestinationJson -Raw | ConvertFrom-Json
$destinationExtension = $getContentfromDestinationJson.extensions
Write-Host $destinationExtension
Output is --
#{Name=a; Type=b; Version=c; ConnectionProperties=; payloadProperties=; repeatOver=System.Object[]}
Also, when I try to write append to this repeatOver array in this json.
Note- the below code is in a for loop, I want to append this multiple times to the array..
for ($i=0; $i -le 3; $i++) {
$toWrite= [PSCustomObject]#{
name = 'd'
file = 'e'
database = 'f'
}
$destinationExtension.repeatOver += (ConvertTo-Json -InputObject $toWrite -Depth 3)
}
I see this error :
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
Also, how do I skip adding a $toWrite value if it already exists, by comparing the name field?
You would need to access the psobject Properties to update that property, I would love to see if there is an easier way of doing this.
Assuming I have the JSON stored in the $json variable, if I try to update .extensions.repeatOver:
PS /> $json.extensions.repeatOver
name file database
---- ---- --------
p q r
PS /> $json.extensions.repeatOver = #()
The property 'repeatOver' cannot be found on this object. Verify that the property exists and can be set.
At line:1 char:1
+ $json.extensions.repeatOver = #()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
To update it you can do it like this:
# Here I'm appending the value from repeatOver with random data
$toAppend = #(
$json.extensions.repeatOver
for($i=0; $i -le 3; $i++)
{
[PSCustomObject]#{
name = Get-Random -Maximum 9
file = Get-Random -Maximum 9
database = Get-Random -Maximum 9
}
}
)
$json.psobject.Properties.where({ $_.Name -eq 'extensions' }).Value.repeatOver = $toAppend
Testing if it works:
PS /> $json.extensions
Name : c
Type : d
Version : e
ConnectionProperties : #{MaxExecutionTime=f; Authentication=}
payloadProperties : #{ConnectionString=; KqlSasUri=; DatabaseName=}
repeatOver : {#{name=p; file=q; database=r}, #{name=3; file=4; database=2}, #{name=5; file=3; database=6}, #{name=1; file=0; database=7}...}
PS /> $json.extensions.repeatOver
name file database
---- ---- --------
p q r
3 4 2
5 3 6
1 0 7
6 3 8
I think this is also relevant: https://github.com/PowerShell/PowerShell/issues/3159
Since the extensions is also an array, you need to loop over that to append your object array to the repeatOver array.
# read the json file and convert it
$json = Get-Content -Path 'Path\To\The\JSON_file.json' -Raw | ConvertFrom-Json
# create your new object array
$toWrite = for ($i=0; $i -le 3; $i++) {
[PSCustomObject]#{
name = "d$i"
file = "e$i"
database = "f$i"
}
}
# loop over the extensions array and append to repeatOver
$json.extensions | ForEach-Object { $_.repeatOver += $toWrite }
$json | ConvertTo-Json -Depth 6 # | Set-Content -Path 'Path\To\The\Extended_JSON_file.json'
P.S. PowerShell does not produce 'pretty' json. If you need to convert it to properly spaced json, see my function Format-Json

How to iterate through a unknown JSON data/object?

Given the two JSON examples
{
"A": {
"name": "noname",
"key": "nokey"
}
and then
{
"B": {
"property1": "value3",
"country": "australia"
}
}
Is it possible to create a Powershell script that could take either one of the JSON examples and loop through them without knowing any names/keys upfront? (Using Windows 2016)
Something similar to what is posted as an answer here How do I loop through or enumerate a JavaScript object?
var p = {
"p1": "value1",
"p2": "value2",
"p3": "value3"
};
for (var key in p) {
if (p.hasOwnProperty(key)) {
console.log(key + " -> " + p[key]);
}
}
Not working
Did try this, but it works only for the first level
$json = #"
{"A": {"property1": "value1", "property2": "value2"}, "B": {"property1":
"value3", "property2": "value4"}}
"#
$parsed = $json | ConvertFrom-Json
$parsed.PSObject.Properties | ForEach-Object {
$next = $_
$name = $_.Name
$value = $_.value
echo "$name = $value"
$next.PSObject.Properties | ForEach-Object {
$name = $_.Name
$value = $_.value
echo "Second level: $name = $value"
}
}
ConvertFrom-Json uses a nested structure of PSCustomObjects to represent the data - you can use $x.psobject.properties to get a collection of json property names and values for each item in the structure, and then you can loop over them however you want.
For example:
$x = ConvertFrom-Json "{ 'A': {'name': 'noname', 'key': 'nokey'} }"
foreach( $rootProperty in #($x.psobject.properties | where-object {$_.MemberType -eq "NoteProperty"}) )
{
write-host "'$($rootProperty.Name)' = '$($rootProperty.Value)'"
foreach( $childProperty in #($rootProperty.Value.psobject.properties | where-object {$_.MemberType -eq "NoteProperty"}) )
{
write-host "'$($childProperty.Name)' = '$($childProperty.Value)'"
}
}
outputs the following:
'A' = '#{name=noname; key=nokey}'
'name' = 'noname'
'key' = 'nokey'
You could also walk the structure recursively to an arbitrary depth if you convert the above into a function that calls itself on nested PSCustomObjects, but I'm not sure if you need to be able to do that or just go 2 levels like in your example json documents.

powershell json object with square brackets

I have an import of 1+ emails that have to be added to a json object. A foreach loop checks to see if the emails are valid (code not addded to this example). When the import contains 2+ cases, the format of the json object is correct:
[
{
"Name": "John Doe",
"Email": "email#gmail.com"
},
{
"Name": "Jane Doe",
"Email": "email#live.com"
}
]
Namely, square brackets with each object in curly brackets, separated by comma.
But when the foreach loop only returns 1 valid email, the format becomes:
{
"Name": "John Doe",
"Email": "email#gmail.com"
}
This is the code I am running:
$body = foreach($row in $mailinglistimp)
{
#{
Email=$row.EPOSTADR
Name=$row.KUNDE_NM
}
}
$body_json = ConvertTo-json $body
How can I force the object to look like this with only one item in the foreach loop?
[
{
"Name": "John Doe",
"Email": "email#gmail.com"
},
]
On a related note, a similar problem was solved by using [int[]] $ids inside another json object, but I cant get a similar code to work using [string[]].
This is somewhat unlikely if you ask me, because you want to declare an array which contains a single item. PowerShell will always see this like a single item and won't add square brackets (array declaration for JSON). But I ran into the same issue when I was making a script to update changes in TOPdesk. So this might help someone.
$requestObject = #()
$action = "Buy a new laptop"
$status = "Rejected"
if($Action){
$requestObject += #{
op = "add"
path = "/progressTrail"
value = $Action
}
}
if($Status){
$requestObject += #{
op = "replace"
path = "/status"
value = $Status
}
}
if($requestObject){
if($requestObject.Count -eq 1){
$requestBody = "[$($requestObject | ConvertTo-Json)]"
}else{
$requestBody = $requestObject | ConvertTo-Json
}
}
The following output is generated:
[
{
"path": "/progressTrail",
"op": "add",
"value": "Buy a new laptop"
},
{
"path": "/status",
"op": "replace",
"value": "Rejected"
}
]
Now if you leave $status empty for instance, you will get the following:
[{
"path": "/progressTrail",
"op": "add",
"value": "Buy a new laptop"
}]
Note that I force the square brackets, when there is only one item in my array ($requestObject.Count -eq 1). This is not very nice imo, but it does the trick.