
Export AD Group Membership to CSV for an Access Review with PowerShell
Before you run this
This guide exports the members of one or more Active Directory groups to a CSV file so you can hand it to a manager or an auditor for a periodic access review. It only reads from AD and writes a file to disk — it does not add, remove, or change a single group membership.
A few things to be clear about:
- Privileges: Reading group membership does not require Domain Admin. In a default AD environment any authenticated domain user can read group membership, and these cmdlets run in an ordinary (non-elevated) PowerShell session. You do need the ActiveDirectory module, which ships with RSAT (Remote Server Administration Tools) — either on a domain controller or on an admin workstation where you've installed RSAT.
- Test first: Read the script before you run it, and run it against a single, known test group first so you can eyeball the output. The pipeline is harmless, but it's worth confirming the columns and counts match what you expect before you export twenty groups and send them out.
- The file is the sensitive part. The CSV lists user accounts, and may include email and department. That's exactly the kind of file that shouldn't sit in a shared folder or a Downloads directory indefinitely. Write it somewhere access-controlled and delete it when the review is done. There's nothing to "undo" in AD — the only cleanup is removing the CSV you created.
What I'm assuming
- A domain-joined Windows workstation or server, running Windows PowerShell 5.1 (the default on Windows 10/11 and Windows Server). The commands work the same in PowerShell 7 as long as the ActiveDirectory module is available.
- RSAT / the ActiveDirectory module is installed. Check with
Get-Module -ListAvailable ActiveDirectory. On Windows 10/11, RSAT is a Feature on Demand under Settings; on Windows Server it's a feature you add with Server Manager orInstall-WindowsFeature RSAT-AD-PowerShell. - You're running from an account that can reach a domain controller (ADWS, the AD Web Services, must be reachable — that's what these cmdlets talk to).
The single-group version
Start here. This exports the members of one group, resolves each member to real user detail, and writes a CSV.
Import-Module ActiveDirectory
# Replace these two with your values.
$GroupName = "Finance-ReadWrite" # the group's sAMAccountName or name
$OutputPath = "C:\path\to\Finance-ReadWrite_membership.csv" # where the CSV goes
Get-ADGroupMember -Identity $GroupName -Recursive |
# -Recursive flattens nested groups, but can return computers too, so keep only users.
Where-Object { $_.objectClass -eq 'user' } |
Get-ADUser -Properties DisplayName, Enabled, EmailAddress, Department |
Select-Object SamAccountName, DisplayName, Enabled, EmailAddress, Department, DistinguishedName |
Sort-Object SamAccountName |
Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
A couple of notes on the non-obvious lines:
-Recursiveflattens nested groups so you get the effective members — the people who actually inherit access — rather than a nested group object. If your review wants the direct members only, drop-Recursive.Where-Object { $_.objectClass -eq 'user' }is there because-Recursivecan also surface computer accounts, and piping a computer intoGet-ADUserthrows an error. Filtering touserkeeps the pipeline clean. If you specifically need computer or service accounts in the report, that's a different query — handle them separately rather than forcing them throughGet-ADUser.Get-ADUser -Properties …—Get-ADGroupMemberreturns only a thin object (name, SID, DN). To getEnabled,EmailAddress, andDepartmentyou have to re-query each member withGet-ADUserand ask for those properties by name. Only the properties you list are returned.
Enabled is the column reviewers care about most — a disabled account still holding group membership is exactly the kind of finding an access review is meant to catch.
Exporting several groups at once
For a real review you usually have a list of groups. This version loops over them and writes one combined CSV with a Group column so each row is attributable.
Import-Module ActiveDirectory
# Replace with the groups you're reviewing.
$Groups = @("Finance-ReadWrite", "HR-Full", "VPN-Users")
$OutputPath = "C:\path\to\access-review_$(Get-Date -Format 'yyyy-MM-dd').csv"
$results = foreach ($group in $Groups) {
Get-ADGroupMember -Identity $group -Recursive |
Where-Object { $_.objectClass -eq 'user' } |
Get-ADUser -Properties DisplayName, Enabled, EmailAddress, Department |
Select-Object @{Name='Group'; Expression={ $group }},
SamAccountName, DisplayName, Enabled, EmailAddress, Department
}
$results | Sort-Object Group, SamAccountName |
Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
The @{Name='Group'; Expression={ $group }} bit is a calculated property — it stamps each row with the group it came from, since Get-ADUser output has no idea which group it was pulled from.
One real limitation to know about
Get-ADGroupMember has a server-side cap: by default it will not return more than 5000 members, and on very large groups it can fail with an error about the size limit. For normal groups this never comes up. If you hit it on a huge group, the standard alternative is to query users by their memberOf attribute using Get-ADUser -LDAPFilter instead — that avoids the Get-ADGroupMember ceiling. I'm not walking through it here because it needs the group's distinguished name and careful filter syntax; if you need it, check the Get-ADUser documentation on Microsoft Learn for the -LDAPFilter examples and build it against a test group first.
Verify the export
The whole point of the report is that its counts are trustworthy, so confirm two things.
Row count matches AD. Read the file back and count:
$csv = Import-Csv "C:\path\to\Finance-ReadWrite_membership.csv"
$csv.Count
Compare that to what AD reports directly:
(Get-ADGroupMember -Identity "Finance-ReadWrite" -Recursive |
Where-Object { $_.objectClass -eq 'user' }).Count
The two numbers should be identical. If they differ, the usual cause is nested computer or contact objects that got filtered out — decide whether the review needs them.
Spot-check the content. Open the CSV in Excel or Import-Csv | Out-GridView and confirm the Enabled column is populated and the names look right. If EmailAddress or Department are blank for everyone, you probably omitted them from -Properties — those two are the easiest to drop by accident.
Cleanup
There is nothing to roll back in Active Directory — this never wrote to the directory. The only artifact is the CSV file. When the review is signed off, remove it:
Remove-Item "C:\path\to\Finance-ReadWrite_membership.csv"
Keep the reviewed-and-signed copy in whatever access-controlled location your audit process uses, and don't leave working copies lying around on the workstation. That's the one piece of this job where a careless step actually matters.
For the exact parameters and property names, see Get-ADGroupMember, Get-ADUser, and Export-Csv on Microsoft Learn — they document every property these cmdlets can return.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Average 4.0 from 1 reader
