Use PowerShell to Manage Exchange Server Mailbox Storage Limits

Use PowerShell to Manage Exchange Server Mailbox Storage Limits

  • Comments 11
  • Likes

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:

Get-MailboxStorageLimit

[CmdletBinding()]

Param([Parameter(Mandatory=$false,ValueFromPipeline=$true)][PSObject]$Identity,

      [Parameter(Mandatory=$false)][string]$Database,

      [Parameter(Mandatory=$false)][string]$Server

      )

 

Set-MailboxStorageLimit

[CmdletBinding(DefaultParameterSetName="Manual")]

Param([Parameter(Mandatory=$true,ValuefromPipeline=$true)][PSObject]$Identity,

      [Parameter(Mandatory=$true,ParameterSetName="Manual")][ValidateRange(0,5)][int]$Level,

      [Parameter(Mandatory=$true,ParameterSetName="DynamicUp")][switch]$IncreaseLevel,

      [Parameter(Mandatory=$true,ParameterSetName="DynamicDown")][switch]$DecreaseLevel

      )

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

              }

            break

            }

          }

        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.

~Jeremy

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 scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • we use a different approach - the "storage level" is defined at the level of database limits. so there are databases for storage level 0, 1, 2, etc. if you want to set a higher storage level for a mailbox, you run New-MoveRequest cmdlet.

    database names contain "storage level" in their names to make the work easier.

    (we don't provide "unlimited" mailboxes)

  • Jeremey,

    The issue you are having with the byte value is when you run remote commands that generate output, the command output is transmitted across the network back to the local computer. Because most live Microsoft .NET Framework objects (such as the objects that Windows PowerShell cmdlets return) cannot be transmitted over the network, the live objects are "serialized". In other words, the live objects are converted into XML representations of the object and its properties. Then, the XML-based serialized object is transmitted across the network. On the local computer, Windows PowerShell receives the XML-based serialized object and "deserializes" it by converting the XML-based object into a standard .NET Framework object. However, the deserialized object is not a live object. It is a snapshot of the object at the time that it was serialized.

    There is a .NET Class Microsoft.PowerShell.DeserializingTypeConverter that can help you change this back, but that can be to complicated for small needs such as this.

    So what I do with those byte values is this.  That take database size for example.  The data returned by the command is a string.  You need to format that string so that it removes everything but the 1.258 GB, then remove the space so that you are left with is 1.258GB.  Then you can use powershell built in size values (1KB, 1MB, 1GB, etc...) to divide the value and your are left with "1288.192".  However, this object isn't an integer but instead an object of type "double".  To convert it to an integer you use the .NET [INT64].  Thus you get a integer value you can work with.

    DatabaseSize: 1.258 GB (1,350,631,424 bytes)

    $edbSize = $_.DatabaseSize.split("(")[0].replace(" ","")

    $edbvalue = $edbSize / 1MB

    $edbSize = [INT64]$edbvalue

  • I might have misread you issue with the byte value, but running commands remotely you will get the issue I described.

  • Hi Jeremy,

    it looks like a very good idea to manage mailbox quotas with Powershell and the Exchange 2010 cmdlets! Levels are surely a good way to customize the mailbox sizes.

    I don't have Exchange 2010 here but I can follow your steps reading the script and that's a good sign regarding script readability. The size issue is something I can't explain, but Aaron's explanantion sounds pretty comprehensive ...

    There is one thing I would personally prefer, because it's easier to read than an array index, using a nested hash like:

    $limits = @{  0 = @{warning=800MB;error=850MB}

                  1 = @{warning=1.0GB;error=1.2GB}

                  2 = @{warning=2.2GB;error=2.4GB}

                  3 = @{warning=4.6GB;error=4.8GB}

                  4 = @{warning=9.4GB;error=9.6GB}

                  5 = @{warning="Unlimited";error="Unlimited"}

                  }

    If I see something like $limits[$i].error or $limits[$i].warning, I can easier remind myself of the meaning of data than $limits[$i][0] eg. but that's a matter of taste!

    Klaus (Schulte)

  • Also Note - The value TotalDeletedItemSize applies to the items in the dumpster, NOT the Deleted items folder.  I used something like this to pull that information.

    # For every item in $mbox select these items to add from Get-MailboxFolderStatistics for each item from above.

    Get-MailboxFolderStatistics -Identity $_.SamAccountName | Where {$_.FolderPath -eq "/Deleted Items"} | % {

    $obj.DeletedItemSize = $_.FolderAndSubfolderSize

    $obj."Deleted Items" = $_.ItemsInFolderAndSubfolders

  • camlost - that's how we originally had it (admittedly messier), but it prevented us from being more dynamic in managing our mailboxes. We only have 3 mailboxes set to unlimited for specific legal purposes.

    Aaron - That's good information. Thanks. I wonder if I converted it to bytes first and then set it with the full byte value it would be more accurate to what I expect. What do you think?

    Klaus - Thanks! I like your idea of using the nested hash - It would definitely increase readability even more. I'll make that change! Much appreciated.

    Tom - Good to know. That definitely helps when users call up complaining their mailbox is full. We also have instituted a policy of cleaning out deleted items after 7 days. That has definitely cut down on the number of calls, though we still get the one-offs.

  • Jeremy, do you produce an associate report on this on a schedule to handle chargeback?  Or is it just people get a mailbox level, and then just have to prove (business case, div leader) that they need a larger mailbox?  Otherwise great way to handle those user requests! (love your SCOM scripts too...)

  • Nick - Thanks for the comment about the SCOM scripts! As for chargebacks, it would be pretty easy to do. However, our finance dept hasn't set up a facility for chargebacks, which has been frustrating in situations like these. Instead, what we are doing is "showback". While it doesn't actually compel people to use less space, it does allow us to justify storage increase requests.

  • Jeremy,

    Are you running this from EMS 2010 or are you calling Exchange remotely via a new-pssession?  If remotely then they way I describe would give you a more accurate value due to the deserializing that is done.

  • I'm very impressed with your script work Jeremy.  The part though that makes me sad is that Microsoft has made this so cumbersome to require such effort.  Getting into code and making scripts can be a lot of fun.  However, does anybody know why Microsoft neutered Exchange by making the Exchange Console into such a base level management tool?  Exchange Shell can be great, but doesn't Microsoft realize a management GUI should be full-featured as well?  Not every person who has to deal with Exchange has the time to learn all the scripting methods to make Exchange do what a mailbox server should be able to do out of the box.

  • To find out who had modified the storage limit or any other changes on mailbox through cmd.