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)
Requirements
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.)
Challenges
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) return } #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?" return } #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) return } } 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 https://blog.wobl.it/2016/04/active-directory-guid-to-friendly-name-using-just-powershell/ function Get-NameForGUID { [CmdletBinding()] Param( [guid]$guid ) 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 https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers $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 [PSCustomObject]@{ 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
Pingback: Saturday Link Roundup – wisefaq.com