KMaks.ActiveDirectoryRights.psm1

<#
.SYNOPSIS
    Get ACLs for ActiveDirectory objects.
.DESCRIPTION
    This function can audit access rights on Active Directory objects.
.PARAMETER SearchBase
Specifies an Active Directory path to search.
 
If you do not provide this parameter it will search the entire domain.
.PARAMETER ObjectClass
.PARAMETER SearchScope
Specifies the scope of an Active Directory search. The acceptable values for this parameter are:
 
        - Base
 
        - OneLevel
 
        - Subtree
 
 
A Base query searches only the current path or object. A OneLevel query searches the immediate children of that path or object. A Subtree query searches the current path or object and all children of that path or object.
.PARAMETER SkipInherited
Skips rights that are not sed directly but instead are inherited.
.PARAMETER SkipBuiltInIdentities
Skips a set of built-in identities for readability.
.PARAMETER IncludeCanonicalName
Includes CanonicalName for better readability for less technical users.
.PARAMETER IncludeObjectOwner
Includes the owner of each object.
.PARAMETER RightsFormat
Output format for permissions. Acceptable values for this parameter are:
 
        - Binary
         
        - Generic
 
        - SeparateValues
 
This parameter accepts multiple values.
 
A Generic format is a string with all the rights as returned by Get-Acl. Binary is a shorter version with binary flags that are easier to parse by external tools. SeparateValues outputs a separate column for each right with either true or false value which helps in filtering output with external tools like Microsoft Excel.
.EXAMPLE
# Get Access Rights on domain root
Get-ADRights
.EXAMPLE
# Get Access Rights on a specific Organizational Unit
Get-ADRights -SearchBase 'OU=Specific OU,DC=contoso,DC=com'
.EXAMPLE
# Get Access Rights on a specific Organizational Unit and all descendents
Get-ADRights -SearchBase 'OU=Specific OU,DC=contoso,DC=com' -SearchScope Subtree
.EXAMPLE
# Get Access Rights on a specific Organizational Unit skipping well known built-in identities
Get-ADRights -SearchBase 'OU=Specific OU,DC=contoso,DC=com' -SearchScope Subtree -SkipBuiltInIdentities
.EXAMPLE
# Get Access Rights on an entire domain skipping all inherited ones to determine where are they set specifically
Get-ADRights -SearchBase 'OU=Specific OU,DC=contoso,DC=com' -SearchScope Subtree -SkipBuiltInIdentities -SkipInherited
.EXAMPLE
# Get Access Rights on an entire domain export it to CSV.
Get-ADRights -SearchBase 'OU=Specific OU,DC=contoso,DC=com' -SearchScope Subtree -SkipBuiltInIdentities -SkipInherited | Export-Csv -Path "ADAccessRights.csv"
#>

function Get-ADRights {
    #Requires -Module ActiveDirectory
    [CmdletBinding(HelpUri = 'https://www.powershellgallery.com/packages/KMaks.ActiveDirectoryRights')]
    Param(
        [parameter(Mandatory=$false)]
        [string]$SearchBase,

        [parameter(Mandatory=$false)]
        [string]$ObjectClass,

        [parameter(Mandatory=$false)]
        [ValidateSet("Base","OneLevel","Subtree")]
        [string]$SearchScope = "Base",

        [parameter(Mandatory=$false)]
        [switch]$SkipInherited,

        [parameter(mandatory=$false)]
        [switch]$SkipBuiltInIdentities,

        [parameter(mandatory=$false)]
        [switch]$IncludeCanonicalName,

        [parameter(mandatory=$false)]
        [switch]$IncludeObjectOwner,

        [parameter(mandatory=$false)]
        [ValidateSet("Generic","SeparateValues","Binary","")]
        [string[]]$RightsFormat = "Generic"
    )

    # Check whether ActiveDirectory module is loaded
    If (-not (Get-Module -Name ActiveDirectory)) {
        Throw "This function requires ActiveDirectory module to be loaded."
    }

    # Built-In identities to skip if -SkipBuiltInIdentities switch is used
    $BuiltInIdentities = @(
        "CREATOR OWNER"
        "Everyone"

        "BUILTIN\Administrators"
        "BUILTIN\Users"
        "BUILTIN\Guests"
        "BUILTIN\Power Users"
        "BUILTIN\Account Operators"
        "BUILTIN\Server Operators"
        "BUILTIN\Print Operators"
        "BUILTIN\Backup Operators"
        "BUILTIN\Replicators"
        "BUILTIN\Pre-Windows 2000 Compatible Access"
        "BUILTIN\Remote Desktop Users"
        "BUILTIN\Network Configuration Operators"
        "BUILTIN\Incoming Forest Trust Builders"
        "BUILTIN\Performance Monitor Users"
        "BUILTIN\Performance Log Users"
        "BUILTIN\Windows Authorization Access Group"
        "BUILTIN\Terminal Server License Servers"
        "BUILTIN\Distributed COM Users"
        "BUILTIN\Cryptographic Operators"
        "BUILTIN\Event Log Readers"
        "BUILTIN\Certificate Service DCOM Access"
        "BUILTIN\RDS Remote Access Servers"
        "BUILTIN\RDS Endpoint Servers"
        "BUILTIN\RDS Management Servers"
        "BUILTIN\Hyper-V Administrators"
        "BUILTIN\Access Control Assistance Operators"
        "BUILTIN\Remote Management Users"

        "NT AUTHORITY\SELF"
        "NT AUTHORITY\SYSTEM"
        "NT AUTHORITY\Authenticated Users"
        "NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS"

        # Source: https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers
        "S-1-5-1"      # Dialup
        "S-1-5-113"    # Local account
        "S-1-5-114"    # Local account and member of Administrators group
        "S-1-5-2"      # Network
        "S-1-5-3"      # Batch
        "S-1-5-4"      # Interactive
        "S-1-5-6"      # Service
        "S-1-5-7"      # Anonymous Logon
        "S-1-5-8"      # Proxy
        "S-1-5-9"      # Enterprise Domain Controllers
        "S-1-5-10"     # Self
        "S-1-5-11"     # Authenticated Users
        "S-1-5-12"     # Restricted Code
        "S-1-5-13"     # Terminal Server User
        "S-1-5-14"     # Remote Interactive Logon
        "S-1-5-15"     # This Organization
        "S-1-5-17"     # IIS_USRS
        "S-1-5-18"     # System (or LocalSystem)
        "S-1-5-19"     # NT Authority (LocalService)
        "S-1-5-20"     # Network Service
        "S-1-5-32-544" # Administrators
        "S-1-5-32-545" # Users
        "S-1-5-32-546" # Guests
        "S-1-5-32-547" # Power Users
        "S-1-5-32-548" # Account Operators
        "S-1-5-32-549" # Server Operators
        "S-1-5-32-550" # Print Operators
        "S-1-5-32-551" # Backup Operators
        "S-1-5-32-552" # Replicators
        "S-1-5-32-554" # Builtin\Pre-Windows 2000 Compatible Access
        "S-1-5-32-555" # Builtin\Remote Desktop Users
        "S-1-5-32-556" # Builtin\Network Configuration Operators
        "S-1-5-32-557" # Builtin\Incoming Forest Trust Builders
        "S-1-5-32-558" # Builtin\Performance Monitor Users
        "S-1-5-32-559" # Builtin\Performance Log Users
        "S-1-5-32-560" # Builtin\Windows Authorization Access Group
        "S-1-5-32-561" # Builtin\Terminal Server License Servers
        "S-1-5-32-562" # Builtin\Distributed COM Users
        "S-1-5-32-569" # Builtin\Cryptographic Operators
        "S-1-5-32-573" # Builtin\Event Log Readers
        "S-1-5-32-574" # Builtin\Certificate Service DCOM Access
        "S-1-5-32-575" # Builtin\RDS Remote Access Servers
        "S-1-5-32-576" # Builtin\RDS Endpoint Servers
        "S-1-5-32-577" # Builtin\RDS Management Servers
        "S-1-5-32-578" # Builtin\Hyper-V Administrators
        "S-1-5-32-579" # Builtin\Access Control Assistance Operators
        "S-1-5-32-580" # Builtin\Remote Management Users
        "S-1-5-64-10"  # NTLM Authentication
        "S-1-5-64-14"  # SChannel Authentication
        "S-1-5-64-21"  # Digest Authentication
        "S-1-5-80"     # NT Service
        "S-1-5-80-0"   # All Services
        "S-1-5-83-0"   # NT VIRTUAL MACHINE\Virtual Machines
    )

    # Search GUID in the dictionary
    Function Get-SchemaIdFromDictionaryByGuid ($Dictionary, $Guid) {
        $Object = $Dictionary | Where-Object GUID -eq $Guid

        If ($Object.Name) {
            Return $Object.Name
        }
        ElseIf ($Guid -eq "00000000-0000-0000-0000-000000000000") {
            Return $null
        }
        Else {
            Return $Guid
        }    
    }

    # Get AD objects
    $GetADObjectConfig = @{
        Filter = "*"
    }
    If ($SearchBase) { $GetADObjectConfig.SearchBase = $SearchBase }
    If ($SearchScope) { $GetADObjectConfig.SearchScope = $SearchScope }
    If ($ObjectClass) { $GetADObjectConfig.Filter = 'objectClass -eq "{0}"' -f $ObjectClass }
    [array]$Objects = Get-ADObject @GetADObjectConfig

    # Build a GUID Dictionary
    $GUIDDictionary = Get-ADObject -Filter 'SchemaIDGUID -like "*"' -SearchBase (Get-ADRootDSE).schemanamingContext -Properties name, schemaIDGUID |
                      Select-Object name, @{n="GUID";e={$_.schemaIDGUID -as [guid]}}
    $GUIDDictionary += Get-ADObject -Filter 'objectClass -eq "controlAccessRight"' -SearchBase "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" -Properties name, rightsGUID |
                       Select-Object name, @{n="GUID";e={$_.rightsGUID -as [guid]}}

    foreach ($Object in $Objects){
        
        # Write progress indicator
        $WriteProgressConfig = @{
            Activity = "Reading ACLs {0}/{1} ({2}%)" -f ++$i, ($Objects.Count), [math]::Round($i/($Objects.Count) * 100)
            Status = "'{0}'" -f $Object.DistinguishedName
            Id = 0
            PercentComplete = $i/($Objects.Count) * 100
        }
        Write-Progress @WriteProgressConfig

        # Get ACLs for $Object
        $Acl = Get-ACL -Path ("AD:\{0}" -f $Object.DistinguishedName)

        foreach ($Ace in $Acl.Access){
            # Skip inherited ACE if -SkipInherited switch is used
            If (
                $SkipInherited -and
                $Ace.IsInherited
            ) { Continue }
            # Skip built-in identities if -SkipBuiltInIdentities
            If (
                $SkipBuiltInIdentities -and
                $BuiltInIdentities -contains $Ace.IdentityReference
            ) { Continue }
            # Build CanonicalName
            If ($IncludeCanonicalName) {
                $CanonicalName = $Object.DistinguishedName -replace ',?DC=.*$' -replace '\w{2}=' -split ','
                [array]::Reverse($CanonicalName)
                $CanonicalName = "/{0}" -f ($CanonicalName -join '/')
            }

            # Build an object to return
            $Return = [PSCustomObject]@{
                DistinguishedName       = $Object.DistinguishedName
                CanonicalName           = $CanonicalName
                ObjectClass             = $Object.ObjectClass
                Identity                = $Ace.IdentityReference
                ObjectOwner             = $Acl.Owner
                RightsType              = $Ace.AccessControlType
                Rights                  = $Ace.ActiveDirectoryRights
                InheritanceType         = $Ace.InheritanceType
                ObjectType              = Get-SchemaIdFromDictionaryByGuid -Dictionary $GUIDDictionary -Guid $Ace.ObjectType
                InheritedObjectType     = Get-SchemaIdFromDictionaryByGuid -Dictionary $GUIDDictionary -Guid $Ace.InheritedObjectType

                #GenericExecute = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::GenericExecute )
                #GenericWrite = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite )
                #GenericRead = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::GenericRead )
                #GenericAll = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::GenericAll )

                CreateChild             = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::CreateChild )
                DeleteChild             = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::DeleteChild )
                ListChildren            = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::ListChildren )
                Self                    = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::Self )
                ReadProperty            = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::ReadProperty )
                WriteProperty           = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty )
                DeleteTree              = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::DeleteTree )
                ListObject              = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::ListObject )
                ExtendedRight           = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight )
                Delete                  = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::Delete )
                ReadControl             = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::ReadControl )
                WriteDacl               = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::WriteDacl )
                WriteOwner              = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::WriteOwner )
                Synchronize             = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::Synchronize )
                AccessSystemSecurity    = $Ace.ActiveDirectoryRights.HasFlag( [System.DirectoryServices.ActiveDirectoryRights]::AccessSystemSecurity )

                BinaryRights            = [Convert]::ToString($Ace.ActiveDirectoryRights.value__,2).PadLeft(32,"0")
                IsInherited             = $Ace.IsInherited
            }

            $ReturnProperties = [System.Collections.ArrayList]::new()
            [void]$ReturnProperties.AddRange(
                @(
                    'DistinguishedName'
                    'ObjectClass'
                    'Identity'
                    'RightsType'
                    'InheritanceType'
                    'ObjectType'
                    'InheritedObjectType'
                )
            )
            If ($ObjectClass) {
                [void]$ReturnProperties.Remove('ObjectClass')
            }
            If (-not $SkipInherited) {
                [void]$ReturnProperties.Add('IsInherited')
            }
            If ($IncludeCanonicalName) {
                [void]$ReturnProperties.Insert(0, 'CanonicalName')
            }
            If ($IncludeObjectOwner) {
                [void]$ReturnProperties.Insert(2, 'ObjectOwner')
            }
            $InsertIndex = 4
            switch ($RightsFormat)
            {
                'Binary' {
                    [void]$ReturnProperties.InsertRange(
                        $InsertIndex,
                        @(
                            'BinaryRights'
                        )
                    )
                }
                'Generic' {
                    [void]$ReturnProperties.InsertRange(
                        $InsertIndex,
                        @(
                            'Rights'
                        )
                    )
                }
                'SeparateValues' {
                    [void]$ReturnProperties.InsertRange(
                        $InsertIndex,
                        @(
                            'CreateChild'
                            'DeleteChild'
                            'ListChildren'
                            'Self'
                            'ReadProperty'
                            'WriteProperty'
                            'DeleteTree'
                            'ListObject'
                            'ExtendedRight'
                            'Delete'
                            'ReadControl'
                            'WriteDacl'
                            'WriteOwner'
                            'Synchronize'
                            'AccessSystemSecurity'
                        )
                    )
                }
            }
            $Return | Select-Object -Property $ReturnProperties
        }
    }
}