internal/functions/Get-FilteredPolicySetRoleDefinitionIds.ps1

function Get-FilteredPolicySetRoleDefinitionIds {
    <#
    .SYNOPSIS
    Removes roleDefinitionIds which are only contributed by Policy Set members whose effect the
    Policy Set hard-codes (pins) to a non-deploying literal value.
 
    .DESCRIPTION
    requiredRoleAssignments for a Policy Set assignment is normally the union of the
    roleDefinitionIds of every member Policy, regardless of the effect each member will actually
    evaluate with. Members pinned by the Policy Set to a literal such as AuditIfNotExists never
    deploy anything, so the roles they contribute are not required by the assignment's Managed
    Identity. Only a recognised non-deploying effect literal counts as pinned. A Policy Set may also
    pin an effect to an ARM expression, which evaluates at runtime and may deploy, so those members
    keep their roles.
 
    A pinned literal is only a default though: an assignment may use an override of kind
    'policyEffect' to raise a member back to a deploying effect. When an override targets a pinned
    member with DeployIfNotExists or Modify, that member's roles are required again and are kept.
 
    Only roles contributed exclusively by pinned, non-overridden members are removed. Roles that are
    also contributed by any other member, or that cannot be attributed to a member, are always kept.
    #>


    [CmdletBinding()]
    param (
        [string] $PolicySetId,
        $PolicySetDetails,
        [hashtable] $PolicyRoleIds,
        $OverridesList,

        # Supplied when the assignment pins a definitionVersion, so the roles to filter are the union
        # calculated for that version rather than for the latest version in $PolicyRoleIds.
        $UnfilteredRoleDefinitionIds = $null
    )

    $unfilteredRoleDefinitionIds = @($PolicyRoleIds.$PolicySetId)
    if ($null -ne $UnfilteredRoleDefinitionIds) {
        $unfilteredRoleDefinitionIds = @($UnfilteredRoleDefinitionIds)
    }
    if ($unfilteredRoleDefinitionIds.Count -eq 0 -or $null -eq $PolicySetDetails -or $null -eq $PolicySetDetails.policyDefinitions) {
        return $unfilteredRoleDefinitionIds
    }

    # Modify and DeployIfNotExists are the only effects which can require a role assignment.
    # Manual is deliberately excluded: no built-in definition combines Manual with roleDefinitionIds.
    $deployingEffects = @("DeployIfNotExists", "Modify")

    # A member only counts as pinned to a non-deploying effect when the Policy Set hard-codes one of
    # these literals. This must be a whitelist: a Policy Set may pin the effect to an ARM expression
    # such as [if(contains(parameters('resourceTypeList'),'...'),parameters('effect'),'Disabled')],
    # which Convert-PolicySetToDetails also reports as "PolicySet Fixed" but which can evaluate to a
    # deploying effect at runtime. Anything not recognised here keeps its roles.
    $nonDeployingEffects = @(
        "AddToNetworkGroup",
        "Append",
        "Audit",
        "AuditIfNotExists",
        "Deny",
        "DenyAction",
        "Disabled",
        "Manual"
    )

    #region classify members into pinned (non-deploying literal) and unconditional

    $pinnedRoleIdsByReferenceId = @{}
    $unconditionalRoleIds = @{}
    foreach ($policyInPolicySet in $PolicySetDetails.policyDefinitions) {
        $policyId = $policyInPolicySet.id
        if (-not $PolicyRoleIds.ContainsKey($policyId)) {
            continue
        }
        $memberRoleIds = $PolicyRoleIds.$policyId
        $isPinnedNonDeploying = $policyInPolicySet.effectReason -eq "PolicySet Fixed" `
            -and $policyInPolicySet.effectValue -in $nonDeployingEffects
        if ($isPinnedNonDeploying) {
            $referenceId = $policyInPolicySet.policyDefinitionReferenceId
            $collected = $pinnedRoleIdsByReferenceId.$referenceId
            if ($null -eq $collected) {
                $collected = @{}
                $null = $pinnedRoleIdsByReferenceId.Add($referenceId, $collected)
            }
            foreach ($roleDefinitionId in $memberRoleIds) {
                $collected[$roleDefinitionId] = $true
            }
        }
        else {
            foreach ($roleDefinitionId in $memberRoleIds) {
                $unconditionalRoleIds[$roleDefinitionId] = $true
            }
        }
    }

    if ($pinnedRoleIdsByReferenceId.psbase.Count -eq 0) {
        return $unfilteredRoleDefinitionIds
    }

    #endregion classify members

    #region determine which pinned members an override raises to a deploying effect

    $raisedReferenceIds = @{}
    foreach ($override in $OverridesList) {
        if ($override.kind -ne "policyEffect" -or $override.value -notin $deployingEffects) {
            continue
        }
        $scopedByReferenceId = $false
        foreach ($selector in $override.selectors) {
            if ($selector.kind -ne "policyDefinitionReferenceId") {
                continue
            }
            $scopedByReferenceId = $true
            if ($null -ne $selector.in) {
                foreach ($referenceId in $selector.in) {
                    $raisedReferenceIds[$referenceId] = $true
                }
            }
            elseif ($null -ne $selector.notIn) {
                $excluded = @{}
                foreach ($referenceId in $selector.notIn) {
                    $excluded[$referenceId] = $true
                }
                foreach ($referenceId in $pinnedRoleIdsByReferenceId.Keys) {
                    if (-not $excluded.ContainsKey($referenceId)) {
                        $raisedReferenceIds[$referenceId] = $true
                    }
                }
            }
        }
        if (-not $scopedByReferenceId) {
            # A deploying override which cannot be attributed to specific members may raise any of
            # them. Keep every role.
            return $unfilteredRoleDefinitionIds
        }
    }

    #endregion determine raised members

    #region subtract only the roles which no remaining member requires

    $requiredRoleIds = $unconditionalRoleIds.Clone()
    foreach ($referenceId in $raisedReferenceIds.Keys) {
        $collected = $pinnedRoleIdsByReferenceId.$referenceId
        if ($null -ne $collected) {
            foreach ($roleDefinitionId in $collected.Keys) {
                $requiredRoleIds[$roleDefinitionId] = $true
            }
        }
    }

    $removableRoleIds = @{}
    foreach ($referenceId in $pinnedRoleIdsByReferenceId.Keys) {
        foreach ($roleDefinitionId in $pinnedRoleIdsByReferenceId.$referenceId.Keys) {
            if (-not $requiredRoleIds.ContainsKey($roleDefinitionId)) {
                $removableRoleIds[$roleDefinitionId] = $true
            }
        }
    }

    if ($removableRoleIds.psbase.Count -eq 0) {
        return $unfilteredRoleDefinitionIds
    }

    # Filter the original union so unattributed roles and the original ordering are preserved.
    $filteredRoleDefinitionIds = [System.Collections.ArrayList]::new()
    foreach ($roleDefinitionId in $unfilteredRoleDefinitionIds) {
        if (-not $removableRoleIds.ContainsKey($roleDefinitionId)) {
            $null = $filteredRoleDefinitionIds.Add($roleDefinitionId)
        }
    }

    #endregion subtract roles

    return $filteredRoleDefinitionIds.ToArray()
}