Last active
January 19, 2026 21:41
-
-
Save SweetAsNZ/ee3daf460987cb9e402e99186b3659c2 to your computer and use it in GitHub Desktop.
Initiates a failover of an Exchange mailbox database to another server within its Database Availability Group (DAG). Waits if too may failovers have happened. Optionally choose the server or by default take the first one available
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Invoke-ExchangeDatabaseFailover { | |
| <# | |
| .SYNOPSIS | |
| Initiates a failover of an Exchange mailbox database to another server within its Database Availability Group (DAG). | |
| .DESCRIPTION | |
| This function allows administrators to trigger a failover of a specified Exchange mailbox database to another server | |
| within its Database Availability Group (DAG). Optionally, a target server can be specified for the failover. | |
| .PARAMETER DatabaseName | |
| The name of the Exchange mailbox database to failover. | |
| .PARAMETER TargetServer | |
| (Optional) The target server to which the database should be failed over. If not specified, the function will choose an appropriate server. | |
| .PARAMETER LogFile | |
| The path to the log file where operation details will be recorded. Defaults to a log file in the user's Documents folder. | |
| .PARAMETER RetryCount | |
| Number of retry attempts if no synchronized passive copies are found. Default is 2 retries (3 total attempts). | |
| .PARAMETER RetryDelayMinutes | |
| Number of minutes to wait between retry attempts. Default is 5 minutes. | |
| .PARAMETER maxSyncAttempts | |
| Maximum number of attempts to verify that the previous active server's copy is synchronized after failover. Default is 60 attempts. | |
| .EXAMPLE | |
| Invoke-ExchangeDatabaseFailover "DB01" | |
| Initiates a failover of the "DB01" mailbox database to the first available healthy passive copy in its DAG. | |
| .EXAMPLE | |
| Invoke-ExchangeDatabaseFailover -DatabaseName "DB01" -TargetServer "Server1" | |
| Initiates a failover of the "DB01" mailbox database to the specific server "Server1". | |
| .NOTES | |
| Author: Tim West | |
| Created: 19/01/2026 | |
| Updated: 20/01/2026 | |
| Status: Production | |
| Version: 1.5.2 | |
| .CHANGELOG | |
| 2026-01-19: Initial version with basic failover logic | |
| 2026-01-19: Fixed DAG lookup to use Get-MailboxDatabase with DatabaseName parameter | |
| 2026-01-19: Added safe Exchange snap-in loading with error handling | |
| 2026-01-19: Fixed server name extraction from Name property (Database\Server format) | |
| 2026-01-19: Added passive copy selection logic that excludes current active server | |
| 2026-01-19: Added comprehensive logging of all database copies with status details | |
| 2026-01-19: Improved error handling - log before throw in all catch blocks | |
| 2026-01-19: Added fallback selection for passive copies when no healthy copies available | |
| 2026-01-19: Enhanced log file structure with timestamps and session markers | |
| 2026-01-19: Added handling for OutOfMemoryException with user guidance | |
| 2026-01-19: Added safety checks - prevent failover to seeding or out-of-sync copies (ReplayQueueLength > 0) | |
| 2026-01-19: Added retry logic with configurable wait time (default: 2 retries with 5 minute delays) | |
| 2026-01-19: Added throttling detection with optional 65-minute wait and retry for 'too many moves' error | |
| 2026-01-20: Added verification loop with do-until to confirm database is mounted on target server (max 30 attempts, 10 second intervals) | |
| 2026-01-20: Added verification loop to confirm previous active server's copy is synchronized after failover (max 60 attempts, 10 second intervals) | |
| #> | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] | |
| [string]$DatabaseName, | |
| [string]$TargetServer, | |
| [string]$LogFile = "$($ENV:USERPROFILE)\Documents\WindowsPowerShell\Scripts\Exchange\Invoke-ExchangeDatabaseFailover\Invoke-ExchangeDatabaseFailover.log", | |
| [int]$RetryCount = 2, | |
| [int]$RetryDelayMinutes = 5, | |
| [int]$maxSyncAttempts = 100 # Suggest 60 for High Performing Servers, 100 for Underpowered Servers/High Traffic Volumes | |
| ) | |
| $LogFileDir = Split-Path -Path $LogFile -Parent | |
| If(-not (Test-Path $LogFileDir)){ | |
| New-Item -Path $LogFileDir -ItemType Directory -Force | Out-Null | |
| } | |
| If(-not (Test-Path $LogFile)){ | |
| New-Item -Path $LogFile -ItemType File -Force | Out-Null | |
| } | |
| $Date = (Get-Date).ToString("yyyyMMdd HH:mm:ss.ffff") | |
| Add-Content $LogFile -Value "`n$Date - Starting Invoke-ExchangeDatabaseFailover for database '$DatabaseName' by $($ENV:USERNAME)" | |
| # Import the Exchange module (safe-add, may already be loaded in EMS) | |
| try { | |
| Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction Stop | |
| } | |
| catch { | |
| Add-Content $LogFile -Value "Warning: Could not add Exchange snap-in: $($_.Exception.Message) by $($ENV:USERNAME)" | |
| } | |
| try { | |
| # Get the mailbox database copy status and the mailbox database object | |
| $mbdb = Get-MailboxDatabase -Identity $DatabaseName -ErrorAction Stop | |
| $dagObj = $mbdb.MasterServerOrAvailabilityGroup | |
| if ($dagObj) { | |
| $dag = $dagObj.Name | |
| Add-Content $LogFile -Value "Database '$DatabaseName' found in DAG '$dag' by $($ENV:USERNAME)." | |
| } | |
| else { | |
| $dag = $null | |
| Add-Content $LogFile -Value "Database '$DatabaseName' is not part of a DAG (checked by $($ENV:USERNAME))." | |
| } | |
| } | |
| catch { | |
| Add-Content $LogFile -Value "Error finding database '$DatabaseName' or DAG: $($_ | Out-String) by $($ENV:USERNAME)" | |
| throw "Database '$DatabaseName' not found or not part of a DAG. See log: $LogFile" | |
| } | |
| # Perform the failover | |
| if($dag){ | |
| if(-not $TargetServer){ | |
| $attempt = 0 | |
| $maxAttempts = $RetryCount + 1 | |
| $targetCopy = $null | |
| while ($attempt -lt $maxAttempts -and -not $targetCopy) { | |
| $attempt++ | |
| if ($attempt -gt 1) { | |
| $waitSeconds = $RetryDelayMinutes * 60 | |
| Write-Host "No synchronized passive copies found. Waiting $RetryDelayMinutes minutes before retry $attempt of $RetryCount..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "No synchronized passive copies found. Waiting $RetryDelayMinutes minutes before retry $attempt of $RetryCount by $($ENV:USERNAME)." | |
| Start-Sleep -Seconds $waitSeconds | |
| Write-Host "Retry attempt $attempt of $RetryCount starting..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Retry attempt $attempt of $RetryCount starting by $($ENV:USERNAME)." | |
| } | |
| try{ | |
| $copies = Get-MailboxDatabaseCopyStatus -Identity $DatabaseName -ErrorAction Stop | |
| # Find the current active server (the one currently mounted) | |
| # The Name property is in format "Database\Server", so we need to parse it | |
| $currentActiveCopy = $copies | Where-Object { $_.Status -eq 'Mounted' } | |
| if ($attempt -eq 1) { | |
| Add-Content $LogFile -Value "Found current active copy for database '$DatabaseName' by $($ENV:USERNAME)." | |
| } | |
| if ($currentActiveCopy.Name -match '\\(.+)$') { | |
| $currentActiveServer = $Matches[1] | |
| if ($attempt -eq 1) { | |
| Write-Host -f Green "Current active server for database '$DatabaseName' is '$currentActiveServer'." | |
| Add-Content $LogFile -Value "Current active server for database '$DatabaseName' is '$currentActiveServer' by $($ENV:USERNAME)." | |
| } | |
| } else { | |
| $currentActiveServer = $currentActiveCopy.MailboxServer | |
| if ($attempt -eq 1) { | |
| Add-Content $LogFile -Value "Parsed current active server for database '$DatabaseName' as '$currentActiveServer' by $($ENV:USERNAME)." | |
| } | |
| } | |
| if ($attempt -eq 1) { | |
| Add-Content $LogFile -Value "Current active server: '$currentActiveServer' by $($ENV:USERNAME)." | |
| } | |
| # Log the status of all copies for debugging | |
| foreach ($copy in $copies) { | |
| if ($copy.Name -match '\\(.+)$') { | |
| $serverName = $Matches[1] | |
| } else { | |
| $serverName = $copy.MailboxServer | |
| } | |
| Add-Content $LogFile -Value "Attempt $attempt - Copy on server '$serverName': Name='$($copy.Name)', Status='$($copy.Status)', ContentIndexState='$($copy.ContentIndexState)', CopyQueueLength='$($copy.CopyQueueLength)', ReplayQueueLength='$($copy.ReplayQueueLength)', ActivationPreference='$($copy.ActivationPreference)' by $($ENV:USERNAME)" | |
| } | |
| # Select target server - exclude the current active server and prefer healthy, synchronized copies by activation preference | |
| $passiveCopies = $copies | Where-Object { $_.Status -ne 'Mounted' } | |
| Add-Content $LogFile -Value "Attempt $attempt - Found $($passiveCopies.Count) passive copies for database '$DatabaseName' by $($ENV:USERNAME)." | |
| # Filter out seeding copies and out-of-sync copies (ReplayQueueLength > 0) | |
| $syncedCopies = $passiveCopies | Where-Object { | |
| $_.Status -ne 'Seeding' -and | |
| $_.Status -ne 'Initializing' -and | |
| $_.ReplayQueueLength -eq 0 | |
| } | |
| Add-Content $LogFile -Value "Attempt $attempt - Found $($syncedCopies.Count) synchronized passive copies (not seeding, ReplayQueueLength = 0) by $($ENV:USERNAME)." | |
| # Try to get healthy synchronized copy first, sorted by activation preference | |
| $targetCopy = $syncedCopies | Where-Object { $_.Status -eq 'Healthy' } | Sort-Object ActivationPreference | Select-Object -First 1 | |
| if (-not $targetCopy) { | |
| # Fallback: any synchronized passive copy | |
| $targetCopy = $syncedCopies | Sort-Object ActivationPreference | Select-Object -First 1 | |
| if ($targetCopy) { | |
| Add-Content $LogFile -Value "Attempt $attempt - No healthy synchronized copies found. Using synchronized copy with status '$($targetCopy.Status)' by $($ENV:USERNAME)." | |
| } | |
| } | |
| if (-not $targetCopy -and $attempt -lt $maxAttempts) { | |
| # Will retry | |
| Add-Content $LogFile -Value "Attempt $attempt - No suitable synchronized passive copies found for database '$DatabaseName'. Will retry after delay by $($ENV:USERNAME)." | |
| continue | |
| } | |
| if (-not $targetCopy) { | |
| Add-Content $LogFile -Value "All $maxAttempts attempts exhausted. No candidate activation server found for database '$DatabaseName'. All passive copies are either seeding or out of sync (ReplayQueueLength > 0) by $($ENV:USERNAME)." | |
| throw "No candidate activation server found for database '$DatabaseName' after $maxAttempts attempts. All passive copies are seeding or out of sync. See log: $LogFile" | |
| } | |
| # Extract server name from the target copy | |
| if ($targetCopy.Name -match '\\(.+)$') { | |
| $activateServer = $Matches[1] | |
| Add-Content $LogFile -Value "Parsed target server for failover as '$activateServer' by $($ENV:USERNAME)." | |
| } else { | |
| $activateServer = $targetCopy.MailboxServer | |
| Add-Content $LogFile -Value "Using MailboxServer property for target server: '$activateServer' by $($ENV:USERNAME)." | |
| } | |
| Add-Content $LogFile -Value "Selected target server: '$activateServer' for failover by $($ENV:USERNAME)." | |
| Add-Content $LogFile -Value "Target copy status: Status='$($targetCopy.Status)', ReplayQueueLength='$($targetCopy.ReplayQueueLength)', CopyQueueLength='$($targetCopy.CopyQueueLength)' by $($ENV:USERNAME)." | |
| Write-Host "Selected target: '$activateServer' (Status: $($targetCopy.Status), ReplayQueue: $($targetCopy.ReplayQueueLength), CopyQueue: $($targetCopy.CopyQueueLength))" -ForegroundColor Cyan | |
| Move-ActiveMailboxDatabase -Identity $DatabaseName -ActivateOnServer $activateServer -Confirm:$false | |
| Write-Host "Failover command executed. Verifying database is now active on '$activateServer'..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "Failover command executed for database '$DatabaseName' to server '$activateServer'. Verifying activation by $($ENV:USERNAME)." | |
| # Verify the failover completed successfully | |
| $failoverComplete = $false | |
| $verifyAttempts = 0 | |
| $maxVerifyAttempts = 30 | |
| do { | |
| $verifyAttempts++ | |
| Start-Sleep -Seconds 10 | |
| $updatedCopies = Get-MailboxDatabaseCopyStatus -Identity $DatabaseName -ErrorAction Stop | |
| $mountedCopy = $updatedCopies | Where-Object { $_.Status -eq 'Mounted' } | |
| if ($mountedCopy.Name -match '\\(.+)$') { | |
| $currentMountedServer = $Matches[1] | |
| } else { | |
| $currentMountedServer = $mountedCopy.MailboxServer | |
| } | |
| if ($currentMountedServer -eq $activateServer) { | |
| $failoverComplete = $true | |
| Write-Host "Failover of database '$DatabaseName' confirmed successfully from '$currentActiveServer' to '$activateServer'." -ForegroundColor Green | |
| Add-Content $LogFile -Value "Failover of database '$DatabaseName' from '$currentActiveServer' to '$activateServer' confirmed successful (verified mounted on target) by $($ENV:USERNAME) - Success" | |
| } | |
| else { | |
| Write-Host "Verification attempt $verifyAttempts of $maxVerifyAttempts - Database still mounted on '$currentMountedServer', waiting..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Verification attempt $verifyAttempts - Database '$DatabaseName' still mounted on '$currentMountedServer' by $($ENV:USERNAME)" | |
| } | |
| } while (-not $failoverComplete -and $verifyAttempts -lt $maxVerifyAttempts) | |
| if (-not $failoverComplete) { | |
| Write-Host "Warning: Failover command completed but database is still mounted on '$currentMountedServer' after $maxVerifyAttempts verification attempts." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Warning: Failover verification timeout - Database '$DatabaseName' still mounted on '$currentMountedServer' after $maxVerifyAttempts attempts by $($ENV:USERNAME)" | |
| } | |
| # Additional verification: Check that the previous active server's copy is now synchronized | |
| if ($failoverComplete) { | |
| Write-Host "Verifying that previous active server '$currentActiveServer' copy is synchronized..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "Checking synchronization status of previous active server '$currentActiveServer' by $($ENV:USERNAME)." | |
| $syncComplete = $false | |
| $syncAttempts = 0 | |
| do { | |
| $syncAttempts++ | |
| Start-Sleep -Seconds 10 | |
| $updatedCopies = Get-MailboxDatabaseCopyStatus -Identity $DatabaseName -ErrorAction Stop | |
| $previousActiveCopy = $updatedCopies | Where-Object { | |
| if ($_.Name -match '\\(.+)$') { $Matches[1] -eq $currentActiveServer } | |
| else { $_.MailboxServer -eq $currentActiveServer } | |
| } | |
| if ($previousActiveCopy.CopyQueueLength -eq 0 -and $previousActiveCopy.ReplayQueueLength -eq 0) { | |
| $syncComplete = $true | |
| Write-Host "Previous active server '$currentActiveServer' is now synchronized (CopyQueue: 0, ReplayQueue: 0)." -ForegroundColor Green | |
| Add-Content $LogFile -Value "Previous active server '$currentActiveServer' synchronized successfully - CopyQueueLength: 0, ReplayQueueLength: 0 by $($ENV:USERNAME) - Success" | |
| } | |
| else { | |
| Write-Host "Sync check $syncAttempts of $maxSyncAttempts - CopyQueue: $($previousActiveCopy.CopyQueueLength), ReplayQueue: $($previousActiveCopy.ReplayQueueLength), waiting..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Sync attempt $syncAttempts - Previous server '$currentActiveServer': CopyQueue=$($previousActiveCopy.CopyQueueLength), ReplayQueue=$($previousActiveCopy.ReplayQueueLength) by $($ENV:USERNAME)" | |
| } | |
| } while (-not $syncComplete -and $syncAttempts -lt $maxSyncAttempts) | |
| if (-not $syncComplete) { | |
| Write-Host "Warning: Previous active server '$currentActiveServer' not fully synchronized after $maxSyncAttempts attempts (CopyQueue: $($previousActiveCopy.CopyQueueLength), ReplayQueue: $($previousActiveCopy.ReplayQueueLength))." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Warning: Sync verification timeout - Previous server '$currentActiveServer' not synchronized after $maxSyncAttempts attempts by $($ENV:USERNAME)" | |
| } | |
| } | |
| } | |
| catch { | |
| $Err = $_.Exception.Message | |
| # Check for "too many moves" throttling error | |
| if ($Err -match 'too many moves have happened recently') { | |
| Write-Host "`nExchange has blocked the move due to throttling - too many moves within 1 hour." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Move blocked by Exchange throttling: $Err by $($ENV:USERNAME)" | |
| $waitMinutes = 65 | |
| $response = Read-Host "`nWait $waitMinutes minutes (1 hour 5 mins) and retry? (Y/N)" | |
| if ($response -eq 'Y' -or $response -eq 'y') { | |
| Write-Host "Waiting $waitMinutes minutes before retrying..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "User chose to wait $waitMinutes minutes for throttling window to expire by $($ENV:USERNAME)" | |
| $waitSeconds = $waitMinutes * 60 | |
| $endTime = (Get-Date).AddSeconds($waitSeconds) | |
| while ((Get-Date) -lt $endTime) { | |
| $remaining = ($endTime - (Get-Date)) | |
| Write-Progress -Activity "Waiting for Exchange throttling window" ` | |
| -Status "Time remaining: $([math]::Floor($remaining.TotalMinutes)) minutes $($remaining.Seconds) seconds" ` | |
| -PercentComplete ((($waitSeconds - $remaining.TotalSeconds) / $waitSeconds) * 100) | |
| Start-Sleep -Seconds 30 | |
| } | |
| Write-Progress -Activity "Waiting for Exchange throttling window" -Completed | |
| Write-Host "Wait complete. Retrying failover..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "Throttling wait complete. Retrying failover for database '$DatabaseName' by $($ENV:USERNAME)" | |
| try { | |
| Move-ActiveMailboxDatabase -Identity $DatabaseName -ActivateOnServer $activateServer -Confirm:$false | |
| Write-Host "Failover command executed after throttling wait. Verifying database is now active on '$activateServer'..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "Failover command executed after throttling wait. Verifying activation by $($ENV:USERNAME)." | |
| # Verify the failover completed successfully | |
| $failoverComplete = $false | |
| $verifyAttempts = 0 | |
| $maxVerifyAttempts = 30 | |
| do { | |
| $verifyAttempts++ | |
| Start-Sleep -Seconds 10 | |
| $updatedCopies = Get-MailboxDatabaseCopyStatus -Identity $DatabaseName -ErrorAction Stop | |
| $mountedCopy = $updatedCopies | Where-Object { $_.Status -eq 'Mounted' } | |
| if ($mountedCopy.Name -match '\\(.+)$') { | |
| $currentMountedServer = $Matches[1] | |
| } else { | |
| $currentMountedServer = $mountedCopy.MailboxServer | |
| } | |
| if ($currentMountedServer -eq $activateServer) { | |
| $failoverComplete = $true | |
| Write-Host "Failover of database '$DatabaseName' confirmed successfully from '$currentActiveServer' to '$activateServer' after throttling wait." -ForegroundColor Green | |
| Add-Content $LogFile -Value "Failover confirmed successful after throttling wait by $($ENV:USERNAME) - Success" | |
| } | |
| else { | |
| Write-Host "Verification attempt $verifyAttempts of $maxVerifyAttempts - Database still mounted on '$currentMountedServer', waiting..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Post-throttling verification attempt $verifyAttempts - Database still on '$currentMountedServer' by $($ENV:USERNAME)" | |
| } | |
| } while (-not $failoverComplete -and $verifyAttempts -lt $maxVerifyAttempts) | |
| if (-not $failoverComplete) { | |
| Write-Host "Warning: Database still mounted on '$currentMountedServer' after $maxVerifyAttempts verification attempts." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Warning: Post-throttling failover verification timeout by $($ENV:USERNAME)" | |
| } | |
| # Additional verification: Check that the previous active server's copy is now synchronized | |
| if ($failoverComplete) { | |
| Write-Host "Verifying that previous active server '$currentActiveServer' copy is synchronized..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "Checking synchronization status of previous active server '$currentActiveServer' by $($ENV:USERNAME)." | |
| $syncComplete = $false | |
| $syncAttempts = 0 | |
| $maxSyncAttempts = 60 | |
| do { | |
| $syncAttempts++ | |
| Start-Sleep -Seconds 10 | |
| $updatedCopies = Get-MailboxDatabaseCopyStatus -Identity $DatabaseName -ErrorAction Stop | |
| $previousActiveCopy = $updatedCopies | Where-Object { | |
| if ($_.Name -match '\\(.+)$') { $Matches[1] -eq $currentActiveServer } | |
| else { $_.MailboxServer -eq $currentActiveServer } | |
| } | |
| if ($previousActiveCopy.CopyQueueLength -eq 0 -and $previousActiveCopy.ReplayQueueLength -eq 0) { | |
| $syncComplete = $true | |
| Write-Host "Previous active server '$currentActiveServer' is now synchronized (CopyQueue: 0, ReplayQueue: 0)." -ForegroundColor Green | |
| Add-Content $LogFile -Value "Previous active server '$currentActiveServer' synchronized successfully - CopyQueueLength: 0, ReplayQueueLength: 0 by $($ENV:USERNAME) - Success" | |
| } | |
| else { | |
| Write-Host "Sync check $syncAttempts of $maxSyncAttempts - CopyQueue: $($previousActiveCopy.CopyQueueLength), ReplayQueue: $($previousActiveCopy.ReplayQueueLength), waiting..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Sync attempt $syncAttempts - Previous server '$currentActiveServer': CopyQueue=$($previousActiveCopy.CopyQueueLength), ReplayQueue=$($previousActiveCopy.ReplayQueueLength) by $($ENV:USERNAME)" | |
| } | |
| } while (-not $syncComplete -and $syncAttempts -lt $maxSyncAttempts) | |
| if (-not $syncComplete) { | |
| Write-Host "Warning: Previous active server '$currentActiveServer' not fully synchronized after $maxSyncAttempts attempts (CopyQueue: $($previousActiveCopy.CopyQueueLength), ReplayQueue: $($previousActiveCopy.ReplayQueueLength))." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Warning: Sync verification timeout - Previous server '$currentActiveServer' not synchronized after $maxSyncAttempts attempts by $($ENV:USERNAME)" | |
| } | |
| } | |
| } | |
| catch { | |
| Add-Content $LogFile -Value "Retry after throttling wait - Move active mailbox database '$DatabaseName' to server '$activateServer' - FAILED" | |
| Add-Content $LogFile -Value "$($_ | Out-String) by $($ENV:USERNAME) - FAILED" | |
| throw "Failed to move active mailbox database '$DatabaseName' to server '$activateServer' after throttling wait. $($_.Exception.Message)" | |
| } | |
| } | |
| else { | |
| Add-Content $LogFile -Value "User declined to wait for throttling window. Move cancelled by $($ENV:USERNAME)" | |
| throw "Failover cancelled by user. Exchange throttling requires waiting before retry. $Err" | |
| } | |
| } | |
| else { | |
| Add-Content $LogFile -Value "Move active mailbox database '$DatabaseName' to server '$activateServer' - FAILED" | |
| Add-Content $LogFile -Value "$($_ | Out-String) by $($ENV:USERNAME) - FAILED" | |
| if ($Err -match 'OutOfMemoryException') { | |
| Write-Host "Exchange Server encountered an OutOfMemoryException. This is a server resource issue, not a script problem." -ForegroundColor Yellow | |
| Write-Host "Recommended actions: Check Exchange server memory usage, restart Exchange services, or retry the operation." -ForegroundColor Yellow | |
| } | |
| throw "Failed to move active mailbox database '$DatabaseName' to server '$activateServer'. $Err" | |
| } | |
| } | |
| } | |
| } | |
| else{ | |
| try{ | |
| Move-ActiveMailboxDatabase -Identity $DatabaseName -ActivateOnServer $TargetServer -Confirm:$false | |
| Write-Host "Failover command executed. Verifying database is now active on '$TargetServer'..." -ForegroundColor Cyan | |
| Add-Content $LogFile -Value "Failover command executed for database '$DatabaseName' to server '$TargetServer'. Verifying activation by $($ENV:USERNAME)." | |
| # Verify the failover completed successfully | |
| $failoverComplete = $false | |
| $verifyAttempts = 0 | |
| $maxVerifyAttempts = 30 | |
| do { | |
| $verifyAttempts++ | |
| Start-Sleep -Seconds 10 | |
| $updatedCopies = Get-MailboxDatabaseCopyStatus -Identity $DatabaseName -ErrorAction Stop | |
| $mountedCopy = $updatedCopies | Where-Object { $_.Status -eq 'Mounted' } | |
| if ($mountedCopy.Name -match '\\(.+)$') { | |
| $currentMountedServer = $Matches[1] | |
| } else { | |
| $currentMountedServer = $mountedCopy.MailboxServer | |
| } | |
| if ($currentMountedServer -eq $TargetServer) { | |
| $failoverComplete = $true | |
| Write-Host "Failover of database '$DatabaseName' confirmed successfully to '$TargetServer'." -ForegroundColor Green | |
| Add-Content $LogFile -Value "Failover of database '$DatabaseName' to server '$TargetServer' confirmed successful (verified mounted on target) by $($ENV:USERNAME) - Success" | |
| } | |
| else { | |
| Write-Host "Verification attempt $verifyAttempts of $maxVerifyAttempts - Database still mounted on '$currentMountedServer', waiting..." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Verification attempt $verifyAttempts - Database '$DatabaseName' still mounted on '$currentMountedServer' by $($ENV:USERNAME)" | |
| } | |
| } while (-not $failoverComplete -and $verifyAttempts -lt $maxVerifyAttempts) | |
| if (-not $failoverComplete) { | |
| Write-Host "Warning: Failover command completed but database is still mounted on '$currentMountedServer' after $maxVerifyAttempts verification attempts." -ForegroundColor Yellow | |
| Add-Content $LogFile -Value "Warning: Failover verification timeout - Database '$DatabaseName' still mounted on '$currentMountedServer' after $maxVerifyAttempts attempts by $($ENV:USERNAME)" | |
| } | |
| } | |
| catch { | |
| Add-Content $LogFile -Value "Move active mailbox database '$DatabaseName' to $($TargetServer) by $($ENV:USERNAME) - FAILED" | |
| Add-Content $LogFile -Value "$($_ | Out-String)" | |
| throw "Failed to move active mailbox database '$DatabaseName' to $($TargetServer) - FAILED. $($_)" | |
| } | |
| } | |
| } | |
| Add-Content $LogFile -Value "`n$Date - Completed Invoke-ExchangeDatabaseFailover.ps1 Script run by $($ENV:USERNAME)" | |
| Add-Content $LogFile -Value "------------------------------------------------------------`n" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment