Setting Json values in Powershell found using pipecommands - json

I have a JSON file that I am reading into a PowerShell object. I want to be able to replace certain values which I find using the where command and pipes.
[
{
"name":"name-1",
"targets":[
{
"attribute":"Country",
"opt":"In",
"values":[
"#country"
]
},
{
"attribute":"Environment",
"opt":"In",
"values":[
"#Environment"
]
}
],
"value":{
"Url":"#url",
"Version":"#version"
}
}
]
I specifically want to replace the what #url, #version, #country and #environment with the values I specify in the powershell.
Setting the value.Url and value.Version seems to work with:
$body=Get-Content -Raw -Path "$path\$filename.json" | ConvertFrom-Json
$body.value.Url=$applicationUrl;
$body.Value.Version = $applicationVersion;
But the targets attribute is a list, so I have to find using the where statement and pipes. Although I can find the correct element using:
$body.Targets | where { $_.attribute -eq "environment" } | Select -First 1 | Select-Object -Property values
All my attempts to set the value have failed, it always remains as is. Powershell is interpreting the object like:
$body.Targets | where { $_.attribute -eq "environment" } | Select -First 1 | Select-Object -Property values
TypeName: Selected.System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
values NoteProperty Object[] values=System.Object[]
How could I set the values property on this object? I just want it to be a string like "qa" or "production"
Thanks

Try to set the value attribute as follows:
($body.Targets | Where-Object { $_.attribute -eq "environment" } | Select-Object -First 1).values = #("VALUE");

Related

How to retrieve UserID from Task Assignments?

I work with API Graph in PowerShell to get Planner tasks. I retrieve the task in my plan but i can't get the user id who the task is assigned.
How can I parse this JSON, in PowerShell for get the value : "8dfb0c3c-5c2b-47eb-924f-ab9365ca9d89"?
A very big thanks for your help.
{
"8dfb0c3c-5c2b-47eb-924f-ab9365ca9d89": {
"#odata.type": "#microsoft.graph.plannerAssignment",
"assignedDateTime": "2018-06-12T09:32:27.9137819Z",
"orderHint": "",
"assignedBy": {
"user": "#{displayName=; id=rHcFhQEnpUm_DNWYU1mTYZYAB64F}"
}
}
}
when getting object by conversion from JSON (ConvertFrom-JSON), you get PSCustomObject. To get the top level keys you can enumerate its members of NoteProperty type. See example below.
$jsonData = Get-Content -Path <FilePath> | ConvertFrom-Json
$topLevelKeys = $jsonData | Get-Member -Type NoteProperty | Foreach-Object {$_.Name}
foreach ($m_topLevelKey in $topLevelKeys) { <do something> }
Hope I got your question right

Convert powershell table to json with column 1 as key and column 2 as value

I'm trying to produce a JSON dictionary from a powershell object with formatting that the ConvertTo-Json cmdlet doesn't provide by default.
For example the ConvertTo-Json will take the output of
(get-counter '\Process(*)\% Processor Time').CounterSamples | select InstanceName, CookedValue
which looks like:
InstanceName CookedValue
------------ -----------
idle 92.02923730929
process1 3
process2 1
process3 0
process4 2
process5 0
process6 2
process7 0
.... ....
and produce a JSON object that looks like this:
[
{
"InstanceName": "idle",
"CookedValue": 92.02923730929
},
{
"InstanceName": "process1",
"CookedValue": 3
},
{
"InstanceName": "process2",
"CookedValue": 1
},
...
]
Would it be possible to instead format the JSON object like this?:
{
idle: 92.02923730929,
process1: 3,
process2: 1,
process3: 0,
process4: 2,
process5: 0,
}
Thanks
Create a new object with properties made up of the sample property values:
$properties = #{}
(Get-Counter '\Process(*)\% Processor Time').CounterSamples |ForEach-Object {
$properties[$_.InstanceName] = $_.CookedValue
}
New-Object psobject -Properties $properties |ConvertTo-Json
Since you could have multiple instances with the same process name, you might want to extract the process name and instance number from the counter Path instead:
$properties = #{}
(Get-Counter '\Process(*)\% Processor Time').CounterSamples |ForEach-Object {
$InstanceName = if($_.Path -match 'process\((.*#\d+)\)'){
$Matches[1]
}
else {
$_.InstanceName
}
$properties[$InstanceName] = $_.CookedValue
}
New-Object psobject -Property $properties |ConvertTo-Json
just pipe it to ConvertTo-Json
(get-counter '\Process(*)\% Processor Time').CounterSamples | select InstanceName, CookedValue | ConvertTo-Json
I cannot answer your second question of reformatting the JSON output

Powershell - Retain Complex objects with ConvertTo-Json

Powershell command-let ConvertTo-Json has the following limitations
1) It returns Enum values as Integers instead of their text
2) It doesn't return the date in a readable format
For point #1 see below, Status and VerificationMethod Properties
PS C:\Windows\system32> Get-Msoldomain | ConvertTo-Json
{
"ExtensionData": {
},
"Authentication": 0,
"Capabilities": 5,
"IsDefault": true,
"IsInitial": true,
"Name": "myemail.onmicrosoft.com",
"RootDomain": null,
"Status": 1,
"VerificationMethod": 1
}
To handle this, I changed my command as below
PS C:\Windows\system32> Get-Msoldomain | ConvertTo-Csv | ConvertFrom-Csv | ConvertTo-Json
{
"ExtensionData": "System.Runtime.Serialization.ExtensionDataObject",
"Authentication": "Managed",
"Capabilities": "Email, OfficeCommunicationsOnline",
"IsDefault": "True",
"IsInitial": "True",
"Name": "curtisjmspartnerhotmail.onmicrosoft.com",
"RootDomain": "",
"Status": "Verified",
"VerificationMethod": "DnsRecord"
}
Now you see, that the enums are being returned with their text values above (Status and VerificationMethod) instead of their integer values.
However, There are a few limitations with this approach:
1) ConvertTo-Csv doesn't retain the Arrays or Complex Objects, and
outputs them as their Class Names (Watch the ExtensionData Properties
in both the outputs). In the second output, we tend to lose the data,
and just get the className
System.Runtime.Serialization.ExtensionDataObject as a string
2) Both ConvertTo-Csv and ConvertFrom-Csv are not the script-level
commandlets, but they are command-level commandlets, which means that
we can't use them at the end of the script , but they will have to be
used with the individual commands like I am doing above. WHEREAS,
ConvertTo-Json need not be applied at the commmandLevel, but just
applied once for the script output.
My question is:
1) How do I still use the convertTo-Json, so that all my enum properties are returned with their texts and not integers, and ALSO the Complex Objects or Arrays are not lost? In the approach I have used, the complex objects are getting lost
2) Also, it should be generic enough so that It can be applied at the end of the script, and not at the command level
ConvertTo-Json and ConvertTo-Csv are both forms of serializing objects in some sort of text representation and both are useful in different use cases.
ConvertTo-Csv is perhaps best used for 2-dimensional data that can be expressed in a table such as a spreadsheet. Hence it is awkward to try to convert "complex" objects, i.e. those with properties that contain other structured data, into a simple table. In such cases PowerShell represents such data as the full name of the data type.
ConvertTo-Json is capable of serializing more complicated objects, since the format allows for nested arrays/data structures, e.g. the ExtensionData property in your example. Note that you may need to use the -Depth parameter to ensure that deeply nested data is serialized correctly.
So the problem really comes down to how the ConvertTo-Json cmdlet serializes enums, which can be demonstrated with:
[PS]> (Get-Date).DayOfWeek
Tuesday
[PS]> (Get-Date).DayOfWeek.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True DayOfWeek System.Enum
[PS]> Get-Date | Select DayOfWeek | ConvertTo-Json
{
"DayOfWeek": 2
}
So before you convert to JSON you need to ensure that the DayOfWeek property (in this example) or Status and VerificationMethod properties (from your example) are converted to their string equivalents first.
You can do this using an expression with Select-Object to convert the data as it passes down the pipe. Note that you do need to include all the properties that you want included in the final JSON:
[PS]> Get-Date |
Select DateTime,#{Label="DayOfWeek";Expression={$_.DayOfWeek.ToString()}} |
ConvertTo-Json
{
"DateTime": "13 June 2017 10:33:51",
"DayOfWeek": "Tuesday"
}
So in your case you'd need something like this:
[PS]> Get-Msoldomain |
Select-Object ExtensionData,IsDefault,IsInitial,Name,RootDomain `
,{Label="Authentication";Expression={$_.Authentication.ToString()}} `
,{Label="Capabilities";Expression={$_.Capabilities.ToString()}} `
,{Label="Status";Expression={$_.Status.ToString()}} `
,{Label="VerificationMethod";Expression={$_.VerificationMethod.ToString()}} |
ConvertTo-Json
#puneet, following your comment on my other answer, here is an example of how you might build up a new object, based on an existing one, with the Enum types converted to strings.
The idea is to create a new "empty" object, then loop through all the properties of the original object and add them to the new one, but if any of the original properties are Enums, then those are converted to strings.
$data = [PSCustomObject]#{}
(Get-Date).PSObject.Properties | Select Name,Value | Foreach-Object {
if($_.Value.GetType().BaseType.FullName -eq "System.Enum"){
$data | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value.ToString()
}
else {
$data | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value
}
}
$data | ConvertTo-Json
You may want to finesse this a little for your own application, but hopefully the idea behind it is clear. Definitely check to see that all the properties are being treated correctly in the JSON output.
to keep enum,array and date when converting psObject to json, you can use newtonsoft. a sample here https://github.com/chavers/powershell-newtonsoft using Nerdy Mishka powershell module.
$obj = New-Object pscustomobject -Property #{Enum = (Get-DAte).DayOfWeek; int = 2; string = "du text"; array = #("un", "deux", "trois"); obj= #{enum = (Get-DAte).DayOfWeek; int = 2; string = "du text"; array = #("un", "deux", "trois")}}
Import-Module Fmg-PrettyJson
$settings = Get-NewtonsoftJsonSettings
$enumconv = "Newtonsoft.Json.Converters.StringEnumConverter"
$e = New-Object $enumconv
$settings.Converters.Add($e)
Set-NewtonsoftJsonSettings $settings
$obj | ConvertTo-NewtonsoftJson
return:
{
"array": [
"un",
"deux",
"trois"
],
"enum": "Thursday",
"int": 2,
"obj": {
"enum": "Thursday",
"array": [
"un",
"deux",
"trois"
],
"int": 2,
"string": "du text"
},
"string": "du text"
}

Powershell update json output with new members

I have looked through the past questions regarding updating a JSON file and can't seem to find exactly what i am looking for.
Lets start with the script itself.
foreach ($Ip in $Ips)
{
Code here that runs against IPs.
foreach ($Cluster in $getCluster) {
Get-Cluster -name $Cluster |
select #{Name='attName';Expression {$IP}},
Name,
att1,
att2,
att3,
att4,
att5,
att6,
att7,
att8 |
Convertto-Json | out-file $ConfigFile.Results.ClusterOutput
}
}
Now the first time this script runs the first time it hits the cluster section. It creates a .txt file where it houses the Json Data. The file looks like this:
First Run of ForEach $IP
[
{
"attName": "1.1.1.1",
"Name": "testData",
"att1": value,
"att2": value,
"att3": value,
"att4": value,
"att5": value,
"att6": null,
"att7": null,
"att8": null
},
{
"attName": "2.2.2.2",
"Name": "testData",
"att1": value,
"att2": value,
"att3": value,
"att4": value,
"att5": value,
"att6": null,
"att7": null,
"att8": null
}
]
Now the when the first foreach gets done with the second foreach i need it to add those attributes for the next IP address that comes up. I would like it to look like the following.
Second Run of ForEach $IP
[
{
"attName": "1.1.1.1",
"Name": "testData",
"att1": value,
"att2": value,
"att3": value,
"att4": value,
"att5": value,
"att6": null,
"att7": null,
"att8": null
},
{
"attName": "2.2.2.2",
"Name": "testData",
"att1": value,
"att2": value,
"att3": value,
"att4": value,
"att5": value,
"att6": null,
"att7": null,
"att8": null
},
{
This is the second runs data.
"attName": "3.3.3.3",
"Name": "testData",
"att1": value,
"att2": value,
"att3": value,
"att4": value,
"att5": value,
"att6": null,
"att7": null,
"att8": null
}
]
I feel like i need an if statement checking to see if the file is created - if it isn't then run it the first time - if it hasn't been written yet then add the "attributes to the existing file".
Here is what I have found and looking at right now. So far I haven't had success in making this work.
EDIT: I should have clarified a little bit better that was my bad. The information below were things i have been testing and trying to get to work. Thats not to say they work - its just things I have found as reference.
<# if (Test-Path $ConfigFile.Results.ClusterOutput){
(New-Object PSObject |
Add-Member -PassThru NoteProperty Name $getClusterConfig.Name |
Add-Member -PassThru NoteProperty Age 10 |
Add-Member -PassThru NoteProperty Amount 10.1
) | Convertto-Json
#pathToJson = "F:\Path\To\JSON\file.json"
# $a = Get-Content $ConfigFile.Results.ClusterOutput | ConvertFrom-Json
#$a.policies.'Framework.DataContext'.connectionString = "Server=ServerName;Database=DateBaseName;Integrated Security=sspi2;"
#$a | ConvertTo-Json | set-content $pathToJson
}
else{
Get-Cluster -name $Cluster |
select #{Name='attName';Expression {$IP}},
Name,
att1,
att2,
att3,
att4,
att5,
att6,
att7,
att8 |
Convertto-Json | out-file $ConfigFile.Results.ClusterOutput
}
My question is how can i get the if than else statement to add the next json child and keep it formatted like it should be. Any help would be greatly appreciated.
Ok, I think you want something like this for the json file appending:
#if file does exist read in json and append to json
if(Test-path "test.json"){
$jsondata = Get-Content test.json | ConvertFrom-Json
#add hashtable of info to array with +=
$jsondata += #{"Test"="ech";"ech"="test"}
}
#else file does not exist, create from scratch
else{
#an array for the initial array of json dicts/hashmaps
$jsondata = #()
#add first hashtable with data
$jsondata += #{"Test"="ech";"ech"="test"}
}
#overwrite file
ConvertTo-Json $jsondata | Out-File test.json
I also think you might want to rethink your use of the pipe, the pipe is fine but it makes the code very hard to read and figure out what is going on.
Consider something like this:
else{
#an array for the initial array of json hashtable
$jsondata = #()
$cluster = Get-Cluster -name $Cluster
$jsondata += #{Name=$cluster.attName; Otherattr=$cluster.data} #etc
ConvertTo-Json $jsondata | Out-File "filehere"
}

finding the character at 'wildcard' position

I'm trying to parse a JSON string which has been flattened from a relational dataset from the looks of it... Into a CSV in order to import into an Oracle database. I have name/value pairs similar to this (simplified) example
Device.1.Service.1.Channel.1.someProperty : 1,
Device.1.Service.1.Channel.1.someOtherProperty : "billy",
Device.1.Service.1.Channel.2.someProperty : 8,
Device.1.Service.1.Channel.2.someOtherProperty : "frank",
Device.1.Service.1.Channel.3.someProperty : 12,
Device.1.Service.1.Channel.3.someOtherProperty : "sam",
Device.1.Service.2.Channel.1.someProperty : 3,
Device.1.Service.2.Channel.1.someOtherProperty : "john",
EDIT: A .json file with a similar structure is produced per day per device. So when consolidated (Get-Content .\*.json -Raw) I see the same property names occur many times in console.
As part of the conversion, I'd like parts of the property name to become fields in the database. This will enable us to visualise the data better using dynamic slider filters etc. later.
| Device | Service | Channel | someOtherProperty |
| 1 | 1 | 1 | billy |
| 1 | 1 | 2 | frank |
| 1 | 1 | 3 | sam |
Right now, I'm using the cmdlet ConvertFrom-Json. I'm then selecting the fields (out of nearly 2000 possible fields) using wildcards. The number of channels for example is dynamic.
Get-Content .\*.json -Raw | ConvertFrom-Json |
Select Device.1.Service.1.Channel.?.someOtherProperty
Which returns a PSCustomObject. I'd like to figure out the channel number and use that as a derived field. Pseudo example:
Select #{n="Channel";e={$_.getCharAtWildcard()}},
$_.theValueofTheCurrentObject()
If I pipe the data selected (using wildcard) to Get-Member, the PSCostomObject contains method names as well as my field names (but not the values).
I'm confused about how I access the field name (since 'Name' gives blank rows), then extract the channel number (the character at wildcard position), and then the value to structure my output correctly.
Any pointers? Googled and have a lynda.com sub, but can't seem to find a solution to this specific problem - probably because I'm not using the correct terms?
## ANSGAR's SOLUTION - WORKS FOR SINGLE FILE ##
$dataDir = "C:\ps_json2csv\dummydata"
CD $dataDir
$dict = Get-Content .\*.json -Raw | ConvertFrom-Json | Select -Expand data
$p = 'DvbId'
$re = "frontend\.(\d+)\.logicalchannel\.(\d+)\.service\.(\d+)\..*?\.$p"
## modified regex to match my data, example string: FrontEnd.2.LogicalChannel.3.Service.1.stat.DvbId
$fullset = $dict.PSObject.Properties | Where-Object {
$_.Name -match $re
} | ForEach-Object {
$prop = [ordered]#{
FrontEnd = $matches[1]
LogicalChannel = $matches[2]
Service = $matches[3]
$p = $_.Value
}
New-Object -Type PSObject -Property $prop
}
## inspect $dict - its populated
## inspect $fullset - its empty! :(
Data of which is 2 files contained in C:\ps_json2csv\dummydata :
File1.json
{
"data": {
"Device.1.Service.1.ChannelInfo.Channel.1.Stats.someProperty" : "1",
"Device.1.Service.1.ChannelInfo.Channel.2.Stats.someProperty" : "8",
"Device.1.Service.1.ChannelInfo.Channel.3.Stats.someProperty" : "12",
"FrontEnd.2.LogicalChannel.3.Service.1.stat.DvbId" : "john",
"FrontEnd.2.LogicalChannel.3.Service.2.stat.DvbId" : "billy",
"FrontEnd.2.LogicalChannel.3.Service.3.stat.DvbId" : "frank",
"FrontEnd.2.LogicalChannel.4.Service.1.stat.DvbId" : "sam",
"Device.1.Service.2.ChannelInfo.Channel.1.Stats.someProperty" : "3",
"Some.value.im.not.intersted.in.just.yet": "Sat Jan 1 00:00:00 GMT 0001",
"foo.bar" : "0",
"random.stuff" : "hi there"
}
}
File2.json
{
"data": {
"Device.1.Service.1.ChannelInfo.Channel.1.Stats.someProperty" : "0",
"Device.1.Service.1.ChannelInfo.Channel.2.Stats.someProperty" : "7",
"Device.1.Service.1.ChannelInfo.Channel.3.Stats.someProperty" : "6",
"FrontEnd.2.LogicalChannel.3.Service.1.stat.DvbId" : "john",
"FrontEnd.2.LogicalChannel.3.Service.2.stat.DvbId" : "billy",
"FrontEnd.2.LogicalChannel.3.Service.3.stat.DvbId" : "frank",
"FrontEnd.2.LogicalChannel.4.Service.1.stat.DvbId" : "sam",
"Device.1.Service.2.ChannelInfo.Channel.1.Stats.someProperty" : "4",
"Some.value.im.not.intersted.in.just.yet": "Sun Jan 2 00:00:00 GMT 0001",
"foo.bar" : "0",
"random.stuff" : "hi there"
}
}
I would probably use a regular expression match to filter and extract your data:
$p = 'someOtherProperty'
$re = "device\.(\d+)\.service\.(\d+)\..*?\.channel\.(\d+)\..*?\.$p"
$fullset = $dict.PSObject.Properties | Where-Object {
$_.Name -match $re
} | ForEach-Object {
$prop = [ordered]#{
Device = $matches[1]
Service = $matches[2]
Channel = $matches[3]
$p = $_.Value
}
New-Object -Type PSObject -Property $prop
}
I'd rather convert a string of this syntax into a PSObject with the entire set of your properties. Like this:
$dict=Get-Content .\*.json -Raw | ConvertFrom-Json
$fullset=$dict.psobject.properties | % {
$parse=$_.name.split('.')
$parse+=,$_.value # since JSON values might be non-plain, we need to add the value as single object
$obj=new-object psobject
for ($i=0;$i -lt $parse.length;$i+=2) { # name-value
$v1=$parse[$i]
$v2=$parse[1+$i]
$obj | add-member -type noteproperty -name $v1 -value $v2
}
$obj
}
Then you parse $fullset like you would with a normal list with where {$_.device -eq '1' -and $_.someOtherProperty -ne $null} etc.