Skip to content

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

01

Check the Read-Only Audit Context

Audit Setup

Load 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.

Terminal window
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.example
Domains : {corp.example, emea.corp.example}
DNSRoot : corp.example
DNSRoot : emea.corp.example

Step 2: Select the High-Impact Groups

02

Define the Review Set for Your Forest

Privileged Access

Use 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.

Terminal window
$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

03

Capture Member and Nested-Group Details

Evidence Snapshot

Export 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.

Terminal window
$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.csv

Step 4: Review, Validate, and Retain the Snapshot

04

Assign Owners to Access Decisions

Review

Have 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.

Terminal window
Import-Csv $report |
Group-Object Domain, GroupName |
Select-Object Name, Count
Get-FileHash $report -Algorithm SHA256
❯ View Expected Console Output
Name Count
---- -----
corp.example, Domain Admins 12
corp.example, Enterprise Admins 3
Algorithm : SHA256
Hash : ...

Comments