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

Discover more from Everything-PowerShell

Subscribe now to keep reading and get access to the full archive.

Continue reading