internal/functions/acl_ace/Compare-AccessRules.ps1

function Compare-AccessRules {
    <#
    .SYNOPSIS
        Compare actual access rules to the system default and configured rules.
     
    .DESCRIPTION
        Compare actual access rules to the system default and configured rules.
     
    .PARAMETER ADRules
        The Access Rules actually deployed on the AD Object
     
    .PARAMETER ConfiguredRules
        The Access Rules configured for the AD Object
     
    .PARAMETER DefaultRules
        The default Access Rules for objects of this type
     
    .PARAMETER ADObject
        The AD Object for which Access Rules are being compared
     
    .PARAMETER Server
        The server / domain to work with.
     
    .PARAMETER Credential
        The credentials to use for this operation.
     
    .EXAMPLE
        PS C:\> Compare-AccessRules @parameters -ADRules ($adAclObject.Access | Convert-AccessRuleIdentity @parameters) -ConfiguredRules $desiredPermissions -DefaultRules $defaultPermissions -ADObject $adObject
         
        Compare actual access rules on the specified AD Object to the system default and configured rules.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    param (
        [AllowEmptyCollection()]
        $ADRules,

        [AllowEmptyCollection()]
        $ConfiguredRules,

        [AllowEmptyCollection()]
        $DefaultRules,

        $ADObject,

        [PSFComputer]
        $Server,
        
        [PSCredential]
        $Credential
    )

    $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential

    # Resolve the mode under which it will be evaluated. Either 'Additive' or 'Constrained'
    $processingMode = Resolve-DMAccessRuleMode @parameters -ADObject $adObject

    function Write-Result {
        [CmdletBinding()]
        param (
            [ValidateSet('Create', 'Delete', 'FixConfig', 'Restore')]
            [Parameter(Mandatory = $true)]
            $Type,

            $Identity,

            [AllowNull()]
            $ADObject,

            [AllowNull()]
            $Configuration,

            [string]
            $DistinguishedName
        )

        $item = [PSCustomObject]@{
            PSTypeName        = 'DomainManagement.AccessRule.Change'
            Type              = $Type
            ACT               = $ADObject.AccessControlType
            Identity          = $Identity
            Rights            = $ADObject.ActiveDirectoryRights
            DistinguishedName = $DistinguishedName
            ADObject          = $ADObject
            Configuration     = $Configuration
        }
        if (-not $ADObject) {
            $item.ACT = $Configuration.AccessControlType
            $item.Rights = $Configuration.ActiveDirectoryRights
        }
        Add-Member -InputObject $item -MemberType ScriptMethod ToString -Value { '{0}: {1}' -f $this.Type, $this.Identity } -Force -PassThru
    }

    $defaultRulesPresent = [System.Collections.ArrayList]::new()
    $relevantADRules = :outer foreach ($adRule in $ADRules) {
        if ($adRule.OriginalRule.IsInherited) { continue }
        #region Skip OUs' "Protect from Accidential Deletion" ACE
        if (($adRule.AccessControlType -eq 'Deny') -and ($ADObject.ObjectClass -eq 'organizationalUnit')) {
            if ($adRule.IdentityReference -eq 'everyone') { continue }
            $eSid = [System.Security.Principal.SecurityIdentifier]'S-1-1-0'
            $eName = $eSid.Translate([System.Security.Principal.NTAccount])
            if ($adRule.IdentityReference -eq $eName) { continue }
            if ($adRule.IdentityReference -eq $eSid) { continue }
        }
        #endregion Skip OUs' "Protect from Accidential Deletion" ACE

        foreach ($defaultRule in $DefaultRules) {
            if (Test-AccessRuleEquality -Parameters $parameters -Rule1 $adRule -Rule2 $defaultRule) {
                $null = $defaultRulesPresent.Add($defaultRule)
                continue outer
            }
        }
        $adRule
    }

    #region Foreach non-default AD Rule: Check whether configured and delete if not so
    :outer foreach ($relevantADRule in $relevantADRules) {
        foreach ($configuredRule in $ConfiguredRules) {
            if (Test-AccessRuleEquality -Parameters $parameters -Rule1 $relevantADRule -Rule2 $configuredRule) {
                # If explicitly defined for deletion, do so
                if ('False' -eq $configuredRule.Present) {
                    Write-Result -Type Delete -Identity $relevantADRule.IdentityReference -ADObject $relevantADRule -DistinguishedName $ADObject
                }
                continue outer
            }
        }

        # Don't generate delete changes
        if ($processingMode -eq 'Additive') { continue }
        # Don't generate delete changes, unless we have configured a permission level for the affected identity
        if ($processingMode -eq 'Defined') {
            if (-not ($relevantADRule.IdentityReference | Compare-Identity -Parameters $parameters -ReferenceIdentity $ConfiguredRules.IdentityReference -IncludeEqual -ExcludeDifferent)) {
                continue
            }
        }

        Write-Result -Type Delete -Identity $relevantADRule.IdentityReference -ADObject $relevantADRule -DistinguishedName $ADObject
    }
    #endregion Foreach non-default AD Rule: Check whether configured and delete if not so

    #region Foreach configured rule: Check whether it exists as defined or make it so
    :outer foreach ($configuredRule in $ConfiguredRules) {
        foreach ($defaultRule in $DefaultRules) {
            if ('True' -ne $configuredRule.Present) { break }
            if ($configuredRule.NoFixConfig) { break }
            if (Test-AccessRuleEquality -Parameters $parameters -Rule1 $defaultRule -Rule2 $configuredRule) {
                Write-Result -Type FixConfig -Identity $defaultRule.IdentityReference -ADObject $defaultRule -Configuration $configuredRule -DistinguishedName $ADObject
                continue outer
            }
        }
        foreach ($relevantADRule in $relevantADRules) {
            if (Test-AccessRuleEquality -Parameters $parameters -Rule1 $relevantADRule -Rule2 $configuredRule) {
                continue outer
            }
        }
        # Do not generate Create rules for any rule not configured for creation
        if ('True' -ne $configuredRule.Present) { continue }
        Write-Result -Type Create -Identity $configuredRule.IdentityReference -Configuration $configuredRule -DistinguishedName $ADObject
    }
    #endregion Foreach configured rule: Check whether it exists as defined or make it so

    #region Foreach non-existent default rule: Create unless configured otherwise
    $domainControllersOUFilter = '*{0}' -f ('OU=Domain Controllers,%DomainDN%' | Resolve-String)
    :outer foreach ($defaultRule in $DefaultRules | Where-Object { $_ -notin $defaultRulesPresent.ToArray() }) {
        # Do not apply restore to Domain Controllers OU, as it is already deployed intentionally diverging from the OU defaults
        if ($ADObject -like $domainControllersOUFilter) { break }

        # Skip 'CREATOR OWNER' Rules, as those should never be restored.
        # When creating an AD object that has this group as default permissions, it will instead
        # Translate those to the identity creating the object
        if ('S-1-3-0' -eq $defaultRule.SID) { continue }

        foreach ($configuredRule in $ConfiguredRules) {
            if (Test-AccessRuleEquality -Parameters $parameters -Rule1 $defaultRule -Rule2 $configuredRule) {
                # If we explicitly don't want the rule: Skip and do NOT create a restoration action
                if ('True' -ne $configuredRule.Present) { continue outer }
            }
        }

        Write-Result -Type Restore -Identity $defaultRule.IdentityReference -Configuration $defaultRule -DistinguishedName $ADObject
    }
    #endregion Foreach non-existent default rule: Create unless configured otherwise
}