ℹ️ Many blog posts do not include full scripts. If you require a complete version, please use the Support section in the menu.
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

Updating a Queue Monitoring System with Daily Pattern Analysis (with EdgeDriver)

I recently enhanced the Proofpoint queue monitoring script to include daily pattern analysis and trending. What started as a simple alert system evolved into a comprehensive monitoring solution that tracks queue behavior over time. Here's how I built it and the challenges I encountered along the way.

The Requirements

The original script monitored two queue types (Attachment Defense and Message Defense) and sent email alerts when queues exceeded zero. I needed to add:

  • Continuous monitoring with configurable intervals
  • Queue pattern tracking throughout the day
  • Daily summary emails at 7 AM showing 24-hour trends
  • Visual heatmaps showing hourly queue states
  • Separate recipient lists for alerts vs. summaries

Visual Overview 

The only new feature here is the daily overview showing the heat map so lets look at that now:


The heat map is the key section here so lets look at that a little more, as you can see that messages will generally be an issue between 07:00 and 15:00 in this example, but this is taken from the JSON file which will track the number of messages in the queue every hour for this report:

Implementation Overview

Upgrading from Plain Text to HTML Emails

The original script sent plain text notifications. I transformed these into professional HTML emails with health card-style status indicators:

# Function to determine status color
function Get-StatusColor($value) {
    if ($value -eq 0) { return "#4CAF50" }  # Green
    elseif ($value -ge 1 -and $value -le 5) { return "#FF9800" }  # Amber
    else { return "#F44336" }  # Red
}

# Function to determine status text
function Get-StatusText($value) {
    if ($value -eq 0) { return "Healthy" }
    elseif ($value -ge 1 -and $value -le 5) { return "Warning" }
    else { return "Critical" }
}

These thresholds provide immediate visual feedback:

  • Green (0): No issues, system healthy
  • Amber (1-5): Warning level, needs attention
  • Red (6+): Critical, immediate action required

The HTML design uses a minimalistic approach with mobile-responsive CSS:

.status-card {
    flex: 1;
    background-color: #f8f9fa;
    border-radius: 8px;
    padding: 20px;
    text-align: center;
    border: 1px solid #e0e0e0;
}

@media only screen and (max-width: 480px) {
    .status-grid {
        flex-direction: column;
    }
}

Adding the Loop Functionality

First, I wrapped the entire monitoring logic in a continuous loop with a configurable interval:

# Loop Settings
$loopIntervalMinutes = 60  # Time to wait between runs (in minutes)

$loopCount = 0
while ($true) {
    $loopCount++
    Write-Host "[INFO] Run #$loopCount started at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
    
    # ... monitoring logic ...
    
    Start-Sleep -Seconds ($loopIntervalMinutes * 60)
}

This allows the script to run indefinitely, checking queues every hour (or whatever interval you configure).

Tracking Queue Data

I implemented a JSON-based storage system to track queue values throughout the day:

# Trending Data Settings
$trendingDataFolder = "C:\ProgramData\ProofpointMonitor"
$trendingDataFile = Join-Path $trendingDataFolder "QueueTrends.json"
$lastSummaryFile = Join-Path $trendingDataFolder "LastSummary.txt"

Each reading captures:

  • Timestamp
  • Hour (for heatmap generation)
  • Attachment queue value
  • Message queue value

Separate Notification Streams

One key requirement was different recipient lists for alerts vs. summaries:

# SMTP Settings for immediate alerts
$to = "lee@croucher.cloud"
$subject = "Proofpoint Queue Notification"

# Summary Email Settings for daily reports
$summaryTo = "lee@croucher.cloud"
$summarySubject = "Proofpoint Queue Daily Summary Report"

This allows operational alerts to go to on-call staff while summaries reach a broader audience for trend analysis.

Why Pattern Analysis Instead of Message Tracking

An important design decision was focusing on queue patterns rather than trying to track individual messages. The challenge: when we see a queue of 3, we can't determine if it's:

  • Three different messages
  • One message stuck for three hours
  • A combination of stuck and new messages

Since we only have visibility into queue counts, not message IDs, I chose to track patterns and trends instead. This approach provides valuable insights without making false assumptions about message identity.

The JSON Array Challenge

The first major issue I encountered was with PowerShell's JSON handling. When appending data to the JSON file, I got this error:

Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.

The problem? PowerShell's ConvertFrom-Json returns different types depending on the JSON structure. Sometimes it's an array, sometimes a PSObject. Here's how I fixed it:

function Add-TrendingData {
    param($AttachmentValue, $MessageValue)
    
    $newEntry = @{
        Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
        Hour = (Get-Date).Hour
        AttachmentQueue = $AttachmentValue
        MessageQueue = $MessageValue
    }
    
    # Read existing data
    $data = @()
    if (Test-Path $trendingDataFile) {
        $jsonContent = Get-Content $trendingDataFile -Raw
        if ($jsonContent) {
            $existingData = $jsonContent | ConvertFrom-Json
            # Convert to array if it's a single object
            if ($existingData -is [System.Array]) {
                $data = @($existingData)
            } else {
                $data = @($existingData)
            }
        }
    }
    
    # Add new entry
    $data += $newEntry
    
    # Save updated data
    $data | ConvertTo-Json -Depth 10 | Set-Content $trendingDataFile -Force
}

The key improvements:

  • Always ensure $data is an array using @()
  • Check if existing data is already an array
  • Add -Depth 10 to ConvertTo-Json for proper serialization

Building the Daily Summary

The daily summary runs at 7 AM and provides insights into the previous 24 hours of queue activity. I calculate several key metrics:

# Calculate statistics
$totalReadings = $data.Count
$attachmentReadings = @($data | Where-Object { $_.AttachmentQueue -gt 0 })
$messageReadings = @($data | Where-Object { $_.MessageQueue -gt 0 })

$maxAttachment = ($data | ForEach-Object { $_.AttachmentQueue } | Measure-Object -Maximum).Maximum
$maxMessage = ($data | ForEach-Object { $_.MessageQueue } | Measure-Object -Maximum).Maximum

$attachmentActiveHours = ($attachmentReadings.Count / [Math]::Max($totalReadings, 1)) * 24
$messageActiveHours = ($messageReadings.Count / [Math]::Max($totalReadings, 1)) * 24

Tracking Queue Events

One interesting metric is "queue events" - how many times a queue went from 0 to a non-zero value. This indicates new incidents rather than sustained issues:

# Count new queue events (transitions from 0 to >0)
$attachmentEvents = 0
$messageEvents = 0
for ($i = 1; $i -lt $data.Count; $i++) {
    if ($data[$i-1].AttachmentQueue -eq 0 -and $data[$i].AttachmentQueue -gt 0) {
        $attachmentEvents++
    }
    if ($data[$i-1].MessageQueue -eq 0 -and $data[$i].MessageQueue -gt 0) {
        $messageEvents++
    }
}

Creating the Visual Heatmap

The most impactful part of the summary is the 24-hour heatmap. I build it dynamically in HTML:

# Build hourly heatmap data
$hourlyMax = @{}
0..23 | ForEach-Object { 
    $hourlyMax[$_] = @{
        Attachment = 0
        Message = 0
    }
}

foreach ($reading in $data) {
    $hour = [int]$reading.Hour
    if ([int]$reading.AttachmentQueue -gt $hourlyMax[$hour].Attachment) {
        $hourlyMax[$hour].Attachment = [int]$reading.AttachmentQueue
    }
    if ([int]$reading.MessageQueue -gt $hourlyMax[$hour].Message) {
        $hourlyMax[$hour].Message = [int]$reading.MessageQueue
    }
}

Note the explicit [int] casting - another lesson learned when PowerShell was treating numbers as strings from the JSON.

HTML Email Generation

I kept the HTML simple and MIME-compatible, avoiding Unicode characters. The design philosophy was minimalistic and professional:

# Generate color-coded cells
0..23 | ForEach-Object {
    $value = $hourlyMax[$_].Attachment
    $class = if ($value -eq 0) { "heat-green" } 
             elseif ($value -le 5) { "heat-amber" } 
             else { "heat-red" }
    $displayValue = if ($value -eq 0) { "0" } else { $value }
    $body += "<td><span class='heat-cell $class'>$displayValue</span></td>"
}

The heat cells use simple CSS classes with clear visual distinction:

.heat-cell {
    width: 30px;
    height: 30px;
    display: inline-block;
    border-radius: 4px;
    line-height: 30px;
    color: white;
    font-weight: 600;
}
.heat-green { background-color: #4CAF50; }
.heat-amber { background-color: #FF9800; }
.heat-red { background-color: #F44336; }

Important: No emojis or special Unicode characters - just colors and numbers that render reliably across all email clients.

Preventing Duplicate Summaries

To ensure the summary only sends once per day at 7 AM, I track the last sent date:

function Should-SendSummary {
    $currentHour = (Get-Date).Hour
    
    # Check if it's 7 AM
    if ($currentHour -ne $summaryHour) {
        return $false
    }
    
    # Check if we already sent summary today
    if (Test-Path $lastSummaryFile) {
        $lastSummaryDate = Get-Content $lastSummaryFile
        $today = (Get-Date).ToString("yyyy-MM-dd")
        if ($lastSummaryDate -eq $today) {
            return $false
        }
    }
    
    return $true
}

Data Cleanup for Fresh Daily Tracking

After sending the 7 AM summary, the script cleans up to start fresh for the new day:

# Send summary email
Send-MailMessage -From $from -To $summaryTo.Split(',') -Subject $summarySubject -Body $body -BodyAsHtml -SmtpServer $smtpServer
Write-Host "[INFO] Daily summary email sent to: $summaryTo"

# Clear trending data for new day
Remove-Item $trendingDataFile -Force -ErrorAction SilentlyContinue
Write-Host "[INFO] Trending data cleared for new day"

# Record that we sent summary today
(Get-Date).ToString("yyyy-MM-dd") | Set-Content $lastSummaryFile -Force

This ensures:

  • Each day starts with a clean JSON file
  • File size remains manageable (only 24 hours of data)
  • Historical data doesn't interfere with new patterns

Maintaining Test Mode

I preserved the original FireEmail test mode, which exits after one run:

# Exit if FireEmail mode (test mode - single run)
if ($FireEmail) {
    Write-Host "[INFO] FireEmail mode - exiting after single run."
    break
}

Conclusion

The enhanced monitoring system now provides:

  • Real-time alerts when queues exceed thresholds
  • Historical pattern analysis showing peak times
  • Quantifiable metrics (active hours, event counts)
  • Visual representation of daily patterns
  • Separate notification streams for different audiences

The daily summaries have already revealed patterns I hadn't noticed before - consistent morning spikes and afternoon peaks that correlate with business email patterns. This data helps with capacity planning and identifying systemic issues versus one-off incidents.

The complete script runs as a scheduled task, providing 24/7 monitoring with minimal overhead. The JSON file remains small (under 1KB for 24 hours of hourly readings), and the script automatically cleans up after sending each daily summary.

Previous Post Next Post

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