Retrieve number of mailboxes per database in Exchange Server 2010

Mailbox databases in Exchange Server 2010 doesnt 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


 

 


 

 

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.


 

 

 

 

 

 

 

 

A problem we stumbled upon was that wildcards didnt seem to work on the homeMDB-attribute, so we couldnt use *Mailbox Database Name* for this value.

When looking up on MSDN, it turned out that wildcards are not supported on LDAP DNs:

For queries that include attributes of DN syntax in a filter, specify full distinguished names. Wildcards (for example, cn=John*) are not supported.

Thats 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 dont need to specify a domain for the LDAP-query.

Back to the performance part, I now ran Measure-Command against the following one-liner:


 

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).

18 thoughts on “Retrieve number of mailboxes per database in Exchange Server 2010

  1. 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.

  2. 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.

  3. 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.

  4. 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

  5. Pingback: Exchange 2010 – размер базы и количество почтовых ящиков в ней | ILYA Sazonov: ITPro

  6. 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.

      • 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.

  7. 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??

  8. 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.

  9. 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
    }
    }
    }

  10. 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.