Every Active Directory environment needs regular reporting — for security audits, compliance reviews, user cleanup, or just answering management questions like "how many inactive accounts do we have?" This guide covers three approaches to pulling AD reports: PowerShell (the manual way), built-in Windows tools, and dedicated reporting software. Each section includes practical examples you can use immediately.
PowerShell with the Active Directory module is the most common way to pull AD reports. It's free, built into Windows Server, and extremely flexible. The downside is that every report requires writing and testing a script, and some data (like accurate last logon times) requires querying multiple domain controllers.
Before running any of the commands below, make sure the AD module is available:
Import-Module ActiveDirectory
If the module isn't installed, you'll need the Remote Server Administration Tools (RSAT) on your workstation, or run the commands directly on a domain controller.
User reports are by far the most requested AD reports. Here are the PowerShell commands for the most common ones:
Get-ADUser -Filter * -Properties DisplayName, Department, Title, `
EmailAddress, LastLogonDate, Enabled |
Select-Object Name, SamAccountName, DisplayName, Department, `
Title, EmailAddress, LastLogonDate, Enabled |
Export-Csv -Path "AllUsers.csv" -NoTypeInformation
$CutoffDate = (Get-Date).AddDays(-90)
Get-ADUser -Filter {LastLogonDate -lt $CutoffDate -and Enabled -eq $true} `
-Properties LastLogonDate, Department |
Select-Object Name, SamAccountName, LastLogonDate, Department |
Export-Csv -Path "InactiveUsers.csv" -NoTypeInformation
$Today = Get-Date
$MaxAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
Get-ADUser -Filter {Enabled -eq $true -and PasswordNeverExpires -eq $false} `
-Properties PasswordLastSet |
Where-Object {
($_.PasswordLastSet.AddDays($MaxAge) - $Today).Days -le 14 -and
($_.PasswordLastSet.AddDays($MaxAge) - $Today).Days -ge 0
} |
Select-Object Name, SamAccountName, PasswordLastSet, `
@{N='ExpiresOn';E={$_.PasswordLastSet.AddDays($MaxAge)}} |
Export-Csv -Path "PasswordExpiring.csv" -NoTypeInformation
Get-ADUser -Filter {Enabled -eq $false} -Properties WhenChanged |
Select-Object Name, SamAccountName, WhenChanged |
Export-Csv -Path "DisabledUsers.csv" -NoTypeInformation
These scripts work well for one-off reports, but they have limitations. Each new report type means writing a new script. Complex attributes like UserAccountControl require manual decoding — the raw value 0x0202 means "normal user account, disabled," but PowerShell won't tell you that. And getting clean, formatted Excel output (not just CSV) requires additional libraries or manual conversion.
Computer account reports are essential for tracking your hardware inventory, identifying machines running outdated operating systems, and cleaning up stale computer accounts.
Get-ADComputer -Filter * -Properties OperatingSystem, `
OperatingSystemVersion, LastLogonDate, IPv4Address |
Select-Object Name, OperatingSystem, OperatingSystemVersion, `
LastLogonDate, IPv4Address |
Export-Csv -Path "AllComputers.csv" -NoTypeInformation
Get-ADComputer -Filter {OperatingSystem -notlike "*Windows 11*" `
-and OperatingSystem -notlike "*Windows 10*" `
-and OperatingSystem -notlike "*Server 2022*" `
-and OperatingSystem -notlike "*Server 2025*"} `
-Properties OperatingSystem, LastLogonDate |
Select-Object Name, OperatingSystem, LastLogonDate |
Sort-Object OperatingSystem |
Export-Csv -Path "OutdatedOS.csv" -NoTypeInformation
$CutoffDate = (Get-Date).AddDays(-90)
Get-ADComputer -Filter {LastLogonDate -lt $CutoffDate} `
-Properties LastLogonDate, OperatingSystem |
Select-Object Name, OperatingSystem, LastLogonDate |
Export-Csv -Path "InactiveComputers.csv" -NoTypeInformation
These commands use LastLogonDate (the replicated lastLogonTimestamp attribute), which can be up to 14 days behind the actual last logon. For most cleanup purposes this is acceptable, but if you need precise data, see the last logon report section below.
Generating an accurate "last logon" report is one of the most common — and most misunderstood — AD reporting tasks. The problem comes down to two different AD attributes:
lastLogonTimestamp (PowerShell: LastLogonDate) — Replicated across domain controllers, but only updated at intervals of 9–14 days. Easy to query but up to two weeks behind.lastLogon — Updated on every logon, but not replicated between DCs. Each domain controller only records logons it handled directly. To get the true last logon time, you must query every DC and take the most recent value.For an approximate report (good enough for identifying accounts inactive for 90+ days), LastLogonDate works fine — see the inactive user scripts above. But for a precise last logon report, here's the PowerShell approach:
# Query all DCs for precise lastLogon values
$DCs = Get-ADDomainController -Filter *
$Users = @{}
foreach ($DC in $DCs) {
Write-Host "Querying $($DC.HostName)..."
Get-ADUser -Filter * -Properties lastLogon -Server $DC.HostName |
ForEach-Object {
$Logon = [DateTime]::FromFileTime($_.lastLogon)
if (-not $Users.ContainsKey($_.SamAccountName) -or
$Users[$_.SamAccountName].LastLogon -lt $Logon) {
$Users[$_.SamAccountName] = [PSCustomObject]@{
Name = $_.Name
SamAccountName = $_.SamAccountName
LastLogon = $Logon
LastLogonDC = $DC.HostName
}
}
}
}
$Users.Values | Sort-Object LastLogon |
Export-Csv -Path "AccurateLastLogon.csv" -NoTypeInformation
This script works, but it has significant drawbacks in practice:
AD FastReporter solves this automatically. When you include the "Last Logon Time" field in any user or computer report, it queries every accessible domain controller in your environment and returns the most recent logon time per account. If a DC is unreachable, it completes the report and warns you which DCs were missed — rather than failing silently.
Download free version — works on any domain-joined workstation, no server installation needed.
Group reports answer questions like "who is in this group?", "which groups have no members?", and "what groups does this user belong to (including nested memberships)?" If you also need to delegate day-to-day group membership changes to department managers, see AD Group Manager Web — a self-service portal that works alongside reporting tools.
Get-ADGroup -Filter * -Properties Members, ManagedBy, GroupScope, `
GroupCategory, Description |
Select-Object Name, GroupScope, GroupCategory, Description, `
@{N='MemberCount';E={$_.Members.Count}},
@{N='ManagedBy';E={
if ($_.ManagedBy) {(Get-ADUser $_.ManagedBy).Name} else {""}
}} |
Sort-Object MemberCount -Descending |
Export-Csv -Path "AllGroups.csv" -NoTypeInformation
Get-ADGroup -Filter * -Properties Members |
Where-Object { $_.Members.Count -eq 0 } |
Select-Object Name, GroupScope, GroupCategory |
Export-Csv -Path "EmptyGroups.csv" -NoTypeInformation
# Direct memberships only
Get-ADPrincipalGroupMembership -Identity "jsmith" |
Select-Object Name, GroupScope, GroupCategory
# Including nested (recursive) memberships
$User = Get-ADUser "jsmith" -Properties MemberOf
$AllGroups = @()
$Queue = [System.Collections.Queue]::new()
$User.MemberOf | ForEach-Object { $Queue.Enqueue($_) }
$Visited = @{}
while ($Queue.Count -gt 0) {
$GroupDN = $Queue.Dequeue()
if ($Visited.ContainsKey($GroupDN)) { continue }
$Visited[$GroupDN] = $true
$Group = Get-ADGroup $GroupDN -Properties MemberOf
$AllGroups += $Group
$Group.MemberOf | ForEach-Object { $Queue.Enqueue($_) }
}
$AllGroups | Select-Object Name, GroupScope, GroupCategory
Nested group membership resolution is where PowerShell gets complicated quickly. The recursive script above works but is slow for users in many groups, and doesn't easily scale to reporting on all users at once.
AD FastReporter handles this natively — it resolves inherited (nested) group memberships for any user, with separate fields for direct membership, inherited membership, membership by group type (security vs distribution), and membership by scope (domain local, global, universal). It also calculates both direct and total member counts for group reports.
The most common request from management: "I need this in Excel." Here are your options:
All the PowerShell examples above use Export-Csv, which produces a CSV file. You can open this directly in Excel, but it has limitations: no formatting, no auto-sized columns, date values may display incorrectly, and Unicode characters can cause encoding issues.
The ImportExcel PowerShell module can export directly to XLSX format:
# Install once
Install-Module ImportExcel -Force
# Export users directly to formatted Excel
Get-ADUser -Filter * -Properties DisplayName, Department, LastLogonDate |
Select-Object Name, SamAccountName, Department, LastLogonDate |
Export-Excel -Path "ADUsers.xlsx" -AutoSize -FreezeTopRow -BoldTopRow
This is the best PowerShell-based option for Excel output, but requires installing a third-party module, which may not be allowed in locked-down environments.
AD FastReporter Pro exports directly to 8 formats: Excel (XLSX), CSV, CSV (append), HTML, PDF, XML, ODS, and JSON. The Excel export includes formatted columns, auto-sizing, and proper date handling. The CSV (append) mode is useful for building cumulative data files — each scheduled run adds rows to the same file instead of overwriting.
Download sample exports to see the output quality:
If you need reports regularly, or if you're not comfortable writing PowerShell scripts, a dedicated tool saves significant time. Here's what to look for and how AD FastReporter compares.
lastLogon attribute, not just lastLogonTimestampAD FastReporter is a Windows desktop application that reads Active Directory data using standard LDAP queries. It includes over 250 built-in report forms organized across 8 categories:
| Category | What it reports on | Example reports |
|---|---|---|
| Users | User accounts — status, logon history, passwords, organization | All users, inactive users (30/60/90 days), password expiring, disabled, locked out, never logged on |
| Computers | Computer accounts — OS details, logon status, IP addresses | All computers, by OS version, inactive computers, servers vs workstations |
| Groups | Security and distribution groups — members, counts, scope | All groups, empty groups, groups by scope, security groups with member counts |
| Exchange | Mail-enabled objects — mailbox settings, protocols | Exchange users, mail-enabled contacts, protocol status (OWA, POP3, IMAP4, MAPI) |
| Contacts | AD contact objects — names, email, organization | All contacts, contacts by company |
| Printers | Published printers — names, locations, servers | All published printers |
| GPOs | Group Policy Objects — links, status | All GPOs with links to OUs/sites/domains, GPO status |
| OUs | Organizational Units — structure, child object counts | All OUs with user/computer/group counts |
How to generate a report:
System requirements: Windows 10 or later (including Windows Server 2016+), .NET 10 Desktop Runtime, domain-joined workstation with read access to AD. Standard domain user permissions are sufficient for most reports. Install on your workstation — no server installation, no agents, no schema changes.
Download AD FastReporter Free — all 250+ reports, no time limit.
One-off reports are useful, but regular automated reporting catches problems before they become incidents. Here are recommended reporting schedules:
| Frequency | Report | Why |
|---|---|---|
| Daily | Locked-out accounts | Identify potential brute-force attacks or user issues |
| Weekly | New users created in the last 7 days | Verify all new accounts are authorized |
| Weekly | Users with passwords expiring in 14 days | Proactive notification before passwords expire |
| Monthly | Inactive users (90+ days no logon) | Clean up abandoned accounts |
| Monthly | Inactive computers (90+ days) | Identify hardware for decommissioning |
| Monthly | Empty groups | Remove unnecessary group objects |
| Quarterly | Domain admin group membership | Audit privileged access |
Save your PowerShell scripts as .ps1 files and schedule them with Windows Task Scheduler:
# Create a scheduled task to run a report script weekly
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-ExecutionPolicy Bypass -File C:\Scripts\WeeklyADReport.ps1"
$Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 7am
Register-ScheduledTask -TaskName "Weekly AD Report" `
-Action $Action -Trigger $Trigger -RunLevel Highest
AD FastReporter Pro includes built-in scheduling that integrates with Windows Task Scheduler. For each scheduled task, you configure:
Tasks run automatically using the command-line tool ADFastReporterCmd.exe — no GUI needed. The tool logs results and can notify you of errors.
| Feature | Free | Pro |
|---|---|---|
| 250+ built-in reports across 8 categories | ✔ | ✔ |
| Multiple domain/forest connections | ✔ | ✔ |
| Accurate lastLogon from all DCs | ✔ | ✔ |
| Export (XLSX, CSV, PDF, HTML, XML, ODS, JSON) | ✘ | ✔ |
| Custom report forms with filters | ✘ | ✔ |
| Scheduled automated reporting + email | ✘ | ✔ |
| Charts and visualizations | ✘ | ✔ |
| Custom LDAP fields | ✘ | ✔ |
| NIS2 compliance dashboard | ✘ | ✔ |
| Report history and storage | ✘ | ✔ |
Pulling Active Directory reports doesn't have to mean hours of PowerShell scripting. For simple, one-off queries, the PowerShell commands in this guide will get you what you need. For regular reporting — especially across multiple report types with Excel export and scheduling — a dedicated tool like AD FastReporter handles the complexity for you, including tricky tasks like querying all domain controllers for accurate last logon data and resolving nested group memberships.
Whatever approach you choose, the important thing is to report regularly. A monthly review of inactive accounts, password status, and privileged group membership catches security issues before they become incidents.