Published on

How to See Delegated Permissions on an OU in Active Directory (PowerShell Script)

The Delegation of Control Wizard is easy to run and easy to forget about. Six months later, nobody remembers which OUs have been delegated to which help desk group, which junior admin still has rights they no longer need, or whether a delegation was ever cleaned up after a role change. ADUC's Advanced Security dialog can show you, but only one OU at a time — there's no built-in way to see delegated permissions across the whole domain at once.

Here's a script that reports it in one pass.

The quick answer

Delegated permissions show up as explicit (non-inherited) access control entries on an OU's ACL. For a single OU:

(Get-Acl -Path "AD:\OU=Sales,DC=contoso,DC=com").Access | Where-Object { -not $_.IsInherited }

That's fine for checking one OU you already suspect. For a domain-wide picture, you need to loop over every OU and do this for each one.

A more useful reporting script

#requires -Modules ActiveDirectory
<#
.SYNOPSIS
    Reports delegated (non-inherited) permissions on Active Directory OUs.
#>

[CmdletBinding()]
param(
    [string]$SearchBase,
    [switch]$ExcludeBuiltIn,
    [string]$OutputCsv
)

Import-Module ActiveDirectory -ErrorAction Stop

$builtInTrustees = @(
    'NT AUTHORITY\SELF',
    'NT AUTHORITY\SYSTEM',
    'NT AUTHORITY\Authenticated Users',
    'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS',
    'BUILTIN\Administrators',
    'BUILTIN\Account Operators',
    'BUILTIN\Print Operators',
    'BUILTIN\Pre-Windows 2000 Compatible Access',
    'CREATOR OWNER'
)

$ouParams = @{ Filter = '*'; Properties = 'DistinguishedName' }
if ($SearchBase) { $ouParams['SearchBase'] = $SearchBase }

$ous = Get-ADOrganizationalUnit @ouParams
$results = New-Object System.Collections.Generic.List[object]

foreach ($ou in $ous) {
    try {
        $acl = Get-Acl -Path "AD:\$($ou.DistinguishedName)" -ErrorAction Stop
    } catch {
        Write-Warning "Could not read ACL for $($ou.DistinguishedName): $($_.Exception.Message)"
        continue
    }

    $explicitAces = $acl.Access | Where-Object { -not $_.IsInherited }

    foreach ($ace in $explicitAces) {
        $trustee = $ace.IdentityReference.ToString()
        if ($ExcludeBuiltIn -and $builtInTrustees -contains $trustee) { continue }

        $results.Add([PSCustomObject]@{
            OU                    = $ou.DistinguishedName
            Trustee               = $trustee
            AccessControlType     = $ace.AccessControlType
            ActiveDirectoryRights = $ace.ActiveDirectoryRights
            InheritanceType       = $ace.InheritanceType
            ObjectType            = $ace.ObjectType
        })
    }
}

if ($results.Count -eq 0) {
    Write-Host "No explicit (delegated) permissions found in scope." -ForegroundColor Yellow
    return
}

$sorted = $results | Sort-Object OU, Trustee

Write-Host "Found $(@($sorted).Count) explicit permission entry/entries across $(@($ous).Count) OU(s)." -ForegroundColor Cyan
$sorted | Format-Table OU, Trustee, AccessControlType, ActiveDirectoryRights -AutoSize

if ($OutputCsv) {
    $sorted | Export-Csv -Path $OutputCsv -NoTypeInformation
    Write-Host "Exported results to $OutputCsv" -ForegroundColor Green
}

Run .\Get-ADOUDelegatedPermissions.ps1 -ExcludeBuiltIn and the noise disappears — you're left with only the ACEs an admin actually added.

Why every OU shows a handful of ACEs even if nobody delegated anything

Fresh OUs in a vanilla AD install already carry a set of default, non-inherited ACEs — grants to SELF, SYSTEM, Account Operators, Authenticated Users, and similar built-in trustees. Without filtering those out, every single OU in the domain will show up in your results whether anyone delegated anything or not, which defeats the purpose of the report. That's exactly what -ExcludeBuiltIn strips away.

The GUI method (no PowerShell)

In ADUC, right-click an OU → PropertiesSecurity tab → Advanced. That shows the same ACL this script reads, but one OU, one dialog, at a time — there's no built-in way to see it across every OU in the domain without scripting it or opening each one by hand.

Where the manual approach runs out of road

A one-off script is fine for a single check. It starts to hurt once you actually need to run this regularly:

  • No scheduling. Cron/Task Scheduler can run the script, but now you own the scheduling, the credentials it runs as, and what happens when it silently fails.
  • No history. A CSV export is a snapshot. Was this the same five accounts as last month, or a growing list? A single export can't tell you.
  • No distribution. Getting the report to the right people (security, IT ops, compliance) on a schedule means building that plumbing yourself.
  • Multi-domain/multi-forest pain. Run it once per domain, reconcile the results yourself, and hope naming/OU conventions are consistent across all of them.
  • No alerting. If something changes unexpectedly between runs — an account re-enabled, a privileged group gaining a member — nothing tells you until you happen to run the script again.

None of that is a PowerShell problem. It's what turns a script into a product.

What SysFlint AD does instead

Our SysFlint AD runs the same kind of discovery shown above automatically, on a schedule, entirely inside your own network. No agents on domain controllers, no data leaving your environment. It's free, forever.

  • Privileged group auditing Who is in Domain Admins, Enterprise Admins, and Schema Admins — including nested membership and accounts that should not be there.
  • Scheduled reports by email Daily, weekly, or monthly runs delivered to the right inbox automatically. No Task Scheduler job for you to babysit.
  • Multi-domain and forest coverage Enumerate every domain in the forest in one pass and get one consolidated report instead of one per domain.

If you're currently doing this with a script on a scheduled task, here's the honest comparison between the two, or go straight to the download page.

Get this script, and the rest of them

The script above is part of awesome-it-scripts, SysFlint's free, open-source library of PowerShell scripts for Active Directory and other IT-admin tasks — no signup, no catch, MIT licensed. Grab this one directly from active-directory/Get-ADOUDelegatedPermissions.ps1, or browse the whole thing.

Find every disabled/locked-out/stale/privileged-account script we've published (plus the FAQ that goes with each one) in the repo's active-directory folder. Star it if it's useful — new scripts land there regularly.