PowerShell Script: Automated User Offboarding Checklist
─□✕

PowerShell Script: Automated User Offboarding Checklist

PowerShell Tips Editor 6 min read
PowerShell Script: Automated User Offboarding Checklist

Why Manual Offboarding Fails

When an employee leaves, IT teams face a checklist of a dozen or more steps across Active Directory, Microsoft 365, Exchange Online, and line-of-business systems. In busy periods, steps get missed. An AD account stays enabled for weeks. A Microsoft 365 license assignment lingers, costing money. A shared mailbox never gets created for the departing user’s email, and colleagues miss important messages. Worse, in regulated industries, an incomplete offboarding is a compliance finding. A PowerShell script that executes the full sequence with a documented audit trail eliminates human omission and produces an evidence package for every offboarded user.

Quick Answer

Disable and reset the AD account, remove all group memberships, revoke Microsoft 365 licenses via the Graph SDK, convert the Exchange Online mailbox to shared, configure mail forwarding to the manager, and export a timestamped audit log of every action taken. Wrap the sequence in a single parameterized function that accepts the departing user’s UPN.

Disabling the AD Account and Resetting the Password

The first two actions must happen atomically: disable the account and simultaneously reset the password to something random. Disabling without resetting leaves a window where someone with the old password could re-enable and authenticate. Generate a cryptographically random password using [System.Web.Security.Membership]::GeneratePassword() or the Get-Random approach below. Record both actions in the audit log.

param(
    [Parameter(Mandatory)]
    [string]$UserUPN
)

Import-Module ActiveDirectory -ErrorAction Stop

$adUser = Get-ADUser -Filter "UserPrincipalName -eq '$UserUPN'" `
    -Properties MemberOf, Manager, DistinguishedName -ErrorAction Stop

if ($null -eq $adUser) { throw "User not found in AD: $UserUPN" }

# Generate random password
$newPassword = ConvertTo-SecureString `
    (-join ((65..90)+(97..122)+(48..57)+(33..47) | Get-Random -Count 24 | ForEach-Object {[char]$_})) `
    -AsPlainText -Force

Disable-ADAccount -Identity $adUser.DistinguishedName -ErrorAction Stop
Set-ADAccountPassword -Identity $adUser.DistinguishedName -NewPassword $newPassword `
    -Reset -ErrorAction Stop

Write-Host "AD account disabled and password reset: $($adUser.SamAccountName)"

Removing All AD Group Memberships

Group memberships in AD control file share access, distribution list subscriptions, and application roles. Removing them at offboarding revokes resource access immediately without requiring individual application owners to act. The primary group cannot be removed directly — filter it out by comparing the PrimaryGroupID. Log each group removal for the audit record.

$auditLog = [System.Collections.Generic.List[string]]::new()
$auditLog.Add("$(Get-Date -Format o) | AD account disabled: $($adUser.SamAccountName)")

$groupsToRemove = $adUser.MemberOf | ForEach-Object {
    Get-ADGroup -Identity $_ -Properties GroupCategory
} | Where-Object { $_.DistinguishedName -ne (Get-ADUser $adUser -Properties PrimaryGroup).PrimaryGroup }

foreach ($group in $groupsToRemove) {
    try {
        Remove-ADGroupMember -Identity $group -Members $adUser -Confirm:$false -ErrorAction Stop
        $auditLog.Add("$(Get-Date -Format o) | Removed from group: $($group.Name)")
    }
    catch {
        $auditLog.Add("$(Get-Date -Format o) | FAILED to remove from group $($group.Name): $_")
    }
}

Write-Host "Group memberships removed: $($groupsToRemove.Count)"

Revoking Microsoft 365 Licenses via Graph SDK

License revocation via the Graph SDK requires the User.ReadWrite.All and Directory.ReadWrite.All scopes. Retrieve the user’s current license assignment with Get-MgUserLicenseDetail, then call Set-MgUserLicense with an empty AddLicenses array and the current SKUs in RemoveLicenses. Note that the license seat does not free up immediately — Microsoft processes the removal within up to 24 hours.

Connect-MgGraph -Scopes "User.ReadWrite.All","Directory.ReadWrite.All" -ErrorAction Stop

$mgUser = Get-MgUser -Filter "userPrincipalName eq '$UserUPN'" -ErrorAction Stop
$licenses = Get-MgUserLicenseDetail -UserId $mgUser.Id

if ($licenses.Count -gt 0) {
    $skuIds = $licenses.SkuId
    Set-MgUserLicense -UserId $mgUser.Id `
        -AddLicenses @() `
        -RemoveLicenses $skuIds `
        -ErrorAction Stop
    $auditLog.Add("$(Get-Date -Format o) | M365 licenses revoked: $($licenses.SkuPartNumber -join ', ')")
    Write-Host "Licenses revoked: $($licenses.Count)"
}
else {
    Write-Host "No licenses assigned to revoke."
}

Converting Mailbox to Shared in Exchange Online

Converting the departing user’s mailbox to a shared mailbox preserves all email history and allows delegated access without consuming a standard license. A shared mailbox only requires a license if its total size exceeds 50 GB. Use Set-Mailbox with -Type Shared after connecting to Exchange Online. The conversion is near-instantaneous but may take up to 30 minutes to propagate fully.

Connect-ExchangeOnline -ShowBanner:$false -ErrorAction Stop

try {
    Set-Mailbox -Identity $UserUPN -Type Shared -ErrorAction Stop
    $auditLog.Add("$(Get-Date -Format o) | Mailbox converted to Shared: $UserUPN")
    Write-Host "Mailbox converted to Shared."
}
catch {
    $auditLog.Add("$(Get-Date -Format o) | FAILED mailbox conversion: $_")
    Write-Warning "Mailbox conversion failed: $_"
}

Forwarding Mail to the Manager’s Mailbox

Configuring mail forwarding ensures business continuity during the transition period. Retrieve the manager’s UPN from the AD object and set it as the forwarding address on the now-shared mailbox. Use -DeliverToMailboxAndForward $true if a copy should remain in the shared mailbox for archive purposes, or $false to forward exclusively.

if ($adUser.Manager) {
    $manager = Get-ADUser -Identity $adUser.Manager -Properties UserPrincipalName
    $managerUPN = $manager.UserPrincipalName

    Set-Mailbox -Identity $UserUPN `
        -ForwardingSmtpAddress $managerUPN `
        -DeliverToMailboxAndForward $true `
        -ErrorAction Stop

    $auditLog.Add("$(Get-Date -Format o) | Mail forwarding set to: $managerUPN")
    Write-Host "Mail forwarding configured to: $managerUPN"
}
else {
    Write-Warning "No manager attribute found in AD — mail forwarding skipped."
    $auditLog.Add("$(Get-Date -Format o) | WARNING: Mail forwarding skipped — no manager in AD")
}

Generating a Full Offboarding Audit Report

Export the audit log to a timestamped text file and optionally email it to the IT manager and HR as a compliance artifact. The log captures every action taken, every failure encountered, and the exact timestamp of each step. Store these files in a protected network share or attach them to the HR ticket for the departing employee.

$reportPath = "C:\OffboardingLogs\$($adUser.SamAccountName)_$(Get-Date -Format yyyyMMdd_HHmm).txt"

$auditLog | Out-File -FilePath $reportPath -Encoding UTF8

Write-Host "Offboarding complete. Audit log: $reportPath"
Write-Host "Actions logged: $($auditLog.Count)"

# Optional: email the audit log
Send-MailMessage `
    -To "[email protected]" `
    -From "[email protected]" `
    -Subject "Offboarding complete: $UserUPN" `
    -Body ($auditLog -join "`n") `
    -SmtpServer "mail.contoso.com"

Common Errors

  • License revocation does not immediately free the seat. Microsoft processes license removals asynchronously — the seat may remain occupied for up to 24 hours after the script completes. Do not include license count in real-time capacity reports. Verify seat counts the following day rather than immediately after offboarding.
  • AD disable does not immediately reflect in Exchange Online for hybrid environments. In hybrid deployments, the AD account disable must sync via Azure AD Connect before Exchange Online sees the disabled state. Sync typically runs every 30 minutes. If Exchange Online mailbox operations fail immediately after disabling the AD account, wait for the next sync cycle and retry.

Related Cmdlets / See Also

Wrapping Up

A scripted offboarding eliminates the missed steps that create security gaps and compliance findings. Disable the AD account, revoke licenses, convert the mailbox, configure forwarding, and generate a timestamped audit log — all in one unattended run. The audit log becomes the evidence package for HR, compliance, and security teams, making every offboarding defensible and consistent.

Send-Item -To