functions/AccessRules/Get-AdcCategoryBasedRules.ps1

function Get-AdcCategoryBasedRules {
    <#
    .SYNOPSIS
        Returns all access rules applicable to an ad object via category rules.
     
    .DESCRIPTION
        Returns all access rules applicable to an ad object via category rules.
     
    .PARAMETER ADObject
        The AD Object for which to resolve access rules by category.
     
    .PARAMETER Server
        The server / domain to work with.
     
    .PARAMETER Credential
        The credentials to use for this operation.
     
    .PARAMETER ConvertNameCommand
        A steppable pipeline wrapping Convert-AdcSchemaGuid converting to name.
     
    .PARAMETER ConvertGuidCommand
        A steppable pipeline wrapping Convert-AdcSchemaGuid converting to guid.
 
    .PARAMETER CategoryRules
        All access rules defined via Object Categories.
        These are compared against the list of ObjectCategories that apply to the object and then - if applicable - converted to access rules.
 
    .PARAMETER ExplicitRules
        Explicitly assigned rules by configuration via Path.
        Path-based assignment overrides category-based assignment.
     
    .EXAMPLE
        PS C:\> Get-AdcCategoryBasedRules -ADObject $foundADObject @parameters -ConvertNameCommand $convertCmdName -ConvertGuidCommand $convertCmdGuid
 
        Returns all access rules applicable to $foundADObject via category rules.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $ADObject,

        [PSFComputer]
        $Server,

        [PSCredential]
        $Credential,

        $ConvertNameCommand,

        $ConvertGuidCommand,

        [hashtable]
        $CategoryRules,

        $ExplicitRules
    )

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

    $resolvedCategories = Resolve-AdcObjectCategory @parameters
    $processedRules = foreach ($resolvedCategory in $resolvedCategories) {
        :byRule foreach ($ruleObject in $CategoryRules[$resolvedCategory.Name]) {
            $objectTypeGuid = $ConvertGuidCommand.Process($ruleObject.ObjectType)[0]
            $objectTypeName = $ConvertNameCommand.Process($ruleObject.ObjectType)[0]
            $inheritedObjectTypeGuid = $ConvertGuidCommand.Process($ruleObject.InheritedObjectType)[0]
            $inheritedObjectTypeName = $ConvertNameCommand.Process($ruleObject.InheritedObjectType)[0]

            try { $identity = Resolve-AdcAceIdentity @parameters -IdentityReference $ruleObject.IdentityReference }
            catch { Stop-PSFFunction -String 'Get-AdcCategoryBasedRules.Identity.ResolutionError' -StringValues $ruleObject.IdentityReference, $resolvedCategory.Name -Target $ruleObject -ErrorRecord $_ -Continue }

            $categoryRule = [PSCustomObject]@{
                PSTypeName              = 'DomainManagement.AccessRule.Converted'
                IdentityReference       = $identity
                AccessControlType       = $ruleObject.AccessControlType
                ActiveDirectoryRights   = $ruleObject.ActiveDirectoryRights
                InheritanceFlags        = $ruleObject.InheritanceFlags
                InheritanceType         = $ruleObject.InheritanceType
                InheritedObjectType     = $inheritedObjectTypeGuid
                InheritedObjectTypeName = $inheritedObjectTypeName
                ObjectFlags             = $ruleObject.ObjectFlags
                ObjectType              = $objectTypeGuid
                ObjectTypeName          = $objectTypeName
                PropagationFlags        = $ruleObject.PropagationFlags
                Present                 = $ruleObject.Present
            }
            # Path-based rules take precedence, when they attempt to do the exact same thing.
            # Mostly so an explicit "Present = $false" can be applied to override a far-reaching category
            foreach ($rule in $ExplicitRules) {
                if (Test-AdcAccessRuleEquality -Rule1 $rule -Rule2 $categoryRule -Parameters $parameters) {
                    continue byRule
                }
            }
            $categoryRule
        }
    }

    # When two category-rules clash, the Present = $false rule wins
    $nonPresent = $processedRules | Where-Object Present -EQ 'False'
    $present = $processedRules | Where-Object Present -NE 'False'

    :main foreach ($rule in $present) {
        foreach ($denyRule in $nonPresent) {
            if (Test-AdcAccessRuleEquality -Rule1 $rule -Rule2 $denyRule -Parameters $parameters) {
                continue main
            }
        }
        $rule
    }
    $nonPresent
}