Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

Exchange Online Mailbox Monitor: From Basic Alerts to Visual Reports

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">&#8593;</span></span>' 
    }
    "decreasing" { 
        $mainArrow = '<span class="trend down"><span class="trend-arrow">&#8595;</span></span>' 
    }
    default { 
        $mainArrow = '<span class="trend stable"><span class="trend-arrow">&#8594;</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.

Previous Post Next Post

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