A Question Provokes Some New Thoughts About How Best to Report Exchange Mailbox Statistics
The roots of Practical365.com lie in ExchangeServerPro.com, a site dedicated to all aspects of Exchange Server management. This means that the site still supports hundreds of posts written in the 2010-2015 period. Questions still arise for the topics covered by those posts, and we do our best to answer them, even if the questions cover issues for long-obsolete servers.
A recent question about an article covering reporting folder statistics for Exchange mailboxes asked how to find the folder with an item with the oldest received date for mailboxes over 50 GB. The PowerShell code written by the questioner is a good example of how complex a set of piped commands can become. Piped one-line commands can be a great way to get things done, but they are also difficult to read and hard to maintain, a point made by Michel de Rooij in his Practical PowerShell column. In any case, I tried to help with a version of the code that worked for me. At least, it works for Exchange Online.
This experience got me thinking about the many scripts written to report Exchange mailbox statistics. This is one of the classic Exchange PowerShell scripts and most Exchange administrators probably have their own version. My last run at the topic used Graph mailbox usage data instead of Exchange cmdlets to gain some speed. Could the world do with yet another version? Well, maybe so.
A New Take on a Classic Script
My new take on this classic script is available from GitHub. The script isn’t fast because it uses the Get-ExoMailboxStatistics and Get-ExoMailboxFolderStatistics cmdlets, neither of which is famed for alacrity. Running the Get-ExoMailbox cmdlet to fetch large quantities of mailboxes to process also takes time. In larger organizations, it would be best to apply a filter with the Get-ExoMailbox cmdlet to find different sets of mailboxes, such as all the mailboxes in a department or office.
The basic outline of the script is:
- Run Get-ExoMailbox to find the mailboxes to process.
- For each mailbox, run Get-ExoMailboxStatistics to fetch mailbox statistics. If the mailbox has an archive, the script runs Get-ExoMailboxStatistics to fetch statistics for the archive mailbox.
- Run Get-ExoMailboxFolderStatistics to fetch details of the mailbox folder. As before, if the mailbox is archive-enabled, the script runs the cmdlet again to fetch details of the folders in the archive mailbox. The commands include a filter to avoid processing folders used for different background operations that are present in mailboxes but not usually accessed by users, like Calendar Logging, Conflicts, PersonMetaData, and Audits.
- The script captures details of each folder including the oldest and newest items in the folder.
- The script also captures overall statistics for both the primary and archive mailboxes.
- Because the Get-ExoMailbox cmdlet does not return details like the user’s city, department, office, or title, the script runs the Get-User cmdlet to fetch this information. Running Get-User adds to the time required to process each mailbox, so you should comment this line out of the script if you don’t want to use this information. Overall, I think the information fetched by Get-User is helpful and that’s why it is in the script.
- After processing all mailboxes, the script generates an HTML report. Figure 1 shows a typical example of the data reported for a mailbox. The script also generates a CSV file containing details of the mailbox statistics.
As you can see, the report lists:
- Overall mailbox statistics.
- Folders in the primary mailbox.
- Recoverable Items folders for the primary mailbox (sometimes it’s good to know if these folders are approaching their quota limit).
- If the mailbox is archive-enabled, the report lists the folders and Recoverable Items folders in the archive mailbox.
Other Ways to Use the Report Data
The data recorded in the two PowerShell lists captured by the script can be sliced and diced to generate other reports. For instance, to list mailboxes in size order, run this command (output shown in Figure 2):
$MbxReport | Sort-Object {$_.'Primary mailbox size bytes' -as [float]} -Descending | Format-Table Mailbox, 'Primary Mailbox Size GB', 'Primary Mailbox total items', 'Archive mailbox size GB' -AutoSize
Because the $MbxReport list includes details for users like city, department, and office, you can sort and interrogate the date based on these properties. For example:
$MbxReport | Where-Object City -match 'Dublin' | Format-Table Mailbox, 'Primary Mailbox Size GB', 'Primary Mailbox total items', 'Archive mailbox size GB' -AutoSize
The script only reports user mailboxes. Some might like to include shared mailboxes. If so, you can amend the Get-ExoMailbox command to:
[array]$Mailboxes = Get-ExoMailbox -RecipientTypeDetails UserMailbox, SharedMailbox -ResultSize Unlimited ` -PropertySets Archive -Properties DisplayName, RecipientTypeDetails | Sort-Object DisplayName
The script captures the mailbox type in the $MbxReport list, so if you do add shared mailboxes, it is to filter user and shared mailboxes and report each type separately. For example, this command finds all the user mailboxes in the report:
$MbxReport | Where-Object 'Mailbox type' -match 'Usermailbox'
I haven’t tested the code on an on-premises Exchange Server, but I think it will work if you replace Get-ExoMailbox with Get-Mailbox, Get-ExoMailboxStatistics with Get-MailboxStatistics, and Get-ExoMailboxFolderStatistics with Get-MailboxFolderStatistics. However, I can’t give a guarantee because I don’t have an on-premises server to run the script on.
Perhaps This Script Will Become a Classic
As always, this script illustrates principles and could be improved in many ways, notably through better error handling. I guess I’m waiting for Michel de Rooij to describe the best ways to deal with errors in a future Practical PowerShell column. In the meantime, I had some fun thinking about how to approach a script that so many people have written since 2006. This script might never be a classic, but I think it has some new elements that are of value. But I guess I’d say that after writing 240-odd lines of PowerShell.
Great script – thanks for the effort!
But there is a small problem, because the property “ArchiveStatus” is not always used, for example for moved mailboxes…
What kind of mailboxes do you mean by “moved”? Mailboxes moved from on-premises to Exchange Online?
Hello Tony,
If i run the script with about 2800 mailboxen then it hangs somewhere, if a stopt the script a can save the csv file and the html file but the html file is than not complete. The last time i see about 1400 mailboxes.
I run it in Exchange online.
I’m afraid that I can’t help much because I can’t see your data or monitor what happens when you run the script. Throttling by the service might cause a problem. You could check by inserting a Start-Sleep -MilliSeconds 500 command after each mailbox.
I got the same error with mailboxes that had no Archive. The problem was with the variable $ArchiveMbxSizeGB in line 145:
‘Archive mailbox size GB’ = (“{0} GB” -f $ArchiveMbxSizeGB.toString())
The fix was above at line 133 – set the $ArchiveMbxSizeGB variable to 0 or ” instead of $null as there is no toString() method for a $null value.
Original code at line 133: $ArchiveMbxSizeGB = $null
Working code at line 133: $ArchiveMbxSizeGB = 0
Hope this helps.
Ta. Script updated in GitHub. I guess all my mailboxes are archive-enabled else I would have hit this issue before.
Mine is throwing the same error as John, in older and newer Powershell versions. $Mbx does show as the last mailbox discovered. In my scenario, there’s only 31 and it throws the error on every mailbox it discovers. The .csv output is empty at the end of the run.
And same questions that I put to John:
Did the script find any mailboxes to process?
What’s the value of the $Mbx variable?
This is PowerShell, so you’ve got to be prepared to do some debugging if things don’t work as well as expected in your tenant.
Are you running the latest version of the Exchange Online management module (3.5)?
Hi Tony
Thanks for the amazing script. It works like a charm 🙂
Is it also possible to run this script without having to login with my user account. I know that when you use MsGraph you can use a ClientSecret from a registered Application. But can you also do that with the Connect-Exchangeonline Command?
Connect-ExchangeOnline supports certificates. See the examoples in https://learn.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps
Hi – ran into an issue your script, hoping you can help. here is what I get when I run it. thoughts?
You cannot call a method on a null-valued expression.
At C:\temp\Report-ExoMailboxFolderStats.PS1:138 char:5
+ $MbxReportLine = [PSCustomObject][Ordered]@{
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Did the script find any mailboxes to process?
What’s the value of the $Mbx variable?
I’m afraid that I can’t see the data from your tenant so you will have to do some debugging.