How to add element to object

I have this use case of manipulating Azure object JSON templates.
Take this example https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-site-to-site-vpn/azuredeploy.parameters.json which looks like

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "value": "GEN-UNIQUE"
    },
    "localGatewayIpAddress": {
      "value": "52.25.48.88"
    },
    "localAddressPrefix": {
      "value": "10.0.0.0/24"
    },
    "azureVNetAddressPrefix": {
      "value": "10.3.0.0/16"
    },
    "subnetPrefix": {
      "value": "10.3.0.0/24"
    },
    "gatewaySubnetPrefix": {
      "value": "10.3.200.0/29"
    },
    "sharedKey": {
      "value": "GEN-UNIQUE"
    },
    "adminPasswordOrKey": {
      "value": "GEN-SSH-PUB-KEY"
    }
  }
}

Now I can read it fine like this:

$Uri = 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-site-to-site-vpn/azuredeploy.parameters.json'
$myParamterSet = Invoke-WebRequest $Uri | ConvertFrom-Json

and we can see the parameters like

$myParamterSet.parameters

adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}

We can even change them like

PS C:\scripts> $myParamterSet.parameters.adminUsername.value = 'bla1'

PS C:\scripts> $myParamterSet.parameters

adminUsername          : @{value=bla1}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}

My question is how to add another parameter to this parameter set?
For example, I want to add

environment     : @{value=dev}

Trying to add an element fails:

PS C:\scripts> $myParamterSet.parameters += [PSCustomObject]@{environment='dev'}
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
At line:1 char:1
+ $myParamterSet.parameters += [PSCustomObject]@{environment='dev'}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (op_Addition:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

Get-Member shows that these parameters are PS Custom Objects:

PS C:\scripts> $myParamterSet.parameters | Get-Member


   TypeName: 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()                                                                      
adminPasswordOrKey     NoteProperty System.Management.Automation.PSCustomObject adminPasswordOrKey=@{value=GEN-SSH-PUB-KEY}
adminUsername          NoteProperty System.Management.Automation.PSCustomObject adminUsername=@{value=bla1}                
azureVNetAddressPrefix NoteProperty System.Management.Automation.PSCustomObject azureVNetAddressPrefix=@{value=10.3.0.0/16}
gatewaySubnetPrefix    NoteProperty System.Management.Automation.PSCustomObject gatewaySubnetPrefix=@{value=10.3.200.0/29} 
localAddressPrefix     NoteProperty System.Management.Automation.PSCustomObject localAddressPrefix=@{value=10.0.0.0/24}    
localGatewayIpAddress  NoteProperty System.Management.Automation.PSCustomObject localGatewayIpAddress=@{value=52.25.48.88} 
sharedKey              NoteProperty System.Management.Automation.PSCustomObject sharedKey=@{value=GEN-UNIQUE}              
subnetPrefix           NoteProperty System.Management.Automation.PSCustomObject subnetPrefix=@{value=10.3.0.0/24}   

How do I add one more?

nvm, this works:

$myParamterSet.parameters | Add-Member -MemberType NoteProperty -Name 'Environmnmet' -Value ([PSCustomObject]@{value='dev'})

You can also do:

$myParameterSet.parameters = $myParameterSet.Parameters | Select-Object -Property @{ Name= 'Environmnmet'; Expression = {[PSCustomObject]@{value='dev'}} }

 

@Joel nice!
Now I’m stuck at trying to add a child object.

For example how do I add a node like:

        "ApplicationName": {
            "type": "string",
            "maxLength": 3,
            "metadata": {
                "description": "some desc"
            }
        }

where the resulting PS object looks like:

$myParameterSet.parameters.ApplicationName

type   maxLength metadata                
----   --------- --------                
string         3 @{description=some desc}

@Joel,
Actually, using Select does not work:

$Uri = 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-site-to-site-vpn/azuredeploy.parameters.json'
$myParameterSet = Invoke-WebRequest $Uri | ConvertFrom-Json
$myParameterSet.parameters

adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}

$myParameterSet.parameters = $myParameterSet.Parameters | 
    Select-Object -Property @{ Name= 'add1'; Expression = {[PSCustomObject]@{value='dev1'}} }

$myParameterSet.parameters

add1         
----         
@{value=dev1}

Whereas Add-Member works:

$Uri = 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-site-to-site-vpn/azuredeploy.parameters.json'
$myParameterSet = Invoke-WebRequest $Uri | ConvertFrom-Json
$myParameterSet.parameters
$myParameterSet.parameters | Add-Member -MemberType NoteProperty -Name 'Environmnmet' -Value ([PSCustomObject]@{value='dev'})
$myParameterSet.parameters


adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}

adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}
Environmnmet           : @{value=dev}

I think I got it:

$Uri = 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-site-to-site-vpn/azuredeploy.parameters.json'
$myParameterSet = Invoke-WebRequest $Uri | ConvertFrom-Json
$myParameterSet.parameters
$myParameterSet.parameters | Add-Member -MemberType NoteProperty -Name 'ApplicationName' -Value ([PSCustomObject]@{})
$myParameterSet.parameters
$myParameterSet.parameters.ApplicationName | Add-Member -MemberType NoteProperty -Name 'type' -Value 'string'
$myParameterSet.parameters.ApplicationName | Add-Member -MemberType NoteProperty -Name 'maxlength' -Value 3
$myParameterSet.parameters.ApplicationName | Add-Member -MemberType NoteProperty -Name 'metadata' -Value ([PSCustomObject]@{description = 'my desc'})
$myParameterSet.parameters
$myParameterSet.parameters.ApplicationName

adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}

adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}
ApplicationName        : 

adminUsername          : @{value=GEN-UNIQUE}
localGatewayIpAddress  : @{value=52.25.48.88}
localAddressPrefix     : @{value=10.0.0.0/24}
azureVNetAddressPrefix : @{value=10.3.0.0/16}
subnetPrefix           : @{value=10.3.0.0/24}
gatewaySubnetPrefix    : @{value=10.3.200.0/29}
sharedKey              : @{value=GEN-UNIQUE}
adminPasswordOrKey     : @{value=GEN-SSH-PUB-KEY}
ApplicationName        : @{type=string; maxlength=3; metadata=}

type      : string
maxlength : 3
metadata  : @{description=my desc}

The trick to creating a subnode is line 4 creating an empty PSCustomObject, otherwise line 6 fails

and when you spit it back out to JSON, it looks right (which is the whole point behind this exercise)

$myParameterSet | ConvertTo-Json | Out-File .\test1.txt 
notepad .\test1.txt
{
    "$schema":  "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion":  "1.0.0.0",
    "parameters":  {
                       "adminUsername":  {
                                             "value":  "GEN-UNIQUE"
                                         },
                       "localGatewayIpAddress":  {
                                                     "value":  "52.25.48.88"
                                                 },
                       "localAddressPrefix":  {
                                                  "value":  "10.0.0.0/24"
                                              },
                       "azureVNetAddressPrefix":  {
                                                      "value":  "10.3.0.0/16"
                                                  },
                       "subnetPrefix":  {
                                            "value":  "10.3.0.0/24"
                                        },
                       "gatewaySubnetPrefix":  {
                                                   "value":  "10.3.200.0/29"
                                               },
                       "sharedKey":  {
                                         "value":  "GEN-UNIQUE"
                                     },
                       "adminPasswordOrKey":  {
                                                  "value":  "GEN-SSH-PUB-KEY"
                                              },
                       "ApplicationName":  {
                                               "type":  "string",
                                               "maxlength":  3,
                                               "metadata":  "@{description=my desc}"
                                           }
                   }
}

This is actually a bug in ConvertTo-Json version 3.1.0.0 out of the C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Commands.Utility.dll

Proof:

@'
{
    "$schema": "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "ApplicationName": {
            "type": "string",
            "maxLength": 3,
            "metadata": {
                "description": "my desc"
            }
        },
        "plan_name": {
            "type": "String"
        }
    },
    "variables": {
        "resourceNames": {
            "name": "EDSENDGRID06",
            "commonResourceGroup": "[tolower(concat(parameters('ApplicationName'),'-',parameters('Environment'),'-',parameters('shortlocation'),'-',parameters('tenant'),'-rgp-','01'))]"
        },
        "TemplateURLs": {
            "sendgrid": "[concat(parameters('artifacts_baseUri'),'/ArmTemplates/master/Public/lib/linkedTemplates/sendgrid.json')]"
        }
    }
}
'@ | ConvertFrom-Json | ConvertTo-Json

This retuns:

{
    "$schema":  "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
    "contentVersion":  "1.0.0.0",
    "parameters":  {
                       "ApplicationName":  {
                                               "type":  "string",
                                               "maxLength":  3,
                                               "metadata":  "@{description=my desc}"
                                           },
                       "plan_name":  {
                                         "type":  "String"
                                     }
                   },
    "variables":  {
                      "resourceNames":  {
                                            "name":  "EDSENDGRID06",
                                            "commonResourceGroup":  "[tolower(concat(parameters(\u0027ApplicationName\u0027),\u0027-\u0027,parameters(\u0027Environ
ment\u0027),\u0027-\u0027,parameters(\u0027shortlocation\u0027),\u0027-\u0027,parameters(\u0027tenant\u0027),\u0027-rgp-\u0027,\u002701\u0027))]"
                                        },
                      "TemplateURLs":  {
                                           "sendgrid":  "[concat(parameters(\u0027artifacts_baseUri\u0027),\u0027/ArmTemplates/master/Public/lib/linkedTemplates/se
ndgrid.json\u0027)]"
                                       }
                  }
}

Note the messed up line 8 in the output compared to what it should be - lines 9,10,11 in the input

I wrote a Fix-Json function to fix this - part of the AZSBTools PS module:

$TempFile = New-TemporaryFile
@'
{
    "$schema": "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "ApplicationName": {
            "type": "string",
            "maxLength": 3,
            "metadata": {
                "description": "my desc"
            }
        },
        "plan_name": {
            "type": "String"
        }
    },
    "variables": {
        "resourceNames": {
            "name": "EDSENDGRID06",
            "commonResourceGroup": "[tolower(concat(parameters('ApplicationName'),'-',parameters('Environment'),'-',parameters('shortlocation'),'-',parameters('tenant'),'-rgp-','01'))]"
        },
        "TemplateURLs": {
            "sendgrid": "[concat(parameters('artifacts_baseUri'),'/ArmTemplates/master/Public/lib/linkedTemplates/sendgrid.json')]"
        }
    }
}
'@ | ConvertFrom-Json | ConvertTo-Json | Out-File $TempFile 
Fix-Json $TempFile 

Now the output looks like:

{
    "$schema":  "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
    "contentVersion":  "1.0.0.0",
    "parameters":  {
                       "ApplicationName":  {
                                               "type":  "string",
                                               "maxLength":  3,
                                               "metadata":  {
                                                   "description": "my desc"
                                               }
                                           },
                       "plan_name":  {
                                         "type":  "String"
                                     }
                   },
    "variables":  {
                      "resourceNames":  {
                                            "name":  "EDSENDGRID06",
                                            "commonResourceGroup":  "[tolower(concat(parameters(\u0027ApplicationName\u0027),\u0027-\u0027,parameters(\u0027Environ
ment\u0027),\u0027-\u0027,parameters(\u0027shortlocation\u0027),\u0027-\u0027,parameters(\u0027tenant\u0027),\u0027-rgp-\u0027,\u002701\u0027))]"
                                        },
                      "TemplateURLs":  {
                                           "sendgrid":  "[concat(parameters(\u0027artifacts_baseUri\u0027),\u0027/ArmTemplates/master/Public/lib/linkedTemplates/se
ndgrid.json\u0027)]"
                                       }
                  }
}

Ah, if you wanted to keep existing properties, you’d need to do Select-Object -Property *, @{ <# rest of the thing I posted before #> }