How to Find Unused Exchange Online Mailboxes

No Point in Having User Mailboxes That Aren’t in Active Use

I don’t like to post repeats of articles, but sometimes it’s necessary. In this case, a reader had difficulty running some PowerShell code in a article I wrote in 2019 to discuss how to find unused Exchange Online mailboxes using diagnostic information. Often the issue is something small (like making sure that you use the correct webhook URI), but I don’t write for Petri any longer and no longer have access to the article, so it seemed like a good idea to revisit the topic here.

Often, the second attempt at writing code takes a different approach to the first. You know what needs to be done and might approach the issue in a different way. New PowerShell cmdlets or Microsoft Graph APIs might be available. Or the original code might simply be not very good, even if it’s written to demonstrate a principal rather than be a complete solution.

Microsoft increased prices for Office 365 and Microsoft 365 licenses on March 1, 2022. The extra $3 or so per month might not seem a lot, but it’s always a good thing to avoid paying license fees for unused mailboxes. Of course, email activity is only one aspect of how someone might use Office 365, and usually people don’t pay separately for Exchange Online because Microsoft bundles it in many SKUs like Office 365 E3. Over the last few years, many Office 365 users have reduced the level of email they send and receive by moving communications to Teams, which means that gathering and analyzing activity data from multiple workloads is a better way to conclude if an account is active or not.

Analyzing Sources of Mailbox Information

The original idea was to use diagnostic information extracted from user mailboxes to understand if mailboxes are in active use. I’m only concerned about user mailboxes because they’re the ones which need licenses. Shared mailboxes don’t need licenses unless they are larger than 50 GB or have an archive.

Three sources of information give some insight into the activity level of mailboxes:

  • The Export-MailboxDiagnosticLogs extracts mailbox diagnostic information (changed subtly over the years). After converting the information to XML format, it’s easy to extract and use the various pieces of data which might tell you about the use of a mailbox.
  • The Get-ExoMailboxStatistics cmdlet provides some basic statistics about each mailbox because unused mailboxes are usually small and hold relatively few items. Thus, if we see a mailbox that hasn’t been logged into for a while and has less than a couple of hundred items, it’s an indication that that this is an unused mailbox.
  • The last logon date for a mailbox is a piece of information that’s long been of suspect quality, largely because so many background processes connect to mailboxes to perform housekeeping tasks like retention processing. Mailbox diagnostics include a last logon time property, so the script reports that. As a backup, the script also calls the Get-MgAuditLogSignIn cmdlet to fetch the last signin audit record from Azure AD (which can only go back 30 days). The last signin event might be associated with Exchange Online, but it could also come from another workload. It’s just another check to help detect unused mailboxes.

As usual for demo scripts, we pipe the output to Out-GridView to view the results sorted by the number of days since the last active date reported by mailbox diagnostics (Figure 1). The most unused Exchange Online mailboxes appear at the top.

 Reporting potentially unused Exchange Online mailboxes
Figure 1: Reporting potentially unused Exchange Online mailboxes

The new script also includes the code from this post to post notifications about the top 25 potentially unused mailboxes to a Teams channel through the incoming webhook connector (Figure 2). I explored this topic previously in this post and have now refined and integrated the code in a single script.

 Posting to Teams about potentially unused Exchange Online mailboxes
Figure 2: Posting to Teams about potentially unused Exchange Online mailboxes

The method used to create the body of the message to post to Teams is different from the approach taken in this article. In the first instance, the code deals with a single item. In this script, the body comprises up to 25 different items. In both cases, you end up with a HTML body of JSON data, which is what Teams expects through the incoming webhook connector.

The full script is available on GitHub. Before you run it, be sure to update the code with the webhook URI for the target teams channel.

Doing Something About the Unused Mailboxes

If some suspiciously unused Exchange Online mailboxes come to light, the next step is to figure out what to do with them. You can:

  • Leave the mailbox alone and find out why its owner is not using the mailbox. Obviously, you’ll need to contact the mailbox owner using something other than email.
  • Convert the mailbox to be a shared mailbox to remove the need to license the mailbox. However, if the mailbox owner uses other workloads, they might have a license like Office 365 E3 which includes Exchange Online, so converting the mailbox to be a shared mailbox won’t have much impact.
  • Put the mailbox on litigation hold and then delete the user account. This makes the mailbox inactive and releases any licenses assigned to the account. Exchange Online keeps the mailbox in this state until the hold lapses. This is a bad approach to take if the account is active in other Microsoft 365 workloads, which is why the data analyzed includes the last sign-in date from Azure AD. This article explains how to use the Microsoft Graph usage reports API to report per-user data from multiple workloads.
  • Relax and do not worry too much. Maybe this isn’t the best way to chase down licensing costs.

Perhaps chasing and closing down unused Exchange Online mailboxes is a pipe dream that isn’t worth the effort. Still, it’s nice to know how to approach the problem.

Learn about working with Exchange Online mailboxes and the rest of Office 365 by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s importance and how best to protect your tenant.

10 Replies to “How to Find Unused Exchange Online Mailboxes”

    1. Add a check for the account status:

      # Get account enabled status
      $AccountEnabled = (Get-MgUser -UserId $M.ExternalDirectoryObjectId).AccountEnabled

      And then report/sort/filter on that property… It is True if the account is enabled, False if it’s disabled. I have updated the script in GitHub to show how.

  1. Great script but when I get above the 70th user I start getting errors:

    Get-MgAuditLogSignIn :
    400 Request Header Or Cookie Too Large

    400 Bad Request
    Request Header Or Cookie Too Large

    Any ideas on how to fix a overly sized cookie?

      1. Version was older 1.9.2 and the upgrade resolved the issue. Appreciate the help and the quick response.

  2. getting erro start parameter for few mailbox
    Line |
    30 | $DaysSinceActive = (New-TimeSpan -Start $LastActive -End $Now).Day …
    | ~~~~~~~~~~~
    | Cannot bind parameter ‘Start’ to the target. Exception setting “Start”: “Cannot convert null to type
    | “System.DateTime”.”

    1. You must have a very new mailbox… In any case, the code is PowerShell, so it’s easy to add a check that the $LastActive variable contains some data before attempting to use it.

      If ($LastActive) {
      $DaysSinceActive = (New-TimeSpan -Start $LastActive -End $Now).Days }
      Else {
      $DaysSinceActiove = “N/A” }

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.