Generating a Mailbox Properties Report in Exchange Online with PowerShell

Exchange Online administrators are often asked deceptively simple questions:

  • How big is this mailbox and how close is it to its quota?
  • Does this shared mailbox have an archive?
  • Is forwarding configured?
  • Is the mailbox on hold?

You can answer some of these questions from the Exchange Admin Center, but as the tenant grows, clicking through individual mailboxes quickly becomes a poor use of time. A scripted approach scales much better – and, importantly, gives you a repeatable way to produce consistent reports.

This post walks through a PowerShell script that generates a CSV report of mailbox properties for:

  • All user mailboxes
  • All shared mailboxes
  • A single, specific mailbox

The report includes mailbox size and quota usage, archive details, forwarding settings, licensing state, holds, auditing, and more. Logs and reports are written to disk so you can review what happened and keep a history of previous runs.

Script overview

The script is designed to be flexible but predictable:

  • It can run using interactive authentication or schedule friendly certificate-based app-only authentication.
  • It supports three selection modes:
  • It writes:

The script uses Get-Mailbox, Get-MailboxStatistics, and Get-MailboxFolderStatistics to pull most of the information, and then adds some logic on top to calculate storage usage and flag interesting states such as holds or forwarding.

Parameters and scenarios

The script supports the following parameters:

  • -UserMailbox
    Include all user mailboxes in the report.
  • -SharedMailbox
    Include all shared mailboxes in the report.
  • -UserUPN <string>
    Generate the report only for the specified mailbox.
    If this parameter is used, it overrides the -UserMailbox and -SharedMailbox switches.
  • -UseCertAuth
    Use certificate-based app-only authentication instead of interactive login.

A few example runs:

If you don’t specify any switches, the script defaults to user + shared mailboxes, which is usually what you want for a tenant-wide snapshot.

Authentication: interactive vs app-only

The script supports two ways to connect to Exchange Online:

Interactive user login

If you don’t pass -UseCertAuth, the script simply calls:

You sign in with your admin account, and the session runs under your identity with delegated permissions. This is fine for ad-hoc runs, lab tenants, or when you’re just exploring.

Certificate-based app-only authentication

For automation, app-only authentication is a better fit. When you use -UseCertAuth, the script expects you to provide:

and then connects with:

You’ll need:

  • An app registration in Entra ID
  • A certificate uploaded to that registration
  • Appropriate Exchange Online application permissions (for example, Exchange.ManageAsApp)

Once that’s in place, you can schedule the script through Task Scheduler, an Azure Automation Hybrid Worker, or any other job runner without needing an interactive sign-in.

Selecting target mailboxes

After connecting, the script decides which mailboxes to process.

  • If -UserUPN is provided, it runs a single Get-Mailbox for that identity. If the mailbox is not found, the script logs an error and exits.
  • Otherwise, it builds an array of recipient type details based on the switches:

It then calls:

and logs how many mailboxes will be included in the report.

In larger tenants, this can still be a substantial list, but it’s far more efficient than trying to click through each mailbox in the admin center.

Collecting mailbox properties

For each mailbox, the script builds a PSCustomObject and appends it to the CSV. The interesting parts are:

Last email sent date

The script uses Get-MailboxFolderStatistics and looks at the Sent Items folder:

If anything goes wrong (for example, throttling or weird folder layouts), the script catches the exception and sets the value to $null instead of failing the whole run.

Mailbox size and archive size

The primary mailbox size is taken from Get-MailboxStatistics:

If the mailbox has an active archive, the script makes a second call:

The sizes are stored as strings without the byte information (for example, “24.56 GB”).

Quota and percentage used

To calculate how much of the mailbox quota is consumed, the script parses the ProhibitSendQuota and TotalItemSize values to bytes and calculates a percentage:

This gives you a very useful PercentageUsed column in the report. It’s much easier to scan a column of percentages than raw sizes when you’re trying to spot mailboxes close to quota.

Holds, forwarding, archive status and more

The script also pulls a number of other properties straight from the mailbox object:

  • LitigationHoldEnabled
  • InPlaceHolds (converted to a simple TRUE/FALSE column)
  • RetentionHoldEnabled
  • ForwardingAddress and ForwardingSMTPAddress
  • AuditEnabled
  • HiddenFromAddressListsEnabled
  • IsDirSynced (on-premises AD sync)
  • SkuAssigned (licensed or not)
  • ArchiveStatus
  • AccountDisabled

These values are all included in the final PSCustomObject that’s exported for each mailbox.

Output: CSV reports and logs

The script writes the report to a .\Reports folder, and logs to a .\Logs folder. Both are created automatically if they don’t exist.

The CSV name encodes the selection and timestamp, for example:

That makes it easy to keep historical snapshots or feed the report into Power BI, Excel, or another analysis tool.

A transcript is started at the beginning and stopped at the end; this, together with the Log-Event function, means you can see exactly what the script did and when.

To avoid clutter, the script deletes reports and logs older than 30 days:

If you want to keep data longer, adjust the retention period accordingly.

Performance considerations

In smaller tenants, the script completes in seconds. In large environments with several thousand mailboxes – especially when you include both user and shared mailboxes – expect the run to take a few minutes. That’s still far better than clicking through the GUI, but it’s worth keeping in mind if you’re planning to run the script interactively during a busy day.

If you intend to run it on a schedule (for example, nightly or weekly), app-only authentication plus a job runner (Task Scheduler, Azure Automation with a Hybrid Worker, etc.) is the obvious next step.

How you can use the report

Once you have the CSV, you can:

  • Identify mailboxes close to quota based on PercentageUsed
  • Find inactive mailboxes by checking the LastEmailSentDate column
  • Track licensing vs usage (large unlicensed shared mailboxes, disabled accounts with big mailboxes, etc.)
  • Verify hold and forwarding configurations ahead of compliance reviews
  • Feed the data into Power BI and build your own dashboards

The script is intentionally written as a starting point. If you need extra columns (such as mailbox region, retention policy, custom attributes, etc.), they can be added to the PSCustomObject with a couple of extra lines.

Requirements

To run the script, you’ll need:

  • The ExchangeOnlineManagement PowerShell module
  • Sufficient permissions to run:
  • If using certificate-based app-only authentication:

Remember to replace the placeholder values:

with values that match your environment before using the -UseCertAuth option.

Script Source

The complete script is available on the Azure365Addict GitHub.
Feel free to download, customize it to your specific needs, and improve your mailbox management processes.

If you have any questions or need further assistance, feel free to reach out!

Happy scripting!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top