
Find Disabled AD Users That Still Hold Group Membership
A disabled account is not a harmless account. If it still holds membership in security groups, it still carries the access rights those groups grant — file shares, application roles, delegated permissions. If that account is ever re-enabled (by mistake, or by an attacker who compromised a helpdesk process), it lights up with all its old access instantly. Periodically finding disabled users that still sit in security groups is a basic hygiene task, and it is easy to script.
This guide does the finding first as a read-only report, then shows an optional, gated cleanup step.
Before you run this
The detection script only reads from Active Directory. It enumerates disabled user accounts, resolves each group they belong to, and writes a CSV of the ones that are security groups. It changes nothing. Any account that can read the directory (essentially any authenticated domain user) can run the report part.
The optional remediation script at the end removes those accounts from those groups. That is a change to your directory. Removing membership is reversible in the sense that you can add the user back with Add-ADGroupMember, but you lose the "when added" history and any downstream automation that keyed off that membership will have already reacted. Treat it as a real change. It needs an account with rights to modify the affected groups — a delegated group manager for those specific groups, or a Domain Admin. Run it from an elevated PowerShell session.
Both scripts require the ActiveDirectory module (part of RSAT). On a domain-joined admin workstation running Windows 10/11, install RSAT's "Active Directory Domain Services and Lightweight Directory Services Tools"; on a member/domain-controller server it comes with the AD DS role or RSAT-AD-PowerShell feature.
Before you touch the remediation step: run the report first, read the CSV, and test the removal against a single throwaway test user in a single test group before you let it loose on the full list. Never pipe the whole CSV into Remove-ADGroupMember blind on a Friday afternoon.
Assumptions for this guide: Windows Server 2019/2022 or a Windows 10/11 admin workstation, Windows PowerShell 5.1, the ActiveDirectory module present, and a single-domain forest. The cmdlets are the same on PowerShell 7 with the module imported via the Windows compatibility session; the syntax below does not change.
The detection script
Save this as something like Find-DisabledUsersInSecurityGroups.ps1. Replace the CSV path with a real path you can write to.
Import-Module ActiveDirectory
# Pull every disabled user and its group memberships in one query.
# MemberOf deliberately does NOT include the primary group (usually
# Domain Users), so we won't flag every account for that.
$disabled = Get-ADUser -Filter 'Enabled -eq $false' -Properties MemberOf
# Cache group lookups so we resolve each group only once, not once per user.
$groupCache = @{}
$report = foreach ($user in $disabled) {
foreach ($groupDN in $user.MemberOf) {
if (-not $groupCache.ContainsKey($groupDN)) {
$groupCache[$groupDN] =
Get-ADGroup -Identity $groupDN -Properties GroupCategory
}
$group = $groupCache[$groupDN]
# We only care about security groups, not distribution groups.
if ($group.GroupCategory -eq 'Security') {
[pscustomobject]@{
UserName = $user.SamAccountName
UserDN = $user.DistinguishedName
GroupName = $group.Name
GroupDN = $group.DistinguishedName
}
}
}
}
# Write the report and show it on screen.
$report | Sort-Object UserName, GroupName |
Export-Csv -Path 'C:\path\to\disabled-users-security-groups.csv' -NoTypeInformation
$report | Sort-Object UserName, GroupName | Format-Table -AutoSize
A few things worth knowing about what this does and does not catch:
- The primary group is excluded on purpose.
MemberOfnever lists an account's primary group (Domain Users for a normal user). That is exactly what you want — you are not trying to flag every disabled user for belonging to Domain Users, you are looking for the extra group memberships that grant real access. - Distribution groups are filtered out by the
GroupCategory -eq 'Security'check. If you want to see distribution-group membership too, drop thatif. - If you only want to audit one OU, add
-SearchBase 'OU=Staff,DC=example,DC=com'to theGet-ADUsercall. Replace that distinguished name with yours.
In a very large directory the Get-ADUser query returns everything at once; if that is a concern, add -ResultPageSize or scope with -SearchBase. For most environments the query as written is fine.
Reading the report
Open the CSV. Each row is one disabled user in one security group. Look especially for:
- Membership in privileged groups (Domain Admins, Enterprise Admins, Server Operators, Account Operators, any custom admin role).
- Accounts disabled long ago that still sit in application or share groups.
Decide group by group whether the membership should go. Some organisations deliberately keep disabled accounts in groups for a grace period, or for licence/attribute reasons. This is a judgement call, which is why the cleanup is a separate, opt-in step rather than baked into the detection.
The optional cleanup — test with -WhatIf first
Do not modify the CSV between running the report and running this — it uses the exact DNs the report captured. Run it first with -WhatIf so you see every change it would make without making any:
Import-Module ActiveDirectory
Import-Csv -Path 'C:\path\to\disabled-users-security-groups.csv' | ForEach-Object {
# -WhatIf: shows the action, changes nothing. Remove it to act for real.
Remove-ADGroupMember -Identity $_.GroupDN -Members $_.UserDN -WhatIf
}
Read that output carefully. When you are satisfied, remove -WhatIf. Remove-ADGroupMember prompts for confirmation by default on each removal, which is a useful safety net; if you have reviewed the CSV and want it to run unattended, add -Confirm:$false — but only once you trust the list.
Note: if any row somehow references a user's primary group, Remove-ADGroupMember will refuse it with an error. That is correct behaviour; you change a primary group by reassigning it, not by removing membership, and it is outside the scope of this cleanup.
For the exact parameters and behaviour of these cmdlets, see Microsoft Learn for Remove-ADGroupMember, Get-ADUser, and Get-ADGroup.
Verify it worked
Re-run the detection script. The users you cleaned up should no longer appear in the CSV (or appear only for the groups you chose to leave them in).
To spot-check a single account directly, list its remaining group memberships:
# Replace with a SamAccountName you just cleaned up.
Get-ADPrincipalGroupMembership -Identity 'jdoe' |
Select-Object Name, GroupCategory
After cleanup, that account should show only its primary group (Domain Users) and any security groups you deliberately kept.
Undo
If you removed a membership you should not have, add it back:
# Replace with the real group and user distinguished names.
Add-ADGroupMember -Identity 'CN=App-Readers,OU=Groups,DC=example,DC=com' `
-Members 'CN=John Doe,OU=Staff,DC=example,DC=com'
This is why the report CSV matters: keep it. It is your record of exactly what each account belonged to before you changed anything, so you can restore precisely. Save a dated copy of the CSV alongside your change record before you run the cleanup, and you have a clean, honest audit trail of what you removed and from where.
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.
Be the first to rate this article.
Related guides
Audit Local Administrators Across Many Windows Machines
This is a read-only audit. It pulls a list of computers from Active Directory, connects to each one over PowerShell Remoting (WinRM), reads the membership of the local Administrators group, and writes everything to a single CSV you can open in Excel. It creates nothing and changes nothing on the target machines, so there is no destructive step and nothing to roll back — the only thing produced is the report file on your own workstation.
Clean Up Stale Computer Accounts in Active Directory with PowerShell
This guide finds Active Directory computer accounts that haven't logged in for a long time and retires them in three deliberate stages: report , disable and move to a holding OU , then delete . The purpose is to keep AD tidy and reduce the attack surface of forgotten machine accounts without accidentally killing a computer that's simply been powered off for a while.
Export AD Group Membership to CSV for an Access Review with PowerShell
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.
Find and Report AD Accounts That Have Never Logged In
This guide gives you a PowerShell script that reads Active Directory and produces a report (on screen and as a CSV) of user accounts that have never authenticated against any domain controller. It changes nothing — it does not disable, delete, or edit a single account. Its purpose is to hand you a clean list of candidates for review before you decide what to do with them.




