Home > Mobile >  PowerShell FilterScript error with some JSON Files
PowerShell FilterScript error with some JSON Files

Time:11-21

Thanks to iRon earlier this week with his help via this question, he helped massively on a piece of work I have on the go at the moment.

In summary, we have an Azure CICD pipeline to deploy out Policies. we have a folder with over 200 JSON policy files and the CICD process brings them all together in 1 JSON file and deploy them out. The initial problem we found was the parameters required a leading extra "[" otherwise the process would fail. And this was highlighted in this article (if you search for [[).

Anyway, to the problem. iRons support helped greatly, but out of the 200 files we get a handful where we receive the following message:

Cannot bind argument to parameter 'FilterScript' because it is null.

The code:

Function supplied by iRon:

function Get-Node {
    [CmdletBinding()][OutputType([Object[]])] param(
        [ScriptBlock]$Where,
        [Parameter(ValueFromPipeLine = $True, Mandatory = $True)]$InputObject,
        [Int]$Depth = 10
    )
    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)
                }
            }
        }
    }
}

Where I call the above function (this code is in a loop per file):

    $policyRule = @("notIn", "in", "value", "equals", "field", "effect", "like", "greaterOrEquals", "name", "resourceGroupName", "resourceGroup", "location", "storageName", "uniqueStorage", "storageContainerPath", "storageAccountAccessKey", "dependsOn", "targetResourceId", "storageId", "enabled")
    
    if ($content.properties -ne $null){
        # Loop all the Policy Rule names for fix the values within them
        foreach ($rule in $policyRule){
            $Node = $content.properties.policyRule | Get-Node -Where {$_.name -eq $rule -and $_.value -Match "^\[\w " -and $_.value -ne ""} 
            $Node | ForEach-Object {$_.Value  = '['   $_.Value}
        }                 
    }

A JSON file that works:

{
  "name": "POLICY-001",
  "properties": {
    "displayName": "Allowed Locations for Resources",
    "policyType": "Custom",
    "mode": "Indexed",
    "description": "This policy enables you to restrict the locations your organization can specify when deploying resources. Use to enforce your geo-compliance requirements. Excludes resource groups, Microsoft.AzureActiveDirectory/b2cDirectories, and resources that use the 'global' region.",
    "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": "deny"
      }
    }
  },
  "excludedScopes": [
    
  ],
  "nonComplianceMessage": "only allow resources to be deployed from UK South and UK West"
}

One that fails:

{
  "Name": "Policy-106",
  "Properties": {
    "Description": "This policy automatically deploys and enable diagnostic settings to Log Analytics",
    "DisplayName": "Apply diagnostic settings for Log Analytics Workspaces",
    "Mode": "Indexed",
    "policyType": "Custom",
    "Metadata": {
      "version": "1.0",
      "category": "Monitoring"
    },
    "Parameters": {
      "diagnosticsSettingNameToUse": {
        "type": "string",
        "metadata": {
          "displayName": "Apply diagnostic settings for Log Analytics Workspaces - Setting name",
          "description": "Name of the policy for the diagnostics settings."
        },
        "defaultValue": "setViaPolicy"
      },
      "logAnalytics": {
        "type": "string",
        "metadata": {
          "displayName": "Apply diagnostic settings for Log Analytics Workspaces - Log Analytics workspace",
          "description": "Select the Log Analytics workspace from dropdown list",
          "strongType": "omsWorkspace",
          "assignPermissions": true
        },
        "defaultValue": "/subscriptions/......"
      }
    },
    "PolicyRule": {
      "if": {
        "field": "type",
        "equals": "Microsoft.OperationalInsights/Workspaces"
      },
      "then": {
        "effect": "deployIfNotExists",
        "details": {
          "type": "Microsoft.Insights/diagnosticSettings",
          "roleDefinitionIds": [
            "/providers/Microsoft.Authorization/roleDefinitions/123"
          ],
          "existenceCondition": {
            "allOf": [
              {
                "field": "Microsoft.Insights/diagnosticSettings/logs.enabled",
                "equals": "True"
              },
              {
                "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled",
                "equals": "True"
              },
              {
                "field": "Microsoft.Insights/diagnosticSettings/workspaceId",
                "matchInsensitively": "[parameters('logAnalytics')]"
              }
            ]
          },
          "deployment": {
            "properties": {
              "mode": "incremental",
              "template": {
                "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
                "contentVersion": "1.0.0.0",
                "parameters": {
                  "diagnosticsSettingNameToUse": {
                    "type": "string"
                  },
                  "resourceName": {
                    "type": "string"
                  },
                  "logAnalytics": {
                    "type": "string"
                  },
                  "location": {
                    "type": "string"
                  }
                },
                "variables": {},
                "resources": [
                  {
                    "type": "Microsoft.OperationalInsights/Workspaces/providers/diagnosticSettings",
                    "apiVersion": "2017-05-01-preview",
                    "name": "[concat(parameters('resourceName'), '/', 'Microsoft.Insights/', parameters('diagnosticsSettingNameToUse'))]",
                    "location": "[parameters('location')]",
                    "dependsOn": [],
                    "properties": {
                      "workspaceId": "[parameters('logAnalytics')]",
                      "metrics": [
                        {
                          "category": "AllMetrics",
                          "timeGrain": null,
                          "enabled": true,
                          "retentionPolicy": {
                            "enabled": false,
                            "days": 0
                          }
                        }
                      ],
                      "logs": [
                        {
                          "category": "Audit",
                          "enabled": true
                        }
                      ]
                    }
                  }
                ],
                "outputs": {}
              },
              "parameters": {
                "diagnosticsSettingNameToUse": {
                  "value": "[parameters('diagnosticsSettingNameToUse')]"
                },
                "logAnalytics": {
                  "value": "[parameters('logAnalytics')]"
                },
                "location": {
                  "value": "[field('location')]"
                },
                "resourceName": {
                  "value": "[field('name')]"
                }
              }
            }
          }
        }
      }
    }
  },
  "excludedScopes": [

  ],
  "nonComplianceMessage": ""
}

As mentioned in my original post, my PowerShell is basic at best and any support would be grateful.

CodePudding user response:

The JSON file whose processing fails contains null values, which turn into $null values in PowerShell when converting the JSON file to an object graph via ConvertFrom-Json.

The Get-Node function as shown in your question doesn't support binding $null to its pipeline-binding -InputObject parameter, which results in an error whenever $_.Value happens to be $null in the following statement:
$_.Value | Get-Node -Where $Where -Depth ($Depth - 1)

The solution is simple: Allow the -InputObject parameter to accept $null, by adding the [AllowNull()] attribute to the original function:

function Get-Node {
  [CmdletBinding()][OutputType([Object[]])] param(
    [ScriptBlock]$Where,
    [AllowNull()]   # <- now $null may be passed too
    [Parameter(ValueFromPipeLine = $True, Mandatory = $True)]$InputObject,
    [Int]$Depth = 10
  )
  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)
        }
      }
    }
  }
}
  • Related