I needed a PowerShell script to monitor Exchange Online mailbox usage for both user and shared mailboxes. What started as a simple monitoring tool evolved into a comprehensive solution that tracks trends, handles archives, and generates beautiful HTML reports. Here's my journey through the challenges I encountered and how I solved them.
Initial Working Script
The original script worked well for basic monitoring. It could:
- Connect to Exchange Online
- Monitor specified mailboxes
- Send alerts when thresholds were exceeded
- Generate HTML reports
However, I quickly discovered several limitations that needed addressing - issued cause by assumptions.
Visual End Results
Issue #1: Shared Mailboxes Not Being Detected
The first major issue emerged when monitoring Shared Mailboxes, despite being a shared mailbox with significant usage, the script kept reporting it didn't exist.
The Problem
The original code simply tried to get mailbox statistics without checking the mailbox type:
# Original approach - worked for user mailboxes only
$mailbox = Get-Mailbox -Identity $EmailAddress -ErrorAction Stop
$stats = Get-MailboxStatistics -Identity $EmailAddress -ErrorAction Stop
The Solution
I discovered that shared mailboxes needed to be identified by their RecepiantTypeDetails property:
# Determine mailbox type
if ($mailbox.RecipientTypeDetails -eq "SharedMailbox") {
$result.MailboxType = "Shared"
Write-Log "Detected shared mailbox: $EmailAddress" -Level Info
} elseif ($mailbox.RecipientTypeDetails -eq "UserMailbox") {
$result.MailboxType = "User"
} else {
$result.MailboxType = $mailbox.RecipientTypeDetails.ToString()
}
Issue #2: Archive Mailboxes Not Being Tracked
My next challenge was tracking archive mailboxes. The shared mailbox had 24.39 GB in its archive, but this wasn't being monitored or tracked historically.
The Problem
While the script could detect archives, it wasn't saving the archive data to the historical JSON file:
# Original historical data - missing archive information
$historicalData[$email] += @{
Timestamp = $CurrentData.Timestamp
MainUsedPercent = $CurrentData.MainMailbox.UsedPercent
MainUsedGB = $CurrentData.MainMailbox.UsedGB
# Archive data was missing!
}
The Solution
I updated the historical data tracking to include archive information:
# Add new data point with archive information
$newDataPoint = @{
Timestamp = $CurrentData.Timestamp
MainUsedPercent = $CurrentData.MainMailbox.UsedPercent
MainUsedGB = $CurrentData.MainMailbox.UsedGB
ArchiveUsedPercent = $CurrentData.ArchiveMailbox.UsedPercent
ArchiveUsedGB = $CurrentData.ArchiveMailbox.UsedGB
ArchiveAvailable = $CurrentData.ArchiveMailbox.Available
}
Issue #3: Shared Mailbox Archives Not Detected
Even after fixing shared mailbox detection, their archive mailboxes still weren't being found. This was particularly frustrating as I could see the archive had 24.39 GB of data.
The Problem
For user mailboxes, the script checked the ArchiveStatus property, but shared mailboxes handle archives differently:
# This didn't work for shared mailboxes
if ($mailbox.ArchiveStatus -eq 'Active') {
# Check for archive...
}
The Solution
I implemented a different approach for shared mailboxes - attempting to get archive statistics directly:
if ($result.MailboxType -eq "Shared") {
# For shared mailboxes, try to get archive statistics directly
try {
$archiveStats = Get-MailboxStatistics -Identity $EmailAddress -Archive -ErrorAction Stop
if ($archiveStats) {
$hasArchive = $true
Write-Log "Archive found for shared mailbox: $EmailAddress" -Level Info
}
}
catch {
Write-Log "No archive for shared mailbox: $EmailAddress" -Level Info
}
} else {
# For user mailboxes, use the ArchiveStatus property
$hasArchive = ($mailbox.ArchiveStatus -eq 'Active')
}
Issue #4: Unicode Arrows Breaking in HTML
The trend indicators were showing as "?" marks in the HTML report instead of arrows.
The Problem
Unicode arrows (↑, ↓, →) weren't rendering properly in all HTML viewers:
# These Unicode characters showed as "?"
$mainArrow = "↑"
$archiveArrow = "↓"
The Solution
I switched to HTML entities that render correctly everywhere:
switch ($mainTrend) {
"increasing" {
$mainArrow = '<span class="trend up"><span class="trend-arrow">↑</span></span>'
}
"decreasing" {
$mainArrow = '<span class="trend down"><span class="trend-arrow">↓</span></span>'
}
default {
$mainArrow = '<span class="trend stable"><span class="trend-arrow">→</span></span>'
}
}
Issue #5: Broken Trend Detection Logic
The most frustrating issue was the trend detection. Despite mailbox usage clearly increasing (39.9% → 40.13% → 40.19%), the script kept showing it as "stable".
The Problem
The original trending logic calculated relative percentage change:
# Original flawed logic
$change = $lastValue - $firstValue
$changePercent = if ($firstValue -gt 0) { ($change / $firstValue) * 100 } else { 0 }
# For 39.9% to 40.19%:
# (40.19 - 39.9) / 39.9 * 100 = 0.73% change
# Since 0.73% < 5%, it showed as "stable"
The Solution
After being perplexed on this for a moment, I realized mailbox percentages should use absolute percentage point changes, not relative changes:
function Calculate-Trend {
param([array]$Values)
if ($Values.Count -lt 2) {
return "stable"
}
# Calculate trend based on absolute percentage point change
$firstValue = $Values[0]
$lastValue = $Values[-1]
$change = $lastValue - $firstValue
# For mailbox percentages, use absolute change in percentage points
if ($change -gt 0.1) { # More than 0.1 percentage point increase
return "increasing"
}
elseif ($change -lt -0.1) { # More than 0.1 percentage point decrease
return "decreasing"
}
else {
return "stable"
}
}
Conclusion
The key to success was iterating on each problem, testing with production data, and not giving up when things didn't work as expected. Sometimes the solution requires thinking differently about the problem - like realizing shared mailboxes need a completely different approach for archive detection.