PoshCode Logo PowerShell Code Repository

MoveMailboxBySize (modification of post by view diff)
embed code: <script type="text/javascript" src="http://PoshCode.org/embed/2029"></script>download | new post

This script was developed to assist a customer with moving customizable batches of users, starting smallest mailboxes first in batches, and move them into datastores sorted by last name. This script is modular and can be extended to different filtering mechanisms, or a different datastore sorting criteria.

  1. ### VARIABLES ###
  2.  
  3. #Stop the script if an error ever occurs
  4. $ErrorActionPreference = "stop"
  5.  
  6. #Base Directory
  7. $BaseDir="C:\Scripts"
  8.  
  9. #Place for Migration Reports
  10. $ReportFileDir="C:\Scripts\Logs"
  11.  
  12. #Exclusion List of Mailboxes to Not Move. This should be a return-separated list of mailbox display names to avoid.
  13. $ExclusionListFile="$BaseDir\ExcludedMailboxes.txt"
  14.  
  15. #Special Mailboxes. These are mailboxes that should be completely exempt, including errorChecking to see if they exist, such as non-standard system mailboxes or others that may cause problems with migration.
  16. $SpecialMailboxes="$BaseDir\SpecialMailboxes.txt"
  17.  
  18. #Excluded 2003 Hosts not to Scan for Mailboxes
  19. $ExcludedHosts="$BaseDir\ExcludedHosts.txt"
  20.  
  21. #Number of Mailboxes to Move in this Batch
  22. $MailboxCount = 10000
  23.  
  24. #Output if a user is excluded by the Exclusion List
  25. $ShowExclusions=$true
  26.  
  27. #Migrate for real. Only do a "what-if" evaluation if false.
  28. $MigrateMailboxes = $false
  29.  
  30. ### Functions ###
  31.  
  32. #Select-MigrateMailboxDatabase
  33. #***READ ME*** REPLACE THE REGEX AND Mailbox Database Names as appropriate.
  34. function Select-TargetDatabase ($NameToMatch) {
  35.     $TargetMailboxDatabaseName = switch -regex ($NameToMatch) {
  36.             "^[0-9].*" {"1 - Mailbox Store A - D"}
  37.             "^[a-d].*" {"1 - Mailbox Store A - D"}
  38.             "^[e-h].*" {"2 - Mailbox Store E - H"}
  39.             "^[i-l].*" {"3 - Mailbox Store I - L"}
  40.             "^[m-p].*" {"4 - Mailbox Store M - P"}
  41.             "^[q-t].*" {"5 - Mailbox Store Q - T"}
  42.             "^[u-z].*" {"6 - Mailbox Store U - Z"}
  43.             default {throw "No Datastore was matched by Select-MigrateMailboxDatabase for $MailboxDisplayName. Check that the user's name will be matched by the select-targetdatabase matching criteria"}
  44.     }
  45.     return $TargetMailboxDatabaseName
  46. }
  47.  
  48. ### INITIALIZE ###
  49.  
  50. #Load Exchange Snapins if not present
  51. if (!(get-pssnapin Microsoft.Exchange.Management.Powershell.Admin -erroraction SilentlyContinue)) {
  52.     write-host -fore cyan "Loading Exchange Powershell Snapins..."
  53.     get-pssnapin -registered | where {$_.name -match "Microsoft.Exchange"} | add-pssnapin
  54. }
  55.  
  56. #Load 2003 Statistics Formatting Module
  57. if (!(get-module Format-2003MailboxStatistics)) {import-module "$BaseDir\Format-2003MailboxStatistics.psm1"}
  58.  
  59. ### END INITIALIZE ###
  60.  
  61. ### SCRIPT ###
  62. write-host -fore darkcyan "===Mailbox Move Script START==="
  63.  
  64.  
  65. #Trim any leading/trailing whitespace from the exclusion list
  66. $ExclusionList = (get-content $ExclusionListFile) | foreach {$_.trim()}
  67.  
  68. #Validate Exclude List. Ensure every item in the exclusion list has a corresponding mailbox. This way we can be sure we don't accidentally miss someone because their name was misspelled.
  69. write-host -fore darkcyan "= Validating Exclusion List..." -nonewline
  70. $ExclusionList | where {(get-content $SpecialMailboxes) -notcontains $_} | foreach {
  71.     if (!(get-mailbox "$_" -errorAction SilentlyContinue)) {throw "Validating the Exclusion List failed. There is no Exchange Mailbox with the display name $_. Verify that the name in the Exclusion List is correct and try again."}
  72. }
  73. write-host -fore green "SUCCESS"
  74.  
  75. ###Collect mailbox statistics on all legacy mailboxes (aka not migrated to 2007) in the organization.
  76.  
  77. #First Get a list of all Exchange 2003 Servers in the organization
  78. write-host -fore darkcyan "= Collecting Exchange 2003 Server Information..." -nonewline
  79. $EX2003Servers = get-exchangeserver | where {$_.AdminDisplayVersion -match "6.5"}
  80. write-host -fore green "SUCCESS"
  81.  
  82. #Collect Mailbox Statistics for Each Server
  83. $2003MailboxStats = @()
  84. $EX2003Servers | where {(get-content $ExcludedHosts) -notcontains $_.Name} | foreach {
  85.     $ServerName = $_.Name
  86.     write-host -fore darkcyan "= Collecting Exchange 2003 Mailbox Statistics on $ServerName..." -nonewline
  87.     $MailboxWMIInfo = Get-WMIObject -ComputerName $ServerName `
  88.         -Namespace "root/MicrosoftExchangeV2" -Class "Exchange_Mailbox"
  89.     $2003MailboxStats += $MailboxWMIInfo | Format-2003MailboxStatistics
  90.     write-host -fore green "SUCCESS"
  91. }
  92.  
  93. #Check if each Mailbox is on the exclusion list, and filter it out if it is. We do this before mailbox selection in case the top X results are all excluded people, so we don't have to run the script a second time.
  94. write-host -fore darkcyan "= Excluding Mailboxes on the Exclusion, System, and Keyword Lists..."
  95.  
  96. $MailboxMoveCandidates = $2003MailboxStats | `
  97. where {(get-content $SpecialMailboxes) -notcontains "$($_.MailboxDisplayName)"} | `
  98. where {
  99.         #@@BUGFIX - This logic is not 100% sound, several implications are made. Works fine but could be cleaner.
  100.        
  101.         #Set some variables for ease-of-use later
  102.         $candidate = $_
  103.         $candidateName = $candidate.MailboxDisplayName.Trim()
  104.        
  105.        
  106.         #Filter Out System Mailboxes
  107.         if ($candidateName -match 'SMTP ' ) {return $false}
  108.         elseif ($candidateName -match 'SystemMailbox'){return $false}
  109.        
  110.         #Filter Out Disabled Users
  111.         elseif ($candidate.DateDiscoveredAbsentInDS) {
  112.             if ($showExclusions) {write-host -fore yellow "= * Skipping $candidateName because the mailbox has no corresponding Active Directory Object."}
  113.             return $false;
  114.         }
  115.        
  116.         #Verify user has a corresponding AD object
  117.         elseif (!(get-mailbox $candidate.Identity -erroraction silentlyContinue)) {
  118.             if ($showExclusions) {write-host -fore red "= * Skipping $candidateName because could not get mailbox info. Verify that the user has an Active Directory Account."}
  119.             return $false;
  120.         }
  121.        
  122.         #Verify a user is not on the exclusion List.
  123.         elseif ($ExclusionList -notcontains "$candidateName") {
  124.             return $true;
  125.         }
  126.        
  127.  
  128.        
  129.         #Exclude everything else as a safety check if it doesn't meet the above rules.
  130.         else {
  131.             if ($showExclusions) {write-host -fore yellow "= * $candidateName was excluded"}
  132.             return $false;
  133.         }
  134. }
  135.  
  136. write-host -fore green "= Excluding Mailboxes SUCCESS"
  137.  
  138. #Select Top X Users
  139. $MailboxesToMove = $MailboxMoveCandidates | sort size | select -first $MailboxCount
  140.  
  141. #Display Mailboxes that will be moved and where they will be moved to. We do this by creating new objects with Select that have a new targetDatastore parameter, and run the detection program to find the correct datastore and populate.
  142. $MBDisplay = $MailboxesToMove | select MailboxDisplayName,@{Name='Size (KB)';Expression={$_.size}},serverName,NameToMatch,targetDatabase | foreach {
  143.     $_.nameToMatch = $_.MailboxDisplayName.split() | select -last 1
  144.     $_.targetDatabase = Select-TargetDatabase "$($_.nameToMatch)";$_
  145. }
  146.  
  147. #Separated this out so that it will process all objects BEFORE showing the out-gridview in case of any errors.
  148. $MBDisplay | out-gridview -title "List of Mailboxes to Migrate in This Batch"
  149.  
  150. if ((read-host "Do you wish to move these $MailboxCount users? Type MIGRATE to perform the migration or anything else to exit.") -notlike "MIGRATE") {throw "Migration Cancelled at User Request";exit 10}
  151.  
  152. #Perform the Migration.
  153.  
  154.     $MailboxesToMove | foreach {
  155.         $mailboxToMove = get-mailbox $_.Identity
  156.         #@@BUGFIX - This is sloppy, would be better to query the AD last name. Is fine for 99% of users.
  157.         $NameToMatch = $_.MailboxDisplayName.split() | select -last 1
  158.         $targetDatabaseName = Select-TargetDatabase $NameToMatch
  159.         #@@BUGFIX - This is also sloppy, but we would need all kinds of logic to get the database server and storage group first and this works just as well for 99% of cases as long as target datastore names are unique (they have to be in EX2010).
  160.         $targetDatabase = get-mailboxdatabase | where {$_.name -like "$targetDatabaseName"}
  161.         $reportFileName = "$ReportFileDir\$($mailboxToMove.Alias)-$(get-date -format "yyyy-MMM-dd-HHmmss").xml"
  162.        
  163.         #Just do a whatif unless MigrateMailboxes is set to true        
  164.         if ($MigrateMailboxes) {move-mailbox $mailboxToMove -targetdatabase $targetDatabase -reportfile $reportFileName -Confirm $false}
  165.         else {move-mailbox $mailboxToMove -targetdatabase $targetDatabase -reportfile $reportFileName -whatif}
  166.     }
  167.  
  168. #Free Up Memory by deleting variable we don't need anymore.
  169. #remove-variable -name MailboxMoveCandidates
  170.  
  171. ###TODO: Rewrite 2003Stats as a pipeline function, get stats, sort by name and filter, exclude, and then kick off move mailbox with -whatif unless MigrateMailboxes is set.

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me