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:
🎯 Generating the CSV data
The script Connects to Exchange Online securely then iterates through all user and shared mailboxes and collects:
- Mailbox sizes (primary & archive)
- Item counts
- Deleted item sizes
- Mailbox plans and limits
- 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:
- TotalItemSize
- ItemCount
- 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:
- The presence of $archiveStats
- 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:
- Total mailboxes: just count the rows
- Total primary size: sum of PrimarySizeGB
- Total archive size
- 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:
- Header with Tailwind link
- Summary cards section
- Loop for detailed mailbox cards
- 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