Mailbox moves, especially when dealing with thousands of mailboxes is a task that can take a long time because you need to check when mailbox moves complete and also keep an eye on space.
I decided to put something together that automates this process, clearing completed move requests, starting new ones and keeping an eye on space, while giving an output in PowerShell.
The script is long and it does have prompts in it that requests the following information:
- Source Mailbox Database
- Target Mailbox Database
- Drive letter to monitor
The script has a timer, you can adjust it if you need to when it refreshes the screen.
Script
#Requires -Modules ActiveDirectory
# Configuration Parameters
param(
[Parameter(Mandatory=$false)]
[int]$StorageThresholdPercent = 12,
[Parameter(Mandatory=$false)]
[int]$CheckIntervalSeconds = 300,
[Parameter(Mandatory=$false)]
[int]$MaxConcurrentMoves = 20,
[Parameter(Mandatory=$false)]
[string]$LogPath = "C:\Logs\MailboxMoves"
)
# Ensure log directory exists
if (!(Test-Path $LogPath)) {
New-Item -ItemType Directory -Path $LogPath -Force | Out-Null
}
$LogFile = Join-Path $LogPath "MailboxMoveMonitor_$(Get-Date -Format 'yyyyMMdd').log"
# Logging function
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogFile -Value $logMessage
switch ($Level) {
"ERROR" { Write-Host $logMessage -ForegroundColor Red }
"WARNING" { Write-Host $logMessage -ForegroundColor Yellow }
"SUCCESS" { Write-Host $logMessage -ForegroundColor Green }
default { Write-Host $logMessage }
}
}
# Connect to Exchange
function Connect-ExchangeServer {
try {
if (!(Get-Command Get-MailboxDatabase -ErrorAction SilentlyContinue)) {
Write-Log "Connecting to Exchange Management Shell..." "INFO"
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction Stop
}
Write-Log "Connected to Exchange successfully" "SUCCESS"
return $true
}
catch {
Write-Log "Failed to connect to Exchange: $($_.Exception.Message)" "ERROR"
return $false
}
}
# Get storage information for a database using custom path
function Get-DatabaseStorageInfo {
param(
[string]$DatabaseName,
[string]$StoragePath,
[string]$ServerName
)
try {
# Check if drive letter format
$isDriveLetter = $StoragePath.Length -eq 2 -and $StoragePath -like "*:"
if ($isDriveLetter) {
# Drive letter like H: - use Win32_LogicalDisk for accurate reporting
$drives = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $ServerName -Filter "DriveType=3" -ErrorAction Stop
$volume = $drives | Where-Object { $_.DeviceID -eq $StoragePath }
if ($volume) {
$freeSpaceGB = [math]::Round($volume.FreeSpace / 1GB, 2)
$totalSpaceGB = [math]::Round($volume.Size / 1GB, 2)
$freeSpacePercent = [math]::Round(($volume.FreeSpace / $volume.Size) * 100, 2)
return @{
Database = $DatabaseName
Server = $ServerName
StoragePath = $StoragePath
FreeSpaceGB = $freeSpaceGB
TotalSpaceGB = $totalSpaceGB
FreeSpacePercent = $freeSpacePercent
BelowThreshold = ($freeSpacePercent -le $StorageThresholdPercent)
}
}
}
else {
# Mount point - use Win32_Volume
$scriptBlock = {
param($path)
$volume = Get-WmiObject -Class Win32_Volume | Where-Object {
$_.Name -eq $path -or $_.Name -eq "$path\"
}
if (!$volume) {
$allVolumes = Get-WmiObject -Class Win32_Volume | Where-Object { $_.DriveType -eq 3 }
foreach ($vol in $allVolumes) {
if ($path -like "$($vol.Name)*") {
$volume = $vol
break
}
}
}
return $volume
}
$volume = Invoke-Command -ComputerName $ServerName -ScriptBlock $scriptBlock -ArgumentList $StoragePath -ErrorAction Stop
if ($volume) {
$freeSpaceGB = [math]::Round($volume.FreeSpace / 1GB, 2)
$totalSpaceGB = [math]::Round($volume.Capacity / 1GB, 2)
$freeSpacePercent = [math]::Round(($volume.FreeSpace / $volume.Capacity) * 100, 2)
return @{
Database = $DatabaseName
Server = $ServerName
StoragePath = $StoragePath
FreeSpaceGB = $freeSpaceGB
TotalSpaceGB = $totalSpaceGB
FreeSpacePercent = $freeSpacePercent
BelowThreshold = ($freeSpacePercent -le $StorageThresholdPercent)
}
}
}
}
catch {
Write-Log "Error getting storage info for $DatabaseName at $StoragePath : $($_.Exception.Message)" "ERROR"
}
return $null
}
# Check if any target database is below storage threshold
function Test-StorageAvailability {
param([hashtable]$TargetDatabasePaths)
$belowThreshold = @()
foreach ($dbName in $TargetDatabasePaths.Keys) {
$pathInfo = $TargetDatabasePaths[$dbName]
$storageInfo = Get-DatabaseStorageInfo -DatabaseName $dbName -StoragePath $pathInfo.Path -ServerName $pathInfo.Server
if ($storageInfo) {
Write-Log "Storage - $($dbName) [$($storageInfo.StoragePath)]: $($storageInfo.FreeSpacePercent)% free ($($storageInfo.FreeSpaceGB) GB / $($storageInfo.TotalSpaceGB) GB)" "INFO"
if ($storageInfo.BelowThreshold) {
$belowThreshold += $storageInfo
Write-Log "WARNING: $dbName storage is at $($storageInfo.FreeSpacePercent)% - Below $StorageThresholdPercent% threshold!" "WARNING"
}
}
}
return $belowThreshold
}
# Clear completed move requests
function Clear-CompletedMoveRequests {
try {
$completedMoves = Get-MoveRequest -MoveStatus Completed
if ($completedMoves) {
Write-Log "Found $($completedMoves.Count) completed move request(s)" "INFO"
foreach ($move in $completedMoves) {
Remove-MoveRequest -Identity $move.Identity -Confirm:$false
Write-Log "Cleared completed move request for: $($move.DisplayName)" "SUCCESS"
}
}
}
catch {
Write-Log "Error clearing completed requests: $($_.Exception.Message)" "ERROR"
}
}
# Get mailboxes to move
function Get-MailboxesToMove {
param(
[array]$SourceDatabases,
[array]$TargetDatabases,
[int]$BatchSize
)
$mailboxesToMove = @()
foreach ($sourceDB in $SourceDatabases) {
try {
$mailboxes = Get-Mailbox -Database $sourceDB -ResultSize Unlimited |
Where-Object {
$mbx = $_
$existingMove = Get-MoveRequest -Identity $mbx.Identity -ErrorAction SilentlyContinue
$null -eq $existingMove
} |
Select-Object -First $BatchSize
$mailboxesToMove += $mailboxes
if ($mailboxesToMove.Count -ge $BatchSize) {
break
}
}
catch {
Write-Log "Error getting mailboxes from $sourceDB : $($_.Exception.Message)" "ERROR"
}
}
return $mailboxesToMove
}
# Start new move requests
function Start-NewMoveRequests {
param(
[array]$SourceDatabases,
[array]$TargetDatabases,
[int]$MaxMoves
)
$activeMoves = @(Get-MoveRequest | Where-Object { $_.Status -ne 'Completed' -and $_.Status -ne 'CompletedWithWarning' })
$availableSlots = $MaxMoves - $activeMoves.Count
if ($availableSlots -le 0) {
Write-Log "Maximum concurrent moves ($MaxMoves) already running" "INFO"
return
}
Write-Log "Available slots for new moves: $availableSlots" "INFO"
$mailboxes = Get-MailboxesToMove -SourceDatabases $SourceDatabases -TargetDatabases $TargetDatabases -BatchSize $availableSlots
if ($mailboxes.Count -eq 0) {
Write-Log "No mailboxes found to move" "INFO"
return
}
$targetIndex = 0
foreach ($mailbox in $mailboxes) {
$targetDB = $TargetDatabases[$targetIndex]
try {
New-MoveRequest -Identity $mailbox.Identity -TargetDatabase $targetDB -BadItemLimit 10 -AcceptLargeDataLoss -Confirm:$false | Out-Null
Write-Log "Started move request: $($mailbox.DisplayName) -> $targetDB" "SUCCESS"
$targetIndex = ($targetIndex + 1) % $TargetDatabases.Count
}
catch {
Write-Log "Failed to start move for $($mailbox.DisplayName): $($_.Exception.Message)" "ERROR"
}
}
}
# Suspend all active move requests
function Suspend-AllMoveRequests {
try {
$activeMoves = Get-MoveRequest | Where-Object { $_.Status -notin @('Completed', 'CompletedWithWarning', 'Suspended') }
foreach ($move in $activeMoves) {
Suspend-MoveRequest -Identity $move.Identity -Confirm:$false
Write-Log "Suspended move request for: $($move.DisplayName)" "WARNING"
}
}
catch {
Write-Log "Error suspending move requests: $($_.Exception.Message)" "ERROR"
}
}
# Resume suspended move requests
function Resume-AllMoveRequests {
try {
$suspendedMoves = Get-MoveRequest -MoveStatus Suspended
foreach ($move in $suspendedMoves) {
Resume-MoveRequest -Identity $move.Identity -Confirm:$false
Write-Log "Resumed move request for: $($move.DisplayName)" "SUCCESS"
}
}
catch {
Write-Log "Error resuming move requests: $($_.Exception.Message)" "ERROR"
}
}
# Display move request status with statistics
function Show-MoveRequestStatus {
param(
[hashtable]$TargetDatabasePaths,
[array]$SourceDatabases,
[array]$TargetDatabases
)
try {
$moves = Get-MoveRequest
if ($moves) {
$totalMoves = $moves.Count
$completedMoves = @($moves | Where-Object { $_.Status -in @('Completed', 'CompletedWithWarning') }).Count
$inProgressMoves = @($moves | Where-Object { $_.Status -in @('InProgress', 'Queued', 'AutoSuspended') }).Count
$suspendedMoves = @($moves | Where-Object { $_.Status -eq 'Suspended' }).Count
$failedMoves = @($moves | Where-Object { $_.Status -eq 'Failed' }).Count
$progressMoves = $moves | Where-Object { $_.Status -in @('InProgress', 'Queued', 'AutoSuspended', 'Suspended') }
$avgProgress = 0
if ($progressMoves) {
$totalProgress = 0
$progressCount = 0
foreach ($move in $progressMoves) {
try {
$stats = Get-MoveRequestStatistics -Identity $move.Identity -ErrorAction SilentlyContinue
if ($stats -and $stats.PercentComplete) {
$totalProgress += $stats.PercentComplete
$progressCount++
}
}
catch {
# Skip
}
}
if ($progressCount -gt 0) {
$avgProgress = [math]::Round($totalProgress / $progressCount, 2)
}
}
$sourceCounts = @{}
$targetCounts = @{}
Write-Host "`nGathering mailbox counts..." -ForegroundColor Gray
foreach ($db in $SourceDatabases) {
try {
$count = @(Get-Mailbox -Database $db -ResultSize Unlimited).Count
$sourceCounts[$db] = $count
}
catch {
$sourceCounts[$db] = "Error"
Write-Log "Could not get count for source database $db" "WARNING"
}
}
foreach ($db in $TargetDatabases) {
try {
$count = @(Get-Mailbox -Database $db -ResultSize Unlimited).Count
$targetCounts[$db] = $count
}
catch {
$targetCounts[$db] = "Error"
Write-Log "Could not get count for target database $db" "WARNING"
}
}
Write-Host "`n╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ MAILBOX MOVE PROGRESS REPORT ║" -ForegroundColor Cyan
Write-Host "║ $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ║" -ForegroundColor Cyan
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
Write-Host "║ Total Move Requests: $($totalMoves.ToString().PadLeft(4)) ║" -ForegroundColor White
Write-Host "║ Completed: $($completedMoves.ToString().PadLeft(4)) ║" -ForegroundColor Green
Write-Host "║ In Progress: $($inProgressMoves.ToString().PadLeft(4)) ║" -ForegroundColor Yellow
Write-Host "║ Suspended: $($suspendedMoves.ToString().PadLeft(4)) ║" -ForegroundColor Magenta
Write-Host "║ Failed: $($failedMoves.ToString().PadLeft(4)) ║" -ForegroundColor Red
Write-Host "║ Average Progress: $($avgProgress.ToString().PadLeft(6))% ║" -ForegroundColor Cyan
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
Write-Host "║ STORAGE STATUS ║" -ForegroundColor Cyan
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
foreach ($dbName in $TargetDatabasePaths.Keys) {
$pathInfo = $TargetDatabasePaths[$dbName]
if ($pathInfo -and $pathInfo.Path -and $pathInfo.Server) {
$storageInfo = Get-DatabaseStorageInfo -DatabaseName $dbName -StoragePath $pathInfo.Path -ServerName $pathInfo.Server
if ($storageInfo) {
$dbNameShort = if ($dbName.Length -gt 40) { $dbName.Substring(0, 37) + "..." } else { $dbName.PadRight(40) }
$storagePathStr = if ($storageInfo.StoragePath) { $storageInfo.StoragePath } else { "Unknown" }
$pathShort = if ($storagePathStr.Length -gt 35) { "..." + $storagePathStr.Substring($storagePathStr.Length - 32) } else { $storagePathStr.PadRight(35) }
$freePercent = $storageInfo.FreeSpacePercent.ToString("0.00").PadLeft(6)
$freeGB = $storageInfo.FreeSpaceGB.ToString("0.0").PadLeft(9)
$color = "Green"
if ($storageInfo.FreeSpacePercent -le $StorageThresholdPercent) {
$color = "Red"
} elseif ($storageInfo.FreeSpacePercent -le ($StorageThresholdPercent + 5)) {
$color = "Yellow"
}
Write-Host "║ $dbNameShort [$pathShort]: $freePercent% ($freeGB GB) ║" -ForegroundColor $color
}
}
}
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
Write-Host "║ DATABASE MAILBOX COUNTS ║" -ForegroundColor Cyan
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
Write-Host "║ SOURCE DATABASES: ║" -ForegroundColor Yellow
foreach ($db in $SourceDatabases) {
$count = $sourceCounts[$db]
$dbNameShort = if ($db.Length -gt 85) { $db.Substring(0, 82) + "..." } else { $db.PadRight(85) }
$countStr = if ($count -eq "Error") { "Error".PadLeft(8) } else { $count.ToString().PadLeft(8) }
Write-Host "║ $dbNameShort : $countStr remaining ║" -ForegroundColor White
}
Write-Host "║ ║" -ForegroundColor Cyan
Write-Host "║ TARGET DATABASES: ║" -ForegroundColor Green
foreach ($db in $TargetDatabases) {
$count = $targetCounts[$db]
$dbNameShort = if ($db.Length -gt 85) { $db.Substring(0, 82) + "..." } else { $db.PadRight(85) }
$countStr = if ($count -eq "Error") { "Error".PadLeft(8) } else { $count.ToString().PadLeft(8) }
Write-Host "║ $dbNameShort : $countStr total ║" -ForegroundColor White
}
Write-Host "╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
if ($totalMoves -gt 0) {
Write-Host "`n╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ ACTIVE MAILBOX MOVES ║" -ForegroundColor Cyan
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
Write-Host "║ Mailbox Name │ Status │ Progress │ Size (GB) │ Target Database ║" -ForegroundColor White
Write-Host "╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
$statusPriority = @{
'InProgress' = 1
'Queued' = 2
'AutoSuspended' = 3
'Suspended' = 4
'Completed' = 5
'CompletedWithWarning' = 6
'Failed' = 7
}
$sortedMoves = $moves | Where-Object { $_ -ne $null } | Sort-Object {
$statusVal = $_.Status
if ($statusPriority.ContainsKey($statusVal)) {
$statusPriority[$statusVal]
} else {
99
}
}, DisplayName
$displayCount = 0
$maxDisplay = 20
foreach ($move in $sortedMoves) {
if ($displayCount -ge $maxDisplay) {
$remaining = $moves.Count - $maxDisplay
Write-Host "║ ... and $remaining more move(s) ║" -ForegroundColor Gray
break
}
if ($null -eq $move) {
continue
}
$moveStats = $null
$percentComplete = 0
$mailboxSizeGB = 0
try {
if ($move.Identity) {
$moveStats = Get-MoveRequestStatistics -Identity $move.Identity -ErrorAction SilentlyContinue
if ($moveStats) {
if ($moveStats.PercentComplete) {
$percentComplete = $moveStats.PercentComplete
}
if ($moveStats.TotalMailboxSize) {
$sizeString = $moveStats.TotalMailboxSize.ToString()
if ($sizeString -match '[\d,\.]+\s*GB') {
$sizeMatch = $sizeString -replace '[^\d\.]', ''
$mailboxSizeGB = [math]::Round([double]$sizeMatch, 2)
}
elseif ($sizeString -match '[\d,]+\s*MB') {
$sizeMatch = $sizeString -replace '[^\d\.]', ''
$mailboxSizeGB = [math]::Round([double]$sizeMatch / 1024, 2)
}
elseif ($sizeString -match '[\d,]+\s*bytes') {
$sizeMatch = $sizeString -replace '[^\d]', ''
$mailboxSizeGB = [math]::Round([double]$sizeMatch / 1GB, 2)
}
}
}
}
}
catch {
if ($move.Status -and $move.Status -in @('Completed', 'CompletedWithWarning')) {
$percentComplete = 100
}
}
$statusColor = 'White'
if ($move.Status) {
$statusColor = switch ($move.Status) {
'InProgress' { 'Green' }
'Suspended' { 'Magenta' }
'Queued' { 'Cyan' }
'Completed' { 'Green' }
'CompletedWithWarning' { 'Yellow' }
'Failed' { 'Red' }
default { 'White' }
}
}
$displayNameStr = if ($move.DisplayName) { $move.DisplayName } else { "Unknown" }
$mailboxName = if ($displayNameStr.Length -gt 33) {
$displayNameStr.Substring(0, 30) + "..."
} else {
$displayNameStr.PadRight(33)
}
$statusStr = if ($move.Status) { $move.Status.ToString() } else { "Unknown" }
$status = $statusStr.PadRight(15)
$progress = "$($percentComplete.ToString().PadLeft(3))%".PadRight(8)
$sizeDisplay = if ($mailboxSizeGB -gt 0) { $mailboxSizeGB.ToString("0.00").PadLeft(9) } else { "N/A".PadLeft(9) }
$targetDBStr = if ($move.TargetDatabase) { $move.TargetDatabase.ToString() } else { "Unknown" }
$targetDB = if ($targetDBStr.Length -gt 36) {
$targetDBStr.Substring(0, 33) + "..."
} else {
$targetDBStr.PadRight(36)
}
Write-Host "║ " -NoNewline
Write-Host $mailboxName -NoNewline -ForegroundColor White
Write-Host " │ " -NoNewline
Write-Host $status -NoNewline -ForegroundColor $statusColor
Write-Host " │ " -NoNewline
Write-Host $progress -NoNewline -ForegroundColor $(if ($percentComplete -eq 100) { 'Green' } elseif ($percentComplete -ge 50) { 'Yellow' } else { 'Cyan' })
Write-Host " │ " -NoNewline
Write-Host $sizeDisplay -NoNewline -ForegroundColor Gray
Write-Host " │ " -NoNewline
Write-Host $targetDB -NoNewline -ForegroundColor Gray
Write-Host " ║"
$displayCount++
}
Write-Host "╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
}
Write-Host ""
} else {
Write-Log "No move requests found" "INFO"
}
}
catch {
Write-Log "Error displaying move status: $($_.Exception.Message)" "ERROR"
}
}
# Main execution
function Start-MailboxMoveMonitor {
Write-Log "========================================" "INFO"
Write-Log "Mailbox Move Monitor Starting" "INFO"
Write-Log "========================================" "INFO"
if (!(Connect-ExchangeServer)) {
Write-Log "Cannot continue without Exchange connection" "ERROR"
return
}
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Available Mailbox Databases:" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
$allDatabases = @(Get-MailboxDatabase | Sort-Object Name)
if ($allDatabases.Count -eq 0) {
Write-Log "No mailbox databases found" "ERROR"
return
}
for ($i = 0; $i -lt $allDatabases.Count; $i++) {
$db = $allDatabases[$i]
$serverName = if ($db.Server) { $db.Server.Name } else { "Unknown" }
Write-Host " [$($i + 1)] $($db.Name.PadRight(40)) (Server: $serverName)" -ForegroundColor White
}
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Select SOURCE database(s) to move FROM:" -ForegroundColor Yellow
Write-Host " Enter numbers separated by commas" -ForegroundColor Gray
Write-Host "========================================" -ForegroundColor Cyan
$sourceInput = Read-Host "Source database numbers"
$sourceDatabases = @()
$sourceNumbers = $sourceInput -split ',' | ForEach-Object { $_.Trim() }
foreach ($num in $sourceNumbers) {
if ($num -match '^\d+$') {
$index = [int]$num - 1
if ($index -ge 0 -and $index -lt $allDatabases.Count) {
$sourceDatabases += $allDatabases[$index].Name
Write-Log "Selected source: $($allDatabases[$index].Name)" "SUCCESS"
}
else {
Write-Log "Invalid selection: $num (out of range)" "WARNING"
}
}
}
if ($sourceDatabases.Count -eq 0) {
Write-Log "No valid source databases selected" "ERROR"
return
}
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Select TARGET database(s) to move TO:" -ForegroundColor Green
Write-Host " Enter numbers separated by commas" -ForegroundColor Gray
Write-Host "========================================" -ForegroundColor Cyan
$targetInput = Read-Host "Target database numbers"
$targetDatabases = @()
$targetNumbers = $targetInput -split ',' | ForEach-Object { $_.Trim() }
foreach ($num in $targetNumbers) {
if ($num -match '^\d+$') {
$index = [int]$num - 1
if ($index -ge 0 -and $index -lt $allDatabases.Count) {
$targetDatabases += $allDatabases[$index].Name
Write-Log "Selected target: $($allDatabases[$index].Name)" "SUCCESS"
}
else {
Write-Log "Invalid selection: $num (out of range)" "WARNING"
}
}
}
if ($targetDatabases.Count -eq 0) {
Write-Log "No valid target databases selected" "ERROR"
return
}
$overlap = $sourceDatabases | Where-Object { $targetDatabases -contains $_ }
if ($overlap) {
Write-Log "ERROR: The following databases are selected as both source AND target: $($overlap -join ', ')" "ERROR"
Write-Log "A database cannot be both source and target at the same time" "ERROR"
return
}
Write-Log "`nValidating databases..." "INFO"
$targetDatabasePaths = @{}
foreach ($db in $targetDatabases) {
try {
$dbInfo = Get-MailboxDatabase $db -Status -ErrorAction Stop
Write-Log "Validated: $db (Server: $($dbInfo.Server.Name))" "SUCCESS"
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Storage Configuration for: $db" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Enter storage location to monitor" -ForegroundColor Yellow
Write-Host " Examples:" -ForegroundColor Gray
Write-Host " - Drive letter: H:" -ForegroundColor Gray
Write-Host " - Mount point: C:\MBXData\Store1" -ForegroundColor Gray
Write-Host "========================================" -ForegroundColor Cyan
$storagePath = Read-Host " Storage Path"
$isDriveLetter = $storagePath.Length -eq 2 -and $storagePath -like "*:"
$isMountPoint = $storagePath.Length -gt 2 -and $storagePath.Contains("\")
if ($isDriveLetter) {
Write-Log " Using drive letter: $storagePath" "INFO"
}
elseif ($isMountPoint) {
Write-Log " Using mount point: $storagePath" "INFO"
}
else {
Write-Log " Invalid path format. Must be like H: or C:\MBXData\Store1" "ERROR"
return
}
$targetDatabasePaths[$db] = @{
Path = $storagePath
Server = $dbInfo.Server.Name
}
$testStorage = Get-DatabaseStorageInfo -DatabaseName $db -StoragePath $storagePath -ServerName $dbInfo.Server.Name
if ($testStorage) {
Write-Log " Storage verified: $($testStorage.FreeSpacePercent)% free ($($testStorage.FreeSpaceGB) GB)" "SUCCESS"
}
else {
Write-Log " WARNING: Could not verify storage at $storagePath" "WARNING"
}
}
catch {
Write-Log "Database validation failed for: $db - $($_.Exception.Message)" "ERROR"
return
}
}
Write-Log "`nValidating source databases..." "INFO"
foreach ($db in $sourceDatabases) {
try {
Get-MailboxDatabase $db -ErrorAction Stop | Out-Null
Write-Log "Validated source: $db" "SUCCESS"
}
catch {
Write-Log "Source database validation failed: $db" "ERROR"
return
}
}
Write-Log "Source Databases: $($sourceDatabases -join ', ')" "INFO"
Write-Log "Target Databases: $($targetDatabases -join ', ')" "INFO"
Write-Log "Storage Locations:" "INFO"
foreach ($db in $targetDatabasePaths.Keys) {
Write-Log " $db -> $($targetDatabasePaths[$db].Path) on $($targetDatabasePaths[$db].Server)" "INFO"
}
Write-Log "Storage Threshold: $StorageThresholdPercent%" "INFO"
Write-Log "Max Concurrent Moves: $MaxConcurrentMoves" "INFO"
Write-Log "Check Interval: $CheckIntervalSeconds seconds" "INFO"
$movesPaused = $false
while ($true) {
try {
Write-Log "`n--- Monitoring Cycle Started ---" "INFO"
$storageIssues = Test-StorageAvailability -TargetDatabasePaths $targetDatabasePaths
if ($storageIssues.Count -gt 0) {
if (!$movesPaused) {
Write-Log "Storage threshold exceeded - Pausing all moves" "WARNING"
Suspend-AllMoveRequests
$movesPaused = $true
}
}
else {
if ($movesPaused) {
Write-Log "Storage availability restored - Resuming moves" "SUCCESS"
Resume-AllMoveRequests
$movesPaused = $false
}
Clear-CompletedMoveRequests
Start-NewMoveRequests -SourceDatabases $sourceDatabases -TargetDatabases $targetDatabases -MaxMoves $MaxConcurrentMoves
}
Show-MoveRequestStatus -TargetDatabasePaths $targetDatabasePaths -SourceDatabases $sourceDatabases -TargetDatabases $targetDatabases
Write-Log "--- Cycle Complete - Waiting $CheckIntervalSeconds seconds ---" "INFO"
Start-Sleep -Seconds $CheckIntervalSeconds
}
catch {
Write-Log "Error in monitoring loop: $($_.Exception.Message)" "ERROR"
Start-Sleep -Seconds 60
}
}
}
Start-MailboxMoveMonitor