Exchange Database growth is something that should be planned properly. Sometimes databases start to grow rapidly over the others, a proper Local Mailbox Redistribution should be performed to maintain the load.
This script EXMB-Planner will plan the mailbox redistribution across new databases. The EXMB-Planner ensures that the new database will not exceed a certain size limit. Also, the EXMB-Planner may create a redistribution plan based on the number used on each DB.
Download the Script from here, or read it at the end of this post.
Please rename the file to .ps1
Details:
As to Microsoft’s best practices, the Max EDB size should not go more than 2 TB on High Availability replicated database and up to 200 GB for the standalone non-replicated database. But in some cases when you have a large EDB, you might need to redistribute the Mailbox between other databases to reduce the size, for example, you have a 1.7 TB of Exchange Database, and want to redistribute the mailboxes in it to a smaller size 3×500 or maybe a group of 700GB.. whatever.
Some reason to make the database smaller is to easy the recovery time as the bigger the database the higher risk of breaching the RTO/RPO SLA, or maybe need to replicate the EBD over the WAN. but which user should be migrated and how to ensure that the total migrated users are filling the wanted database size not more, not less.
The script will not create or change anything on your Exchange database, the only thing it needs is to read the mailboxes only, and it will create a plan on a CSV which can help you in the migration process.
The Powershell script I wrote below will do the following:
– Read all the mailboxes inside the required database (Usermailbox – Archive mailbox).
– Create a plan for a database with a certain size (RequireDBSizeInGB parameter), the script will calculate the number of mailboxes that should be migrated to a new database to reach the required size, so if the user sets the RequireDBSizeInGB parameter value to 500, the script will create a plan (CSV file) which include the users with a total mailbox of 500 GB or less.
– Set a number of Mailboxes in a Database (MaxMBxPerDB parameter), so you can also redistribute the users from the named database to multiple databases, each database has a certain number of mailboxes, such as 100 mailbox per-database.
– Support Archive Mailboxes (EXMBxArchiveLog), This parameter will fetch all Archive Mailboxes (not the primary only the archive) and also create a similar plan.
– Combining the two options above: You can combine both parameters together if you want each database to have a certain number of users and at the same time don’t exceed a certain number of GB, you can both parameter as required. As users mailboxes size vary, from less than 1GB up to whatever you set in your policy. So the plan will be created accordingly.
Script usage:
Example 1, Prepare a plan to redistribute users from 1.7 TB EDB to multiple databases with a MAX size of each EDB not exceeding 700 GB
#Without using the MaxMBxPerDB parameter #Without using the EXMBxArchiveLog parameter .\New-EXuserDBMigration.ps1 -DatabaseName MyBigDBName -OutputFolder C:\PathToFolder -RequireDBSizeInGB 700 -MgmtPSFullUri "http://MyExchangeServer.FQDN/powershell"
The Above script will create CSV files and how users should be redistributed.
The Files will be named as the following EXDB[N], these files contain the user’s mailboxes emailaddress property that met the required parameter, you can use these files to import them to the Exchange Server Migration Patch wizard.
Also, an Additional file will be created EX-Planfull, This file will include detailed information about the plan, you can review the file and confirm the calculation, such as the number of mailboxes size, match the requirement along with the database number.
The console will show an output with basic information such as the number of required DB, Each database and how many Mailbox it should host and the Total size of the mailboxes in it.
So the script is telling, if you create a new database and move all the users in one of the CSV to this database, the database should grow to the size set in the RequireDBSizeInGB Parameter. Please note that this excludes the Database and mailbox overhead size.
Example 2: Re-distribute users from 1 EX-DB to multiple databases, were the MAX EDB size should not exceed 700GB and the MAX number of Mailboxes in each EX-DB should not be more than 50 Mailbox Per-Database.
#Without using the -EXMBxArchiveLog Parameter. .\ExchangeStorageSpace.ps1 -DatabaseName MyBigDBName -OutputFolder C:\PathToFolder -RequireDBSizeInGB 700 -MgmtPSFullUri "http://MyExchangeServer.FQDN/powershell" -MaxMBxPerDB 50
The output of the above script after executing it on a ≈ 1.7TB DB is a collection is CSV files which include the emailaddress for the users in the EX-DB MyBigDBName, each file contains a list of 50 users (as it was passed in the parameter above -MaxMBxPerDB) and DB size is less than 700. If the total number of Mailbox size is above 700GB and the MaxMBxPerDB threshold did not reach, a new database plan (CSV) will be created.
Example 3
Using the EXMBxArchiveLog Parameter, will enable the script not only to get the Primary email address, but also the Archive Mailboxes inside the same database, the main concern is information inside this database, so if a user is hosted in MyBigDBName and his mailbox is in another database outside this database, this user archive wont show in the result. and if a user Archive is hosted in MyBigDBName and his Primary Mailbox hosted in another database, only the archive mailbox will show in the result, as we need to get the information inside this DB “MyBigDBName“
.\ExchangeStorageSpace.ps1 -DatabaseName MyBigDBName -OutputFolder C:\PathToFolder -RequireDBSizeInGB 700 -MgmtPSFullUri "http://MyExchangeServer.FQDN/powershell" -EXMBxArchiveLog
<# .SYNOPSIS This script help you in planning the migration of users from 1 DB to other smaller database. .DESCRIPTION If you have a large Exchange Database and want to move the users to other database for load distribution, this script will help you in planning such migration as it will give you an idea about how many users should be migrated to a single database and how many database required, which can be controlled by parameter .PARAMETER $DatabaseName $DatabaseName: Required, The Exchange Database Name, as you can get it using Get-Mailboxdatabase .PARAMETER $OutputFolder $OutputFolder: Required, The Path to a Folder, NOT a file where the script will write the result to. .PARAMETER $RequireDBSizeInGB $RequireDBSizeInGB: How many GB of information the new database should hold, you can set this to any number above 50. .PARAMETER $MaxMBxPerDB $MaxMBxPerDB: Not Required, if you want to set a number of users per-Mailbox database, then you can use this parameter, default value is 50000, but you can limit the number per mailbox database to be 100 or whatever as long as the value is above 10. .PARAMETER $EXMBxArchiveLog $EXMBxArchiveLog: Not Required, Type is Switch, so you only add it if you need it, This switch will enable the script to get similar list but for primary mailbox with its Archive mailbox. Note that enabling this option might significantly increase the execution time for the script, as the script will need to search all your exchange archive mailboxes for any mailbox archive located in the required database. .PARAMETER $MgmtPSFullUri $MgmtPSFullUri: Required, The URI to connect to MGMT Powershell URI to initial the session, usually its https://Exchange_Name_Space_URL/powershell ,or maybe http://servername_FQDN/Powershell You can find the URI through the following powershell command (Get-PowerShellVirtualDirectory -Server MyServerName).InternalUrl.AbsoluteUri .OUTPUTS *************************Report******************** Number of DB Required: XX Number of Archive DB required is XX ************************************* Number of Mailbox on each Proposed Database Count : XX Sum : XX ************************************* Number of Archive Mailbox on each Proposed Database Count : XX Sum : XX -------------------------- .EXAMPLE .\New-EXuserDBMigration.ps1 -DatabaseName EXDatabaseName -OutputFolder C:\MyFolder -RequireDBSizeInGB 100 -MgmtPSFullUri "http://myserver.FQDN/powershell" -EXMBxArchiveLog .NOTES Feel free and let me know if you got any comment by sending me an email to farisnt@gmail.com #> [cmdletbinding()] param( [parameter(mandatory=$true)]$DatabaseName, [parameter(mandatory=$true)]$OutputFolder, [parameter(mandatory=$true)]$RequireDBSizeInGB, [parameter(mandatory=$true)]$MgmtPSFullUri, [parameter(mandatory=$false)][int]$MaxMBxPerDB=50000, [parameter(mandatory=$false)][switch]$EXMBxArchiveLog=$false ) Function Get-ExDatabaseusage{ [cmdletbinding()] param( [parameter(Mandatory=$true)]$DBName, [parameter(mandatory=$false)]$RequiredDBSize ) $UsersResults=@() [System.Collections.ArrayList]$ExchCmdLine=@() [System.Collections.ArrayList]$ExchArchiveCmdLine=@() [System.Collections.ArrayList]$ExchArchiveCmdLine=@() $ExCMDWithArchive="" Write-Host "Getting users information, this might take few Minuts to complete... Please wait" -ForegroundColor Yellow Write-Host "Maybe during this we can check your facebook or take a coffee break :)" -ForegroundColor Yellow Write-Host "The process depend on the hardware and the speed of Exchange server response"-ForegroundColor Yellow $ReadMailboxFromDB=get-mailbox -Database $DBName foreach ($singleMailbox in $ReadMailboxFromDB){ Write-Host "Getting " -NoNewline Write-Host $($singleMailbox) -NoNewline -ForegroundColor Green Write-Host " Information..." $ExchCmdLine.Add(($singleMailbox.PrimarySmtpAddress | Get-MailboxStatistics |select DisplayName,TotalItemSize, @{N="EmailAddress";E={$singleMailbox.PrimarySmtpAddress}})) |Out-Null } if ($EXMBxArchiveLog){ Write-Host "Enabling EXMBxArchiveLog, will increase the time the script will take to complete, so please wait..." -ForegroundColor Green Write-Host "Searching for Archive mailboxes in the required database..." -ForegroundColor Green $ReadMailboxFromDBWithArchive=get-mailbox foreach ($singleMailboxwithArc in $ReadMailboxFromDBWithArchive){ Write-Host "Getting Archive Info for " -NoNewline Write-Host $singleMailboxwithArc -NoNewline -ForegroundColor red Write-Host " if its available..." $ExCMDWithArchive=$singleMailboxwithArc.PrimarySmtpAddress | Get-MailboxStatistics -Archive -ErrorAction silentlycontinue|Where {$_.databasename -like $DBName} |select DisplayName,TotalItemSize, @{N="EmailAddress";E={$singleMailboxwithArc.PrimarySmtpAddress}} if ($ExCMDWithArchive -notlike $null){ $ExchArchiveCmdLine.Add(($ExCMDWithArchive | where {$_.displayname -notlike $null})) |Out-Null } } } #### Get Value for TotalItemSize,prepare the format and convert it to Int $UsersResults=Prepare-thelist -UsersList $ExchCmdLine if ($ExchArchiveCmdLine){ $ExchArchiveCmdLine=Prepare-thelist -UsersList $ExchArchiveCmdLine return $UsersResults,$ExchArchiveCmdLine } return $UsersResults } function Prepare-thelist { [cmdletbinding()] param( [parameter(mandatory=$true)]$UsersList ) $parsedResult=@() Foreach ($singlemb in $UsersList){ $formatedusers=New-Object PSObject $formatedusers | Add-Member -NotePropertyName "User" -NotePropertyValue $singlemb.DisplayName [string]$Strval1=($singlemb.TotalItemSize.Value) $formatedusers | Add-Member -NotePropertyName "MailboxGB" -NotePropertyValue ([math]::Round( ([int64](($Strval1.Substring($Strval1.IndexOf("(")+1)).Split(" ")[0]).Replace(",","")/1GB),3)) $formatedusers | Add-Member -NotePropertyName "EmailAddress" -NotePropertyValue $singlemb.EmailAddress $parsedResult+=$formatedusers } return $parsedResult } function Prepare-MyDBLog { [cmdletbinding()] param( [parameter(mandatory=$true)]$UsersList, [parameter(mandatory=$true)][int]$RequireDBSizeInGB, [parameter(mandatory=$false)][int]$MaxMBxPerDB=50000 ) $Databases=@() $i=1 $TotalNewDBSize=0 foreach ($SingleUser in $UsersList){ $databasename=New-Object psobject $databasename | Add-Member -NotePropertyName "DBName" -NotePropertyValue "" $databasename | Add-Member -NotePropertyName "UserMBX" -NotePropertyValue "" $databasename | Add-Member -NotePropertyName "UserMBXSize" -NotePropertyValue "" $databasename | Add-Member -NotePropertyName "EmailAddress" -NotePropertyValue "" if (($Databases| where {$_.DBName -match $i}).count -ge $MaxMBxPerDB){ $TotalNewDBSize=$RequireDBSizeInGB +1 } if (($SingleUser.MailboxGB + $TotalNewDBSize)-gt $RequireDBSizeInGB){ $i++ $TotalNewDBSize=0 } $databasename.DBName ="$i" $databasename.UserMBX =$SingleUser.User $databasename.UserMBXSize =$SingleUser.MailboxGB $TotalNewDBSize=$TotalNewDBSize+$SingleUser.MailboxGB $databasename.Emailaddress=$SingleUser.EmailAddress $Databases+=$databasename } return $Databases } $ErrorActionPreference="stop" ######Validation of Parameters ######## try{ Write-Host "Checking Parameter...Please wait" Write-Host "connecting to Exchange Server... Please wait" -ForegroundColor Yellow if (!(Get-PSSession | where {($_.ConfigurationName -like "microsoft.exchange") -and ($_.State -like "Opened")})){ $ExchangeSession=New-PSSession -ConnectionUri $MgmtPSFullUri -ConfigurationName microsoft.exchange -ErrorAction Stop -AllowClobber Import-PSSession $ExchangeSession Write-Host "Connection is established and will start getting the result.. please wait" } Write-Host "Checking Database name...$($DatabaseName)" Get-MailboxDatabase -Identity $DatabaseName -ErrorAction stop Write-Host "" Write-Host "Testing Output Folder" $temppath=Join-Path $OutputFolder -ChildPath "tmpwrite.tmp" Add-Content -Path $temppath -Value "Writing test value" if ($RequireDBSizeInGB -lt 50){Write-Host "RequireDBSizeInGB should be bigger than 50"; return} if ($MaxMBxPerDB -lt 10){Write-Host "MaxBMxPerDB should be higher than 10"; return} } catch{ Write-Host $_.exception.message -ForegroundColor Red return } Write-Host "Validation is completed, lets start the fun :)" if (!($EXMBxArchiveLog)){ $AllUsersFromDB=Get-ExDatabaseusage -DBName $DatabaseName | Sort-Object MailboxGB $TotalDBs=Prepare-MyDBLog -UsersList $AllUsersFromDB -RequireDBSizeInGB $RequireDBSizeInGB -MaxMBxPerDB $MaxMBxPerDB } Else{ $AllUsersFromDB,$AllUsersArchFromDB=Get-ExDatabaseusage -DBName $DatabaseName | Sort-Object MailboxGB $TotalDBs=Prepare-MyDBLog -UsersList $AllUsersFromDB -RequireDBSizeInGB $RequireDBSizeInGB -MaxMBxPerDB $MaxMBxPerDB $TotalArcDBs=Prepare-MyDBLog -UsersList $AllUsersArchFromDB -RequireDBSizeInGB $RequireDBSizeInGB -MaxMBxPerDB $MaxMBxPerDB } Write-Host "*************************Report********************" -ForegroundColor Green Write-Host "Number of DB Required:" -NoNewline -ForegroundColor Green Write-Host $TotalDBs[-1].DBName -ForegroundColor Red if ($EXMBxArchiveLog){Write-Host "Number of Archive DB required is" -ForegroundColor Green -NoNewline write-host $TotalArcDBs[-1].DBName -ForegroundColor red} Write-Host "*************************************" -ForegroundColor Yellow Write-Host "Number of Mailbox on each Proposed Database" $TotalDBs | export-csv -Path (Join-Path -Path $OutputFolder -ChildPath "Ex-Planfull.csv") -NoTypeInformation -Force for ($i=1;$i -le ([int]$TotalDBs[-1].DBName);$i++){ $TotalDBs| where {$_.DBName -match $i} | Measure-Object -Property UserMBXSize -Sum | select count,sum $TotalDBs| where {$_.DBName -match $i} | select emailaddress |export-csv -Path (join-path -path $OutputFolder -childpath "\EXDB$i.csv") -NoTypeInformation |Out-Null } if ($EXMBxArchiveLog){ Write-Host "*************************************" -ForegroundColor Yellow Write-Host "Number of Archive Mailbox on each Proposed Database" $TotalArcDBs | export-csv -Path (Join-Path -Path $OutputFolder -ChildPath "ExArch-Planfull.csv") -NoTypeInformation -Force for ($i=1;$i -le ([int]$TotalArcDBs[-1].DBName);$i++){ $TotalArcDBs| where {$_.DBName -match $i} | Measure-Object -Property UserMBXSize -Sum | select count,sum $TotalArcDBs| where {$_.DBName -match $i} | select emailaddress |export-csv -Path (join-path -path $OutputFolder -childpath "\Arch-EXDB$i.csv") -NoTypeInformation |Out-Null } }
How about read these topics too
I see this as being a very handy tool, when I attempt to run it on a Exchange 2019 CU22 server each user mailbox is getting the following error and the CSV files are empty.
“The input object cannot be bound to any parameters for the command either because the command does not take pipeline
input or the input and its properties do not match any of the parameters that take pipeline input.
+ CategoryInfo : InvalidArgument: ({USER EMAIL}:PSObject) [Get-MailboxStatistics], ParameterBinding
Exception
+ FullyQualifiedErrorId : InputObjectNotBound,Get-MailboxStatistics
+ PSComputerName : {SERVER NAME}
Thank you
Hi,
Thanks for your comment, I did not test it on Ex2019, but I will update it soon and make sure that it works fine.
In regards to Exchange 2019 I had the same error.
What I did:
#89
in line 88 i did added:
$mbsize = $null
$mbsize = Get-MailboxStatistics $singleMailbox.Userprincipalname
$ExchCmdLine1 = New-Object PSObject
$ExchCmdLine1 | Add-Member PrimarySMTPaddress $_.PrimarySMTPAddress
$ExchCmdLine1 | Add-Member Displayname $mbsize.DisplayName
$ExchCmdLine1 | Add-Member TotalItemSize $mbsize.TotalItemSize
$ExchCmdLine += $ExchCmdLine1
It works now. I didnt done archives, so most probably shall be done in similar way.
Great script.
Apologies, should be as follow:
$mbsize = $null
$mbsize = Get-MailboxStatistics $singleMailbox.Userprincipalname
$ExchCmdLine1 = New-Object PSObject
$ExchCmdLine1 | Add-Member Displayname $mbsize.DisplayName
$ExchCmdLine1 | Add-Member TotalItemSize $mbsize.TotalItemSize
$ExchCmdLine1 | Add-Member EmailAddress $Singlemailbox.PrimarySMTPAddress
$ExchCmdLine += $ExchCmdLine1
Thanks for the update, I will try to update the script with the new version asap.
Appreciate your comments and correction.
Could you please share the modified script
If I could suggest some fancy improvement it would be to make possible to target against multiple DBs at once. So if You would like to optimise/reorganize whole DB setup it could be done in one shot.
Didn’t seem to produce anything useful on Exchange 2016, same issue as the firs guy and produced a pretty much blank csv with some 0’s and 1’s
This script is not updated since longtime
BTW, I will take a look on it.
What is the Exchange Server version you are using