More Quota madness - this time MailboxStatistics

In the last post, I walked through how to get mailboxes who met certain quota criteria (including inheriting these values from the mailbox database values).

Today, let's cover this from a different perspective: "who is over their quota".

Sure, it's possible to get this with a simple one-liner like:

Get-MailboxStatistics | fl DisplayName,StorageLimitStatus

But, in addition to that being a bit boring as an Exchange+PowerShell blog example, it's also not real-time. It only updates to represent "over quota" only perodically. If I want to check at 3pm based on current mailbox size at 3pm, I might need something more versatile. Plus, this is supposed to be an interesting Exchange+PowerShell scripting example, so we can't just do it the simple way. :)

Back to the problem... getting back the real-time size vs. quota information is a bit more involved than the last quota example, as it requires you to not only find the accurate mailbox quotas for the users, but be able to use the value to compare against the actual mailbox sizes for each mailbox. This is a bit more time consuming, since it requires a callout to each mailbox store to get the MailboxStatistics info.

Makes sense? Well, let's get to it then and hold onto your seatbelts for this one!

Let's first catalog what we need to do at a logical level:

  • Get all of the effective mailbox quotas for the mailboxes
  • For each of these mailboxes, check if the current mailbox size exceeds the mailbox quota

Sounds easy. But at a glance it sounds like we're going to need to head back into hash-table land since we have two totally disparate objects we're trying to map against one another (see my "Bringing users and mailboxes together" post for the last time we hit this).

Here's a crazy-long one-liner that seems to do the trick (I've broken it out into many lines to make it easier to read):

1 Get-Mailbox -ResultSize Unlimited |
2 % { if ($_UseDatabaseQuotaDefaults -eq $false)
3  { New-Object psobject |
4      Add-Member -passthru noteproperty Name $_.Name |
5      Add-Member -passthru noteproperty DistinguishedName $_.DistinguishedName |
6      Add-Member -passthru noteproperty ExchangeGuid $_.ExchangeGuid |
7      Add-Member -passthru noteproperty IssueWarningQuota $_.IssueWarningQuota
8  } else {
9    New-Object psobject |
10    Add-Member -passthru noteproperty Name $_.Name |
11    Add-Member -passthru noteproperty DistinguishedName $_.DistinguishedName |
12    Add-Member -passthru noteproperty ExchangeGuid $_.ExchangeGuid |
13    Add-Member -passthru noteproperty IssueWarningQuota $($(Get-MailboxDatabase $_.Database).IssueWarningQuota)
15   }
16 } | % { $Quotas = @{} }
17           { $Quotas[$_.ExchangeGuid] = $_.IssueWarningQuota ; $_ } |
18    % { Get-MailboxStatistics $_.DistinguishedName } |
19      Where { $_.TotalItemSize -gt $Quotas[$_.MailboxGuid].Value }

Whew!

That "one-liner" dumps out mailbox statistics objects, filtered for only those mailboxes that are over their configured (effective) quota at the time they're evaluated. With not too much extra work (and following patterns demonstrated just above), this one-liner could easily be modified to output an object that had both the current size of the mailbox and the current, effective IssueWarningQuota of the mailbox... suitable for framing, er feeding into a report of some sort.

And how does it do it? Line by line analysis:

  1. Gets back all the mailbox objects
  2. Branch on whether we're using the database quota settings or the mailbox ones
  3. (we're using the mailbox values) so create a new object
  4. Load in Name
  5. DistinguishedName
  6. ExchangeGuid (we'll need this as the key to our hash table)
  7. ... and the IssueWarningQuota value from the mailbox object
  8. Otherwise...
  9. (we're using the database values) so create a new object
  10. Load in Name
  11. DistinguishedName
  12. ExchangeGuid (still need this for the hash table later on)
  13. ... and the IssueWarningQuota from the mailbox database object
  14. (this is a wrapped line from #13, potentially)
  15. }
  16. Next lets feed these new objects into foreach and create our hash table out of it.
  17. We end up with $Quotas containing the ExchangeGuid as the key and IssueWarningQuota as the value. Then we dump the objects (the same ones we just used to create $Quotas) back into the pipeline.
  18. Another ForEach loop and we're getting the MailboxStatistics objects one-by-one for each object in the pipeline. Fortunately, our objects still have DistinguishedName so we can use that as the MailboxStatistics identity.
  19. Finally, filter out so we're only keeping the ones where the MailboxStatistics "totalitemsize" is greater than our hash-table (matched by ExchangeGuid/MailboxGuid) IssueWarningQuota.

Now, this is certainly the longest contiguous one-liner I've created to date, and even at a glance it looks pretty inefficient (in terms of repetitive looping, if nothing else). I'm a bit curious if anyone has a more effective way of doing this same work. Feel free to weigh in at the comments section or send me some email if you have suggestions, and perhaps we can post about this again in the future.