Home > other >  PowerShell Replace value in JSON
PowerShell Replace value in JSON

Time:11-18

In our Azure CICD Pipeline, we have an element where we are trying to deploy Policies. We have JSON file per policy in the repo and we bring all these json files together into one file as part of CI which later is deployed via the CD. The PowerShell wasn't written by me, but a Microsoft consultant who was on site a few years back.

The problem is that when all the JSON comes together, we get an illegal syntax e.g. enter image description here

Altering the code to this works and deploys, but means we have to go through all our files manually replace [ with [[: enter image description here

In summary the PowerShell bring all of this together, does some manipulation and outputs to a file in the artifacts folder.

This is just a small snippet of the json, but highlights the area and there are many areas like this in the total json that need replacing:

{
"functions": [

],
"variables": {
    "location": "UK South"
},
"resources": [{
    "properties": {
        "displayName": "Allowed Locations for Resources",
        "policyType": "Custom",
        "mode": "Indexed",
        "description": "description.",
        "metadata": {
            "version": "1.0.0",
            "category": "General"
        },
        "parameters": {
            "listOfAllowedLocations": {
                "type": "Array",
                "metadata": {
                    "description": "The list of locations that can be specified when deploying resources.",
                    "strongType": "location",
                    "displayName": "Allowed locations"
                },
                "allowedValues": [
                    "uksouth",
                    "ukwest"
                ],
                "defaultValue": [
                    "uksouth",
                    "ukwest"
                ]
            }
        },
        "policyRule": {
            "if": {
                "allOf": [{
                        "field": "location",
                        "notIn": "[parameters('listOfAllowedLocations')]"
                    },
                    {
                        "field": "location",
                        "notEquals": "global"
                    },
                    {
                        "field": "type",
                        "notEquals": "Microsoft.Resources/subscriptions/resourceGroups"
                    },
                    {
                        "field": "type",
                        "notEquals": "Microsoft.Resources/b2cDirectories"
                    }
                ]
            },
            "then": {
                "effect": "audit"
            }
        }
    },
    "name": "Policy1",
    "apiVersion": "2019-01-01",
    "type": "Microsoft.Authorization/policyDefinitions",
    "location": "[variables('location')]"
}]

}

My PowerShell is intro level at best, so I am struggling to get a replace working.

I can obtain the offending area and replace it in a Write-Host, but I don't know how to write the back to the originating object with without making a right mess of things:

        if ($content.properties.policyRule.if.allOf -ne $null){
        foreach ($param in $content.properties.policyRule.if.allOf){
            Write-Host "were here..................."
            #$param = ($param | ConvertTo-Json -Depth 100 | % { [System.Text.RegularExpressions.Regex]::Unescape($_) })

            if ($param.notIn -ne $null){
                $param.notIn.replace('[', '[[')
                Write-Host $param.notIn
            }    
        }

Any suggestions would be grateful.

CodePudding user response:

The point is that the allOf node contains an array. Due to the member-access enumeration feature you will be able to easily read the notIn property but to write to it, you will need to be specific on the index ([0]) in the allOf node:

$Data = ConvertFrom-Json $Json # $Json contains your $Json snippet
$Data.resources.properties.policyRule.if.allOf[0].notIn = "[[parameters('listOfAllowedLocations')]"
$Data |ConvertTo-Json -Depth 9

In case you want to recursively find your items based on e.g. a specific name and value format from a specific properyy level, you might use this common reusable function to recursively find (and replace) a node in a complex PowerShell object:

function Get-Node {
    [CmdletBinding()][OutputType([Object[]])] param(
        [ScriptBlock]$Where,
        [Parameter(ValueFromPipeLine = $True, Mandatory = $True)]$InputObject,
        [Int]$Depth = 9
    )
    process {
        if ($_ -isnot [String] -and $Depth -gt 0) {
            if ($_ -is [Collections.IDictionary]) {
                if (& $Where) { $_ }
                $_.get_Values() |Get-Node -Where $Where -Depth ($Depth -1)
            }
            elseif  ($_ -is [Collections.IEnumerable]) {
                for ($i = 0; $i -lt $_.get_Count(); $i  ) { $_[$i] |Get-Node -Where $Where -Depth ($Depth -1) }
            }
            elseif ( $Nodes = $_.PSObject.Properties.Where{ $_.MemberType -eq 'NoteProperty'} ) {
                $Nodes.ForEach{
                    if (& $Where) { $_ }
                    $_.Value |Get-Node -Where $Where -Depth ($Depth -1)
                }
            }
        }
    }
}

Usage

Finding the value of specific nodes:

$Node = $Data.resources.properties.policyRule.if |Get-Node -Where {
    $_.name -eq 'notIn' -and $_.value -Match "^\[parameters\('\w '\)\]$"
}
$Node
Value           : [parameters('listOfAllowedLocations')]
MemberType      : NoteProperty
IsSettable      : True
IsGettable      : True
TypeNameOfValue : System.String
Name            : notIn
IsInstance      : True

Replacing all values of the concerned nodes:

$Node |ForEach-Object {
    $_.Value  = '['   $_.Value
}
$Data |ConvertTo-Json -Depth 9

Results

{
  "functions": [],
  "variables": {
"location": "UK South"
  },
  "resources": [
{
  "properties": {
    "displayName": "Allowed Locations for Resources",
    "policyType": "Custom",
    "mode": "Indexed",
    "description": "description.",
    "metadata": {
      "version": "1.0.0",
      "category": "General"
    },
    "parameters": {
      "listOfAllowedLocations": {
        "type": "Array",
        "metadata": {
          "description": "The list of locations that can be specified when deploying resources.",
          "strongType": "location",
          "displayName": "Allowed locations"
        },
        "allowedValues": [
          "uksouth",
          "ukwest"
        ],
        "defaultValue": [
          "uksouth",
          "ukwest"
        ]
      }
    },
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "location",
            "notIn": "[[parameters('listOfAllowedLocations')]"
          },
          {
            "field": "location",
            "notEquals": "global"
          },
          {
            "field": "type",
            "notEquals": "Microsoft.Resources/subscriptions/resourceGroups"
          },
          {
            "field": "type",
            "notEquals": "Microsoft.Resources/b2cDirectories"
          }
        ]
      },
      "then": {
        "effect": "audit"
      }
    }
  },
  "name": "Policy1",
  "apiVersion": "2019-01-01",
  "type": "Microsoft.Authorization/policyDefinitions",
  "location": "[variables('location')]"
}
  ]
}

  • Related