Active Directory Policies

Group cleanup scripts with usage analysis

Group cleanup scripts with usage analysis

Active Directory group sprawl is not just “messy directory hygiene”—it directly affects access risk, troubleshooting time, audit outcomes, and even authentication performance at scale. The hard part isn’t deleting groups; it’s proving that a group is no longer needed, and doing it without breaking access.

A “group cleanup script” that only finds empty groups or groups older than X days is easy to write—and easy to regret. The safer and more defensible approach is usage analysis: combine multiple signals that approximate whether a group is still referenced for access, then remediate in stages (report → quarantine → disable-like patterns → delete).

What “usage” means for AD groups

Groups in AD are used in several different ways, and not all of them leave clean, centralized evidence. When we say “usage analysis,” we’re really building a case from partial signals:

  • Membership activity: has the membership changed recently? (Direct changes are visible; nested impacts are indirect.)
  • Access enforcement: are resources still secured by this group? (NTFS ACLs, share permissions, GPO filtering, app roles, etc.)
  • Identity workflows: is the group managed by a process? (AGDLP patterns, naming standards, owners, tickets.)
  • Directory references: is this group nested elsewhere, or used by policy objects? (memberOf, gpLink, delegation, etc.)
  • Authentication artifacts: does it show up in user tokens? (Hard to prove at scale; you can sample or target.)

The key mindset shift: you rarely get a single “yes/no” proof. Instead, you compute a confidence score and apply staged controls to reduce blast radius.

High-signal use cases for cleanup scripts

Cleanup scripts are most valuable when they focus on categories with clear remediation paths:

1) Orphaned access groups

Groups created for a project, a share, or an application that no longer exists. The best “usage” indicator here is whether the group is still referenced in ACLs or app configs. If you can’t check the target systems, your script should at least flag the group for manual verification instead of auto-deleting.

2) Empty or near-empty groups that are not nested

“Empty” alone is not enough—empty groups can still be applied to ACLs or used as future targets. But empty and not nested, with no linked purpose/owner metadata, and no modification activity for long periods, is a strong candidate for quarantine.

3) Duplicate or shadow groups

Naming drift leads to duplicates (e.g., APP-Finance-Read and APP-Finance_Read). Usage analysis helps you identify which one is referenced by policies/resources and which one is just carrying members.

4) Stale groups in hybrid environments

Sync complicates cleanup. On-prem groups may be referenced by cloud services, or vice versa. Your script should explicitly detect scope/sync markers (e.g., OU placement, extension attributes, group type, mail-enabled flags) and treat “hybrid-connected” groups as higher-risk candidates requiring extra confirmation steps.

Define the inventory first: what you must capture

Every cleanup pipeline starts with a complete inventory, because you can’t reliably score what you didn’t measure. A robust inventory record for each group typically includes:

  • Identity: distinguishedName, objectGUID, sAMAccountName, SID
  • Type/scope: security vs distribution, domain local / global / universal
  • Lifecycle signals: whenCreated, whenChanged, managedBy, description, info, mail, custom tags
  • Membership size: direct members count, and optionally expanded/nested count
  • Nesting: memberOf count, and “is nested elsewhere?” flag
  • Change evidence: last membership change time (best-effort from logs or replication metadata)
  • Ownership signals: managedBy present? owner resolvable? ticket/reference in description?

Treat whenChanged carefully: it changes for many reasons and is not “membership last changed” by default. If you want membership-specific evidence, you need additional techniques.

Where to get membership-change evidence (and what lies to you)

Option A: Security event logs (best when you have it)

If you have AD auditing enabled for group management, Domain Controllers log membership changes. Your script can query events within a time window and map them back to groups. This is high-signal, but depends on:

  • Audit policy configuration for group management
  • Log retention (often too short unless forwarded to a SIEM)
  • Multiple DCs (membership changes may be on different DCs)

Practical approach: if you have centralized logging (WEF/SIEM), use it. Otherwise, treat this as “nice-to-have” evidence.

Option B: Replication metadata (useful, but nuanced)

AD stores per-attribute replication metadata. In theory, you can infer whether member changed recently. In practice, it’s easy to misinterpret, especially with linked attributes and replication behaviors. Use it as a supporting signal, not as your only proof.

Option C: Directory “last touched” heuristics

Heuristics such as whenChanged, description updates, or adminCount changes are weak signals. They can still help you rank groups (e.g., “hasn’t changed in years”), but they cannot confirm “unused.”

Option D: Token sampling (surgical, not universal)

If you’re investigating a narrow set of groups, you can sample whether users still present the group in their access tokens (Kerberos PAC / tokenGroups). At enterprise scale this becomes expensive and incomplete, but it’s powerful for targeted validation.

Usage analysis beyond AD: the resource-reference problem

The most decisive signal is whether a group is referenced by something that enforces access. The catch: those references live outside AD. You have three realistic strategies:

  1. Integrate with known platforms: scan specific systems you control (file servers, SharePoint, SQL, IIS apps, RDS collections, etc.) and build a “group → resource reference” map.
  2. Sample and prioritize: for high-risk OU prefixes or naming patterns, do deeper scans; for low-risk, rely on membership + nesting + ownership signals.
  3. Shift left with standards: enforce group naming + ownership + purpose tags so future cleanup is evidence-driven.

A pragmatic cleanup script doesn’t pretend it can see everything. It surfaces confidence, gaps, and recommended next steps.

A scoring model that doesn’t require perfect telemetry

Instead of “delete if empty,” build a scoring model and classify groups into action buckets. Example signals (customize to your org):

  • Low-risk indicators (push toward cleanup): empty, not nested, no managedBy, no description/purpose tag, old creation date
  • Medium-risk indicators (hold/quarantine): small membership, nested in one place, weak ownership metadata
  • High-risk indicators (protect): nested widely, universal scope in complex environments, tied to GPO/app patterns, privileged naming, adminCount
  • Recent activity indicators (protect): membership changes in last N days (from logs/SIEM), recent ticket tags

Output a numeric score plus a human-readable reason list. Auditors and reviewers trust “why” more than a single number.

Staged remediation: the safest deletion is the one you didn’t do yet

The most mature cleanup scripts implement stages and time buffers:

  1. Report: inventory + score + recommended action, no changes.
  2. Notify owners: if managedBy/owner exists, send a review request and set a review window.
  3. Quarantine: move group to a “Quarantine OU” and add an obvious prefix/suffix.
  4. Access neutralization (optional): remove it from being nested/used as a parent (careful), or strip members only if policy allows.
  5. Delete: only after a quiet period with no exceptions, and ideally after verifying resource references.

Quarantine is your rollback strategy. If you delete immediately, your rollback becomes restore + SID history implications + incident tickets.

PowerShell approach: patterns you can adapt

Below are patterns—not a copy/paste “magic script.” In group cleanup, correctness and environment fit matter more than cleverness. The goal is to show how to structure: collect → enrich → score → export → (optionally) remediate.

Inventory collection essentials

# Example pattern (adapt to your OU/base DN and filters)
# Get-ADGroup -Filter * -SearchBase "OU=Groups,DC=contoso,DC=com" -Properties *
# Select key properties into a clean object for scoring and reporting

Nesting and membership counts

# Direct member count: (Get-ADGroupMember -Identity $g -Recursive:$false).Count
# Nested/expanded count (expensive at scale): (Get-ADGroupMember -Identity $g -Recursive).Count
# memberOf nesting: $g.MemberOf.Count

Guardrails filters (do this early)

# Exclude privileged/system groups by DN path, name patterns, adminCount, or well-known SIDs.
# Keep an allowlist/denylist that is version-controlled.

When you turn this into production code, add: paging, retry logic, DC targeting strategy, timeouts, and parallelism boundaries. “Works in my console” is not the same as “safe in prod.”

Pitfalls that break access (and reputations)

1) Treating emptiness as unused

Empty groups can still be applied to ACLs, GPO security filtering, or app role mappings. If your organization uses “pre-created” groups for onboarding or automation, emptiness is expected.

2) Ignoring nesting

A group with no direct members can still be “full” via nested groups—or it can be used as a nested component in AGDLP designs. If you only check direct membership, you will misclassify groups.

3) Confusing whenChanged with membership changes

whenChanged is broad and noisy. A description edit looks the same as a critical membership change if you treat it as a “last used” indicator.

4) Not accounting for hybrid and cloud dependencies

Groups may be referenced by Entra ID apps, SaaS provisioning rules, or legacy sync logic. If you can’t trace those dependencies, your script should label the group as “unknown external references possible” and require manual review.

5) Deleting instead of quarantining

Deletion is irreversible in practice once incident response costs are considered. Quarantine preserves identity, simplifies rollback, and builds confidence in the process.

6) No change management trail

A cleanup script should produce an audit trail: input snapshot, scoring output, approval references, and the exact changes made. If you can’t reconstruct what happened, you can’t defend it.

Operationalizing: how to run this monthly without chaos

  • Start with reporting-only for 1–2 cycles: tune scoring, filters, and exceptions.
  • Define ownership rules: if no managedBy, route to a default group stewardship queue.
  • Use an exceptions registry: a simple CSV/JSON allowlist (with expiry dates) prevents recurring false positives.
  • Build standard tags: require description formats like “Owner=…, System=…, Ticket=…, Purpose=…”.
  • Separate duties: script generates recommendations; humans approve; automation executes with logs.

The most important metric is not “how many groups deleted,” but “how many cleanup candidates were resolved without incidents.”

Quick reference: a defensible cleanup checklist

  • Inventory captured with immutable identifiers (GUID/SID) and exported
  • Privileged/system groups excluded by policy and validated
  • Nesting evaluated (both “contains” and “is contained by”)
  • Usage signals collected (logs/metadata/ownership/resource scans where possible)
  • Scoring produces reasons, not just a number
  • Staged workflow: report → quarantine → delete after quiet period
  • Rollback plan tested (restore/quarantine reversal)
  • Change log retained for audit

Where to take this next

Once you have a reliable reporting pipeline, you can extend usage analysis in high-value ways:

  • Resource reference scanning: build or adopt ACL scanners for file servers and key platforms.
  • Integration with ITSM: require a ticket for quarantine/delete and auto-log outcomes.
  • Group lifecycle automation: expiration dates, attestation campaigns, and “owner must re-certify” workflows.
  • Privileged group protections: separate logic paths for Tier 0/Tier 1 groups with stricter approvals.

Done right, group cleanup becomes an access-governance practice—not a risky script someone runs during a quiet Friday.

Practical takeaway: Don’t aim for perfect “unused” detection. Aim for evidence-weighted classification, staged remediation, and rollback-friendly operations.

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.