Add windows env variables to json object using powershell - json

I'm quite new to powershell and just need it for a small task so please excuse my complete and utter ineptitude for the language. I was wondering if it were possible to form a json object based off environment variables and a variable that has already been declared earlier in my script. The variable that was already declared is based off a json config named optionsConfig.json and the contents of that file are here.
{"test1": ["options_size", "options_connection", "options_object"],
"test2":["options_customArgs", "options_noUDP", "options_noName"]}
The purpose of the $Options variable in the code below is to take each element in the list value for the respective test and assume that those elements are environment variables in the system, then find their values and form a dictionary object that will be used in the json.
Here is what I have so far.
# Read the JSON file into a custom object.
$configObj = Get-Content -Raw optionsConfig.json |
ConvertFrom-Json
# Retrieve the environment variables whose
# names are listed in the $env:test property
# as name-value pairs.
Get-Item -Path env:* -Include $configObj.$env:testTool
$Options = Get-Item -Path env:* -Include $configObj.$env:testTool |
% {$hash = #{}} {$hash[$_.Name]=$_.Value} {$hash}
The $Options variable looks like so when converted to json
{
"options_size": "default",
"options_object": "forward open",
"options_connection": "connected"
}
I have a few other environment variable values that I would like to be a part of the json object. Those 3 other environment variables I would like the value of are listed below.
$Env.testTool = "test1"
$Env.RecordName = "Record1"
$Env.Target = "Target1"
How would I construct a powershell statement to get the json object to be formatted like this? -
data = {"test": $Env.testTool, "target": "$Env.Target",
"options": "$Options", "RecordName': "$Env.RecordName"}
The keys are all predefined strings and $Options is the dict object from up above. Am I able to form a Json object like this in powershell and how would it be done? Any help would be appreciated. This appears to be the last step in my struggle with powershell.
Here is what I have done.
$jObj = [ordered]#{test= $Env:testTool}
When I change this variable to $jObj = [ordered]#{test= $Env:testTool,options= $Options} I get an error saying missing expression after ','

When I change this variable to $jObj = [ordered]#{test= $Env:testTool,options= $Options} I get an error saying missing expression after ','
Entries of a hashtable literal (#{ ... } or, in its ordered form, [ordered] #{ ... }) must be separated:
either by newlines (each entry on its own line)
or by ; if placed on the same line.
Thus, the following literals are equivalent:
# Multiline form
#{
test= $env:testTool
RecordName= $env:RecordName
Target= $env.Target
options=$Options
}
# Single-line form; separator is ";"
#{ test= $env:testTool; RecordName= $env:RecordName; Target= $env.Target; options=$Options }
Get-Help about_Hashtables has more information.

$jObj = #{test= $env:testTool
RecordName= $env:RecordName
Target= $env.Target
options=$Options}
$jObj | ConvertTo-Json | Set-Content jsonStuff.json
JsonStuff.json is the new json file for the new json object. This syntax for forming $jObj seems to have done the trick.

Related

PowerShell: How can I output a hashtable as json and store it in a variable using a foreach loop?

I'm trying to write a script that interacts with a Rest API to make changes in active directory (I know that doesn't make a lot of sense, but it's how our company does things). The change is to modify the homeDirectory attribute of a list of users belonging to a specific security group. The user's aren't all in the same domain, but are in the same forest and are registered in the global catalog.
So far, this is what I have that works:
function get-groupusers{
$memberlist = get-adgroup -filter "name -eq 'group.users'" -server "server.domain.com" -property member |select -expandproperty member
$GlobalCatalog = Get-ADDomainController -Discover -Service GlobalCatalog
foreach ($member in $memberlist){
get-aduser -identity $member -server "$($GlobalCatalog.name):3268"
}
}
$sAMAccountName = (get-groupusers).samaccountname
This is the part I'm running into problems with:
foreach($an in $SamAccountName){
$json = #{
input = #{
key = "homeDirectory"
value = "\\filepath\$an"
}
}
}
The goal here, is to convert the hashtables into json (the required format for interacting with the API), and save it in the following format (note, the "input" "key" and "value" keys are all what the API actually uses, not just substitutions):
{
"input":{
"key":"homeDirectory",
"value":"\\\\filepath\\$an"
},
{
"key":"homeDirectory",
"value":"\\\\filepath\\$an"
}
}
But right now, the foreach loop just overwrites the $json variable with the last set of hashtables. The loop is iterating over the list correctly, as I can put a convertto-json|write-host cmdlet in it but then the $an variable outputs as "#(samaccountname="$an") instead of just whatever the $an actually is.
If my guess is right and you want a resulting Json as the one shown in your question (with the exception that, as mclayton stated in comments, the input property value should be wrapped in [...] to be an array) then this should do it:
$json = #{
input = #(
foreach($an in $SamAccountName) {
[ordered]#{
key = "homeDirectory"
value = "\\filepath\$an"
}
}
)
}
$json | ConvertTo-Json -Depth 99

Powershell "##vso[task.setvariable variable=testvar1] Not Working in pipeline

In a devops pipeline Im using a powershell script in a YAML file to read a json parameter file and push these values into YAML variables.
I can successfully read and extract the parameters
ScriptType: InlineScript
Inline: |
$params = Get-Content main.parameters.json -Raw | ConvertFrom-Json
$clientname = Out-String -InputObject $params.parameters.clientname
$clientname = $clientname.ToLower()
but when i try to set the variables using
Write-Host "##vso[task.setvariable variable=testvar2]$clientname"
it does not set the testvar2 to the $clientname value - however if i explicitly assign a string value to the variable $clientname = 'pickles' - this does update the variable.
The $clientname type returns as a string. Is there something to do with how the json keys are returned?
full code img
output img
EDIT:
I can read the json absolutely fine - $clientname is populated with the correct value - the issue is when i try to assign it to a YAML variable using task.setvariable. If you look at the full code the $newclientname works as expected e.g populates the yAML variable - $clientname does not.

How can we create dynamic json object in powershell

I am creating powershell script which generation json file where i want to create json object runtime based on mentioned name in $env variable and put some value inside it as json.
$env="prd,test,dev,..,...." # there are total 15 env & $env has 1 and more env.
Desire JSON :
{
"prd" : {
"Sid": "xxxxxx" }
"test" : {
"Sid": "xxx" }
"So on" : {}
}
Trying to write script:
$env="prd,test,..,...,.."
$env=$env - split ","
For ( $i = 0 $i -le ($env.length -1) ;$i+=1){
$env[$i] = New-object System.Collection.ArrayList
$env[$i].add("anydata10")
}
But this approach not working well since $env variable has any 1 or more env value.
In the powershell, can we create dynamic json object at runtime and any other approch to archive it ?
For the root-level object ({"prd":{...}, "test":{...}}), I'd suggest an ordered dictionary, then create a custom object with the Sid property for each property:
# prepare env keys
$environments = "prd,test,dev,stg,qa" -split ","
# create ordered dictionary
$jsonObject = [ordered]#{}
foreach($envName in $environments){
# create 1 property, the value of which will be an object with a `Sid` property
$jsonObject[$envName] = [pscustomobject]#{
# resolve the correct `Sid` value from somewhere
Sid = Get-SidForEnv $envName
}
}
# convert the whole thing to json
$jsonObject |ConvertTo-Json -Depth 3
Based on your comments about getting the correct sid value "from somewhere", I assume you already have a function that translates prd to prodsid1 and so forth, so simply replace Get-SidForEnv with whatever function that is :)
I'm not sure I understand what you're trying to achieve. You can access environment variables using $env. So, if you have those values prd,test,... in some environment variable and you want to generate new environment variables based on those, you can do it like this:
# $env:foo contains 'prd,test'
$env:foo -split ',' | % {
New-Item -Path Env: -Name $_ -Value (#("anydata10") | ConvertTo-Json)
}

Set property value on object loaded from json containing comments

When loading an object from a json file one can normally set the value on properties and write the file back out like so:
$manifest = (gc $manifestPath) | ConvertFrom-Json -AsHashtable
$manifest.name = "$($manifest.name)-sxs"
$manifest | ConvertTo-Json -depth 100 | Out-File $manifestPath -Encoding utf8NoBOM
But if the source json file contains comments, the object's properties can't be set:
// *******************************************************
// GENERATED FILE - DO NOT EDIT DIRECTLY
// *******************************************************
{
"name": "PublishBuildArtifacts"
}
Running the code above throws an error:
$manifest
id : 1D341BB0-2106-458C-8422-D00BCEA6512A
name : PublishBuildArtifacts
friendlyName : ms-resource:loc.friendlyName
description : ms-resource:loc.description
category : Build
visibility : {Build}
author : Microsoft Corporation
version : #{Major=0; Minor=1; Patch=71}
demands : {}
inputs : {#{name=CopyRoot; type=filePath; label=ms-resource:loc.input.label.CopyRoot; defaultValue=;
required=False; helpMarkDown=Root folder to apply copy patterns to. Empty is the root of the
repo.}, #{name=Contents; type=multiLine; label=ms-resource:loc.input.label.Contents;
defaultValue=; required=True; helpMarkDown=File or folder paths to include as part of the
artifact.}, #{name=ArtifactName; type=string; label=ms-resource:loc.input.label.ArtifactName;
defaultValue=; required=True; helpMarkDown=The name of the artifact to create.},
#{name=ArtifactType; type=pickList; label=ms-resource:loc.input.label.ArtifactType;
defaultValue=; required=True; helpMarkDown=The name of the artifact to create.; options=}…}
instanceNameFormat : Publish Artifact: $(ArtifactName)
execution : #{PowerShell=; Node=}
$manifest.name
PublishBuildArtifacts
$manifest.name = "sxs"
InvalidOperation: The property 'name' cannot be found on this object. Verify that the property exists and can be set.
When I strip the comments, I can overwrite the property.
Is there a way I can coax PowerShell to ignore the comments while loading the json file/convert the object and generate a writable object?
I'm not sure if this is intended, but seems like ConvertFrom-Json is treating the comments on the Json as $null when converting it to an object. This only happens if it's receiving an object[] from pipeline, with a string or multi-line string it works fine.
A simple way to demonstrate this using the exact same Json posted in the question:
$contentAsArray = Get-Content test.json | Where-Object {
-not $_.StartsWith('/')
} | ConvertFrom-Json -AsHashtable
$contentAsArray['name'] = 'hello' # works
Here you can see the differences and the workaround, it is definitely recommended to use -Raw on Get-Content so you're passing a multi-line string to ConvertFrom-Json:
$contentAsString = Get-Content test.json -Raw | ConvertFrom-Json -AsHashtable
$contentAsArray = Get-Content test.json | ConvertFrom-Json -AsHashtable
$contentAsString.PSObject, $contentAsArray.PSObject | Select-Object TypeNames, BaseObject
TypeNames BaseObject
--------- ----------
{System.Collections.Hashtable, System.Object} {name}
{System.Object[], System.Array, System.Object} {$null, System.Collections.Hashtable}
$contentAsArray['name'] # null
$null -eq $contentAsArray[0] # True
$contentAsArray[1]['name'] # PublishBuildArtifacts
$contentAsArray[1]['name'] = 'hello'
$contentAsArray[1]['name'] # hello
Santiago Squarzon's helpful answer shows an effective solution and analyzes the symptom. Let me complement it with some background information.
tl;dr
You're seeing a variation of a known bug, still present as of PowerShell 7.2.1, where a blank line or a single-line comment as the first input object unexpectedly causes $null to be emitted (first) - see GitHub issue #12229.
Using -Raw with Get-Content isn't just a workaround, it is the right - and faster - thing to do when piping a file containing JSON to be parsed as whole to ConvertFrom-Json
For multiple input objects (strings), ConverFrom-Json has a(n unfortunate) heuristic built in that tries to infer whether the multiple strings represent either (a) the lines of a single JSON document or (b) separate, individual JSON documents, each on its own line, as follows:
If the first input string is valid JSON by itself, (b) is assumed, and an object representing the parsed JSON ([pscustomobject] or , with -AsHashtable, [hastable], or an array of either) is output for each input string.
Otherwise, (a) is assumed and all input strings are collected first, in a multi-line string, which is then parsed.
The aforementioned bug is that if the first string is an empty/blank line or a single-line comment[1] (applies to both // ... comments, which are invariably single-line, and /* ... */ if they happen to be single-line), (b) is (justifiably) assumed, but an extraneous $null is emitted before the remaining (non-blank, non-comment) lines are parsed and their object representation(s) are output.
As a result an array is invariably returned, whose first element is $null - which isn't obvious, but results in subtle changes in behavior, as you've experienced:
Notably, attempting to set a property on what is presumed to be a single object then fails, because the fact that an array is being accessed makes the property access an instance of member-access enumeration - implicitly applying property access to all elements of a collection rather than the collection itself - which only works for getting property values - and fails obscurely when setting is attempted - see this answer for details.
A simplified example:
# Sample output hashtable parsed from JSON
$manifest = #{ name = 'foo' }
# This is what you (justifiably) THOUGHT you were doing.
$manifest.name = 'bar' # OK
# Due to the bug, this is what you actually attempted.
($null, $manifest).name = 'bar' # !! FAILS - member-access enumeration doesn't support setting.
As noted above, the resulting error message - The property 'name' cannot be found on this object. ... - is unhelpful, as it doesn't indicate the true cause of the problem.
Improving it would be tricky, however, as the user's intent is inherently ambiguous: the property name may EITHER be a situationally unsuccessful attempt to reference a nonexistent property of the collection itself OR, as in this case, a fundamentally unsupported attempt at setting properties via member-access enumeration.
Conceivably, the following would help if the target object is a collection (enumerable) from PowerShell's perspective: The property 'name' cannot be found on this collection, and setting a collection's elements' properties by member-access enumeration isn't supported.
[1] Note that comments in JSON are supported in PowerShell (Core) v6+ only.

Merging JSON Objects at runtime in Powershell

Following on from a question yesterday (JSON and references to other JSON objects).
Is it possible to merge JSON objects at runtime in a similar fashion?
In my test.json I wish to insert the $Defaults.wimos object into WIMS.wimos at runtime similar to what I did for the $Paths.drive value in $Defaults.
{
Paths: {drive: "W:"},
Defaults: {wimos: {dstdrive: "$($Paths.drive)"}
},
WIMS: {
winos: "$($Defaults.wimos)",
wimre: {dstdrive: "$($Paths.drive)"}
}
}
In the following code I cannot work out the syntax to have the object replaced at runtime.
$JSONConfig="test.json"
$rawJSON = (Get-Content $JSONConfig -Raw)
$pathJSON = $rawJSON | ConvertFrom-Json
#
# Load Paths from JSON into $Paths
#
$Paths=$pathJSON.Paths
#
# Merge JSON objects to have the defaults replaced
#
$DefaultsJSON=($ExecutionContext.InvokeCommand.ExpandString($rawJSON)) | ConvertFrom-Json
#
# Load Defaults into $Defaults
#
$Defaults=$DefaultsJSON.Defaults
#
# Merge JSON objects to have the System replaced
#
$JSON = ($ExecutionContext.InvokeCommand.ExpandString($rawJSON)) | ConvertFrom-Json
$JSON.WIMS
write-host ("JSON.WIMS.wimre.dstdrive =" + $JSON.WIMS.wimre.dstdrive)
write-host ("JSON.WIMS.wimos.dstdrive =" + $JSON.WIMS.wimos.dstdrive) #This fails to access and print "W:"
I would like to be able to replace $($Defaults.wimos) and be able to query the dstdrive member.
Is this possible to achieve this in powershell? Any suggestions on how?
Thanks
Stuart
There's a couple of issues in your code. First, the value that would be inserted into your template would be interpolated as a string and not a json-object.
To do this, you could use the ConvertTo-json within the template. I'll show you how in a minute.
If you're gonna be recreating and using different "templates" within the same file (like path, defaults etc), I would create a helper function for recreating / filling out the template on your session variables all the time (in order to avoid repeating code).
Create the following method:
function Get-ParsedJsonTemplate(){
return ($ExecutionContext.InvokeCommand.ExpandString((Get-Content $JSONConfig -Raw))) | ConvertFrom-Json
}
Change your template json to the following (showing you that you can use powershell code within your template file:
{
Paths: {drive: "W:"},
Defaults: {wimos: {dstdrive: "$($Paths.drive)"}},
WIMS: {
winos: $(
if($Defaults -and $Defaults.wimos){
$Defaults.wimos|ConvertTo-Json -Compress
} else {
#{"dstdrive"=""}|ConvertTo-Json -compress
}
),
wimre: {dstdrive: "$($Paths.drive)"}
}
}
You also had a typo in either your template or your printout (I see that you used wimos in your "printout" in powershell and "winos" in your template. I modified the code and the following should work for you (given the above test.json):
$JSONConfig="c:\tmp\test.json"
function Get-ParsedJsonTemplate(){
return ($ExecutionContext.InvokeCommand.ExpandString((Get-Content $JSONConfig -Raw))) | ConvertFrom-Json
}
$pathJSON = Get-ParsedJsonTemplate;
$Paths=$pathJSON.Paths
$DefaultsJSON=Get-ParsedJsonTemplate;
$Defaults=$DefaultsJSON.Defaults
$JSON = Get-ParsedJsonTemplate;
$JSON.WIMS
write-host ("JSON.WIMS.wimre.dstdrive =" + $JSON.WIMS.wimre.dstdrive)
write-host ("JSON.WIMS.wimos.dstdrive =" + $JSON.WIMS.winos.dstdrive)
The reason your script was failing was due to the fact that $Defaults.wimos was not the string { dstdrive: "W:" } it was instead a PSCustomObject which would be interpolated to a the value #{drive=W:}. To obtain the correct behavior, you need to convert the $Defaults.wimos value back to a json string.
$Defaults = $DefaultsJSON.Defaults
$Defaults.wimos = $Defaults.wimos | ConvertTo-Json -Compress