PowerShell Scripts for AD Offboarding: Free Templates for IT Admins

Manual Active Directory offboarding is time-consuming and error-prone. PowerShell automation can reduce termination time from hours to minutes while ensuring consistency and compliance.

This guide provides production-ready PowerShell scripts you can customize for your environment.

Prefer ready-to-download files? All six scripts are available in our free GitHub repository — MIT licensed, with usage notes and parameters documented in each script.

Prerequisites

Before using these scripts, ensure you have:

  • Active Directory PowerShell module installed
  • Domain Admin or delegated permissions for user management
  • PowerShell 5.1 or later (PowerShell 7 recommended)
  • Execution policy set appropriately

Install AD Module

On Windows Server:

Install-WindowsFeature RSAT-AD-PowerShell

On Windows 10/11:

Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools

Script 1: Basic AD Account Disable

What It Does

  • Disables user account
  • Resets password
  • Adds description with termination date
  • Logs all actions

The Script

# Basic AD Account Disable Script
# Usage: Disable-ADUser.ps1 -Username "jsmith"

param(
    [Parameter(Mandatory=$true)]
    [string]$Username
)

# Import AD Module
Import-Module ActiveDirectory

# Get current date
$Date = Get-Date -Format "yyyy-MM-dd"

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

try {
    # Disable account
    Disable-ADAccount -Identity $Username
    Write-Host "Account disabled: $Username" -ForegroundColor Green

    # Reset password
    Set-ADAccountPassword -Identity $Username -NewPassword $SecurePassword -Reset
    Write-Host "Password reset for: $Username" -ForegroundColor Green

    # Update description
    Set-ADUser -Identity $Username -Description "Terminated: $Date"
    Write-Host "Description updated: $Username" -ForegroundColor Green

    # Log action
    $LogEntry = "$Date - $Username disabled by $env:USERNAME"
    Add-Content -Path "C:\Logs\AD_Terminations.log" -Value $LogEntry

    Write-Host "`nOffboarding completed for: $Username" -ForegroundColor Cyan
}
catch {
    Write-Host "Error: $_" -ForegroundColor Red
}

Script 2: Complete Offboarding with Groups

What It Does

  • Disables account
  • Removes from ALL security groups (except Domain Users)
  • Moves to Disabled OU
  • Hides from Global Address List
  • Sets account expiration
  • Exports before/after state

The Script

# Complete AD Offboarding Script

param(
    [Parameter(Mandatory=$true)]
    [string]$Username,
    
    [Parameter(Mandatory=$false)]
    [string]$DisabledOU = "OU=Disabled Users,DC=company,DC=com",
    
    [Parameter(Mandatory=$false)]
    [int]$ExpirationDays = 90
)

Import-Module ActiveDirectory

$Date = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$ExpirationDate = (Get-Date).AddDays($ExpirationDays)

# Backup current state
$User = Get-ADUser -Identity $Username -Properties *
$User | Select-Object Name, SamAccountName, Enabled, DistinguishedName, MemberOf | 
    Export-Csv "C:\Logs\$Username-BeforeOffboarding-$Date.csv" -NoTypeInformation

Write-Host "Starting offboarding for: $($User.Name)" -ForegroundColor Cyan
Write-Host "==========================================`n"

# Step 1: Disable account
Disable-ADAccount -Identity $Username
Write-Host "[1/7] Account disabled" -ForegroundColor Green

# Step 2: Reset password
$NewPassword = ConvertTo-SecureString (-join ((65..90) + (97..122) + (48..57) | Get-Random -Count 20 | % {[char]$_})) -AsPlainText -Force
Set-ADAccountPassword -Identity $Username -NewPassword $NewPassword -Reset
Write-Host "[2/7] Password reset" -ForegroundColor Green

# Step 3: Remove from groups
$Groups = Get-ADUser -Identity $Username -Properties MemberOf | Select-Object -ExpandProperty MemberOf
$RemovedGroups = @()

foreach ($Group in $Groups) {
    if ($Group -notlike "*Domain Users*") {
        Remove-ADGroupMember -Identity $Group -Members $Username -Confirm:$false
        $RemovedGroups += $Group
    }
}
Write-Host "[3/7] Removed from $($RemovedGroups.Count) security groups" -ForegroundColor Green

# Step 4: Clear manager
Set-ADUser -Identity $Username -Manager $null -Clear Manager
Write-Host "[4/7] Manager relationship cleared" -ForegroundColor Green

# Step 5: Hide from GAL
Set-ADUser -Identity $Username -Replace @{msExchHideFromAddressLists=$true}
Write-Host "[5/7] Hidden from Global Address List" -ForegroundColor Green

# Step 6: Move to Disabled OU
Move-ADObject -Identity $User.DistinguishedName -TargetPath $DisabledOU
Write-Host "[6/7] Moved to Disabled Users OU" -ForegroundColor Green

# Step 7: Set expiration date
Set-ADAccountExpiration -Identity $Username -DateTime $ExpirationDate
Write-Host "[7/7] Account expiration set to: $($ExpirationDate.ToString('yyyy-MM-dd'))" -ForegroundColor Green

# Update description
$Description = "Terminated: $Date by $env:USERNAME"
Set-ADUser -Identity $Username -Description $Description

# Export final state
Get-ADUser -Identity $Username -Properties * | 
    Select-Object Name, SamAccountName, Enabled, DistinguishedName, AccountExpirationDate | 
    Export-Csv "C:\Logs\$Username-AfterOffboarding-$Date.csv" -NoTypeInformation

Write-Host "`n=========================================="
Write-Host "Offboarding completed successfully!" -ForegroundColor Green
Write-Host "User: $($User.Name)"
Write-Host "Groups removed: $($RemovedGroups.Count)"
Write-Host "Expires on: $($ExpirationDate.ToString('yyyy-MM-dd'))"
Write-Host "`nLogs saved to: C:\Logs\"

Script 3: Bulk Offboarding from CSV

CSV Format

Username,TerminationDate,Manager
jsmith,2026-01-15,mjones
bdoe,2026-01-15,sjohnson

The Script

# Bulk Offboarding Script

param(
    [Parameter(Mandatory=$true)]
    [string]$CSVPath
)

Import-Module ActiveDirectory

$Users = Import-Csv $CSVPath
$Results = @()

foreach ($User in $Users) {
    $Status = @{
        Username = $User.Username
        TerminationDate = $User.TerminationDate
        Success = $false
        Error = ""
    }

    try {
        # Disable account
        Disable-ADAccount -Identity $User.Username
        
        # Reset password
        $NewPass = ConvertTo-SecureString (-join ((65..90) + (97..122) + (48..57) | Get-Random -Count 16 | % {[char]$_})) -AsPlainText -Force
        Set-ADAccountPassword -Identity $User.Username -NewPassword $NewPass -Reset
        
        # Remove from groups
        $Groups = Get-ADUser -Identity $User.Username -Properties MemberOf | Select -ExpandProperty MemberOf
        foreach ($Group in $Groups) {
            if ($Group -notlike "*Domain Users*") {
                Remove-ADGroupMember -Identity $Group -Members $User.Username -Confirm:$false
            }
        }
        
        $Status.Success = $true
        Write-Host "✓ $($User.Username) - Completed" -ForegroundColor Green
    }
    catch {
        $Status.Error = $_.Exception.Message
        Write-Host "✗ $($User.Username) - Failed: $($Status.Error)" -ForegroundColor Red
    }
    
    $Results += New-Object PSObject -Property $Status
}

# Export results
$Results | Export-Csv "C:\Logs\BulkOffboarding-Results-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv" -NoTypeInformation

Write-Host "`n=========================================="
Write-Host "Bulk offboarding completed!"
Write-Host "Total: $($Users.Count) | Success: $(($Results | Where {$_.Success}).Count) | Failed: $(($Results | Where {-not $_.Success}).Count)"

Script 4: Exchange Mailbox Automation

What It Does

  • Connects to Exchange Online
  • Converts mailbox to shared
  • Sets auto-reply
  • Hides from GAL
  • Grants manager access

The Script

# Exchange Mailbox Offboarding

param(
    [Parameter(Mandatory=$true)]
    [string]$UserEmail,
    
    [Parameter(Mandatory=$true)]
    [string]$ManagerEmail,
    
    [Parameter(Mandatory=$false)]
    [string]$AutoReplyMessage = "Thank you for your email. This employee is no longer with the company. For assistance, please contact HR at hr@company.com"
)

# Connect to Exchange Online (requires Exchange Online Management module)
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline

Write-Host "Processing mailbox: $UserEmail" -ForegroundColor Cyan

try {
    # Step 1: Convert to shared mailbox
    Set-Mailbox -Identity $UserEmail -Type Shared
    Write-Host "[1/4] Converted to shared mailbox" -ForegroundColor Green
    
    # Step 2: Set auto-reply
    Set-MailboxAutoReplyConfiguration -Identity $UserEmail -AutoReplyState Enabled -InternalMessage $AutoReplyMessage -ExternalMessage $AutoReplyMessage
    Write-Host "[2/4] Auto-reply message configured" -ForegroundColor Green
    
    # Step 3: Hide from GAL
    Set-Mailbox -Identity $UserEmail -HiddenFromAddressListsEnabled $true
    Write-Host "[3/4] Hidden from Global Address List" -ForegroundColor Green
    
    # Step 4: Grant manager Full Access and Send As
    Add-MailboxPermission -Identity $UserEmail -User $ManagerEmail -AccessRights FullAccess -InheritanceType All
    Add-RecipientPermission -Identity $UserEmail -Trustee $ManagerEmail -AccessRights SendAs -Confirm:$false
    Write-Host "[4/4] Permissions granted to: $ManagerEmail" -ForegroundColor Green
    
    Write-Host "`nMailbox offboarding completed!" -ForegroundColor Green
    Write-Host "Note: Wait 24-48 hours before removing Microsoft 365 license"
}
catch {
    Write-Host "Error: $_" -ForegroundColor Red
}

Disconnect-ExchangeOnline -Confirm:$false

Script 5: Azure AD Device Removal

What It Does

  • Lists all Azure AD devices for user
  • Removes devices from Azure AD
  • Revokes refresh tokens
  • Resets MFA

The Script

# Azure AD Device and Session Cleanup

param(
    [Parameter(Mandatory=$true)]
    [string]$UserPrincipalName
)

# Install module if not present
if (-not (Get-Module -ListAvailable -Name AzureAD)) {
    Install-Module AzureAD -Force -AllowClobber
}

Import-Module AzureAD
Connect-AzureAD

$User = Get-AzureADUser -ObjectId $UserPrincipalName

Write-Host "Processing Azure AD cleanup for: $($User.DisplayName)" -ForegroundColor Cyan

# Step 1: Get and remove devices
$Devices = Get-AzureADUserRegisteredDevice -ObjectId $User.ObjectId
Write-Host "`n[1/3] Found $($Devices.Count) registered devices" -ForegroundColor Yellow

foreach ($Device in $Devices) {
    Remove-AzureADDevice -ObjectId $Device.ObjectId
    Write-Host "  ✓ Removed: $($Device.DisplayName)" -ForegroundColor Green
}

# Step 2: Revoke refresh tokens
Revoke-AzureADUserAllRefreshToken -ObjectId $User.ObjectId
Write-Host "[2/3] All refresh tokens revoked" -ForegroundColor Green

# Step 3: Reset MFA (requires admin consent)
$MFAMethods = Get-MsolUser -UserPrincipalName $UserPrincipalName | Select-Object -ExpandProperty StrongAuthenticationMethods
if ($MFAMethods) {
    Reset-MsolStrongAuthenticationMethodByUpn -UserPrincipalName $UserPrincipalName
    Write-Host "[3/3] MFA registration reset" -ForegroundColor Green
} else {
    Write-Host "[3/3] No MFA methods found" -ForegroundColor Yellow
}

Write-Host "`nAzure AD cleanup completed!" -ForegroundColor Green

Disconnect-AzureAD

Script 6: All-in-One Master Offboarding

This comprehensive script combines AD, Exchange, and Azure AD:

# Master Offboarding Script - All Systems

param(
    [Parameter(Mandatory=$true)]
    [string]$Username,
    
    [Parameter(Mandatory=$true)]
    [string]$ManagerEmail
)

$LogPath = "C:\Logs\Offboarding_$Username_$(Get-Date -Format 'yyyyMMdd-HHmmss').txt"
Start-Transcript -Path $LogPath

Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "  MASTER OFFBOARDING SCRIPT" -ForegroundColor Cyan
Write-Host "  User: $Username" -ForegroundColor Cyan
Write-Host "  Date: $(Get-Date)" -ForegroundColor Cyan
Write-Host "========================================`n" -ForegroundColor Cyan

# SECTION 1: Active Directory
Write-Host "SECTION 1: Active Directory" -ForegroundColor Yellow
Import-Module ActiveDirectory

$ADUser = Get-ADUser -Identity $Username -Properties *
$UserEmail = $ADUser.EmailAddress

Disable-ADAccount -Identity $Username
Write-Host "  ✓ Account disabled" -ForegroundColor Green

$NewPassword = ConvertTo-SecureString (-join ((65..90) + (97..122) + (48..57) | Get-Random -Count 20 | % {[char]$_})) -AsPlainText -Force
Set-ADAccountPassword -Identity $Username -NewPassword $NewPassword -Reset
Write-Host "  ✓ Password reset" -ForegroundColor Green

$Groups = Get-ADUser -Identity $Username -Properties MemberOf | Select -ExpandProperty MemberOf
foreach ($Group in $Groups) {
    if ($Group -notlike "*Domain Users*") {
        Remove-ADGroupMember -Identity $Group -Members $Username -Confirm:$false
    }
}
Write-Host "  ✓ Removed from $($Groups.Count - 1) groups" -ForegroundColor Green

# SECTION 2: Exchange Online
Write-Host "`nSECTION 2: Exchange Online" -ForegroundColor Yellow
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -ShowBanner:$false

Set-Mailbox -Identity $UserEmail -Type Shared
Write-Host "  ✓ Mailbox converted to shared" -ForegroundColor Green

Set-MailboxAutoReplyConfiguration -Identity $UserEmail -AutoReplyState Enabled -InternalMessage "This employee is no longer with the company." -ExternalMessage "This employee is no longer with the company."
Write-Host "  ✓ Auto-reply configured" -ForegroundColor Green

Set-Mailbox -Identity $UserEmail -HiddenFromAddressListsEnabled $true
Write-Host "  ✓ Hidden from GAL" -ForegroundColor Green

Add-MailboxPermission -Identity $UserEmail -User $ManagerEmail -AccessRights FullAccess -InheritanceType All
Write-Host "  ✓ Manager granted access" -ForegroundColor Green

Disconnect-ExchangeOnline -Confirm:$false

# SECTION 3: Azure AD
Write-Host "`nSECTION 3: Azure AD & Devices" -ForegroundColor Yellow
Import-Module AzureAD
Connect-AzureAD

$AzureUser = Get-AzureADUser -ObjectId $UserEmail
$Devices = Get-AzureADUserRegisteredDevice -ObjectId $AzureUser.ObjectId

foreach ($Device in $Devices) {
    Remove-AzureADDevice -ObjectId $Device.ObjectId
}
Write-Host "  ✓ Removed $($Devices.Count) devices" -ForegroundColor Green

Revoke-AzureADUserAllRefreshToken -ObjectId $AzureUser.ObjectId
Write-Host "  ✓ Refresh tokens revoked" -ForegroundColor Green

Disconnect-AzureAD

# COMPLETION
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "  OFFBOARDING COMPLETED SUCCESSFULLY" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "User: $Username ($UserEmail)"
Write-Host "Manager: $ManagerEmail"
Write-Host "Log saved to: $LogPath"
Write-Host "`nREMINDER: Remove Microsoft 365 license in 24-48 hours`n"

Stop-Transcript

Best Practices for PowerShell Offboarding

1. Test in Non-Production First

  • Use test accounts before running on real users
  • Validate each section independently
  • Use -WhatIf parameter where supported

2. Error Handling

try {
    # Your command
    Disable-ADAccount -Identity $Username -ErrorAction Stop
}
catch {
    Write-Error "Failed: $_"
    # Send alert email
    # Log to ticketing system
}

3. Logging Everything

  • Use Start-Transcript for full session logs
  • Export before/after states to CSV
  • Include timestamps and operator names
  • Store logs for compliance (1-7 years)

4. Scheduling with Task Scheduler

# Create scheduled task for monthly cleanup
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\MonthlyCleanup.ps1"
$Trigger = New-ScheduledTaskTrigger -Monthly -DaysOfMonth 1 -At 2am
Register-ScheduledTask -TaskName "AD Account Cleanup" -Action $Action -Trigger $Trigger

5. Security Considerations

  • Store scripts in secure location (not user desktops)
  • Use service accounts with minimum required permissions
  • Never hardcode passwords in scripts
  • Audit who runs offboarding scripts
  • Require manager approval before execution

Common Errors and Solutions

Error: "Cannot find an object with identity"

Cause: Username is incorrect or user doesn't exist

Solution: Verify username with Get-ADUser first

Error: "Insufficient access rights"

Cause: Running account lacks permissions

Solution: Use account with Domain Admin or delegated rights

Error: "The operation couldn't be performed because object"

Cause: User is member of protected group (e.g., Domain Admins)

Solution: Remove from protected groups manually first

Error: "The term 'Disable-ADAccount' is not recognized"

Cause: Active Directory module not installed

Solution: Install RSAT tools or run on Domain Controller

Limitations of PowerShell Automation

What PowerShell Can't Do Well

  • GUI/Dashboard: No visual interface for non-technical users
  • Cross-Tenant: Managing multiple M365 tenants requires complex credential handling
  • Error Recovery: Manual intervention needed if scripts fail mid-execution
  • Scheduling: Requires Task Scheduler or external orchestration
  • Notifications: Must add email/Teams integration manually
  • Audit Reporting: Need to build custom reporting dashboards

When to Consider Dedicated Tools

If you're experiencing:

  • Scripts breaking after Microsoft Graph API updates
  • Managing 10+ terminations per month
  • Multi-tenant environments (MSPs)
  • Compliance requirements for detailed audit trails
  • Need for non-technical staff to run offboarding
  • Time spent maintaining custom scripts (10+ hours/month)

Dedicated automation tools like ADATT provide:

  • ✓ GUI for easy operation
  • ✓ Multi-tenant support out-of-the-box
  • ✓ Automatic updates when APIs change
  • ✓ Built-in compliance reporting
  • ✓ Error handling and recovery
  • ✓ Email notifications

For a full cost and capability breakdown, see our ADATT vs PowerShell scripts comparison.

Beyond PowerShell: Automated Offboarding

While these PowerShell scripts are powerful, ADATT provides a production-ready solution with GUI, multi-tenant support, and comprehensive audit logging - no script maintenance required.

Continue Reading

Automate onboarding & offboarding across Active Directory and Microsoft 365