Mailbox databases in Exchange Server 2010 doesn`t contain any information regarding the numbers of mailboxes stored in them. A common approach to retrieve this information is using the Exchange Management Shell.
Two examples
1: #Example 1
2: (Get-MailboxDatabase) | ForEach-Object {Write-Host $_.Name (Get-Mailbox -Database $_.Name).Count}
1: #Example 2
2: (Get-MailboxDatabase) | Select-Object Name,@{Name="Number of users";Expression={(Get-Mailbox -Database $_.name).Count}}
Either of these works fine if you want to get the number of mailboxes quick and dirty. However, in larger environments, these one-liners may run for a while.
I did a quick measurement using the Measure-Command cmdlet; Example 1 ran for 36 seconds and example 2 ran for 30 seconds. The environment I ran this in got 5 mailbox databases and approximately 1300 mailboxes.
When running these as one-liners from the Exchange Management Shell, or as part of a scheduled task, the performance might not be an issue.
A colleague of mine was using the Cmdlet Extension Scripting Agent to provision new mailboxes to the mailbox database with the least number of mailboxes in it. In this scenario the performance is key. To achieve better performance, we used an LDAP-query using System.DirectoryServices.DirectorySearcher.
Get-MDBMailboxCount function
I created a PowerShell function to retrieve the number of mailboxes per mailbox database using the DN of a mailbox database as an argument.
function Get-MDBMailboxCount ([string]$DN) {
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry ("LDAP://$(([system.directoryservices.activedirectory.domain]::GetCurrentDomain()).Name)")
$Searcher.Filter = "(&(objectClass=user)(homeMDB=$DN))"
$Searcher.PageSize = 10000
$Searcher.SearchScope = "Subtree"
$results = $Searcher.FindAll()
$returnValue = $results.Count
#dispose of the search and results properly to avoid a memory leak
$Searcher.Dispose()
$results.Dispose()
return $returnValue
}
A problem we stumbled upon was that wildcards didn`t seem to work on the homeMDB-attribute, so we couldn`t use *Mailbox Database Name* for this value.
When looking up on MSDN, it turned out that wildcards are not supported on LDAP DN`s:
For queries that include attributes of DN syntax in a filter, specify full distinguished names. Wildcards (for example, cn=John*) are not supported.
That`s the reason for the function taking DN as an argument, and not the name of a mailbox database.
The function runs the query against the current domain, and hence you don`t need to specify a domain for the LDAP-query.
Back to the performance part, I now ran Measure-Command against the following one-liner:
1: (Get-MailboxDatabase) | ForEach-Object {Write-Host $_.Name (Get-MDBMailboxCount -DN $_.DistinguishedName)}
This time the performance was 3,7 seconds, almost 10x faster. Actually, using a foreach-loop instead of Foreach-Object also makes it slightly faster; 3,5 seconds. You can read up on the difference between these two approaches here.
Get-MDBMailboxCount script
Having the Get-MDBMailboxCount function, I also decided to create a script using this function, which generates a CSV-file.
The script uses a foreach-loop to go through each database in the Exchange-organization, and then creates a custom object for each database containing the name and number of mailboxes. The custom objects are added to an array which can be used for other things like determining the smallest/largest database. You could also use this information to generate graphs like I showed in this blog-post.
Note that I`ve only tested the above against Exchange Server 2010. I suppose it also should work against Exchange Server 2007, if someone can verify this it would be nice if you could leave a comment below.
Update 04.08.2011: The function has been updated to avoid a memory leak when the function is called multiple times. Thanks to Weston McNamee for the tip (see his comment below).
You can also use
get-mailbox -resultsize unlimited | group-object -property Database -noelement
In my environment ~5000 mailboxes, your example #1 and #2 take 89 seconds, the above takes 52 seconds.
Your get-mdbmailboxcount method takes 25 seconds, so it still wins, though it’s also good to know there is a slightly faster way to group and count using exchange powershell commands only.
Nice to know, thanks for sharing the information!
Sweet. This is just what I was looking for.
In my environment with 175 databases and 75000 mailboxes:
your script = 3 minutes
KB’s comment = killed the process after 35 minutes.
The above code has a memory leak issue. Powershell process memory consumption continues to grow after every call to the function (I’ve commented out everything else to isolate). $Searcher.Dispose() isn’t enough either. Still researching how to fix.
Solution Found:
the last line of the function should be rewritten to this:
# The results from the searcher need to be assigned to a variable so we can dispose of them per the MS article:
# http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx
#
# Due to implementation restrictions, the SearchResultCollection class cannot release all of its unmanaged resources when it is garbage collected.
# To prevent a memory leak, you must call the Dispose method when the SearchResultCollection object is no longer needed.
#
$results = $Searcher.FindAll()
$returnValue = $results.Count
#dispose of the search and results properly to avoid a memory leak
$Searcher.Dispose()
$results.Dispose()
return $returnValue
Thanks Weston, I`ve updated the post.
By the way, I wanted to thank you for the post. I’m in the BPOS team at Microsoft, and I always hated the idea of using @(Get-Mailbox -ResultSize Unlimited).count to get a mailbox count, because get-mailbox loads a bunch of extra stuff in memory that I don’t really care about.
Pingback: Exchange 2010 – размер базы и количество почтовых ящиков в ней | ILYA Sazonov: ITPro
I understand what’s going on in the script, but how can I use it to determine the smallest database? In other words I’ve combined the Get-MDBMailboxCount function and the last one-liner listed above and all I want to see is the database with the least number of mailboxes on it.
Thanks in advance.
Hi,
Try this: Get-MailboxDatabase | Select-Object Name,@{Name=”Count”;Expression={Get-MDBMailboxCount -DN $_.DistinguishedName}} | Sort-Object count | Select-Object -First 1
Worked like a champ. That beats the way I was doing this by 15 minutes.
One more question if I may. Is it possible to return just the name of the database and not the count? I would like to be able to assign the line you sent me to a variable, and then use that variable as the database to where I want a mailbox created or whatever.
Thanks.
Sure:
Get-MailboxDatabase | Select-Object Name,@{Name=”Count”;Expression={Get-MDBMailboxCount -DN $_.DistinguishedName}} | Sort-Object count | Select-Object -First 1 | Select-Object name
Worked like a champ.
Thanks for all your help.
hi, how can i edit the above script to count mailboxes with ex.: (custom attribute 1 = ‘test’) ???
another notice when running any of the two commands at the beginning, it gets the DB names at the first coloum (ex: 8 DB names) and only the count value for the first (4 DBs ) …. any advise??
Param(
[Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[String]
$DistinguishedName
)
Process
{
[ADSI] $objDB = “GC://$($DistinguishedName -replace ‘/’,'\/’)”
if ( ($objDB.objectClass -ne $null) -and ($objDB.objectClass.Contains(“msExchPrivateMDB”)) )
{
[Math]::Max(0,([Int] ($objDB.homeMDBBL.Count)) – 1)
}
}
==> save the code to Get-MailboxCount.ps1
==> use with pipelines, eg: Get-MailboxDatabase | .\Get-MailboxCount.ps1
==> takes less than 35 ms for one DBs
==> takes less than 220 ms for eight DBs
Trick is to use the homeMDBBL backlink attribute and only gt the count (minus 1 because of the SystemMailbox for the DB’s Store, just in case the [Math]::Min() statement allows returning >=0 but if performance is your only goal, then you can remove it…).
It’s not aware of the mailbox type (usermailbox, room, equipment, etc) this could be added at some additional costs although.
Me again
Consider the two functions below and test them in your environment. I’m pretty sure that they’ll be slightly faster than yours…
Doing some tests, I realized the FindAll() method is pretty slow since it returns all attributes by default. But who cares about all attributes to perform a count? Therefore the way you use it in your script slows down the beast. To resolve this, remove all properties from the result set and only add one, a specific one: ADsPath. You’ll demultiply speed of your function and your colleage will be happy-happy
Remember:
- Count-MailboxFaster ==> uses the homeMDBBL attribute
- Count-Mailbox ==> searches against all user/inetOrgPerson in the Forest – obviousuly can’t be faster that the first, but doesn’t require read access to the mailbox DB AD object (which in your case isn’t an issue since you’re running through the Exchange Server / Scripting Agent so you’ve got full power…)
function Count-Mailboxes
{
Param(
[Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[String]
$DistinguishedName
)
Begin
{
[ADSI] $RootDSE = “LDAP://RootDSE”
[Object] $RootDomain = New-Object System.DirectoryServices.DirectoryEntry “GC://$($RootDSE.rootDomainNamingContext)”
[String] $Query = “(objectCategory=person)(|(objectClass=user)(objectClass=interOrgPerson))(mailNickName=*)(!(mailNickName=SystemMailbox{*})(msExchHomeServerName=*))”
[Object] $Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $RootDomain
$Searcher.PageSize = 1000
$null = $Searcher.PropertiesToLoad.Clear()
$null = $Searcher.PropertiesToLoad.Add(“ADsPath”)
$Searcher.CacheResults = $false
}
Process
{
$Searcher.Filter = “(&$Query(homeMDB=$DistinguishedName))”
$Results = $Searcher.FindAll()
$Results.Count
$Results.Dispose()
}
End
{
$Searcher.Dispose()
}
}
function Count-MailboxesFaster
{
Param(
[Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[String]
$DistinguishedName
)
Process
{
[ADSI] $objDB = “GC://$($DistinguishedName -replace ‘/’,'\/’)”
if ( ($objDB.objectClass -ne $null) -and ($objDB.objectClass.Contains(“msExchPrivateMDB”)) )
{
$objDB.homeMDBBL.Count-1
}
}
}
2 additional optimisations for environments with a bunch of users (your script logic with the searcher ran 11mins @ our company, get-mailbox wasn’t even worth trying):
- Since we’re interested in the count only, add $Searcher.PropertiesToLoad.Add($null) (and if you don’t like the output “0″, pipe it to $null
- Create an index on homeMDB in AD
Result: 6s.