Use PowerShell to Manage Exchange Server Mailbox Storage Limits

Summary: Guest blogger, Jeremy Engel, shows how to use Windows PowerShell to manage mailbox storage limits on an Exchange Server.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have a real special treat in store. The other day I received an email from Jeremy Engel (the author of PowerShell Module for DHCP, which is available on the Scripting Guys Script repository). Jeremy said that he had been wrestling with a problem at work, and he came up with a cool Windows PowerShell solution. I was immediately intrigued. I will let Jeremy tell you the rest of the story…

I had a problem. The existing database and mailbox storage quota/limit design (or lack thereof) in my company’s Exchange Server environment was not allowing my team to be agile and responsive enough to end-user storage requests or to maintenance issues with the databases. The problem was that we had no standardized way of managing storage limits. We would move mailboxes around and spend the next day resolving storage limit issues. I was taking a lot of heat, and I needed to come up with a solution that satisfied both end users and the Exchange Server administrators.

First, I needed to get an understanding of what the current environment looked like. To do so, I ran the following queries:

Get-MailboxDatabase | Select-Object Name,IssueWarningQuota,ProhibitSendQuota,ProhibitSendReceiveQuota | Sort-Object Name | Export-Csv –Path .\DatabaseLimits.csv –NoTypeInformation

Get-Mailbox | Select-Object DisplayName,Database,IssueWarningQuota,ProhibitSendQuota,ProhibitSendReceiveQuota | Sort-Object DisplayName | Export-Csv –Path .\MailboxLimits.csv –NoTypeInformation

As I discovered (much to my horror), the databases had no predictable storage limits. Some were set with warning limits, but no send limits; some were set with send limits, but no warning limits; some had what I would call “normal” limits; and still others had no limits at all. I even found some that actually had a receive quota! To make matters worse, many of the mailboxes themselves had varying storage limits, all of which were even more ad hoc and arbitrary. In short, it was a mess.

Certain mailbox databases were becoming too full, and we desperately needed to shuffle mailboxes around. As you can see from our lack of standardization, moving mailboxes around was an extremely tricky and tedious endeavor. What we needed was something agile, standardized, and easy to administrate. A little thought and Windows PowerShell got the job done! Here’s what I did…

Agility and standardization

My first goals were agility and standardization. To that end, I decided that all mailbox databases should have the same storage limits. Hence, any exceptions to these limits would be managed at the mailbox level. This would allow our Exchange Server administrators the freedom to move mailboxes as needed without worrying about causing end-user issues and dissatisfaction.

Up until this point, when the admins would receive a storage limit increase request, it was essentially at their discretion (or the end user’s), what the new limits for the mailbox would be. Instead, I came up with the concept of the StorageLevel. Here is what I developed as our environment’s storage levels:

StorageLevel     IssueWarning    ProhibitSend

0                              800MB                  850MB (Default/Database Limits)

1                              1.0GB                    1.2GB

2                              2.2GB                    2.4GB

3                              4.6GB                    4.8GB

4                              9.4GB                    9.6GB

5                              Unlimited            Unlimited

This would allow both users and administrators a standardized way of defining their storage limits and preventing confusion.

With the thinking done, I talked to my boss about the deplorable state of affairs and what my wonderfully graceful solution for this was. I got his buy-in, and he in turn, got buy-in from his bosses. This is key—always seek to get as much acceptance as necessary for a new idea. This makes execution and enforcement that much easier. Another good idea is to set increasingly more stringent requirements and approvals to increase the StorageLevel of a mailbox. For example, an increase from 0 to 1 might just require a manager’s approval, but an increase from 3 to 4 might require a business explanation and approval from the division leader.

Ease of administration

With acceptance complete, I got to work on ease of administration. I needed a way to report on and define the mailbox storage limits which would adhere to my new design. Therefore, I created two scripts for the administrators to use: Get-MailboxStorageLimit.ps1 and Set-MailboxStorageLimit.ps1. I wanted to maintain the look and feel of other Exchange Server cmdlets, so I used the following parameters:















The Identity parameter can be piped, and I use the PSObject data type so that administrators can input a mailbox object or use any of the other standard ways we define Identity in Exchange. You’ll also notice the more advanced features in the Set-MailboxStorageLimit script because I want to control what integer values are available for the Level parameter, and also prevent “cross-parameterization.” I don’t know if that’s a word, but it sure sounds legit, doesn’t it?

Begin, process, end

Next, I use the begin, process, end functionality so that piping actually works the way we expect. In the begin section of both scripts, I define those limits I talked about earlier in a hash table.

begin {

  $limits = @{ 0 = @(800MB,850MB)

               1 = @(1.0GB,1.2GB)

               2 = @(2.2GB,2.4GB)

               3 = @(4.6GB,4.8GB)

               4 = @(9.4GB,9.6GB)

               5 = @(“Unlimited”,”Unlimited”)



In the process section, all the work gets done. I first validate that the mailbox(es) in question exist, and then I determine what their current storage level is based on the previously defined $limits hash table.

    if($mailbox.UseDatabaseQuotaDefaults) { $Level = 0 }

    else {

      $limit = $mailbox.ProhibitSendQuota.Value

      $warning = $mailbox.IssueWarningQuota.Value

      if(!$limit) { $Level = $limits.Count-1 }

      else {

        for($i=0;$i-lt$limits.Count-1;$i++) {

          if($limit -le $limits[$i][1]+1MB -and $limit -ge $limits[$i][1]-1MB) {

            $Level = $i

            if($warning -gt $limits[$i][0]+1MB -or $warning -lt $limits[$i][0]-1MB) {              

              $Level = $null





        if(!$Level) { $Level = “Invalid” }



I had to do a little trickery here with checking the limits because the byte values apparently come out differently between setting and getting the values—they will not exactly match up. I am not sure what Exchange Server is doing on the back end that causes this discrepancy, but it makes me sad. As such, I was not able to determine with 100% accuracy whether someone fits directly into a particular storage level, so I had to adjust the number by 1 MB.

With that done, I put all the limit information into a custom PSObject and output it for your viewing pleasure. The Get-MailboxStorageLimit script has a little bit more data in its output because I wanted to build a nice report. But it also turns out that the StorageLimitStatus property within Get-MailboxStatistics doesn’t update immediately (it checks it on a schedule). So I was running into a situation where if someone’s mailbox was in a warning state and I increased their storage limit, it would report back that they were still in a warning state. I did not want that to confuse anyone, so I removed it from the Set-MailboxStorageLimit script. In the following image, you can see the differences in output between the two commands in addition to the varying byte counts and the out-of-date StorageLimitStatus.

Image of command output

Finally, the end section is there simply to look pretty because I really do not have anything for it to do.

If you decide to use these scripts, I would recommend performing a detailed analysis of your current design, determining the most appropriate storage levels for your organization, and then modifying my scripts accordingly.


Thank-you, Jeremy, for once again sharing your knowledge and time. The complete scripts are posted in Script Center Repository.

Join me tomorrow when I will introduce the sponsors for the 2012 Scripting Games.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy