Private/Get-AACPolicyResourceType.ps1
|
function Get-AACPolicyResourceType { <# .SYNOPSIS The resource types a policy definition's rule applies to, read from its 'if' condition - with [parameters('...')] resolved to the values the assignment gives them. .DESCRIPTION Walks the rule's 'if' (allOf, anyOf, not, count) and collects: Include the types in "field": "type" conditions - equals, in, like, match - outside a 'not' Exclude the types in negated ones - notEquals, notIn, notLike, notMatch, or any of the above inside a 'not' (as "Allowed resource types" does: not in the allowed list) Aliased the types of the property aliases the rule reads, e.g. Microsoft.Storage/storageAccounts/minimumTlsVersion -> Microsoft.Storage/storageAccounts A value of "[parameters('name')]" is replaced by -Parameter[name] (the effective value: assigned, else the default); other template expressions are left out. The 'then' block (deployIfNotExists' related resource, say) isn't the policy's target and is ignored. Returns @{ Include; Exclude; Aliased; Text }. Text is what the AAC.AssignedPolicy rows show: the included types, else the aliased ones, else 'All except ...', else 'All'. #> [CmdletBinding()] [OutputType([hashtable])] param( [AllowNull()] $Rule, # Parameter name -> effective value. [System.Collections.IDictionary] $Parameter = @{} ) $include = [System.Collections.Generic.List[string]]::new() $exclude = [System.Collections.Generic.List[string]]::new() $aliased = [System.Collections.Generic.List[string]]::new() $positive = 'equals', 'in', 'like', 'match', 'matchInsensitively' $negative = 'notEquals', 'notIn', 'notLike', 'notMatch', 'notMatchInsensitively' $resolve = { param($Value) foreach ($item in @($Value)) { if ($item -is [string] -and $item -match "^\[parameters\('([^']+)'\)\]$") { $name = $Matches[1] $key = @($Parameter.Keys | Where-Object { $_ -eq $name }) | Select-Object -First 1 if ($null -ne $key) { foreach ($v in @($Parameter[$key])) { if ($v -is [string] -and $v) { $v } } } } elseif ($item -is [string] -and $item -and -not $item.StartsWith('[')) { $item } } } # Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType -> # Microsoft.Compute/virtualMachines: the type segments run until one # holds a '.' or '[' - or, with none, all but the last (the property). $aliasType = { param([string] $Alias) $segments = $Alias.Split('/') $types = [System.Collections.Generic.List[string]]::new() $stopped = $false for ($i = 1; $i -lt $segments.Count; $i++) { if ($segments[$i] -match '[.\[]') { $stopped = $true; break } $types.Add($segments[$i]) } if (-not $stopped -and $types.Count) { $types.RemoveAt($types.Count - 1) } if ($types.Count) { "$($segments[0])/$($types -join '/')" } } # Policy JSON is case-insensitive ('allOf' or 'AllOf'); the hashtables # from ConvertFrom-Json -AsHashtable aren't. $get = { param([System.Collections.IDictionary] $Map, [string] $Name) foreach ($k in $Map.Keys) { if ($k -eq $Name) { return $Map[$k] } } } $isAlias = { param([string] $Field) $Field -match '^[A-Za-z0-9]+(\.[A-Za-z0-9]+)+/[^/]' } $walk = $null $walk = { param($Node, [bool] $Negated) if ($Node -is [System.Collections.IList]) { foreach ($child in $Node) { & $walk $child $Negated }; return } if ($Node -isnot [System.Collections.IDictionary]) { return } foreach ($key in @($Node.Keys)) { switch ($key) { 'not' { & $walk $Node[$key] (-not $Negated) } { $_ -in 'allOf', 'anyOf' } { & $walk $Node[$key] $Negated } 'count' { $count = $Node[$key] if ($count -is [System.Collections.IDictionary]) { $field = [string](& $get $count 'field') if ($field -and (& $isAlias $field)) { $type = & $aliasType $field; if ($type) { $aliased.Add($type) } } & $walk (& $get $count 'where') $Negated } } } } $field = & $get $Node 'field' if ($field -isnot [string] -or -not $field) { return } if ($field -eq 'type') { foreach ($operator in $positive + $negative) { $match = @($Node.Keys | Where-Object { $_ -eq $operator }) | Select-Object -First 1 if ($null -eq $match) { continue } $toExclude = $Negated -xor ($operator -in $negative) foreach ($value in @(& $resolve $Node[$match])) { if ($toExclude) { $exclude.Add($value) } else { $include.Add($value) } } } } elseif (& $isAlias $field) { $type = & $aliasType $field if ($type) { $aliased.Add($type) } } } if ($Rule -is [System.Collections.IDictionary]) { & $walk (& $get $Rule 'if') $false } $includeList = @($include | Sort-Object -Unique) $excludeList = @($exclude | Sort-Object -Unique) $aliasedList = @($aliased | Sort-Object -Unique) $text = if ($includeList.Count) { $includeList -join ', ' } elseif ($aliasedList.Count) { $aliasedList -join ', ' } elseif ($excludeList.Count) { "All except $($excludeList -join ', ')" } else { 'All' } @{ Include = $includeList; Exclude = $excludeList; Aliased = $aliasedList; Text = $text } } |