Active Directory Policies

How to export group membership lists with PowerShell

Exporting group membership lists with PowerShell

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 member attribute.
  • 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 ObjectGUID and/or SID so 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-ADUser per 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 -Server for 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: ObjectGUID and/or SID in 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 -Properties on Get-ADUser.
  • Duplicates: recursive nesting can surface duplicates in certain edge paths; use Sort-Object ObjectGUID -Unique when necessary.
  • Script fails on a member: don’t assume every member is a user; branch by ObjectClass.
  • CSV looks garbled: set -Encoding UTF8 and 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:

  1. Direct members (raw, includes nested groups as objects)
  2. Nested groups (direct group-to-group structure)
  3. 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.

Related posts
Active Directory Policies

Use Protected Groups for critical OU containment

Active Directory Policies

Build departmental OU structures for decentralization

Active Directory Policies

Best practices for naming conventions in group management

Active Directory Policies

Managing dynamic distribution groups in AD

×

There are over 8,500 people who are getting towards perfection in Active Directory, IT Management & Cyber security through our insights from Identitude.

Wanna be a part of our bimonthly curation of IAM knowledge?

  • -Select-
  • By clicking 'Become an insider', you agree to processing of personal data according to the Privacy Policy.