Powershell : Scripting Litigation Hold

In the world of Exchange administration, automation is key. As environments grow and mailbox management becomes more complex, having reliable scripts to handle routine tasks becomes essential.

What makes this journey interesting isn't just the end result, but the process itself - particularly how I identified and corrected misconceptions along the way. This serves as a reminder that even when scripting, it's crucial to verify assumptions and test carefully before deployment.

Note : If you are after Exchange Online commands then they will be at the bottom of this article, up first is Exchange On-Premises.

The Initial Request: Disabling Litigation Hold

Our journey began with a straightforward request: create a script that would read mailbox names from a text file and disable litigation hold for each of those mailboxes in Exchange 2016.

The script created handled this task efficiently:

# Disable-LitigationHold.ps1
param (
    [Parameter(Mandatory=$true)]
    [string]$MailboxFile,
    
    [Parameter(Mandatory=$false)]
    [bool]$Confirm = $true,
    
    [Parameter(Mandatory=$false)]
    [string]$LogFile = "LitigationHoldDisabled_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
)

The script included robust error handling, logging, and confirmation prompts before making changes - all important features for administrative scripts.

Expanding Functionality: Enabling Litigation Hold

Next came the natural counterpart - a script to enable litigation hold. Initially, I created a script with similar functionality but with options for setting a default hold duration and comment:

param (
    [Parameter(Mandatory=$true)]
    [string]$MailboxFile,
    
    [Parameter(Mandatory=$false)]
    [bool]$Confirm = $true,
    
    [Parameter(Mandatory=$false)]
    [int]$HoldDuration = 2555,  # Default: 7 years (2555 days)
    
    [Parameter(Mandatory=$false)]
    [string]$HoldComment = "Litigation hold enabled on account"
)

However, I quickly learned that the actual requirement was different - unlimited hold duration with no message was the preferred default as if you set a message the user is aware of that message as its a "user" message:

param (
    [Parameter(Mandatory=$true)]
    [string]$MailboxFile,
    
    [Parameter(Mandatory=$false)]
    [bool]$Confirm = $true,
    
    [Parameter(Mandatory=$false)]
    [int]$HoldDuration = -1,  # Default: Unlimited
    
    [Parameter(Mandatory=$false)]
    [string]$HoldComment = ""
)

I also improved the code to handle the parameters more intelligently:

# Add duration parameter
if ($HoldDuration -eq -1) {
    $params.Add("LitigationHoldDuration", "Unlimited")
} else {
    $params.Add("LitigationHoldDuration", $HoldDuration)
}

# Add comment only if it's not empty
if (-not [string]::IsNullOrWhiteSpace($HoldComment)) {
    $params.Add("LitigationHoldComment", $HoldComment)
    $params.Add("LitigationHoldOwner", "Admin")
}

This was our first lesson in the importance of clarifying requirements - assumptions can lead to rework.

Decision Point : Mailbox Disabling vs. Removal

The most significant learning came when I moved to handling the complete removal of mailboxes from Exchange. this was not a remove of the mailboxes, but it turned out that I needed to completely remove them from Exchange while preserving the associated AD accounts.

The first attempt used Remove-Mailbox, with the assumption that it would remove the mailbox while preserving the AD account:

# Remove the mailbox
Remove-Mailbox $mailbox -Confirm:$false -ErrorAction Stop

However, when the script was being tested, I discovered a crucial fact: Remove-Mailbox actually removes both the mailbox and the Active Directory user object. This was made clear by Exchange's own warning:

Are you sure you want to perform this action?
Removing mailbox "Temp.User@pokebearswithsticks.com" will remove the Active Directory 
user object and mark the mailbox and the archive (if present) in the database for removal.

Fortunately, this issue was caught during the confirmation process before any accounts were deleted.

Correct Decision: Using Disable-Mailbox

The script now uses the correct cmdlet: Disable-Mailbox. This cmdlet removes the Exchange mailbox while preserving the AD account - exactly what was needed.

# Disable the mailbox
Write-Log "Disabling mailbox $mailbox (AD account will be preserved)" "INFO"
Disable-Mailbox $mailbox -Confirm:$false -ErrorAction Stop

I also added verification to check that the mailbox was gone but the AD account remained:

# Verify the mailbox is gone but the user remains
try {
    $verifyMbx = Get-Mailbox $mailbox -ErrorAction Stop
    Write-Log "Failed to verify mailbox was disabled: 
    $mailbox (still exists as a mailbox)" "ERROR"
    $failCount++
} catch {
    # Check if the user still exists in AD
    try {
        $adUser = Get-User $mailbox -ErrorAction Stop
        if ($adUser.RecipientType -eq "User" -or $adUser.RecipientType 
        -eq "DisabledUser") {
            Write-Log "Successfully disabled mailbox: 
            $mailbox (AD account preserved)" "SUCCESS"
            $successCount++
        }
    }
    # Error handling code...
}

Final Scripts

Our journey resulted in three production-ready scripts:

  1. Disable-LitigationHold.ps1: Disables litigation hold for mailboxes in a text file
  2. Enable-LitigationHold.ps1: Enables unlimited litigation hold without comments
  3. Disable-ExchangeMailboxes.ps1: Removes mailboxes from Exchange while preserving AD accounts

Each script includes robust error handling, logging, confirmation prompts, and verification steps - making them safe and reliable tools for Exchange administration.

Exchange Online Equivalent Operations

While the first scripts were designed for on-premises Exchange 2016, many organizations are migrating to Exchange Online as part of Microsoft 365. Here's how to perform the same operations in Exchange Online, with important differences highlighted.

Connecting to Exchange Online

Before executing any of the commands, you'll need to connect to Exchange Online using the Exchange Online PowerShell module:

# Install the module if you haven't already
# Install-Module -Name ExchangeOnlineManagement

# Connect to Exchange Online
Connect-ExchangeOnline -UserPrincipalName lee@pokebearswithsticks.com
Enabling Litigation Hold

# Enable Litigation Hold with unlimited duration
Set-Mailbox -Identity $mailbox -LitigationHoldEnabled $true

Disable Litigation Hold
Set-Mailbox -Identity user@yourdomain.com -LitigationHoldEnabled $false
Retention Policy : Litigation Hold

If you wish to capture all newly created users then you could create a organisational wide retention policy, the will capture all the new users that are added after you create this policy, this option only applies for Exchange Online:
# Connect to Security & Compliance PowerShell
Connect-IPPSSession

# Create a new retention policy for all mailboxes (including future ones)
New-RetentionCompliancePolicy -Name "Automatic Litigation Hold" 
-ExchangeLocation All -Enabled $true

# Create a retention rule with unlimited retention
New-RetentionComplianceRule -Name "Hold All Content" 
-Policy "Automatic Litigation Hold" -RetentionDuration Unlimited 
-RetentionComplianceAction Hold
If you are looking to complete the same option with Exchange On premsis then you will need a Powershell script to create this automation as below: 
Script : Find-NewMailboxesAndEnableLitigationHold.ps1
# Define the threshold for "new" users (e.g., created in the last 24 hours)
$newUserThreshold = (Get-Date).AddHours(-24)

# Get all mailboxes created after the threshold that don't have litigation hold enabled
$newMailboxes = Get-Mailbox -Filter "WhenCreated -gt 
'$($newUserThreshold.ToString("MM/dd/yyyy"))' -and LitigationHoldEnabled -eq 'False'"

# Log file path
$logFile = "C:\Logs\LitigationHoldEnabled_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

# Log function
function Write-Log {
    param([string]$message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logMessage = "[$timestamp] $message"
    Add-Content -Path $logFile -Value $logMessage
}

Write-Log "Starting litigation hold check for new mailboxes"
Write-Log "Found $($newMailboxes.Count) new mailboxes without litigation hold"

foreach ($mailbox in $newMailboxes) {
    try {
        # Enable litigation hold
        Set-Mailbox -Identity $mailbox.Identity -LitigationHoldEnabled $true
        Write-Log "Successfully enabled litigation hold for: $($mailbox.UserPrincipalName)"
    } catch {
        Write-Log "ERROR: Failed to enable litigation hold for 
$($mailbox.UserPrincipalName): $_"
    }
}

Write-Log "Completed litigation hold processing"
Previous Post Next Post

نموذج الاتصال