Exporting group membership seems simple until you try to do it in a real environment: nested groups, thousands of members, mixed object types (users, computers, service accounts, contacts), inconsistent naming, and “why is this person still in the report?” because you only pulled direct members. This guide gives you practical patterns to export membership lists cleanly and repeatably— with the right defaults, the right properties, and the right approach for auditing and operations.
What you are actually exporting
In Active Directory, “group membership” has multiple meanings:
- Direct members: objects explicitly listed in the group’s
memberattribute. - Transitive (effective) members: direct members plus members of nested groups (and their nested groups).
- Different member types: users, computers, groups, managed service accounts, contacts, and sometimes foreign security principals.
- Different identity fields:
samAccountName,userPrincipalName,mail,SID, and the object’s distinguished name.
Before exporting, decide what question you’re answering. “Who is directly in the group?” is different from “Who effectively gets access because of this group?” Auditors usually want both: the direct nesting structure and the effective list.
Prerequisites and guardrails
-
Active Directory module: Most examples use the RSAT ActiveDirectory module
(
Get-ADGroup,Get-ADGroupMember,Get-ADUser). - Permissions: Reading group membership typically requires only standard read permissions, but protected groups, restricted ACLs, or cross-domain scenarios can complicate this.
-
Be explicit about output encoding: If your environment has non-ASCII names, prefer
-Encoding UTF8. -
Always include stable identifiers: Add
ObjectGUIDand/orSIDso reports remain usable after renames.
The simplest export: direct members to CSV
If you only need direct members (no nesting expansion), Get-ADGroupMember is the cleanest starting point.
The trick is to select consistent fields and include the group name so multi-group exports remain readable.
Import-Module ActiveDirectory
$group = "Finance-Share-Read"
$members = Get-ADGroupMember -Identity $group -Recursive:$false
$members |
Select-Object `
@{n="Group"; e={$group}}, `
Name, SamAccountName, ObjectClass, DistinguishedName, ObjectGUID |
Export-Csv -Path ".\Finance-Share-Read_members_direct.csv" -NoTypeInformation -Encoding UTF8
This produces a simple row-per-member CSV. Notice the explicit -Recursive:$false to keep intent clear.
If someone later changes the code, you’ll see it in review.
Exporting effective members: nested groups with -Recursive
For access reviews, you often need the effective membership (transitive closure through nested groups). For that,
you can use -Recursive.
$group = "Finance-Share-Read"
$members = Get-ADGroupMember -Identity $group -Recursive
$members |
Select-Object `
@{n="Group"; e={$group}}, `
Name, SamAccountName, ObjectClass, DistinguishedName, ObjectGUID |
Export-Csv -Path ".\Finance-Share-Read_members_effective.csv" -NoTypeInformation -Encoding UTF8
Two operational notes:
- Expect mixed object types: nested expansion often returns groups, users, and foreign security principals.
- Cycles and weird nesting: AD generally prevents direct cyclical nesting, but complex structures can still create surprises. Treat reports as evidence—validate anomalies.
Export only users (and pull the fields you actually care about)
Security teams usually want user-centric fields like UPN, email, department, enabled status, and last logon indicators.
Get-ADGroupMember doesn’t return all of those by default. The right pattern is:
get members → filter to users → query users with Get-ADUser -Properties ....
$group = "Finance-Share-Read"
Get-ADGroupMember -Identity $group -Recursive |
Where-Object { $_.ObjectClass -eq "user" } |
ForEach-Object {
Get-ADUser -Identity $_.DistinguishedName -Properties mail, userPrincipalName, department, title, enabled
} |
Select-Object `
@{n="Group"; e={$group}}, `
Name, SamAccountName, UserPrincipalName, Mail, Department, Title, Enabled, DistinguishedName, ObjectGUID |
Export-Csv -Path ".\Finance-Share-Read_users_effective.csv" -NoTypeInformation -Encoding UTF8
This approach is slower than exporting raw member objects, but it’s far more useful for audits and recertification because you’re exporting “human” fields and operational flags.
Multiple groups at once: one file per group, or one combined report
In the real world, you export dozens or hundreds of groups. There are two common output shapes:
- One CSV per group: easy to hand to app owners, but creates file sprawl.
- One combined CSV: best for analytics, diffing, and ingestion into a BI tool.
One combined CSV for a list of groups
$groups = @(
"Finance-Share-Read",
"Finance-Share-Modify",
"HR-Share-Read"
)
$report = foreach ($g in $groups) {
Get-ADGroupMember -Identity $g -Recursive |
Select-Object `
@{n="Group"; e={$g}}, `
Name, SamAccountName, ObjectClass, DistinguishedName, ObjectGUID
}
$report | Export-Csv -Path ".\group_membership_combined.csv" -NoTypeInformation -Encoding UTF8
The important detail is adding the Group column to every row. Without it, the combined CSV becomes meaningless.
One CSV per group with safe filenames
$outDir = ".\Exports"
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
$groups = @("Finance-Share-Read","Finance-Share-Modify","HR-Share-Read")
foreach ($g in $groups) {
$safeName = ($g -replace '[\\/:*?"<>|]', '_')
$path = Join-Path $outDir "$safeName.csv"
Get-ADGroupMember -Identity $g -Recursive |
Select-Object @{n="Group"; e={$g}}, Name, SamAccountName, ObjectClass, DistinguishedName, ObjectGUID |
Export-Csv -Path $path -NoTypeInformation -Encoding UTF8
}
Capturing the nesting structure (who is nested where)
Effective membership lists are great for access validation, but they hide the path of inheritance. When you’re cleaning up group sprawl, you also want “which groups are directly in this group?”
$group = "Finance-Share-Read"
Get-ADGroupMember -Identity $group -Recursive:$false |
Where-Object { $_.ObjectClass -eq "group" } |
Select-Object @{n="ParentGroup"; e={$group}}, Name, SamAccountName, DistinguishedName, ObjectGUID |
Export-Csv -Path ".\Finance-Share-Read_nested_groups_direct.csv" -NoTypeInformation -Encoding UTF8
Use this report to understand your dependency chain, then pair it with the effective-membership export when you actually recertify access.
Handling foreign security principals and cross-domain members
In multi-domain or trusting environments, groups can contain foreign security principals (FSPs). These often show up with
ObjectClass values like foreignSecurityPrincipal and have names that are not directly meaningful.
Practical guidance:
- Export SID whenever possible so you can resolve identities later.
- Expect resolution gaps if the trusted domain is offline, permissions are missing, or the object was deleted.
- Don’t silently drop them: unknown members are exactly what auditors care about.
$group = "Finance-Share-Read"
Get-ADGroupMember -Identity $group -Recursive |
Select-Object `
@{n="Group"; e={$group}}, `
Name, SamAccountName, ObjectClass, DistinguishedName, ObjectGUID, SID |
Export-Csv -Path ".\Finance-Share-Read_members_with_sid.csv" -NoTypeInformation -Encoding UTF8
Large groups and performance: what tends to break
Most “PowerShell exporting group membership is slow/broken” issues come from a few predictable causes:
-
N+1 directory calls: calling
Get-ADUserper member can be expensive at scale. Consider exporting fewer properties or batching your lookups where possible. - Recursive expansion cost: deep nesting can explode result size; use it only when you need effective membership.
-
Network/DC selection: exporting from a distant DC can add latency; optionally specify
-Serverfor consistency. - Non-user objects: scripts that assume every member is a user will fail in real environments.
A pragmatic approach is to start with a “raw members” export (fast), then enrich only the user objects for business-facing reports.
Operational patterns that make exports trustworthy
If you want these exports to survive audits, migrations, and re-runs, adopt a few habits:
- Include stable IDs:
ObjectGUIDand/orSIDin every report. - Stamp the report: add a timestamp column or record it in the filename.
- Log the source: record domain, DC used, and whether the export was recursive.
- Separate “structure” from “effective access”: one report for nesting, one for effective members.
- Keep a diff-friendly format: consistent column order, one row per member, no human formatting in CSVs.
Example: add report metadata columns
$group = "Finance-Share-Read"
$ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$domain = (Get-ADDomain).DNSRoot
Get-ADGroupMember -Identity $group -Recursive |
Select-Object `
@{n="ReportTime"; e={$ts}}, `
@{n="Domain"; e={$domain}}, `
@{n="Group"; e={$group}}, `
Name, SamAccountName, ObjectClass, DistinguishedName, ObjectGUID, SID |
Export-Csv -Path ".\Finance-Share-Read_members_effective_$((Get-Date).ToString('yyyyMMdd_HHmm')).csv" -NoTypeInformation -Encoding UTF8
Troubleshooting checklist
-
“Cannot find an object with identity”: verify the group name, try DN, or use
Get-ADGroup -Filter 'Name -eq "..."'to confirm the exact identity. -
Missing attributes like mail/department: you must request them via
-PropertiesonGet-ADUser. -
Duplicates: recursive nesting can surface duplicates in certain edge paths; use
Sort-Object ObjectGUID -Uniquewhen necessary. -
Script fails on a member: don’t assume every member is a user; branch by
ObjectClass. -
CSV looks garbled: set
-Encoding UTF8and open with a tool that respects UTF-8 (or import properly in Excel).
Putting it together: a practical export “recipe”
For most organizations, a solid default is to generate three artifacts per high-impact group:
- Direct members (raw, includes nested groups as objects)
- Nested groups (direct group-to-group structure)
- Effective users (expanded membership, enriched user attributes)
That combination lets you answer both “who effectively has access?” and “why do they have access?” without re-running queries under pressure.


