
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.
