At my organization, I ran into an interesting situation. Our company, because of some policies which I do not comprehend, must keep departed personnel’s mailboxes for over one year. Now in most cases, I’d have exported these mailboxes to PSTs, delete the mailbox, then after a year, nix the PSTs.
The burn is that other active employees may need access to the departed person’s mailbox. That makes PST management nightmarish, so we just avoid that as an issue.
What we decided to do was to build a few Mailbox Servers for our terminated users. These servers are not members of any DAG, but just sit alone on less expensive storage because I/O isn’t nearly as critical. We gave the database and logs mount points a whole bunch of storage, so that we could grow and not really worry about this server. But to be on the safe side, I though it best to make sure that the databases grow at about the same rate. Going back to a previous post about balancing mailbox databases and creating moves, I altered that script specifically for terminated users.
The script is a little ugly, does virtually no logging, and only has the simplest error trapping, but this is for terminated user mailboxes, so what’s it matter, right? So now, on to the script:
################################################# # Script Name: Migrate-TerminatedUsers # Created By: Kevin Sparenberg # # Last Modified: 2014-01-28 # # This script will search through the "active" user databases on the Exchange 2010 # Servers and search for users with the "TERM" flag in Custom Attrbiute 4. The # Attribute's format is such: "TERM YYYY-MM-DD" where "YYYY-MM-DD" is the year, # month, and date of the users termination. # # The script specifically "balances" the mailboxes so that no single mailbox database # is drastically larger than another. # # If mailboxes are found with this attribute and over 30 days have passed since their # termination date, the server will move them to databases on the terminated user server(s). # # Assumptions: # The databases that you have configured for terminated mailbox users need to be configured # with IsExcludedFromProvisioning set to true. # The script assumes that there are no current mailbox moves taking place. If mailbox # moves ARE taking place, they are not taking into consideration with regards to EDB Sizing. # # Indicators of a Terminated Mailbox: # CustomAttribute4 is populated with "TERM YYYY-MM-DD" per the description above. # The user accounts have been moved to a separate "Disabled Users" Organizational Unit # within Active Directory (This is just good practice anyway) ################################################# # Clean up any variables that are still sitting out there suppressing errors Get-Variable | Remove-Variable -ErrorAction SilentlyContinue $Error.Clear() Clear-Host # Check to see if the Exchange 2010 Snapins are Imported. If not, add them. if ( -not ( Get-Command -Name Get-MailboxDatabase -ErrorAction SilentlyContinue ) ) { Add-PSSnapin -Name Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction SilentlyContinue } #region Variable Declaration $TerminatedUserServers = "MBXTERM*" # This can be a single server or a wildcard (as shown in this example) $NumDaysPostTerm = 30 $CreateMoveRequests = $true # Create the move requests or just run through a simulation? $Suspend = $true # Suspend the moves from the beginning $SuspendOnCompletion = $false # When you get ready to complete the move, should we go into "AutoSuspended?" $DisabledOu = "MyDomain.Root.Local/DisabledUsers/*" # All Users need to also be in this Organizational Unit or children #endregion # Get all 2010 Databases Terminated User Databases $TargetDatabases = Get-MailboxDatabase -Server $TerminatedUserServers -Status | Sort-Object Name # Build a New Collection to Track the Database Size as mailboxes are added $TargetDBs = @() $TargetDatabaseCount = $TargetDatabases.Count; $i = 1 # Total & Counter for Progress Bar # Cycle through each Target Database ForEach ( $TargetDatabase in $TargetDatabases ) { Write-Progress -Activity "Building Custom Mailbox Database Object Collection" -Status ( " " ) -CurrentOperation ( "Building Custom Object for Database: " + $TargetDatabase.Name ) -PercentComplete ( ( $i / $TargetDatabaseCount ) * 100 ) $TargetDBItem = New-Object PSObject -Property @{ Name = $TargetDatabase.Name OriginalSize = $TargetDatabase.DatabaseSize OriginalWhiteSpace = $TargetDatabase.AvailableNewMailboxSpace ProjectedSize = $TargetDatabase.DatabaseSize - $TargetDatabase.AvailableNewMailboxSpace MailboxCount = ( Get-Mailbox -ResultSize Unlimited -Database $TargetDatabase | Measure-Object ).Count DiskSize = [Microsoft.Exchange.Data.ByteQuantifiedSize]( Get-WMIObject -ComputerName $TargetDatabase.ServerName -Class Win32_Volume | Where-Object { ( $TargetDatabase.EDBFilePath.PathName -like ( $_.Caption + "*" ) ) -and ( $_.Caption.Length -gt 3 ) } ).Capacity } $TargetDBItem | Add-Member -MemberType ScriptProperty -Name ProjectedDiskFree -Value { $this.DiskSize - $this.ProjectedSize } # Add this item to the collection $TargetDBs += $TargetDBItem $i++ } Write-Progress -Activity "Building Custom Mailbox Database Object Collection" -Status "Completed" -Completed # Get all Legacy Mailboxes and Filter off any we don't want $ActiveDatabases = Get-MailboxDatabase | Where-Object { -not ( $_.IsExcludedFromProvisioning ) } $TermMailboxes = $ActiveDatabases | Get-Mailbox -ResultSize Unlimited -Filter { CustomAttribute4 -like "TERM*" } | Where-Object { $_.CustomAttribute4 -match "TERM d{4}-d{2}-d{2}" } # Create an empty collection for the move requests $MoveRequests = @() $TermMailboxCount = $TermMailboxes.Count $i = 1 # Counter ForEach ( $Mailbox in $TermMailboxes ) { Write-Progress -Activity "Building Custom Mailbox Object Collection" -Status " " -CurrentOperation ( "Mailbox: " + $Mailbox.DisplayName ) -PercentComplete ( ( $i / $TermMailboxCount ) * 100 ) # Initialize from variables for assignment later $TermDate = $null $DoMove = $false # Check to see if CustomAttribute4 matches the REGEX of 'TERM YYYY-MM-DD' if ( $Mailbox.CustomAttribute4 -match "TERM d{4}-d{2}-d{2}" ) { # Extract the date from the CustomAttribute $TermDate = Get-Date -Date ( $Matches.Values[0].Split(" ")[-1] ) # Determine if the date is far enough in the past to process the move $DoMove = ( $TermDate.AddDays($NumDaysPostTerm) -lt ( Get-Date ) ) } if ( $Mailbox.OrganizationalUnit -like $DisabledOu ) { $MoveRequest = New-Object PSObject -Property @{ DisplayName = $Mailbox.DisplayName TermNote = $Mailbox.CustomAttrbiute4 Office = $Mailbox.Office Title = ( Get-User $Mailbox ).Title SourceDB = $Mailbox.Database.Name MBXIdentity = $Mailbox.Identity MBXSize = ( Get-MailboxStatistics $Mailbox ).TotalItemSize DoMove = $DoMove TermDate = $TermDate TargetDB = $null # We'll update this later } # Add it to the Move Requests Collection $MoveRequests += $MoveRequest } else { Write-Warning "Mailbox [$( $Mailbox.DisplayName )] does not exist in a Disabled Organizational Unit.`n`tCurrent OU: [$( $Mailbox.OrganizationalUnit )].`n`tThis mailbox will not be moved." } $i++ } Write-Progress -Activity "Building Custom Mailbox Object Collection" -Status "Completed" -Completed # Filter for Only Mailboxes with DoMove set to true and also sort the Mailboxes by Mailbox Size (Largest First) $MoveRequests = $MoveRequests | Where-Object { $_.DoMove } | Sort-Object MBXSize -Descending $MoveRequestCount = $MoveRequests.Count; $i = 1 # Total & Counter for Progress Bar ForEach ( $MoveRequest in $MoveRequests ) { Write-Progress -Activity "Determining Mailbox Placement" -Status " " -CurrentOperation ( "Mailbox: " + $MoveRequest.DisplayName ) -PercentComplete ( ( $i / $MoveRequestCount ) * 100 ) # Assign the target database for this move $MoveRequest.TargetDB = ( $TargetDBs | Sort-Object ProjectedSize )[0].Name # "Bump" the projected database size with the mailbox size ( $TargetDBs | Sort-Object ProjectedSize )[0].ProjectedSize += $MoveRequest.MBXSize.Value # "Bump" the mailbox count as well ( $TargetDBs | Sort-Object ProjectedSize )[0].MailboxCount++ $i++ } Write-Progress -Activity "Determining Mailbox Placement" -Status "Completed" -Completed if ( $CreateMoveRequests ) { $i = 1 # Reset Counter ForEach ( $Move in $MoveRequests ) { Write-Progress -Activity "Creating Move Requests" -Status " " -CurrentOperation ( "Creating Move Request for " + $Move.DisplayName ) -PercentComplete ( ( $i / $MoveRequestCount ) * 100 ) ( $Move.MBXIdentity ) | New-MoveRequest -BatchName ( $Move.DisplayName + "-->" + $Move.TargetDB ) -TargetDatabase ( $Move.TargetDB ) -BadItemLimit 10 -SuspendWhenReadyToComplete:$SuspendOnCompletion -Suspend:$Suspend -WarningAction SilentlyContinue $i++ } Write-Progress -Activity "Creating Move Requests" -Status "Completed" -Completed } else { $MoveRequests | Out-GridView -Title "Projected Terminated Mailbox Moves" }
If you have any quemments (questions/comments) please let me know and I’ll try to respond in a reasonable amount of time. Until next time script-kiddies…