Audit Active Directory Privileged Group Membership
Privileged groups grant broad control over domain identities, systems, and policy. A periodic membership review helps identify stale access, unexpected nested groups, and accounts that need owner confirmation.
This walkthrough reads every domain in one AD forest and exports a dated snapshot of direct membership in each domain’s Domain Admins and Builtin Administrators groups, plus the forest-root Enterprise Admins and Schema Admins groups. Run it from an approved administrative workstation with the Active Directory PowerShell module, network access to each domain’s AD Web Services, and read permissions in every domain. It does not include local machine administrators or Microsoft Entra roles.
Step 1: Confirm the Domain and Module
Check the Read-Only Audit Context
Audit SetupLoad the Active Directory module and list the forest root and all domains in scope. The script queries each domain explicitly; access failures should be resolved or recorded before treating the export as forest-wide.
Import-Module ActiveDirectory$forest = Get-ADForest$forest | Select-Object RootDomain, Domains$domains = foreach ($dnsName in $forest.Domains) { Get-ADDomain -Identity $dnsName}$domains | Select-Object DNSRoot, DistinguishedName, PDCEmulator❯ View Expected Console Output
RootDomain : corp.exampleDomains : {corp.example, emea.corp.example}
DNSRoot : corp.exampleDNSRoot : emea.corp.exampleStep 2: Select the High-Impact Groups
Define the Review Set for Your Forest
Privileged AccessUse each built-in group’s well-known SID rather than its display name, which can be localized or renamed. Domain Admins exists in every domain; Enterprise Admins and Schema Admins are forest-root groups. The Builtin Administrators SID is queried separately in each domain.
$root = Get-ADDomain -Identity $forest.RootDomain$targets = foreach ($domain in $domains) { [pscustomobject]@{ Domain = $domain.DNSRoot; Name = 'Domain Admins'; SID = "$($domain.DomainSID.Value)-512" } [pscustomobject]@{ Domain = $domain.DNSRoot; Name = 'Builtin Administrators'; SID = 'S-1-5-32-544' }}$targets += [pscustomobject]@{ Domain = $root.DNSRoot; Name = 'Enterprise Admins'; SID = "$($root.DomainSID.Value)-519" }$targets += [pscustomobject]@{ Domain = $root.DNSRoot; Name = 'Schema Admins'; SID = "$($root.DomainSID.Value)-518" }
$groups = foreach ($target in $targets) { try { $group = Get-ADGroup -Identity $target.SID -Server $target.Domain -Properties Description -ErrorAction Stop [pscustomobject]@{ Domain = $target.Domain; Name = $target.Name; Group = $group } } catch { Write-Warning "Could not query $($target.Name) in $($target.Domain): $_" }}$groups | Select-Object Domain, Name, @{Name='SID'; Expression={$_.Group.SID.Value}}, @{Name='Description'; Expression={$_.Group.Description}}❯ View Expected Console Output
Domain Name SID Description------ ---- --- -----------corp.example Domain Admins S-1-5-21-...-512 ...emea.corp.example Domain Admins S-1-5-21-...-512 ...corp.example Enterprise Admins S-1-5-21-...-519 ...Step 3: Export Direct Membership
Capture Member and Nested-Group Details
Evidence SnapshotExport direct membership first so nested groups remain visible as separate review items. The domain column identifies where each group was queried. Resolve each member to capture its object class, distinguished name, and SID, then include the group identity in the same report.
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'$report = Join-Path $PWD "privileged-group-members-$stamp.csv"$rows = foreach ($entry in $groups) { foreach ($member in (Get-ADGroupMember -Identity $entry.Group -Server $entry.Domain -ErrorAction Stop)) { $memberSid = if ($member.SID) { $member.SID.Value } else { $null } [pscustomobject]@{ Domain = $entry.Domain GroupName = $entry.Name GroupSID = $entry.Group.SID.Value MemberName = $member.Name MemberType = $member.objectClass MemberDN = $member.DistinguishedName MemberSID = $memberSid } }}$rows | Sort-Object Domain, GroupName, MemberName | Export-Csv -NoTypeInformation -Encoding UTF8 $report$report❯ View Expected Console Output
D:\Audit\privileged-group-members-20261004-101500.csvStep 4: Review, Validate, and Retain the Snapshot
Assign Owners to Access Decisions
ReviewHave the relevant service or directory owners confirm each member’s purpose and approval. Check nested groups recursively as a separate analysis so the report preserves both direct assignments and their effective membership paths. Review warnings from Step 2 and ensure every forest domain was queried successfully.
Import-Csv $report | Group-Object Domain, GroupName | Select-Object Name, CountGet-FileHash $report -Algorithm SHA256❯ View Expected Console Output
Name Count---- -----corp.example, Domain Admins 12corp.example, Enterprise Admins 3
Algorithm : SHA256Hash : ...