Powershell : Building a Dynamic Exchange Mailbox Health Dashboard

Monitoring Exchange Online mailboxes is a critical task for IT administrators. You need to understand growth trends, storage limits, archive usage, and more — all at a glance. Instead of sifting through CSV files manually, wouldn’t it be great to have a professional-looking HTML dashboard that does it for you?

This example is limited to certain key mailboxes that need extra monitoring for process reasons, you can add more if required.

In this guide, we’ll walk through how to use PowerShell, CSV logs, and Tailwind CSS to generate a static HTML report with trending indicators, summary cards, and a clean, readable design.

This is what the dashboard looks like, optimised for a quick glance view of the mailbox status:


You will also get a mailbox data location breakdown chart as below, here you can see that the "Lee Croucher" mailbox has lots in the archive deleted (red bar) and the other mailbox as the majority of the messages in the Inbox or the Archive:


🎯 Generating the CSV data

The script Connects to Exchange Online securely then iterates through all user and shared mailboxes and collects:

  1. Mailbox sizes (primary & archive)
  2. Item counts
  3. Deleted item sizes
  4. Mailbox plans and limits
  5. Exports the results to a CSV file

The script will then prepares the data for use in an HTML dashboard in the form of a CSV file.

🔐  Connect to Exchange Online

Note : You must authenticate with an account that has access to read all mailboxes.

Connect-ExchangeOnline -UserPrincipalName exchange@bythepowerorgreyskull.com

📬 Get the Mailboxes

This fetches all user and shared mailboxes in your tenant.

$mailboxes = Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox,SharedMailbox

📊 Collect Mailbox Stats

For each mailbox, we collect the following:

$mailboxStats = Get-MailboxStatistics -Identity $mailbox.UserPrincipalName

The script then checks:

  1. TotalItemSize
  2. ItemCount
  3. DeletedItemSize

We also fetch archive data (if enabled):

$archiveStats = Get-MailboxStatistics -Archive -Identity $mailbox.UserPrincipalName

Process & Convert Sizes

Exchange returns size values like "34.16 GB (36,672,215,040 bytes)". These are not immediately usable, so we use:

$totalSizeGB = [math]::Round($mailboxStats.TotalItemSize.Value.ToBytes() / 1GB, 2)

But in remote sessions, the object might be deserialized, so we account for it using string parsing if needed.

✅ Handle Archive Status

We determine if archiving is enabled based on:

  1. The presence of $archiveStats
  2. A non-null TotalItemSize

$archiveEnabled = $archiveStats -ne $null -and ($archiveStats.TotalItemSize -ne $null)

📁 Build the Output Object

$result = [PSCustomObject]@{
    Timestamp           = Get-Date -Format "dd/MM/yyyy HH:mm:ss"
    UserPrincipalName   = $mailbox.UserPrincipalName
    DisplayName         = $mailbox.DisplayName
    PrimarySizeGB       = $primarySize
    ItemCount           = $itemCount
    DeletedItemSizeGB   = $deletedSize
    ArchiveEnabled      = $archiveEnabled
    ArchiveSizeGB       = $archiveSize
    ArchiveItemCount    = $archiveItemCount
    ArchiveDeletedGB    = $archiveDeletedSize
    MailboxPlan         = $mailbox.SkuPartNumber
    MaxMailboxSizeGB    = 100
    MaxArchiveSizeGB    = 100
}

📤 Export to CSV

After gathering all results:

$results | Export-Csv -Path "MailboxReport.csv" -NoTypeInformation

This becomes your source of truth for usage data, trends, and alerting.

📄 The CSV File

This script will then use the CSV from the previous script:

"Timestamp","UserPrincipalName","DisplayName","PrimarySizeGB","ItemCount","DeletedItemSizeGB","ArchiveEnabled","ArchiveSizeGB","ArchiveItemCount","ArchiveDeletedGB","MailboxPlan","MaxMailboxSizeGB","MaxArchiveSizeGB"
"09/04/2025 15:18:29","Lee.Croucher@bythepowerofgreyskull.com","Lee Croucher","2.52","7051","2.07","True","3.62","39334","27.63","ExchangeOnline","100","100"

Each row represents a snapshot at a point in time for a given mailbox.

🔄 Generating the HTML view

Gather Your Data

The process starts with a CSV file (you already have this), exported from Exchange Online reporting or a custom script.

Each row is a snapshot of a mailbox's size, item count, archive usage, and more.

Load and Parse the CSV in PowerShell

PowerShell reads this file using:

$mailboxData = Import-Csv -Path "MailboxReport.csv"

This converts each line into a PowerShell object, so now we can work with each mailbox’s data programmatically.

Calculate Summary Stats

We calculate the following:

  1. Total mailboxes: just count the rows
  2. Total primary size: sum of PrimarySizeGB
  3. Total archive size
  4. Average mailbox size

These are used to generate the summary health cards at the top of the HTML report.

($mailboxData | Measure-Object -Property PrimarySizeGB -Sum).Sum

Compare Rows for Trend Arrows

Because the same mailbox can appear multiple times in the file (e.g. at different times), we use simple comparison logic to detect whether mailbox size is trending up or down.

if ($current.PrimarySizeGB -gt $previous.PrimarySizeGB) {
    $primaryTrend = "↑"
}

This provides visual indicators in the final report to show growth or reduction in usage.

Construct HTML with Tailwind CSS

We use Tailwind CSS via a CDN (no need to install anything) to style the HTML. Cards are styled with rounded corners, shadow, and clean typography.

The PowerShell script builds the HTML in parts:

  1. Header with Tailwind link
  2. Summary cards section
  3. Loop for detailed mailbox cards
  4. Close the HTML tags

Example:

$htmlContent = @"
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div class='health-card'> ... </div>
</body>
</html>
"@

Each card is filled in dynamically with data from $mailbox objects.

Inject Dynamic Data

Using string replacement in PowerShell, we take placeholders like:

<!-- Summary Cards -->
<!-- Mailbox data will be inserted here -->

And replace them with the actual built strings using .Replace().

$htmlContent = $htmlContent.Replace("<!-- Summary Cards -->", $summaryHtml)

Output the HTML File

Finally, the full HTML is written to disk:

$htmlContent | Set-Content -Path "MailboxHealthReport.html"

Scripts

The script is not available on this blog because it’s quite lengthy so if you would like it, please email me at scripts@a6n.co.uk

Previous Post Next Post

نموذج الاتصال