Private/Resolve-METSafeLinksEffectivePolicy.ps1

function Resolve-METSafeLinksEffectivePolicy {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [AllowEmptyCollection()] [string[]] $AllMailboxes,
        [Parameter(Mandatory)] [hashtable] $GroupCache,
        [AllowEmptyCollection()] [object[]] $Rules,
        [AllowEmptyCollection()] [object[]] $Policies,
        [AllowEmptyCollection()] [object[]] $PresetRules,
        [System.Collections.Generic.List[string]] $RetrievalErrors
    )

    function Get-RuleScopeDescription {
        param([object] $Rule)
        if (-not $Rule) { return 'Microsoft-managed fallback' }

        $parts = [System.Collections.Generic.List[string]]::new()
        if ($Rule.SentTo) { $parts.Add("Recipients: $(@($Rule.SentTo).Count)") }
        if ($Rule.SentToMemberOf) { $parts.Add("Groups: $(@($Rule.SentToMemberOf).Count)") }
        if ($Rule.RecipientDomainIs) { $parts.Add("Domains: $(@($Rule.RecipientDomainIs) -join ', ')") }
        if ($parts.Count -eq 0) { $parts.Add('Catch-all (no inclusion conditions)') }

        $exceptions = @($Rule.ExceptIfSentTo | Where-Object { $_ }).Count + @($Rule.ExceptIfSentToMemberOf | Where-Object { $_ }).Count +
            @($Rule.ExceptIfRecipientDomainIs | Where-Object { $_ }).Count
        if ($exceptions -gt 0) { $parts.Add("Exceptions: $exceptions") }
        return ($parts -join '; ')
    }

    if (-not $PSBoundParameters.ContainsKey('Rules')) {
        try { $Rules = @(Get-SafeLinksRule -ErrorAction Stop) }
        catch {
            $Rules = @()
            if ($null -ne $RetrievalErrors) { $RetrievalErrors.Add("Unable to retrieve Safe Links rules. $($_.ToString())") }
        }
    }
    if (-not $PSBoundParameters.ContainsKey('Policies')) {
        try { $Policies = @(Get-SafeLinksPolicy -ErrorAction Stop) }
        catch {
            $Policies = @()
            if ($null -ne $RetrievalErrors) { $RetrievalErrors.Add("Unable to retrieve Safe Links policies. $($_.ToString())") }
        }
    }
    if (-not $PSBoundParameters.ContainsKey('PresetRules')) {
        try { $PresetRules = @(Get-ATPProtectionPolicyRule -ErrorAction Stop) }
        catch {
            $PresetRules = @()
            if ($null -ne $RetrievalErrors) { $RetrievalErrors.Add("Unable to retrieve MDO preset policy rules. $($_.ToString())") }
        }
    }

    $assignments = @{}
    $policyDetails = [System.Collections.Generic.List[object]]::new()
    $enabledRules = @($Rules | Where-Object { $_.State -eq 'Enabled' } | Sort-Object Priority)

    $evaluationRules = @($PresetRules | Where-Object {
        $_.State -eq 'Enabled' -and $_.Name -match 'Evaluation'
    })
    if ($evaluationRules.Count -gt 0 -and $null -ne $RetrievalErrors) {
        $RetrievalErrors.Add('An enabled Defender for Office 365 evaluation policy was detected. Its Safe Links precedence is not yet modeled, so effective coverage cannot be established authoritatively.')
    }

    # Preset policies always take precedence over custom policies. Strict wins
    # over Standard for recipients included in both preset rules.
    foreach ($tier in @('Strict', 'Standard')) {
        $presetName = "$tier Preset Security Policy"
        $presetRule = $PresetRules | Where-Object { $_.Name -eq $presetName -and $_.State -eq 'Enabled' } |
            Select-Object -First 1
        $presetPolicy = $Policies | Where-Object { $_.Name -eq $presetName } | Select-Object -First 1
        if (-not $presetPolicy) {
            # Exchange-generated preset Safe Links policy names include a numeric
            # suffix. Bind those objects to the stable preset-rule identity.
            $presetPolicy = $Policies | Where-Object { $_.Name -like "$presetName*" } | Select-Object -First 1
        }
        $covered = if ($presetRule) {
            @(Expand-METRuleRecipients -Rule $presetRule -AllMailboxes $AllMailboxes -GroupCache $GroupCache -RetrievalErrors $RetrievalErrors)
        } else { @() }
        foreach ($recipient in $covered) {
            if (-not $assignments.ContainsKey($recipient)) {
                $assignments[$recipient] = [PSCustomObject]@{
                    Recipient = $recipient; PolicyName = if ($presetPolicy) { $presetPolicy.Name } else { $presetName }
                    PolicyType = 'Preset'; Tier = $tier; Priority = $null
                    ScopeDescription = Get-RuleScopeDescription $presetRule
                    Policy = $presetPolicy; Rule = $presetRule
                }
            }
        }
        if ($presetRule -or $presetPolicy) {
            $policyDetails.Add([PSCustomObject]@{
                PolicyName = if ($presetPolicy) { $presetPolicy.Name } else { $presetName }
                PolicyType = 'Preset'; Tier = $tier; Priority = $null
                ScopeDescription = Get-RuleScopeDescription $presetRule
                EffectiveRecipients = @($covered | Where-Object { $assignments[$_].Tier -eq $tier }).Count
                State = if ($presetRule.State) { [string]$presetRule.State } else { 'Inactive' }
                Policy = $presetPolicy; Rule = $presetRule
            })
            if ($presetRule -and -not $presetPolicy -and $null -ne $RetrievalErrors) {
                $RetrievalErrors.Add("Enabled $tier preset rule was returned, but its Safe Links policy object was not returned.")
            }
        }
    }

    foreach ($rule in $enabledRules) {
        $policyName = [string]$rule.SafeLinksPolicy
        if (-not $policyName) { $policyName = [string]$rule.Name }
        $policy = $Policies | Where-Object { $_.Name -eq $policyName } | Select-Object -First 1
        if (-not $policy -and $null -ne $RetrievalErrors) {
            $RetrievalErrors.Add("Enabled Safe Links rule '$($rule.Name)' references policy '$policyName', but that policy was not returned.")
        }
        $matched = @(Expand-METRuleRecipients -Rule $rule -AllMailboxes $AllMailboxes -GroupCache $GroupCache -RetrievalErrors $RetrievalErrors)
        $covered = @($matched | Where-Object { -not $assignments.ContainsKey($_) })
        $shadowed = @($matched | Where-Object { $assignments.ContainsKey($_) })
        $shadowedBy = @($shadowed | ForEach-Object {
            $prior = $assignments[$_]
            if ($prior.PolicyType -eq 'Preset') { "Preset: '$($prior.PolicyName)'" }
            elseif (-not $prior.Rule.SentTo -and -not $prior.Rule.SentToMemberOf -and -not $prior.Rule.RecipientDomainIs) { "CustomCatchAll: '$($prior.PolicyName)'" }
            else { "Custom: '$($prior.PolicyName)'" }
        } | Sort-Object -Unique)
        foreach ($recipient in $covered) {
            if (-not $assignments.ContainsKey($recipient)) {
                $assignments[$recipient] = [PSCustomObject]@{
                    Recipient = $recipient; PolicyName = $policyName; PolicyType = 'Custom'; Tier = 'Custom'
                    Priority = $rule.Priority; ScopeDescription = Get-RuleScopeDescription $rule
                    Policy = $policy; Rule = $rule
                }
            }
        }
        $policyDetails.Add([PSCustomObject]@{
            PolicyName = $policyName; PolicyType = 'Custom'; Tier = 'Custom'; Priority = $rule.Priority
            ScopeDescription = Get-RuleScopeDescription $rule; MatchedRecipients = @($matched).Count
            EffectiveRecipients = @($covered).Count; ShadowedRecipients = @($shadowed).Count; ShadowedBy = $shadowedBy
            State = [string]$rule.State
            Policy = $policy; Rule = $rule
        })
    }

    foreach ($rule in @($Rules | Where-Object { $_.State -ne 'Enabled' })) {
        $policyName = if ($rule.SafeLinksPolicy) { [string]$rule.SafeLinksPolicy } else { [string]$rule.Name }
        $policy = $Policies | Where-Object { $_.Name -eq $policyName } | Select-Object -First 1
        $policyDetails.Add([PSCustomObject]@{
            PolicyName = $policyName; PolicyType = 'Custom'; Tier = 'Custom'; Priority = $rule.Priority
            ScopeDescription = Get-RuleScopeDescription $rule; EffectiveRecipients = 0
            State = if ($rule.State) { [string]$rule.State } else { 'Disabled' }
            Policy = $policy; Rule = $rule
        })
    }

    $associatedPolicyNames = @($Rules | ForEach-Object {
        if ($_.SafeLinksPolicy) { [string]$_.SafeLinksPolicy } else { [string]$_.Name }
    })
    foreach ($policy in @($Policies | Where-Object {
        $_.Name -ne 'Built-In Protection Policy' -and
        $_.Name -notmatch '^(Strict|Standard) Preset Security Policy' -and
        $associatedPolicyNames -notcontains $_.Name
    })) {
        $policyDetails.Add([PSCustomObject]@{
            PolicyName = [string]$policy.Name; PolicyType = 'Unassociated'; Tier = 'Custom'; Priority = $null
            ScopeDescription = 'No associated rule'; EffectiveRecipients = 0; State = 'Inactive'
            Policy = $policy; Rule = $null
        })
    }

    $builtIn = $Policies | Where-Object { $_.Name -eq 'Built-In Protection Policy' } | Select-Object -First 1
    $builtInCount = 0
    foreach ($recipient in $AllMailboxes) {
        if (-not $assignments.ContainsKey($recipient)) {
            $assignments[$recipient] = [PSCustomObject]@{
                Recipient = $recipient; PolicyName = 'Built-In Protection Policy'; PolicyType = 'BuiltIn'
                Tier = 'BuiltIn'; Priority = $null; ScopeDescription = 'Microsoft-managed fallback'
                Policy = $builtIn; Rule = $null
            }
            $builtInCount++
        }
    }
    if ($builtIn -or $builtInCount -gt 0) {
        $policyDetails.Add([PSCustomObject]@{
            PolicyName = 'Built-In Protection Policy'; PolicyType = 'BuiltIn'; Tier = 'BuiltIn'
            Priority = $null; ScopeDescription = 'Microsoft-managed fallback'
            EffectiveRecipients = $builtInCount; State = 'Enabled'; Policy = $builtIn; Rule = $null
        })
    }
    if ($builtInCount -gt 0 -and -not $builtIn -and $null -ne $RetrievalErrors) {
        $RetrievalErrors.Add('Recipients fall through to Built-in Protection, but the Built-in Safe Links policy object was not returned.')
    }

    return [PSCustomObject]@{
        RecipientAssignments = @($AllMailboxes | ForEach-Object { $assignments[$_] })
        PolicySummaries = @($policyDetails)
    }
}