Published on

How to Export a Groups Report in Active Directory (PowerShell Script)

Before a permissions cleanup project, you need to know what you're dealing with at the group level, not the member level — how many groups exist, which ones are empty, who's responsible for each one. That's a different report from a membership dump: one row per group, not one row per member.

The quick answer

Get-ADGroup -Filter * -Properties whenCreated, GroupCategory, GroupScope, ManagedBy |
    Select-Object Name, GroupCategory, GroupScope, ManagedBy, whenCreated |
    Export-Csv -Path .\groups.csv -NoTypeInformation

A more useful reporting script

#requires -Modules ActiveDirectory
<#
.SYNOPSIS
    Exports an inventory of every group in Active Directory.
#>

[CmdletBinding()]
param(
    [string]$SearchBase,
    [string]$OutputCsv
)

Import-Module ActiveDirectory -ErrorAction Stop

$params = @{
    Filter     = '*'
    Properties = @('GroupCategory', 'GroupScope', 'ManagedBy', 'member', 'whenCreated', 'Description')
}
if ($SearchBase) { $params['SearchBase'] = $SearchBase }

$groups = Get-ADGroup @params

$results = foreach ($group in $groups) {
    $managerName = if ($group.ManagedBy) {
        (Get-ADObject -Identity $group.ManagedBy -ErrorAction SilentlyContinue).Name
    } else { $null }

    [PSCustomObject]@{
        Name              = $group.Name
        GroupCategory     = $group.GroupCategory
        GroupScope        = $group.GroupScope
        MemberCount       = @($group.member).Count
        ManagedBy         = $managerName
        Description       = $group.Description
        WhenCreated       = $group.whenCreated
        DistinguishedName = $group.DistinguishedName
    }
}

if (-not $results) {
    Write-Host "No groups found matching the given criteria." -ForegroundColor Yellow
    return
}

$sorted = $results | Sort-Object Name

Write-Host "Found $(@($sorted).Count) group(s)." -ForegroundColor Cyan
$sorted | Format-Table Name, GroupCategory, GroupScope, MemberCount, ManagedBy -AutoSize

$emptyCount = @($sorted | Where-Object { $_.MemberCount -eq 0 }).Count
if ($emptyCount -gt 0) {
    Write-Host "$emptyCount group(s) have no members." -ForegroundColor Yellow
}

if ($OutputCsv) {
    $sorted | Export-Csv -Path $OutputCsv -NoTypeInformation
    Write-Host "Exported results to $OutputCsv" -ForegroundColor Green
}

The ManagedBy column resolves straight to a readable name instead of a raw distinguished name, which matters here because "who owns this group" is usually the very first question in a cleanup conversation — you want to know who to ask before you touch anything.

This vs. a membership report

This is a group-level inventory — one row per group. If you need the actual member list for every group instead, Get-ADAllGroupsMembershipReport.ps1 in the same repo produces one row per group/member pair, which is the right shape for a full access-control review rather than a groups-only cleanup pass.

Where the manual approach runs out of road

A one-off script is fine for a single check. It starts to hurt once you actually need to run this regularly:

  • No scheduling. Cron/Task Scheduler can run the script, but now you own the scheduling, the credentials it runs as, and what happens when it silently fails.
  • No history. A CSV export is a snapshot. Was this the same five accounts as last month, or a growing list? A single export can't tell you.
  • No distribution. Getting the report to the right people (security, IT ops, compliance) on a schedule means building that plumbing yourself.
  • Multi-domain/multi-forest pain. Run it once per domain, reconcile the results yourself, and hope naming/OU conventions are consistent across all of them.
  • No alerting. If something changes unexpectedly between runs — an account re-enabled, a privileged group gaining a member — nothing tells you until you happen to run the script again.

None of that is a PowerShell problem. It's what turns a script into a product.

What SysFlint AD does instead

Our SysFlint AD runs the same kind of discovery shown above automatically, on a schedule, entirely inside your own network. No agents on domain controllers, no data leaving your environment. It's free, forever.

  • Privileged group auditing Who is in Domain Admins, Enterprise Admins, and Schema Admins — including nested membership and accounts that should not be there.
  • Scheduled reports by email Daily, weekly, or monthly runs delivered to the right inbox automatically. No Task Scheduler job for you to babysit.
  • Multi-domain and forest coverage Enumerate every domain in the forest in one pass and get one consolidated report instead of one per domain.

If you're currently doing this with a script on a scheduled task, here's the honest comparison between the two, or go straight to the download page.

Get this script, and the rest of them

The script above is part of awesome-it-scripts, SysFlint's free, open-source library of PowerShell scripts for Active Directory and other IT-admin tasks — no signup, no catch, MIT licensed. Grab this one directly from active-directory/Export-ADGroupsInventory.ps1, or browse the whole thing.

Find every disabled/locked-out/stale/privileged-account script we've published (plus the FAQ that goes with each one) in the repo's active-directory folder. Star it if it's useful — new scripts land there regularly.