
Automate User Offboarding in Active Directory with PowerShell
Before you run this
This script offboards one leaving user in a single pass: it disables their AD account, records and removes their group memberships (except the primary group), and moves the account into a disabled-users OU. A separate, clearly marked step sets mail forwarding on their mailbox. The point is a consistent, logged procedure so nothing gets missed and you can reconstruct exactly what changed.
Privileges: run this in an elevated PowerShell session (Run as Administrator) under an account with rights to modify the target user — typically a delegated account-operator or domain-admin. You need the ActiveDirectory module (part of RSAT) on the machine you run it from. The mail-forwarding step is Exchange, not AD: run it in the Exchange Management Shell on an Exchange server, under an account with the Recipient Management role. The two halves use different cmdlet sets — don't expect Set-Mailbox to exist in a plain AD session.
Test first. Read the whole script before you run it. It ships in dry-run mode — without the -Execute switch it only reports what it would do and writes a log. Run it that way first, against a disposable test user in a lab OU, and confirm the log looks right before you ever add -Execute on a real leaver.
What is reversible and what is not. Disabling, moving, and forwarding are all reversible. Group removal is the one that bites you — once you strip a user from thirty groups, ADUC won't tell you which ones they were. That is why the script writes the removed group list to a file before removing anything. Keep that file; it is your undo. Removing a user from a group is not "deleted data", but reconstructing membership from memory is effectively impossible, so treat it with care.
Assumed environment: Windows Server 2019/2022, a single AD domain (example.com), on-premises Exchange Server 2016/2019 for the mail step, PowerShell 5.1, RSAT installed. For Exchange Online the forwarding cmdlet is the same name but you connect with the Exchange Online module instead — I note that where it matters.
The offboarding script
Save this as Disable-Leaver.ps1. Replace the obvious placeholders — the OU distinguished names and the log paths — with your own values.
#Requires -Modules ActiveDirectory
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$SamAccountName, # the leaver's login name
[Parameter(Mandatory)][string]$TargetOU, # "OU=Disabled Users,DC=example,DC=com"
[switch]$Execute # omit for a dry run
)
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$logPath = "C:\path\to\offboard-$SamAccountName-$stamp.log"
$grpFile = "C:\path\to\groups-$SamAccountName-$stamp.txt"
function Write-Log ($Message) {
"{0} {1}" -f (Get-Date -Format 'u'), $Message |
Tee-Object -FilePath $logPath -Append
}
# Fail early if the account doesn't exist — don't guess.
$user = Get-ADUser -Identity $SamAccountName -Properties DistinguishedName
Write-Log "Target: $($user.DistinguishedName) (Execute = $Execute)"
# --- 1. Record group memberships, then remove them -----------------
# Domain Users is the primary group and cannot be removed this way,
# so it is filtered out. If you changed a user's primary group, adjust this.
$groups = Get-ADPrincipalGroupMembership -Identity $user |
Where-Object { $_.Name -ne 'Domain Users' }
$groups | Select-Object -ExpandProperty DistinguishedName |
Set-Content -Path $grpFile # this file is your undo — keep it
Write-Log "Recorded $($groups.Count) groups to $grpFile"
foreach ($g in $groups) {
if ($Execute) {
Remove-ADGroupMember -Identity $g -Members $user -Confirm:$false
Write-Log "Removed from $($g.Name)"
} else {
Write-Log "[DRYRUN] Would remove from $($g.Name)"
}
}
# --- 2. Disable the account ----------------------------------------
if ($Execute) {
Disable-ADAccount -Identity $user
Write-Log "Account disabled"
} else {
Write-Log "[DRYRUN] Would disable account"
}
# --- 3. Move to the disabled-users OU ------------------------------
if ($Execute) {
Move-ADObject -Identity $user.DistinguishedName -TargetPath $TargetOU
Write-Log "Moved to $TargetOU"
} else {
Write-Log "[DRYRUN] Would move to $TargetOU"
}
Write-Log "Done."
Run the dry run first:
.\Disable-Leaver.ps1 -SamAccountName jsmith `
-TargetOU "OU=Disabled Users,DC=example,DC=com"
Read the log. When it matches what you expect, run it for real by adding -Execute:
.\Disable-Leaver.ps1 -SamAccountName jsmith `
-TargetOU "OU=Disabled Users,DC=example,DC=com" -Execute
A note on ordering: I remove groups and record them before the move so that if the OU move fails for any reason, the group file is already written. If your disabled-users OU blocks inheritance or has a restrictive GPO, moving the account there also cuts off logon-affecting policy — which is usually the intent, but check it in your lab OU first.
The mail-forwarding step (Exchange)
Forwarding lives on the mailbox, not the AD object, so this runs separately in the Exchange Management Shell — not in the AD script above. For on-premises Exchange:
# Forward the leaver's mail to their manager, do NOT keep a copy in the disabled mailbox.
Set-Mailbox -Identity jsmith `
-ForwardingSMTPAddress "manager@example.com" `
-DeliverToMailboxAndForward $false
-ForwardingSMTPAddress takes an external-style SMTP string and does not require a contact object; -DeliverToMailboxAndForward $false means forward-only. If you'd rather keep a copy in the disabled mailbox as well, set that switch to $true. On Exchange Online, the same Set-Mailbox cmdlet applies once you've connected with the Exchange Online PowerShell module — the parameter names are identical, but confirm against Microsoft Learn for Set-Mailbox, as tenant policy can restrict automatic external forwarding.
If you are AD-only with no Exchange, there is no supported "forward mail" attribute to set from the AD module — mail routing is not an AD concern. Point the redirect at your actual mail system instead.
Verify it worked
Check the AD side directly:
# Enabled should be False; DistinguishedName should show the new OU.
Get-ADUser -Identity jsmith -Properties Enabled, DistinguishedName |
Select-Object Name, Enabled, DistinguishedName
# Should now show only the primary group.
Get-ADPrincipalGroupMembership -Identity jsmith | Select-Object Name
Check forwarding in the Exchange Management Shell:
Get-Mailbox -Identity jsmith |
Format-List ForwardingSMTPAddress, DeliverToMailboxAndForward
Group membership changes replicate between domain controllers, so if you query a different DC than the one you wrote to, allow for replication before you panic.
Undo
Everything here can be reversed. Re-enable and move the account back:
Enable-ADAccount -Identity jsmith
Move-ADObject -Identity (Get-ADUser jsmith).DistinguishedName `
-TargetPath "OU=Staff,DC=example,DC=com" # the original OU
Restore group membership from the file the script saved — this is why you keep it:
Get-Content "C:\path\to\groups-jsmith-<stamp>.txt" | ForEach-Object {
Add-ADGroupMember -Identity $_ -Members jsmith
}
Remove forwarding by clearing the attribute:
Set-Mailbox -Identity jsmith -ForwardingSMTPAddress $null
Keep the per-user log and group files somewhere durable — a share the helpdesk can reach — for as long as your retention policy for former staff requires. When a leaver's account is finally deleted for good, that log is the only record of what they had access to.
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
Automate New-User Onboarding in Active Directory
This guide builds a PowerShell script that onboards one new employee in four steps: it creates an Active Directory user account , adds them to security groups , provisions an on-premises Exchange mailbox , and creates their home folder on a file server and sets NTFS permissions . The purpose is to replace the error-prone click-through in Active Directory Users and Computers with one repeatable, reviewable run.
Automate Moving AD Users Between OUs From a CSV
This procedure reads a CSV of user accounts and their destination OUs, then moves each account to its target OU with PowerShell's Move-ADObject . Moving a user changes its distinguished name (DN). That matters because anything scoped by DN or OU — Group Policy links, delegated permissions, and OU-based filters — will start or stop applying to the account the moment it moves. The move itself does not delete the account or its group memberships, and it is reversible if you know where the account came from — which is why the script below records the original OU of every user before it touches anything.
Automate Expiring AD Password Notifications with PowerShell
This script reads every enabled AD user's password-expiry date, works out who expires within a window you set (say, the next 14 days), and emails each of those users a reminder. It reads Active Directory and sends mail — it does not change a single account, reset a password, or alter a policy. That makes it low-risk, but it can still misfire loudly: point it at the whole domain with a bad window and you can email hundreds of people at once, so treat the first live run as the dangerous part.
Automate a GPO Backup and Export with PowerShell
This guide sets up a PowerShell script that backs up every Group Policy Object in your domain to a dated folder and, optionally, exports a human-readable HTML report of each one. Backing up GPOs is a read-only operation — Backup-GPO does not change, delete, or unlink anything in Active Directory. It copies the policy settings, security filtering, and WMI filter links into files on disk. The risky part is not the backup; it's the restore , which I cover at the end and which you should treat with real care.




