Automate the cleanup of dormant Active Directory user accounts with intelligent DC health monitoring, detailed auditing, beautiful interactive HTML reports, and email notifications. Primarily written to help fellow SysAdmins out who work in air-gapped environments that must adhere to RMF requirements under either STIG or JSIG compliance regimes.
This repository contains a complete, production-ready PowerShell solution:
AD-UserMaintenance-v2.0.ps1– The core maintenance scriptAD-UserMaintenance-Installer-v1.0.ps1– One-click installer that deploys the script as a daily Scheduled Task (supports standard accounts and gMSAs)
- Identifies inactive, expired password, and never-set-initial-password accounts
- Disables accounts, removes non-protected group memberships, adds audit comments, moves to a Disabled Users OU
- Automatic exclusion of service accounts (configurable prefixes)
- Intelligent Domain Controller health check (critical for PowerShell 7+)
- Parallel processing in PowerShell 7+ for large environments
- Interactive, collapsible HTML reports with visual dashboard
- Dual logging (human-readable + JSONL for SIEM)
- Full WhatIf / dry-run support
- Email reports with embedded HTML and full report attachment
- Centralized XML configuration (perfect for scheduled tasks)
- PowerShell 5.1+ (PowerShell 7+ strongly recommended)
- ActiveDirectory PowerShell module (RSAT)
- Domain Admin or equivalent permissions
- Target "Disabled Users" OU must already exist
The installer does everything: creates folders, copies the script, generates an XML config, and registers a Scheduled Task.
- Place both scripts in the same folder
- Open PowerShell as Administrator
- Execute the installer
It will guide you interactively, or you can run fully non-interactively (example with gMSA):
.\AD-UserMaintenance-Installer-v1.0.ps1 `
-UseGMSA `
-GMSAAccount "CONTOSO\ADMaint-GMSA$" `
-ScriptADDomain "contoso.com" `
-ScriptTargetOU "OU=Disabled Users,DC=contoso,DC=com" `
-ScriptInactivityThreshold 90 `
-ScriptSendEmailReport $true `
-ScriptSMTPServer "smtp.contoso.com" `
-ScriptEmailFrom "ad-automation@contoso.com" `
-ScriptEmailTo @("admin@contoso.com","security@contoso.com")| Switch | Purpose |
|---|---|
-WhatIfMode |
Test installer – no files or tasks created |
-ScriptWhatIfMode |
Task runs in dry-run mode (reports only) |
-UseGMSA |
Use a Group Managed Service Account (recommended) |
-InstallPathCustom |
install location (default: C:\Scripts\ADMaintenance) |
-TaskNameCustom |
task name (default: AD-UserMaintenance-Daily) |
-ScheduleTimeDaily |
run time (default: 02:00) |
After installation you get:
- Script copied to the install path
AD-UserMaintenance-Config.xmlINSTALLATION_CONFIG.txt(full documentation)- Daily Scheduled Task
-
Copy
AD-UserMaintenance-v2.0.ps1to your desired locationExample:
C:\Scripts\ADMaintenance -
(Recommended) Create an XML configuration file:
$config = @{
ADDomain = "contoso.com"
TargetOU = "OU=Disabled Users,DC=contoso,DC=com"
InactivityThreshold = 90
NewAccountThreshold = 30
RemoveGroupMemberships = $true
AddCommentsToObject = $true
ServiceAccountPrefixes = @("svc_","app_")
SendEmailReport = $true
SMTPServer = "smtp.contoso.com"
SMTPPort = 25
EmailUseSSL = $false
EmailFrom = "ad-automation@contoso.com"
EmailTo = @("admin@contoso.com")
LogPath = "C:\Scripts\ADMaintenance\Logs"
HTMLReportPath = "C:\Scripts\ADMaintenance\Reports"
VerboseLogging = $true
SaveHTMLReport = $true
}
$config | Export-Clixml -Path "C:\Scripts\ADMaintenance\AD-UserMaintenance-Config.xml"- (If using authenticated SMTP) Create encrypted credential file as the task account:
$cred = Get-Credential
$cred | Export-Clixml -Path "C:\SecureStore\smtp-cred.xml"- Create the Scheduled Task (example using gMSA):
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\Scripts\ADMaintenance\AD-UserMaintenance-v2.0.ps1`" -ConfigFile `"C:\Scripts\ADMaintenance\AD-UserMaintenance-Config.xml`""
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 2)
Register-ScheduledTask -TaskName "AD-UserMaintenance-Daily" `
-Action $action -Trigger $trigger -Settings $settings `
-User "CONTOSO\ADMaint-GMSA$"Always test in WhatIf mode first:
# With config file
.\AD-UserMaintenance-v2.0.ps1 -ConfigFile "C:\Scripts\ADMaintenance\AD-UserMaintenance-Config.xml" -WhatIfMode
# Or with direct parameters (add others as necessary)
.\AD-UserMaintenance-v2.0.ps1 -ADDomain "contoso.com" -TargetOU "OU=Disabled Users,DC=contoso,DC=com" -WhatIfModeReview the generated HTML report – it shows exactly what would have happened.
| Type | Default Path |
|---|---|
| Logs | C:\Scripts\ADMaintenance\Logs |
| HTML Reports | C:\Scripts\ADMaintenance\Reports |
| Config File | C:\Scripts\ADMaintenance\AD-UserMaintenance-Config.xml |
# Run task manually
Start-ScheduledTask -TaskName "AD-UserMaintenance-Daily"
# View last run result
(Get-ScheduledTaskInfo -TaskName "AD-UserMaintenance-Daily").LastTaskResultAuthor: Kismet Agbasi GitHub | Email
AI Contributors: ClaudeAI, Grok
Version:
- Main Script v2.0 (2025-11-15)
- Installer v1.0 (2025-11-25)
Pull requests are welcome! For major changes, please open an issue first.
MIT License
Enjoy a cleaner, more secure Active Directory