Report on Active Directory Container permissions using PowerShell

In a previous blog post (Link), I showed a way to create a report on OU (Organizational Unit) permissions. One of the replies I got about that was: How about the Container permissions? Those are important too 🙂 And that’s correct, they are! In this blog post, I will show you how to create a report on those (The script is based on the OU report)


The script should scan all Containers in the Active Directory Domain, but you should also be able to specify a specific Container to start in, including all child Containers. The output should be stored in a CSV file for easy import in Excel or other tools which could report on the data. Things like inheritance should be reported, and on what type of objects the permissions were given. (For example, when Delegate Control was used for Helpdesk tasks like resetting/unlocking accounts etc.)


During the creation and testing of the Get-ActiveDirectoryContainerPermissions function, I ran into a few issues:

  • Some ObjectTypes were not translated to a friendly/more readable name. Luckily someone already wrote a function for that which I used in the script (Downloaded that from here)
  • Some Built-In Security Identifiers were also not translated. They were translated using a Microsoft Docs page as input (here)

Running the script

After running the script, the Get-ActiveDirectoryContainerPermissions function is available with two Parameters:

  • Output, enter the path to where the CSV file should be stored, e.g c:\temp\Container_ACL.csv
  • StartContainer, the start Container to scan including child Containers, format it like ‘CN=Users,DC=domain, DC=Local’

In the example below, I ran the Function with the -Output c:\temp\containeracl.csv Parameter

When the script is done, it will tell you how many permissions were exported and where:

The containeracl.csv looks like this in Excel:

The script

Below is the script. You should run it as Administrator on a Domain-Joined computer with the ActiveDirectory Module installed or on a Domain Controller 🙂

function Get-ActiveDirectoryContainerPermissions {
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Enter the path to where the CSV file should be stored, e.g c:\temp\Container_ACL.csv")][string]$Output,
        [Parameter(Mandatory = $false, HelpMessage = "Start Container to scan including child Containers, format it like 'CN=Users,DC=domain,DC=Local'")][string]$StartContainer

    #Validate output by creating the file, stop if location is inaccessible
    try {
        New-Item -Path $Output -ItemType File -Force:$true -ErrorAction Stop | Out-Null
        Write-Host ("Output to {0} is valid" -f $Output) -ForegroundColor Green
    catch {
        Write-Warning ("The output can't be saved as {0}, is specified path accessible?" -f $Output)

    #Try to detect the Active Directory Domain name before continuing and stop script if it fails
    try {
        $domain = (Get-ADDomain -ErrorAction Stop).DNSroot
        Write-Host ('Domain {0} detected' -f $domain) -ForegroundColor Green
    catch {
        Write-Warning "Could not retrieve Domain Name, is the ActiveDirectory module installed or are you running this from a non-domain-joined device?"

    #Continue if Active Directory Domain was detected and retrieve list of Containers from the whole domain
    #or from the Ou specified in the StartContonainer parameter
    if ($domain) {
        if ($StartContainer) {
            try {
                Write-Host ("Retrieving Containers for Domain {0} starting from {1}" -f $domain, $StartContainer) -ForegroundColor Green
                #$containerlist = Get-ADOrganizationalUnit -SearchBase $StartContainer -Filter * -ResultSetSize 10000 -SearchScope Subtree -ErrorAction Stop | Sort-Object DistinguishedName
                $containerlist = Get-ADObject -Filter { (objectClass -eq "Container") } -SearchBase $StartContainer -ResultSetSize 10000 -SearchScope Subtree -ErrorAction Stop | Sort-Object DistinguishedName
            catch {
                Write-Warning ("Could not use {0}, check spelling and format it like 'OU=Servers,DC=domain,DC=Local')" -f $StartContainer)
        else {
            Write-Host ("Retrieving all Containers for Domain {0}" -f $domain, $StartContainer) -ForegroundColor Green
            #$containerlist = Get-ADOrganizationalUnit -Filter * -ResultSetSize 10000 -SearchScope Subtree -ErrorAction Stop | Sort-Object DistinguishedName
            $containerlist = Get-ADObject -Filter { (objectClass -eq "Container") } -ResultSetSize 10000 -SearchScope Subtree -ErrorAction Stop | Sort-Object DistinguishedName
    #Function for translating ObjectTypes to name
    #Thanks go out for the blog here
    function Get-NameForGUID {
        Begin {
            $DomainDC = ([ADSI]"").distinguishedName
            $ExtendedRightGUIDs = "LDAP://cn=Extended-Rights,cn=configuration,$DomainDC"
            $PropertyGUIDs = "LDAP://cn=schema,cn=configuration,$DomainDC"
        Process {
            If ($guid -eq "00000000-0000-0000-0000-000000000000") {
                Return "All"
            Else {
                $rightsGuid = $guid
                $property = "cn"
                $SearchAdsi = ([ADSISEARCHER]"(rightsGuid=$rightsGuid)")
                $SearchAdsi.SearchRoot = $ExtendedRightGUIDs
                $SearchAdsi.SearchScope = "OneLevel"
                $SearchAdsiRes = $SearchAdsi.FindOne()
                If ($SearchAdsiRes) {
                    Return $SearchAdsiRes.Properties[$property]
                Else {
                    $SchemaGuid = $guid
                    $SchemaByteString = "\" + ((([guid]$SchemaGuid).ToByteArray() | ForEach-Object { $_.ToString("x2") }) -Join "\")
                    $property = "ldapDisplayName"
                    $SearchAdsi = ([ADSISEARCHER]"(schemaIDGUID=$SchemaByteString)")
                    $SearchAdsi.SearchRoot = $PropertyGUIDs
                    $SearchAdsi.SearchScope = "OneLevel"
                    $SearchAdsiRes = $SearchAdsi.FindOne()
                    If ($SearchAdsiRes) {
                        Return $SearchAdsiRes.Properties[$property]
                    Else {
                        Return $guid.ToString()

    #Custom object for certain Security Identifiers which don't report a friendly name
    #List is from
    $customidentifiers = @{
        '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-568' = 'Builtin\IIS_IUSRS'
        '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'

    #Create empty variable acltotal, loop through all Containers and save the ACL's to $acltotal
    $acltotal = foreach ($container in $containerlist) {
        Write-Host ("Processing {0}" -f $container.DistinguishedName) -ForegroundColor Green
        $acls = (Get-Acl -path "AD:$($container.DistinguishedName)").Access
        foreach ($acl in $acls) {            
            #If IdentityReference matches item in $customidentifiers, change it to the friendly name
            #Otherwise just use the IdentityReference found by Get-Acl
            if ($customidentifiers | Select-string "$($acl.IdentityReference.Value)" -SimpleMatch ) {
                $IdentityReference = ($customidentifiers | Select-Object -Property $acl.IdentityReference.Value)#.$($acl.IdentityReference.Value)
            else {
                $IdentityReference = "$($acl.IdentityReference)"

            Write-Host ("- Retrieving {0} details for {1}" -f $acl.ActiveDirectoryRights, $IdentityReference) -ForegroundColor Gray

                Container       = $container.DistinguishedName
                Principal       = $IdentityReference
                Rights          = $acl.ActiveDirectoryRights
                AppliesTo       = Get-NameForGUID $acl.InheritedObjectType
                Item            = Get-NameForGUID $acl.ObjectType
                Access          = $acl.AccessControlType
                Inheritance     = $acl.InheritanceType
                InheritanceFrom = $acl.InheritanceFlags

    #Export results to CSV file
    if ($acltotal.count -gt 0) {
        Write-Host ("Exporting {0} results to {1}" -f $acltotal.count, $Output) -ForegroundColor Green
        $acltotal | Sort-Object Container, Principal, Rights, AppliesTo, Item, Access, Inheritance, InheritanceFrom | Export-Csv -Path $Output -Encoding UTF8 -Delimiter ';' -NoTypeInformation

Download the script(s) from GitHub here

3 thoughts on “Report on Active Directory Container permissions using PowerShell

  1. Pingback: Saturday Link Roundup –

  2. Hey Harm, I am probably doing something really simple incorrectly here. When running this with a dedicated starting container the call runs through, completes the output validation, says the domain is detected and that it is retrieving containers. However that is it, no objects are showing as detected (though there are some) and the call quickly finishes. The OU I am starting on has sub OUs with users and security groups contained within them.

    Has that come up in any of your testing?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.