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
Complete Active Directory Offboarding Checklist for IT Admins
A comprehensive guide to properly offboarding employees in Active Directory environments. Learn the critical steps IT administrators must take to ensure security and compliance.
How to Automate Exchange Mailbox Conversion to Shared Mailbox
Learn how to convert user mailboxes to shared mailboxes in Exchange Online and On-Premises. Includes PowerShell scripts and best practices for email retention.
Intune Device Management During Employee Termination
Complete guide to managing Intune devices when employees leave. Learn about selective wipe vs full wipe, BYOD policies, and compliance requirements.
Automate onboarding & offboarding across Active Directory and Microsoft 365