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)
}
}
}
}
}