PowerShell - How do I iterate a PSCustomObject nested object? - json

I feel like this is something simple and I'm just not getting it, and I'm not sure if my explanation is great.
I have this below JSON file, and I want to get "each App" (App1, App2, App3) under the "New" object
In this script line below I'm essentially trying to replace "TestApp2" with some variable. I guess I'm trying to get TestApp2 as an object without knowing the name.
And I realize that the foreach loop doesn't do anything right now
Write-Host $object.Value.TestApp2.reply_urls
JSON:
{
"New": {
"App1": {
"reply_urls": [
"https://testapp1url1"
]
},
"App2": {
"reply_urls": [
"https://testapp2url1",
"https://testapp2url2"
]
},
"App3": {
"reply_urls": [
"https://testapp3url1",
"https://testapp3url2",
"https://testapp3url3"
]
}
},
"Remove": {
"object_id": [
""
]
}
}
Script:
$inputFile = Get-Content -Path $inputFilePath -Raw | ConvertFrom-Json
foreach ($object in $inputFile.PsObject.Properties)
{
switch ($object.Name)
{
New
{
foreach ($app in $object.Value)
{
Write-Host $object.Value.TestApp2.reply_urls
# essentially want to replace this line with something like
# Write-Host $app.reply_urls
}
}
Remove
{
}
}
}
Output:
https://testapp2url1 https://testapp2url2

You can access the object's PSObject.Properties to get the property Names and property Values, which you can use to iterate over.
For example:
foreach($obj in $json.New.PSObject.Properties) {
$out = [ordered]#{ App = $obj.Name }
foreach($url in $obj.Value.PSObject.Properties) {
$out[$url.Name] = $url.Value
}
[pscustomobject] $out
}
Produces the following output:
App reply_urls
--- ----------
App1 {https://testapp1url1}
App2 {https://testapp2url1, https://testapp2url2}
App3 {https://testapp3url1, https://testapp3url2, https://testapp3url3}
If you just want to output the URL you can skip the construction of the PSCustomObject:
foreach($obj in $json.New.PSObject.Properties) {
foreach($url in $obj.Value.PSObject.Properties) {
$url.Value
}
}

Complementing Santiago Squarzon's helpful answer, I've looked for a generalized approach to get JSON properties without knowing the names of their parents in advance.
Pure PowerShell solution
I wrote a little helper function Expand-JsonProperties that "flattens" the JSON. This allows us to use simple non-recursive Where-Object queries for finding properties, regardless how deeply nested they are.
Function Expand-JsonProperties {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject] $Json,
[Parameter()] [string] $Path,
[Parameter()] [string] $Separator = '/'
)
process {
$Json.PSObject.Properties.ForEach{
$propertyPath = if( $Path ) { "$Path$Separator$($_.Name)" } else { $_.Name }
if( $_.Value -is [PSCustomObject] ) {
Expand-JsonProperties $_.Value $propertyPath
}
else {
[PSCustomObject]#{
Path = $propertyPath
Value = $_.Value
}
}
}
}
}
Given your XML sample we can now write:
$inputFile = Get-Content -Path $inputFilePath -Raw | ConvertFrom-Json
$inputFile | Expand-JsonProperties | Where-Object Path -like '*/reply_urls'
Output:
Path Value
---- -----
New/App1/reply_urls {https://testapp1url1}
New/App2/reply_urls {https://testapp2url1, https://testapp2url2}
New/App3/reply_urls {https://testapp3url1, https://testapp3url2, https://testapp3url3}
Optimized solution using inline C#
Out of curiosity I've tried out a few different algorithms, including ones that don't require recursion.
One of the fastest algorithms is written in inline C# but can be called through an easy to use PowerShell wrapper cmdlet (see below). The C# code basically works the same as the pure PowerShell function but turned out to be more than 9 times faster!
This requires at least PowerShell 7.x.
# Define inline C# class that does most of the work.
Add-Type -TypeDefinition #'
using System;
using System.Collections.Generic;
using System.Management.Automation;
public class ExpandPSObjectOptions {
public bool IncludeObjects = false;
public bool IncludeLeafs = true;
public string Separator = "/";
}
public class ExpandPSObjectRecursive {
public static IEnumerable< KeyValuePair< string, object > > Expand(
PSObject inputObject, string parentPath, ExpandPSObjectOptions options ) {
foreach( var property in inputObject.Properties ) {
var propertyPath = parentPath + options.Separator + property.Name;
if( property.Value is PSObject ) {
if( options.IncludeObjects ) {
yield return new KeyValuePair< string, object >( propertyPath, property.Value );
}
// Recursion
foreach( var prop in Expand( (PSObject) property.Value, propertyPath, options ) ) {
yield return prop;
}
continue;
}
if( options.IncludeLeafs ) {
yield return new KeyValuePair< string, object >( propertyPath, property.Value );
}
}
}
}
'#
# A PowerShell cmdlet that wraps the C# class.
Function Expand-PSObjectRecursive {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)] [PSObject] $InputObject,
[Parameter()] [string] $Separator = '/',
[Parameter()] [switch] $IncludeObjects,
[Parameter()] [switch] $ExcludeLeafs
)
process {
$options = [ExpandPSObjectOptions]::new()
$options.IncludeObjects = $IncludeObjects.ToBool()
$options.IncludeLeafs = -not $ExcludeLeafs.ToBool()
$options.Separator = $Separator
[ExpandPSObjectRecursive]::Expand( $InputObject, '', $options )
}
}
The C# code is wrapped by a normal PowerShell cmdlet, so you can basically use it in the same way as the pure PowerShell function, with minor syntactic differences:
$inputFile | Expand-PSObjectRecursive | Where-Object Key -like '*/reply_urls'
I've added some other useful parameters that allows you to define the kind of elements that the cmdlet should output:
$inputFile |
Expand-PSObjectRecursive -IncludeObjects -ExcludeLeafs |
Where-Object Key -like '*/App*'
Parameter -IncludeObjects also includes PSObject properties from the input, while -ExcludeLeafs excludes the value-type properties, resulting in this output:
Key Value
--- -----
/New/App1 #{reply_urls=System.Object[]}
/New/App2 #{reply_urls=System.Object[]}
/New/App3 #{reply_urls=System.Object[]}
While the table format output in itself is not too useful, you could use the output objects for further processing, e. g.:
$apps = $inputFile |
Expand-PSObjectRecursive -IncludeObjects -ExcludeLeafs |
Where-Object Key -like '*/App*'
$apps.Value.reply_urls
Prints:
https://testapp1url1
https://testapp2url1
https://testapp2url2
https://testapp3url1
https://testapp3url2
https://testapp3url3
Implementation notes:
The C# code uses the yield return statement to return properties one-by-one, similar to what we are used from PowerShell pipelines. In inline C# code we can't use the pipeline directly (and it wouldn't be advisable for performance reasons), but yield return allows us to smoothly interface with PowerShell code that can be part of a pipeline.
PowerShell automatically inserts the return values of the C# function into the success output stream, one by one. The difference to returning an array from C# is that we may exit early at any point without having to process the whole input object first (e. g. using Select -First n). This would actually cancel the C# function, something that would otherwise only be possible using multithreading (with all its complexities)! But all of this is just single-threaded code.

Related

I want to create json file by substituting values from environment variables in a json template file

One requirement of mine is - Using windows, not use any tools not already available as part of aws cli or windows
For example, I have this json file test.json with below content:
"My number is $myvar"
I read this into a powershell variable like so:
$myobj=(get-content .\test.json | convertfrom-json)
$myvar=1
From here, I would like to do something with this $myobj which will enable me to get this output:
$myobj | tee json_with_values_from_environment.json
My number is 1
I got some limited success with iex, but not sure if it can be made to work for this example
You can use $ExecutionContext.InvokeCommand.ExpandString()
$myobj = '{test: "My number is $myvar"}' | ConvertFrom-Json
$myvar = 1
$ExecutionContext.InvokeCommand.ExpandString($myobj.test)
Output
My number is 1
Here is one way to do it using the Parser to find all VariableExpressionAst and replace them with the values in your session.
Given the following test.json:
{
"test1": "My number is $myvar",
"test2": {
"somevalue": "$env:myothervar",
"someothervalue": "$anothervar !!"
}
}
We want to find and replace $myvar, $myothervar and $anothervar with their corresponding values defined in the current session, so the code looks like this (note that we do the replacement before converting the Json string into an object, this way is much easier):
using namespace System.Management.Automation.Language
$isCore7 = $PSVersionTable.PSVersion -ge '7.2'
# Define the variables here
$myvar = 10
$env:myothervar = 'hello'
$anothervar = 'world'
# Read the Json
$json = Get-Content .\test.json -Raw
# Now parse it
$ast = [Parser]::ParseInput($json, [ref] $null, [ref] $null)
# Find all variables in it, and enumerate them
$ast.FindAll({ $args[0] -is [VariableExpressionAst] }, $true) |
Sort-Object { $_.Extent.Text } -Unique | ForEach-Object {
# now replace the text with the actual value
if($isCore7) {
# in PowerShell Core is very easy
$json = $json.Replace($_.Extent.Text, $_.SafeGetValue($true))
return
}
# in Windows PowerShell not so much
$varText = $_.Extent.Text
$varPath = $_.VariablePath
# find the value of the var (here we use the path)
$value = $ExecutionContext.SessionState.PSVariable.GetValue($varPath.UserPath)
if($varPath.IsDriveQualified) {
$value = $ExecutionContext.SessionState.InvokeProvider.Item.Get($varPath.UserPath).Value
}
# now replace the text with the actual value
$json = $json.Replace($varText, $value)
}
# now we can safely convert the string to an object
$json | ConvertFrom-Json
If we were to convert it back to Json to see the result:
{
"test1": "My number is 10",
"test2": {
"somevalue": "hello",
"someothervalue": "world !!"
}
}

Powershell 7.2: ConvertFrom-Json - Date Handling

With Powershell 7.2 there seems to be a change in how a JSON is deserialized into an object in terms of dates -> instead of string it is now datetime. But I want to have the "old" behavior, i.e. that it is handled as string and NOT datetime.
How can I achieve that when using ConvertFrom-Json in Powershell 7.2 all dates are deserialized as string and not datetime?
EDIT:
$val = '{ "date":"2022-09-30T07:04:23.571+00:00" }' | ConvertFrom-Json
$val.date.GetType().FullName
This is actually a known issue, see: #13598 Add a -DateKind parameter to ConvertFrom-Json to control how System.DateTime / System.DateTimeOffset values are constructed. Yet I think there is no easy solution for this. One thing you might do is just invoke (Windows) PowerShell. Which isn't currently straights forward as well therefore I have created a small wrapper to send and receive complex objects between PowerShell sessions (see also my #18460 Invoke-PowerShell purpose):
function Invoke-PowerShell ($Command) {
$SerializeOutput = #"
`$Output = $Command
[System.Management.Automation.PSSerializer]::Serialize(`$Output)
"#
$Bytes = [System.Text.Encoding]::Unicode.GetBytes($SerializeOutput)
$EncodedCommand = [Convert]::ToBase64String($Bytes)
$PSSerial = PowerShell -EncodedCommand $EncodedCommand
[System.Management.Automation.PSSerializer]::Deserialize($PSSerial)
}
Usage:
Invoke-PowerShell { '{ "date":"2022-09-30T07:04:23.571+00:00" }' | ConvertFrom-Json }
date
----
2022-09-30T07:04:23.571+00:00
Update
As commented by mklement0, I clearly complicated the answer.
Calling via powershell.exe is a pragmatic workaround (albeit slow and Windows-only), but note that you don't need a helper function: if you pass a script block to powershell.exe (or pwsh.exe) from PowerShell, Based64 CLIXML-based serialization happens automatically behind the scenes: try powershell.exe -noprofile { $args | ConvertFrom-Json } -args '{ "date":"2022-09-30T07:04:23.571+00:00" }' For that reason, I don't think there's a need for an Invoke-PowerShell cmdlet.
$Json = '{ "date":"2022-09-30T07:04:23.571+00:00" }'
powershell.exe -noprofile { $args | ConvertFrom-Json } -args $Json
date
----
2022-09-30T07:04:23.571+00:00
iRon's helpful answer provides a pragmatic solution via the Windows PowerShell CLI, powershell.exe, relying on the fact that ConvertFrom-Json there does not automatically transform ISO 8601-like timestamp strings to [datetime] instances.
Hopefully, the proposal in the GitHub issue he links to, #13598, will be implemented in the future, which would then simplify the solution to:
# NOT YET IMPLEMENTED as of PowerShell 7.2.x
'{ "date":"2022-09-30T07:04:23.571+00:00" }' |
ConvertFrom-Json -DateTimeKind None
However, a powershell.exe workaround has two disadvantages: (a) it is slow (a separate PowerShell instance in a child process must be launched), and (b) it is Windows-only. The solution below is a generalization of your own approach that avoids these problems.
Here's a generalization of your own in-process approach:
It injects a NUL character ("`0") at the start of each string that matches the pattern of a timestamp - the assumption is that the input itself never contains such characters, which is fair to assume.
This, as in your approach, prevents ConvertFrom-Json from recognizing timestamp strings as such, and leaves them untouched.
The [pscustomobject] graph that ConvertFrom-Json outputs must then be post-processed in order to remove the injected NUL characters again.
This is achieved with a ForEach-Object call that contains a helper script block that recursively walks the object graph, which has the advantage that it works with JSON input whose timestamp strings may be at any level of the hierarchy (i.e. they may also be in properties of nested objects).
Note: The assumption is that the timestamp strings are only ever contained as property values in the input; more work would be needed if you wanted to handle input JSON such as '[ "2022-09-30T07:04:23.571+00:00" ]' too, where the strings are input objects themselves.
# Sample JSON.
$val = '{ "date":"2022-09-30T07:04:23.571+00:00" }'
$val -replace '"(?=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}")', "`"`0" | #"
ConvertFrom-Json |
ForEach-Object {
# Helper script block that walks the object graph
$sb = {
foreach ($o in $args[0]) {
if ($o -is [Array]) { # nested array -> recurse
foreach ($el in $o) { & $sb $el } # recurse
}
elseif ($o -is [System.Management.Automation.PSCustomObject]) {
foreach ($prop in $o.psobject.Properties) {
if ($prop.Value -is [Array]) {
foreach ($o in $prop.Value) { & $sb $o } # nested array -> recurse
}
elseif ($prop.Value -is [System.Management.Automation.PSCustomObject]) {
& $sb $prop.Value # nested custom object -> recurse
}
elseif ($prop.Value -is [string] -and $prop.Value -match '^\0') {
$prop.Value = $prop.Value.Substring(1) # Remove the NUL again.
}
}
}
}
}
# Call the helper script block with the input object.
& $sb $_
# Output the modified object.
if ($_ -is [array]) {
# Input object was array as a whole (implies use of -NoEnumerate), output as such.
, $_
} else {
$_
}
}
Based on the input from #zett42 here my solution:
Assuming we know the regex pattern of the date used in the JSON I get the JSON as string, add a prefix so that ConvertFrom-Json does not convert dates to datetime but keeps it as string, convert it with ConvertFrom-Json to a PSCustomObject, do whatever I need to do on the object, serialize it back to a JSON string with ConvertTo-Json and then remove the prefix again.
[string]$json = '{ "date":"2022-09-30T07:04:23.571+00:00", "key1": "value1" }'
[string]$jsonWithDatePrefix = $json -replace '"(\d+-\d+.\d+T\d+:\d+:\d+\.\d+\+\d+:\d+)"', '"#$1"'
[pscustomobject]$jsonWithDatePrefixAsObject = $jsonWithDatePrefix | ConvertFrom-Json
$jsonWithDatePrefixAsObject.key1 = "value2"
[string]$updatedJsonString = $jsonWithDatePrefixAsObject | ConvertTo-Json
[string]$updatedJsonStringWithoutPrefix = $updatedJsonString -replace '"(#)(\d+-\d+.\d+T\d+:\d+:\d+\.\d+\+\d+:\d+)"', '"$2"'
Write-Host $updatedJsonStringWithoutPrefix
Two additional ways to change the date format:
Get-Node
Using this Get-Node which is quiet similar to mklement0 recursive function:
$Data = ConvertFrom-Json $Json
$Data |Get-Node -Where { $_.Value -is [DateTime] } | ForEach-Object {
$_.Value = GetDate($_.Value) -Format 'yyyy-MM-ddTHH\:mm\:ss.fffzzz' -AsUTC
}
$Data
DIY
Or do-it-yourself and build your own Json deserializer:
function ConvertFrom-Json {
[CmdletBinding()][OutputType([Object[]])] param(
[Parameter(ValueFromPipeLine = $True, Mandatory = $True)][String]$InputObject,
[String]$DateFormat = 'yyyy-MM-ddTHH\:mm\:ss.fffffffzzz', # Default: ISO 8601, https://www.newtonsoft.com/json/help/html/datesinjson.htm
[Switch]$AsLocalTime,
[Switch]$AsOrdered
)
function GetObject($JObject) {
switch ($JObject.GetType().Name) {
'JValue' {
switch ($JObject.Type) {
'Boolean' { $JObject.Value }
'Integer' { 0 + $JObject.Value } # https://github.com/PowerShell/PowerShell/issues/14264
'Date' { Get-Date $JObject.Value -Format $DateFormat -AsUTC:(!$AsLocalTime) } # https://github.com/PowerShell/PowerShell/issues/13598
Default { "$($JObject.Value)" }
}
}
'JArray' {
,#( $JObject.ForEach{ GetObject $_ } )
}
'JObject' {
$Properties = [Ordered]#{}
$JObject.ForEach{ $Properties[$_.Name] = GetObject $_.Value }
if ($AsOrdered) { $Properties } else { [PSCustomObject]$Properties } # https://github.com/PowerShell/PowerShell/pull/17405
}
}
}
GetObject ([Newtonsoft.Json.Linq.JObject]::Parse($InputObject))
}
Usage:
ConvertFrom-Json $Json -DateFormat 'yyyy-MM-ddTHH\:mm\:ss.fffzzz' |ConvertTo-Json -Depth 9

Loop json object using for loop or while loop in Powershell

As I am new to Powershell, can someone please support on the looping part?
Below is the json format from Test.json file:
{
"Pre-Production_AFM": {
"allowedapps": ["app1", "app2"]
},
"Production_AFM": {
"allowedapps": ["app1", "app2"]
}
}
I am reading the json file as below
$json = (Get-Content "Test.json" -Raw) | ConvertFrom-Json
I need to loop and get the 1st and 2nd objects - "Pre-Production_AFM" and "Production_AFM" one after another dynamically.
right now I have written the code as below :
foreach($i in $json){
if($i -contains "AFM"){
Write host "execute some code"
}
}
My dout is - Will $i holds the object "Pre-Production_AFM" dynamically?
If not please suggest the way to get the objects one after one dynamically for further execution.
# read the json text
$json = #"
{
"Pre-Production_AFM": {
"allowedapps": ["app1", "app2"]
},
"Production_AFM": {
"allowedapps": ["app1", "app2"]
}
}
"#
# convert to a PSCustomObject
$data = $json | ConvertFrom-Json
# just to prove it's a PSCustomObject...
$data.GetType().FullName
# System.Management.Automation.PSCustomObject
# now we can filter the properties by name like this:
$afmProperties = $data.psobject.Properties | where-object { $_.Name -like "*_AFM" };
# and loop through all the "*_AFM" properties
foreach( $afmProperty in $afmProperties )
{
$allowedApps = $afmProperty.Value.allowedApps
# do stuff
}

Passing switch parameter thru pipeline in PowerShell

Passing switch parameter thru pipeline in PowerShell
Problem
I am trying to make a function that has a switch parameter, but also I want to able to pass all function parameters thru pipeline in a script, and I don't know ho to do that. Is it that even possible? I my case I load parameters from .csv file in witch values are string values.
Exposition
To simplify my problem and to make it easier for others to use answers of this question, I am not going to use my code but an abstract version of my code. Let us call my function New-Function that has a -StringParameter, a -IntParameter and a -SwitchParameter parameters. And just to be clear in my .csv file all fields are named same as the New-Function parameters.
Using the function
Normally I you can use the New-Function this way:
New-Function -StringParameter "value" -IntParameter 123 -SwitchParameter
But I also want to use the New-Function this way:
$Data = Import-Csv -Path "$PSScriptRoot\Data.csv" -Delimiter ';'
$Data | New-Function
My attempts
I have tried to convert the string values in pipe line to boolean but it seems like the function's -SwitchParameter does not accept boolean($true, $false) values, because it skipping the process block completely when I debug it.
$Data | ForEach-Object -Process {
if ($_.SwitchParameter -eq "true") {
$_.SwitchParameter = $true
}
else {
$_.SwitchParameter = $false
}
} | New-Function
My temporary workaround
I have settled to use a string parameter instead of a switch parameter, so I can feed the New-Function with data thru pipeline from a .csv file with no problem.
function New-Function {
param (
[Parameter(Position = 0, Mandatory, ValueFromPipelineByPropertyName)]
[string]
$StringParameter,
[Parameter(Position = 1, Mandatory, ValueFromPipelineByPropertyName)]
[int]
$IntParameter,
[Parameter(Position = 2, ValueFromPipelineByPropertyName)]
[string]
$SwitchParameter = "false"
)
#----------------------------------------------------------------------------
}
You have to convert values for switch parameter to boolean type.
It works to me:
function Out-Test
{
param
(
[Parameter(ValueFromPipelineByPropertyName)]
[String]
$Label,
[Parameter(ValueFromPipelineByPropertyName)]
[Switch]
$Show
)
process
{
$Color = if ($Show) { 'Yellow' } else { 'Gray' }
Write-Host -ForegroundColor $Color $Label
}
}
$row1 = '' | select Label, Show
$row1.Label = 'First'
$row1.Show = 'True'
$row2 = '' | select Label, Show
$row2.Label = 'Second'
$row1.Show = 'False'
$rows = $row1, $row2
$rows |% { $_.Show = [bool]$_.Show }
$rows | Out-Test
Result:
You can convert your string to a Boolean object while leaving your parameter as type [switch] in your function. The Boolean type will be coerced into [switch] during binding.
$Data | Foreach-Object {
$_.SwitchParameter = [boolean]::Parse($_.SwitchParameter)
$_
} | New-Function
Alternatively, you can update all of your objects first and then pipe to your function. It matters how your function handles the input objects.
$Data | Foreach-Object {
$_.SwitchParameter = [boolean]::Parse($_.SwitchParameter)
}
$Data | New-Function
Part of the issue with your Foreach-Object attempt is that you never output the updated object $_ before piping into your function.

How to replace a property in a json file in powershell

I'm having a difficult time reading in a JSON file in powershell, replacing a value and writing back to the file or a file.
Given the following:
[Object]$QuickJson = #'
{
"swagger": "1.0",
"info": {
"version": "0.0.1",
"title": "this is a title",
"description": "this is a description"
},
"basePath": "/test",
"paths" : {
"/GetSomething": {
"get": {
"name" : "test01"
}
},
"/GetSomethingElse" : {
"get": {
"name" : "test02"
}
},
"/GetAnotherThing": {
"get": {
"name" : "test03"
}
}
}
}
'#
What I'm interested in here is replacing the values in the "paths" object with something else. I read the file and can access the object however with what I'm trying:
[object]$MyPSJson = ConvertFrom-Json -InputObject $QuickJson
foreach($info in $MyPSJson.paths.PSObject.Properties)
{
$path = $info.Name
Write-Host "path: $path"
$info.Name = "$path?code=fsfsfsfsfsfdfds"
Write-Host $info.Name
}
I don't know what those "paths" will be, I need to take the existing value and append a code value to it so I need to be able to iterate through all the paths and do this replacement. When I try the above I get an error:
path: /GetSomething
-- 'Name' is a ReadOnly property. At C:\scripts\test.ps1:44 char:5
+ $info.Name = "$path?code=fsfsfsfsfsfdfds"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
I've tried several different things and yet to come up with a good, or workable for that matter, solution. Any help or pointing in the right direction is greatly appreciated.
You could do something very similar to what you have now, just instead of changing the existing properties you record the initial properties, add new ones with the modifications you want, then set $MyPSJson.paths to itself excluding the old properties, so all it has are the new properties.
#Find initial paths
$Paths=$MyPSJson.paths.psobject.Properties.Name
#Add new paths with the modification to the name, and set the value to the same as the old path
$Paths|%{Add-Member -InputObject $MyPSJson.paths -NotePropertyName "$_`?code=fsfsfsfsfsfdfds" -NotePropertyValue $MyPSJson.paths.$_}
#Set the paths object to itself, excluding the original paths
$MyPSJson.paths = $MyPSJson.paths|Select * -ExcludeProperty $Paths
Try the following (PSv3+):
$MyPSJson.paths = $MyPSJson.paths.psobject.properties |
ForEach-Object { $renamedProps = [ordered] #{} } {
$renamedProps[$_.Name + '?foo=bar&baz=bam'] = $_.Value
} { [pscustomobject] $renamedProps }
Of technically necessity, this recreates the .paths object with modified property names.
It does so by enumerating the original properties, adding their values under the modified name to an ordered hashtable, which on completion of the pipeline is converted to a custom object ([pscustomobject]).
As for what you tried:
A given object's properties cannot be renamed - only its properties' values can be changed.
Therefore, you must create a new object with the desired new property names and the same values as the original.
As an aside: an [object] cast is pointless in PowerShell - it is an effective no-op.
By contrast, a [pscustomobject] cast can be used to create [pscustomobject] instances from [ordered] hashtables, which is the technique used above; casting from other types is again a virtual no-op.
In addition to the accepted answer which worked for me I also found another way which was initially suggested which was to use a hashtable:
function Convert-JsonFileToHashTable([string]$file) {
$text = [IO.File]::ReadAllText($file)
$parser = New-Object Web.Script.Serialization.JavaScriptSerializer
$parser.MaxJsonLength = $text.length
$hashtable = $parser.Deserialize($text, #{}.GetType())
return $hashtable
}
$hashtable = Convert-JsonFileToHashTable (Resolve-Path -Path $DefinitionFile)
$keys = New-Object System.Collections.ArrayList($null)
$keys.AddRange($hashtable["paths"].Keys)
foreach ($key in $keys)
{
$newPath = $key + "?code=$Code"
$hashtable["paths"].$newPath = $hashtable["paths"].$key
$hashtable["paths"].Remove($key) | Out-Null
}
$json = $hashtable | ConvertTo-Json -Depth 20
Write-Output $json | Out-File $newFile -Encoding utf8