Shore Up
An office desk being cleared out — a name plate lifted off its holder, keys dropped into a labelled box, and a small arrow redirecting a stack of letters toward a neighbouring desk.
WindowsMail

Automate User Offboarding in Active Directory with PowerShell

Ketan Aagja9 min read
No ratings yet

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.

Written by
Ketan Aagja

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.