a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
1294 lines
68 KiB
PowerShell
1294 lines
68 KiB
PowerShell
# Exchange Metrics Collector - Outputs Prometheus-compatible metrics
|
|
# Requires Exchange Management Shell and appropriate permissions
|
|
|
|
param(
|
|
[switch]$InstallScheduledTask,
|
|
[int]$TaskIntervalMinutes = 5
|
|
)
|
|
|
|
if ($InstallScheduledTask) {
|
|
$taskName = "ExchangeMetricsExporter"
|
|
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
|
|
if (-not $existingTask) {
|
|
$taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`""
|
|
|
|
if (-not $TaskIntervalMinutes -or $TaskIntervalMinutes -le 0) {
|
|
throw "TaskIntervalMinutes must be a positive integer"
|
|
}
|
|
|
|
$taskTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes $TaskIntervalMinutes) -RepetitionDuration (New-TimeSpan -Days 365)
|
|
$taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
|
|
|
try {
|
|
Write-Host "Creating scheduled task: $taskName"
|
|
Register-ScheduledTask -TaskName $taskName -Action $taskAction -Trigger $taskTrigger -Principal $taskPrincipal -Description "Exports Exchange metrics for Prometheus every $TaskIntervalMinutes minutes"
|
|
|
|
$createdTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
if (-not $createdTask) {
|
|
throw "Failed to verify scheduled task creation"
|
|
}
|
|
Write-Host "Successfully created scheduled task: $taskName" -ForegroundColor Green
|
|
} catch {
|
|
Write-Error "Failed to create auto-start task: $($_.Exception.Message)"
|
|
throw
|
|
}
|
|
} else {
|
|
Write-Host "Scheduled task '$taskName' already exists, skipping creation"
|
|
}
|
|
}
|
|
|
|
$StartTime = Get-Date
|
|
$Hostname = $env:COMPUTERNAME
|
|
|
|
# Try to load Exchange Management Shell if not already loaded
|
|
if (-not (Get-Command Get-TransportServer -ErrorAction SilentlyContinue)) {
|
|
try {
|
|
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction SilentlyContinue
|
|
} catch {
|
|
# Try loading as module for newer Exchange versions
|
|
try {
|
|
$ExchangeInstallPath = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup -ErrorAction SilentlyContinue).MsiInstallPath
|
|
if ($ExchangeInstallPath) {
|
|
. "$ExchangeInstallPath\bin\RemoteExchange.ps1" -ErrorAction SilentlyContinue
|
|
Connect-ExchangeServer -auto -ErrorAction SilentlyContinue
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
# Helper function to safely get counter values
|
|
function Get-SafeCounter {
|
|
param([string]$CounterPath)
|
|
try {
|
|
$counter = Get-Counter -Counter $CounterPath -ErrorAction SilentlyContinue
|
|
if ($counter -and $counter.CounterSamples) {
|
|
return [math]::Max(0, [math]::Round($counter.CounterSamples[0].CookedValue, 2))
|
|
}
|
|
} catch {}
|
|
return 0
|
|
}
|
|
|
|
# Helper function to safely count event log entries
|
|
function Get-EventLogCount {
|
|
param(
|
|
[string]$LogName,
|
|
[string]$Source,
|
|
[int]$EventId,
|
|
[int]$Hours = 24
|
|
)
|
|
try {
|
|
$startDate = (Get-Date).AddHours(-$Hours)
|
|
$filter = @{
|
|
LogName = $LogName
|
|
StartTime = $startDate
|
|
}
|
|
if ($Source) { $filter['ProviderName'] = $Source }
|
|
if ($EventId) { $filter['Id'] = $EventId }
|
|
|
|
return (Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# TRANSPORT QUEUES
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_queue_length Number of messages in each transport queue"
|
|
Write-Output "# TYPE exchange_queue_length gauge"
|
|
|
|
try {
|
|
$queues = Get-Queue -ErrorAction SilentlyContinue
|
|
if ($queues) {
|
|
foreach ($queue in $queues) {
|
|
$queueType = $queue.DeliveryType -replace '\s+', '_'
|
|
$queueStatus = $queue.Status
|
|
$nextHop = ($queue.NextHopDomain -replace '["\s]', '') -replace '\.', '_'
|
|
Write-Output "exchange_queue_length{queue_type=`"$queueType`",status=`"$queueStatus`",next_hop=`"$nextHop`",hostname=`"$Hostname`"} $($queue.MessageCount)"
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# Queue totals by status
|
|
Write-Output "# HELP exchange_queue_total_messages Total messages in queues by status"
|
|
Write-Output "# TYPE exchange_queue_total_messages gauge"
|
|
try {
|
|
$queueStats = Get-Queue -ErrorAction SilentlyContinue | Group-Object Status
|
|
foreach ($stat in $queueStats) {
|
|
$total = ($stat.Group | Measure-Object -Property MessageCount -Sum).Sum
|
|
Write-Output "exchange_queue_total_messages{status=`"$($stat.Name)`",hostname=`"$Hostname`"} $total"
|
|
}
|
|
} catch {}
|
|
|
|
# Submission queue (messages waiting to be categorized)
|
|
Write-Output "# HELP exchange_submission_queue_length Messages in submission queue"
|
|
Write-Output "# TYPE exchange_submission_queue_length gauge"
|
|
$submissionQueue = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Submission Queue Length"
|
|
Write-Output "exchange_submission_queue_length{hostname=`"$Hostname`"} $submissionQueue"
|
|
|
|
# Poison queue (messages that failed repeatedly)
|
|
Write-Output "# HELP exchange_poison_queue_length Messages in poison queue"
|
|
Write-Output "# TYPE exchange_poison_queue_length gauge"
|
|
$poisonQueue = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Poison Queue Length"
|
|
Write-Output "exchange_poison_queue_length{hostname=`"$Hostname`"} $poisonQueue"
|
|
|
|
# Unreachable queue
|
|
Write-Output "# HELP exchange_unreachable_queue_length Messages in unreachable queue"
|
|
Write-Output "# TYPE exchange_unreachable_queue_length gauge"
|
|
$unreachableQueue = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Unreachable Queue Length"
|
|
Write-Output "exchange_unreachable_queue_length{hostname=`"$Hostname`"} $unreachableQueue"
|
|
|
|
# Active mailbox delivery queue
|
|
Write-Output "# HELP exchange_active_mailbox_queue_length Active mailbox delivery queue"
|
|
Write-Output "# TYPE exchange_active_mailbox_queue_length gauge"
|
|
$activeMailbox = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Active Mailbox Delivery Queue Length"
|
|
Write-Output "exchange_active_mailbox_queue_length{hostname=`"$Hostname`"} $activeMailbox"
|
|
|
|
# Active non-SMTP delivery queue
|
|
Write-Output "# HELP exchange_active_nonsmtp_queue_length Active non-SMTP delivery queue"
|
|
Write-Output "# TYPE exchange_active_nonsmtp_queue_length gauge"
|
|
$activeNonSmtp = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Active Non-Smtp Delivery Queue Length"
|
|
Write-Output "exchange_active_nonsmtp_queue_length{hostname=`"$Hostname`"} $activeNonSmtp"
|
|
|
|
# Retry mailbox delivery queue
|
|
Write-Output "# HELP exchange_retry_mailbox_queue_length Retry mailbox delivery queue"
|
|
Write-Output "# TYPE exchange_retry_mailbox_queue_length gauge"
|
|
$retryMailbox = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Retry Mailbox Delivery Queue Length"
|
|
Write-Output "exchange_retry_mailbox_queue_length{hostname=`"$Hostname`"} $retryMailbox"
|
|
|
|
# ============================================================================
|
|
# MESSAGE THROUGHPUT
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_messages_received_total Total messages received"
|
|
Write-Output "# TYPE exchange_messages_received_total counter"
|
|
$messagesReceived = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Messages Received Total"
|
|
Write-Output "exchange_messages_received_total{hostname=`"$Hostname`"} $messagesReceived"
|
|
|
|
Write-Output "# HELP exchange_messages_sent_total Total messages sent"
|
|
Write-Output "# TYPE exchange_messages_sent_total counter"
|
|
$messagesSent = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\Messages Sent Total"
|
|
Write-Output "exchange_messages_sent_total{hostname=`"$Hostname`"} $messagesSent"
|
|
|
|
Write-Output "# HELP exchange_messages_received_per_second Messages received per second"
|
|
Write-Output "# TYPE exchange_messages_received_per_second gauge"
|
|
$messagesReceivedSec = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Messages Received/sec"
|
|
Write-Output "exchange_messages_received_per_second{hostname=`"$Hostname`"} $messagesReceivedSec"
|
|
|
|
Write-Output "# HELP exchange_messages_sent_per_second Messages sent per second"
|
|
Write-Output "# TYPE exchange_messages_sent_per_second gauge"
|
|
$messagesSentSec = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\Messages Sent/sec"
|
|
Write-Output "exchange_messages_sent_per_second{hostname=`"$Hostname`"} $messagesSentSec"
|
|
|
|
# Bytes transferred
|
|
Write-Output "# HELP exchange_bytes_received_total Total bytes received"
|
|
Write-Output "# TYPE exchange_bytes_received_total counter"
|
|
$bytesReceived = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Bytes Received Total"
|
|
Write-Output "exchange_bytes_received_total{hostname=`"$Hostname`"} $bytesReceived"
|
|
|
|
Write-Output "# HELP exchange_bytes_sent_total Total bytes sent"
|
|
Write-Output "# TYPE exchange_bytes_sent_total counter"
|
|
$bytesSent = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\Bytes Sent Total"
|
|
Write-Output "exchange_bytes_sent_total{hostname=`"$Hostname`"} $bytesSent"
|
|
|
|
# ============================================================================
|
|
# SMTP CONNECTIONS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_smtp_connections_current Current SMTP connections"
|
|
Write-Output "# TYPE exchange_smtp_connections_current gauge"
|
|
$smtpInbound = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Connections Current"
|
|
$smtpOutbound = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\Connections Current"
|
|
Write-Output "exchange_smtp_connections_current{direction=`"inbound`",hostname=`"$Hostname`"} $smtpInbound"
|
|
Write-Output "exchange_smtp_connections_current{direction=`"outbound`",hostname=`"$Hostname`"} $smtpOutbound"
|
|
|
|
Write-Output "# HELP exchange_smtp_connections_total Total SMTP connections"
|
|
Write-Output "# TYPE exchange_smtp_connections_total counter"
|
|
$smtpInboundTotal = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Connections Total"
|
|
$smtpOutboundTotal = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\Connections Total"
|
|
Write-Output "exchange_smtp_connections_total{direction=`"inbound`",hostname=`"$Hostname`"} $smtpInboundTotal"
|
|
Write-Output "exchange_smtp_connections_total{direction=`"outbound`",hostname=`"$Hostname`"} $smtpOutboundTotal"
|
|
|
|
# SMTP connection failures
|
|
Write-Output "# HELP exchange_smtp_connection_failures_total SMTP connection failures"
|
|
Write-Output "# TYPE exchange_smtp_connection_failures_total counter"
|
|
$smtpSendFailures = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\Connection Failures"
|
|
Write-Output "exchange_smtp_connection_failures_total{hostname=`"$Hostname`"} $smtpSendFailures"
|
|
|
|
# ============================================================================
|
|
# MESSAGE TRACKING (from logs)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_tracked_messages_total Messages by event type (last 24h)"
|
|
Write-Output "# TYPE exchange_tracked_messages_total counter"
|
|
|
|
try {
|
|
$trackingStart = (Get-Date).AddHours(-24)
|
|
$eventTypes = @('RECEIVE', 'SEND', 'DELIVER', 'FAIL', 'DSN', 'DEFER', 'EXPAND', 'REDIRECT', 'RESOLVE', 'DROP', 'BADMAIL', 'POISONMESSAGE')
|
|
|
|
foreach ($eventType in $eventTypes) {
|
|
try {
|
|
$count = (Get-MessageTrackingLog -Start $trackingStart -EventId $eventType -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_tracked_messages_total{event_type=`"$eventType`",hostname=`"$Hostname`"} $count"
|
|
} catch {
|
|
Write-Output "exchange_tracked_messages_total{event_type=`"$eventType`",hostname=`"$Hostname`"} 0"
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# Message delivery latency from tracking logs
|
|
Write-Output "# HELP exchange_message_latency_seconds Message delivery latency statistics"
|
|
Write-Output "# TYPE exchange_message_latency_seconds gauge"
|
|
try {
|
|
$deliveries = Get-MessageTrackingLog -Start (Get-Date).AddHours(-1) -EventId DELIVER -ResultSize 1000 -ErrorAction SilentlyContinue
|
|
if ($deliveries -and $deliveries.Count -gt 0) {
|
|
$latencies = $deliveries | ForEach-Object {
|
|
$received = Get-MessageTrackingLog -MessageId $_.MessageId -EventId RECEIVE -ResultSize 1 -ErrorAction SilentlyContinue
|
|
if ($received) {
|
|
($_.Timestamp - $received.Timestamp).TotalSeconds
|
|
}
|
|
} | Where-Object { $_ -gt 0 }
|
|
|
|
if ($latencies -and $latencies.Count -gt 0) {
|
|
$avgLatency = ($latencies | Measure-Object -Average).Average
|
|
$maxLatency = ($latencies | Measure-Object -Maximum).Maximum
|
|
Write-Output "exchange_message_latency_seconds{stat=`"avg`",hostname=`"$Hostname`"} $([math]::Round($avgLatency, 2))"
|
|
Write-Output "exchange_message_latency_seconds{stat=`"max`",hostname=`"$Hostname`"} $([math]::Round($maxLatency, 2))"
|
|
} else {
|
|
Write-Output "exchange_message_latency_seconds{stat=`"avg`",hostname=`"$Hostname`"} 0"
|
|
Write-Output "exchange_message_latency_seconds{stat=`"max`",hostname=`"$Hostname`"} 0"
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Output "exchange_message_latency_seconds{stat=`"avg`",hostname=`"$Hostname`"} 0"
|
|
Write-Output "exchange_message_latency_seconds{stat=`"max`",hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAILBOX STATISTICS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_mailbox_count Total number of mailboxes"
|
|
Write-Output "# TYPE exchange_mailbox_count gauge"
|
|
try {
|
|
$mailboxCount = (Get-Mailbox -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_mailbox_count{hostname=`"$Hostname`"} $mailboxCount"
|
|
} catch {
|
|
Write-Output "exchange_mailbox_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# Mailbox database sizes and stats
|
|
Write-Output "# HELP exchange_database_size_bytes Mailbox database size in bytes"
|
|
Write-Output "# TYPE exchange_database_size_bytes gauge"
|
|
Write-Output "# HELP exchange_database_available_space_bytes Available space in mailbox database"
|
|
Write-Output "# TYPE exchange_database_available_space_bytes gauge"
|
|
Write-Output "# HELP exchange_database_mailbox_count Mailboxes per database"
|
|
Write-Output "# TYPE exchange_database_mailbox_count gauge"
|
|
|
|
try {
|
|
$databases = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue
|
|
foreach ($db in $databases) {
|
|
$dbName = $db.Name -replace '\s+', '_'
|
|
$dbSize = if ($db.DatabaseSize) { $db.DatabaseSize.ToBytes() } else { 0 }
|
|
$dbAvailable = if ($db.AvailableNewMailboxSpace) { $db.AvailableNewMailboxSpace.ToBytes() } else { 0 }
|
|
Write-Output "exchange_database_size_bytes{database=`"$dbName`",hostname=`"$Hostname`"} $dbSize"
|
|
Write-Output "exchange_database_available_space_bytes{database=`"$dbName`",hostname=`"$Hostname`"} $dbAvailable"
|
|
|
|
$mbxInDb = (Get-Mailbox -Database $db.Name -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_database_mailbox_count{database=`"$dbName`",hostname=`"$Hostname`"} $mbxInDb"
|
|
}
|
|
} catch {}
|
|
|
|
# Database mount status
|
|
Write-Output "# HELP exchange_database_mounted Database mount status (1=mounted, 0=dismounted)"
|
|
Write-Output "# TYPE exchange_database_mounted gauge"
|
|
try {
|
|
$databases = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue
|
|
foreach ($db in $databases) {
|
|
$dbName = $db.Name -replace '\s+', '_'
|
|
$mounted = if ($db.Mounted) { 1 } else { 0 }
|
|
Write-Output "exchange_database_mounted{database=`"$dbName`",hostname=`"$Hostname`"} $mounted"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# CONTENT FILTERING / ANTI-SPAM
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_antispam_messages_total Anti-spam filter results"
|
|
Write-Output "# TYPE exchange_antispam_messages_total counter"
|
|
|
|
# Content Filter Agent
|
|
$cfBlocked = Get-SafeCounter "\MSExchange Content Filter Agent\Messages that Triggered the Content Filter"
|
|
$cfQuarantined = Get-SafeCounter "\MSExchange Content Filter Agent\Messages Quarantined"
|
|
$cfRejected = Get-SafeCounter "\MSExchange Content Filter Agent\Messages Rejected"
|
|
$cfDeleted = Get-SafeCounter "\MSExchange Content Filter Agent\Messages Deleted"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"content`",action=`"triggered`",hostname=`"$Hostname`"} $cfBlocked"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"content`",action=`"quarantined`",hostname=`"$Hostname`"} $cfQuarantined"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"content`",action=`"rejected`",hostname=`"$Hostname`"} $cfRejected"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"content`",action=`"deleted`",hostname=`"$Hostname`"} $cfDeleted"
|
|
|
|
# Sender Filter Agent
|
|
$sfBlocked = Get-SafeCounter "\MSExchange Sender Filter Agent\Messages Evaluated by Sender Filter"
|
|
$sfRejected = Get-SafeCounter "\MSExchange Sender Filter Agent\Messages Rejected by Sender Filter"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"sender`",action=`"evaluated`",hostname=`"$Hostname`"} $sfBlocked"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"sender`",action=`"rejected`",hostname=`"$Hostname`"} $sfRejected"
|
|
|
|
# Sender ID Agent
|
|
$sidPass = Get-SafeCounter "\MSExchange Sender Id Agent\Messages That Passed Sender ID Validation"
|
|
$sidFail = Get-SafeCounter "\MSExchange Sender Id Agent\Messages That Failed Sender ID Validation"
|
|
$sidNeutral = Get-SafeCounter "\MSExchange Sender Id Agent\Messages with Neutral Sender ID Validation Result"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"sender_id`",action=`"pass`",hostname=`"$Hostname`"} $sidPass"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"sender_id`",action=`"fail`",hostname=`"$Hostname`"} $sidFail"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"sender_id`",action=`"neutral`",hostname=`"$Hostname`"} $sidNeutral"
|
|
|
|
# Recipient Filter Agent
|
|
$rfBlocked = Get-SafeCounter "\MSExchange Recipient Filter Agent\Recipients Rejected by Recipient Validation"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"recipient`",action=`"rejected`",hostname=`"$Hostname`"} $rfBlocked"
|
|
|
|
# Connection Filter Agent (IP Block List)
|
|
$connBlocked = Get-SafeCounter "\MSExchange Connection Filtering Agent\Connections on IP Block List"
|
|
$connAllowed = Get-SafeCounter "\MSExchange Connection Filtering Agent\Connections on IP Allow List"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"connection`",action=`"blocked`",hostname=`"$Hostname`"} $connBlocked"
|
|
Write-Output "exchange_antispam_messages_total{filter=`"connection`",action=`"allowed`",hostname=`"$Hostname`"} $connAllowed"
|
|
|
|
# ============================================================================
|
|
# SPAM CONFIDENCE LEVEL (SCL) DISTRIBUTION
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_scl_distribution Messages by SCL rating"
|
|
Write-Output "# TYPE exchange_scl_distribution counter"
|
|
for ($scl = -1; $scl -le 9; $scl++) {
|
|
$sclCount = Get-SafeCounter "\MSExchange Content Filter Agent\Messages with SCL Rating $scl"
|
|
Write-Output "exchange_scl_distribution{scl=`"$scl`",hostname=`"$Hostname`"} $sclCount"
|
|
}
|
|
|
|
# ============================================================================
|
|
# AUTHENTICATION
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_auth_total Authentication attempts"
|
|
Write-Output "# TYPE exchange_auth_total counter"
|
|
|
|
# SMTP AUTH
|
|
$smtpAuthSuccess = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Auth Succeeded"
|
|
$smtpAuthFail = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Auth Failed"
|
|
Write-Output "exchange_auth_total{type=`"smtp`",result=`"success`",hostname=`"$Hostname`"} $smtpAuthSuccess"
|
|
Write-Output "exchange_auth_total{type=`"smtp`",result=`"failed`",hostname=`"$Hostname`"} $smtpAuthFail"
|
|
|
|
# OWA Authentication (from event logs)
|
|
$owaAuthSuccess = Get-EventLogCount -LogName "MSExchange Management" -Source "MSExchange OWA" -Hours 24
|
|
Write-Output "exchange_auth_total{type=`"owa`",result=`"success`",hostname=`"$Hostname`"} $owaAuthSuccess"
|
|
|
|
# ActiveSync Authentication
|
|
$easAuthFail = Get-EventLogCount -LogName "Application" -Source "MSExchange ActiveSync" -EventId 1053 -Hours 24
|
|
Write-Output "exchange_auth_total{type=`"activesync`",result=`"failed`",hostname=`"$Hostname`"} $easAuthFail"
|
|
|
|
# ============================================================================
|
|
# TLS/ENCRYPTION
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_tls_connections_total TLS connection statistics"
|
|
Write-Output "# TYPE exchange_tls_connections_total counter"
|
|
|
|
$tlsInbound = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\TLS Sessions Started"
|
|
$tlsOutbound = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\TLS Sessions Started"
|
|
Write-Output "exchange_tls_connections_total{direction=`"inbound`",hostname=`"$Hostname`"} $tlsInbound"
|
|
Write-Output "exchange_tls_connections_total{direction=`"outbound`",hostname=`"$Hostname`"} $tlsOutbound"
|
|
|
|
Write-Output "# HELP exchange_tls_negotiation_failures TLS negotiation failures"
|
|
Write-Output "# TYPE exchange_tls_negotiation_failures counter"
|
|
$tlsInboundFail = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\TLS Negotiations Failed"
|
|
$tlsOutboundFail = Get-SafeCounter "\MSExchangeTransport SmtpSend(_total)\TLS Negotiations Failed"
|
|
Write-Output "exchange_tls_negotiation_failures{direction=`"inbound`",hostname=`"$Hostname`"} $tlsInboundFail"
|
|
Write-Output "exchange_tls_negotiation_failures{direction=`"outbound`",hostname=`"$Hostname`"} $tlsOutboundFail"
|
|
|
|
# ============================================================================
|
|
# CLIENT ACCESS (OWA, EWS, ActiveSync, Outlook Anywhere)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_owa_requests_total OWA requests"
|
|
Write-Output "# TYPE exchange_owa_requests_total counter"
|
|
$owaRequests = Get-SafeCounter "\MSExchange OWA\Requests/sec"
|
|
$owaRequestsTotal = Get-SafeCounter "\MSExchange OWA\Total Requests"
|
|
Write-Output "exchange_owa_requests_total{hostname=`"$Hostname`"} $owaRequestsTotal"
|
|
|
|
Write-Output "# HELP exchange_owa_requests_per_second OWA requests per second"
|
|
Write-Output "# TYPE exchange_owa_requests_per_second gauge"
|
|
Write-Output "exchange_owa_requests_per_second{hostname=`"$Hostname`"} $owaRequests"
|
|
|
|
Write-Output "# HELP exchange_owa_current_users Current OWA users"
|
|
Write-Output "# TYPE exchange_owa_current_users gauge"
|
|
$owaUsers = Get-SafeCounter "\MSExchange OWA\Current Unique Users"
|
|
Write-Output "exchange_owa_current_users{hostname=`"$Hostname`"} $owaUsers"
|
|
|
|
# EWS
|
|
Write-Output "# HELP exchange_ews_requests_per_second EWS requests per second"
|
|
Write-Output "# TYPE exchange_ews_requests_per_second gauge"
|
|
$ewsRequests = Get-SafeCounter "\MSExchangeWS\Requests/sec"
|
|
Write-Output "exchange_ews_requests_per_second{hostname=`"$Hostname`"} $ewsRequests"
|
|
|
|
Write-Output "# HELP exchange_ews_current_connections Current EWS connections"
|
|
Write-Output "# TYPE exchange_ews_current_connections gauge"
|
|
$ewsConns = Get-SafeCounter "\MSExchangeWS\Current Connections"
|
|
Write-Output "exchange_ews_current_connections{hostname=`"$Hostname`"} $ewsConns"
|
|
|
|
# ActiveSync
|
|
Write-Output "# HELP exchange_activesync_requests_per_second ActiveSync requests per second"
|
|
Write-Output "# TYPE exchange_activesync_requests_per_second gauge"
|
|
$easRequests = Get-SafeCounter "\MSExchange ActiveSync\Requests/sec"
|
|
Write-Output "exchange_activesync_requests_per_second{hostname=`"$Hostname`"} $easRequests"
|
|
|
|
Write-Output "# HELP exchange_activesync_current_requests Current ActiveSync requests"
|
|
Write-Output "# TYPE exchange_activesync_current_requests gauge"
|
|
$easCurrent = Get-SafeCounter "\MSExchange ActiveSync\Current Requests"
|
|
Write-Output "exchange_activesync_current_requests{hostname=`"$Hostname`"} $easCurrent"
|
|
|
|
Write-Output "# HELP exchange_activesync_sync_commands ActiveSync sync commands per second"
|
|
Write-Output "# TYPE exchange_activesync_sync_commands gauge"
|
|
$easSync = Get-SafeCounter "\MSExchange ActiveSync\Sync Commands/sec"
|
|
Write-Output "exchange_activesync_sync_commands{hostname=`"$Hostname`"} $easSync"
|
|
|
|
# Outlook Anywhere (RPC over HTTP)
|
|
Write-Output "# HELP exchange_rpc_requests_per_second RPC requests per second"
|
|
Write-Output "# TYPE exchange_rpc_requests_per_second gauge"
|
|
$rpcRequests = Get-SafeCounter "\MSExchange RpcClientAccess\RPC Requests"
|
|
Write-Output "exchange_rpc_requests_per_second{hostname=`"$Hostname`"} $rpcRequests"
|
|
|
|
Write-Output "# HELP exchange_rpc_active_users Active RPC users"
|
|
Write-Output "# TYPE exchange_rpc_active_users gauge"
|
|
$rpcUsers = Get-SafeCounter "\MSExchange RpcClientAccess\Active User Count"
|
|
Write-Output "exchange_rpc_active_users{hostname=`"$Hostname`"} $rpcUsers"
|
|
|
|
Write-Output "# HELP exchange_rpc_connection_count RPC connection count"
|
|
Write-Output "# TYPE exchange_rpc_connection_count gauge"
|
|
$rpcConns = Get-SafeCounter "\MSExchange RpcClientAccess\Connection Count"
|
|
Write-Output "exchange_rpc_connection_count{hostname=`"$Hostname`"} $rpcConns"
|
|
|
|
# Outlook MAPI/HTTP
|
|
Write-Output "# HELP exchange_mapi_requests_per_second MAPI over HTTP requests per second"
|
|
Write-Output "# TYPE exchange_mapi_requests_per_second gauge"
|
|
$mapiRequests = Get-SafeCounter "\MSExchange MapiHttp Emsmdb\Requests/sec"
|
|
Write-Output "exchange_mapi_requests_per_second{hostname=`"$Hostname`"} $mapiRequests"
|
|
|
|
Write-Output "# HELP exchange_mapi_current_connections Current MAPI connections"
|
|
Write-Output "# TYPE exchange_mapi_current_connections gauge"
|
|
$mapiConns = Get-SafeCounter "\MSExchange MapiHttp Emsmdb\Current Unique Users"
|
|
Write-Output "exchange_mapi_current_connections{hostname=`"$Hostname`"} $mapiConns"
|
|
|
|
# ============================================================================
|
|
# POP3/IMAP
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_pop3_connections Current POP3 connections"
|
|
Write-Output "# TYPE exchange_pop3_connections gauge"
|
|
$pop3Conns = Get-SafeCounter "\MSExchangePop3\Connections Current"
|
|
Write-Output "exchange_pop3_connections{hostname=`"$Hostname`"} $pop3Conns"
|
|
|
|
Write-Output "# HELP exchange_pop3_connections_total Total POP3 connections"
|
|
Write-Output "# TYPE exchange_pop3_connections_total counter"
|
|
$pop3Total = Get-SafeCounter "\MSExchangePop3\Connections Total"
|
|
Write-Output "exchange_pop3_connections_total{hostname=`"$Hostname`"} $pop3Total"
|
|
|
|
Write-Output "# HELP exchange_imap4_connections Current IMAP4 connections"
|
|
Write-Output "# TYPE exchange_imap4_connections gauge"
|
|
$imap4Conns = Get-SafeCounter "\MSExchangeImap4\Connections Current"
|
|
Write-Output "exchange_imap4_connections{hostname=`"$Hostname`"} $imap4Conns"
|
|
|
|
Write-Output "# HELP exchange_imap4_connections_total Total IMAP4 connections"
|
|
Write-Output "# TYPE exchange_imap4_connections_total counter"
|
|
$imap4Total = Get-SafeCounter "\MSExchangeImap4\Connections Total"
|
|
Write-Output "exchange_imap4_connections_total{hostname=`"$Hostname`"} $imap4Total"
|
|
|
|
# ============================================================================
|
|
# STORE (Information Store)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_store_rpc_requests RPC requests to Information Store"
|
|
Write-Output "# TYPE exchange_store_rpc_requests gauge"
|
|
$storeRpc = Get-SafeCounter "\MSExchangeIS Store(_total)\RPC Requests"
|
|
Write-Output "exchange_store_rpc_requests{hostname=`"$Hostname`"} $storeRpc"
|
|
|
|
Write-Output "# HELP exchange_store_rpc_latency_avg Average RPC latency (ms)"
|
|
Write-Output "# TYPE exchange_store_rpc_latency_avg gauge"
|
|
$storeLatency = Get-SafeCounter "\MSExchangeIS Store(_total)\RPC Average Latency"
|
|
Write-Output "exchange_store_rpc_latency_avg{hostname=`"$Hostname`"} $storeLatency"
|
|
|
|
Write-Output "# HELP exchange_store_messages_queued Messages queued for submission"
|
|
Write-Output "# TYPE exchange_store_messages_queued gauge"
|
|
$storeQueued = Get-SafeCounter "\MSExchangeIS Store(_total)\Messages Queued For Submission"
|
|
Write-Output "exchange_store_messages_queued{hostname=`"$Hostname`"} $storeQueued"
|
|
|
|
# ============================================================================
|
|
# TRANSPORT DUMPSTER / SAFETY NET
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_safetynet_messages Messages in Safety Net"
|
|
Write-Output "# TYPE exchange_safetynet_messages gauge"
|
|
$safetynetMsgs = Get-SafeCounter "\MSExchangeTransport Safety Net\Safety Net Total Messages"
|
|
Write-Output "exchange_safetynet_messages{hostname=`"$Hostname`"} $safetynetMsgs"
|
|
|
|
Write-Output "# HELP exchange_safetynet_size_bytes Safety Net size in bytes"
|
|
Write-Output "# TYPE exchange_safetynet_size_bytes gauge"
|
|
$safetynetSize = Get-SafeCounter "\MSExchangeTransport Safety Net\Safety Net Total Size"
|
|
Write-Output "exchange_safetynet_size_bytes{hostname=`"$Hostname`"} $safetynetSize"
|
|
|
|
# ============================================================================
|
|
# DSN (DELIVERY STATUS NOTIFICATIONS)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_dsn_total DSN messages generated"
|
|
Write-Output "# TYPE exchange_dsn_total counter"
|
|
$dsnGenerated = Get-SafeCounter "\MSExchangeTransport DSN(_total)\Delivery Status Notifications Generated"
|
|
Write-Output "exchange_dsn_total{type=`"generated`",hostname=`"$Hostname`"} $dsnGenerated"
|
|
|
|
$dsnFailure = Get-SafeCounter "\MSExchangeTransport DSN(_total)\Failure DSNs Total"
|
|
$dsnDelay = Get-SafeCounter "\MSExchangeTransport DSN(_total)\Delay DSNs Total"
|
|
$dsnRelayed = Get-SafeCounter "\MSExchangeTransport DSN(_total)\Relayed DSNs Total"
|
|
$dsnExpanded = Get-SafeCounter "\MSExchangeTransport DSN(_total)\Expanded DSNs Total"
|
|
Write-Output "exchange_dsn_total{type=`"failure`",hostname=`"$Hostname`"} $dsnFailure"
|
|
Write-Output "exchange_dsn_total{type=`"delay`",hostname=`"$Hostname`"} $dsnDelay"
|
|
Write-Output "exchange_dsn_total{type=`"relayed`",hostname=`"$Hostname`"} $dsnRelayed"
|
|
Write-Output "exchange_dsn_total{type=`"expanded`",hostname=`"$Hostname`"} $dsnExpanded"
|
|
|
|
# ============================================================================
|
|
# RESOURCE HEALTH
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_resource_health Resource health status"
|
|
Write-Output "# TYPE exchange_resource_health gauge"
|
|
|
|
# Back Pressure indicators
|
|
$backPressure = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Messages Submitted Recently"
|
|
Write-Output "exchange_resource_health{resource=`"back_pressure_messages`",hostname=`"$Hostname`"} $backPressure"
|
|
|
|
# Database log generation
|
|
$dbLogGen = Get-SafeCounter "\MSExchange Database ==> Instances(_total)\Log Generation Checkpoint Depth"
|
|
Write-Output "exchange_resource_health{resource=`"log_checkpoint_depth`",hostname=`"$Hostname`"} $dbLogGen"
|
|
|
|
# ============================================================================
|
|
# SERVICE STATUS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_service_status Exchange service status (1=running, 0=stopped)"
|
|
Write-Output "# TYPE exchange_service_status gauge"
|
|
|
|
$services = @(
|
|
'MSExchangeTransport',
|
|
'MSExchangeIS',
|
|
'MSExchangeMailboxAssistants',
|
|
'MSExchangeDelivery',
|
|
'MSExchangeSubmission',
|
|
'MSExchangeFrontEndTransport',
|
|
'MSExchangeEdgeSync',
|
|
'MSExchangeServiceHost',
|
|
'MSExchangeRPC',
|
|
'MSExchangeMailboxReplication',
|
|
'MSExchangeADTopology',
|
|
'MSExchangeAntispamUpdate',
|
|
'MSExchangeThrottling',
|
|
'MSExchangeHM',
|
|
'MSExchangeDiagnostics',
|
|
'MSExchangePop3',
|
|
'MSExchangeImap4',
|
|
'MSExchangeUM',
|
|
'MSExchangeUMCR'
|
|
)
|
|
|
|
foreach ($svc in $services) {
|
|
$service = Get-Service -Name $svc -ErrorAction SilentlyContinue
|
|
if ($service) {
|
|
$status = if ($service.Status -eq 'Running') { 1 } else { 0 }
|
|
Write-Output "exchange_service_status{service=`"$svc`",hostname=`"$Hostname`"} $status"
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# SERVER HEALTH (Exchange 2013+)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_server_health Server component health (1=healthy, 0=unhealthy)"
|
|
Write-Output "# TYPE exchange_server_health gauge"
|
|
try {
|
|
$health = Get-ServerComponentState -Identity $Hostname -ErrorAction SilentlyContinue
|
|
foreach ($component in $health) {
|
|
$state = if ($component.State -eq 'Active') { 1 } else { 0 }
|
|
$compName = $component.Component -replace '\s+', '_'
|
|
Write-Output "exchange_server_health{component=`"$compName`",hostname=`"$Hostname`"} $state"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# HEALTH MANAGER MONITORS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_health_monitor Health monitor status (1=healthy, 0=unhealthy)"
|
|
Write-Output "# TYPE exchange_health_monitor gauge"
|
|
try {
|
|
$monitors = Get-ServerHealth -Identity $Hostname -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.AlertValue -ne "Healthy" } |
|
|
Select-Object Name, AlertValue -Unique
|
|
|
|
$unhealthyCount = ($monitors | Measure-Object).Count
|
|
Write-Output "exchange_health_monitor{status=`"unhealthy_count`",hostname=`"$Hostname`"} $unhealthyCount"
|
|
} catch {
|
|
Write-Output "exchange_health_monitor{status=`"unhealthy_count`",hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# DAG (DATABASE AVAILABILITY GROUP) STATUS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_dag_copy_status DAG database copy status (1=mounted/healthy, 0=failed)"
|
|
Write-Output "# TYPE exchange_dag_copy_status gauge"
|
|
try {
|
|
$dagStatus = Get-MailboxDatabaseCopyStatus -ErrorAction SilentlyContinue
|
|
foreach ($copy in $dagStatus) {
|
|
$dbName = $copy.DatabaseName -replace '\s+', '_'
|
|
$status = switch ($copy.Status) {
|
|
'Mounted' { 1 }
|
|
'Healthy' { 1 }
|
|
default { 0 }
|
|
}
|
|
Write-Output "exchange_dag_copy_status{database=`"$dbName`",server=`"$($copy.MailboxServer)`",status=`"$($copy.Status)`",hostname=`"$Hostname`"} $status"
|
|
|
|
# Copy queue length
|
|
if ($copy.CopyQueueLength -ne $null) {
|
|
Write-Output "exchange_dag_copy_queue_length{database=`"$dbName`",server=`"$($copy.MailboxServer)`",hostname=`"$Hostname`"} $($copy.CopyQueueLength)"
|
|
}
|
|
|
|
# Replay queue length
|
|
if ($copy.ReplayQueueLength -ne $null) {
|
|
Write-Output "exchange_dag_replay_queue_length{database=`"$dbName`",server=`"$($copy.MailboxServer)`",hostname=`"$Hostname`"} $($copy.ReplayQueueLength)"
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# CERTIFICATE EXPIRY
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_cert_expiry_seconds Seconds until certificate expires"
|
|
Write-Output "# TYPE exchange_cert_expiry_seconds gauge"
|
|
try {
|
|
$certs = Get-ExchangeCertificate -ErrorAction SilentlyContinue | Where-Object { $_.NotAfter -gt (Get-Date) }
|
|
foreach ($cert in $certs) {
|
|
$services = ($cert.Services -join '_') -replace ',', '_'
|
|
$thumbprint = $cert.Thumbprint.Substring(0, 8)
|
|
$expirySeconds = [math]::Round(($cert.NotAfter - (Get-Date)).TotalSeconds)
|
|
Write-Output "exchange_cert_expiry_seconds{thumbprint=`"$thumbprint`",services=`"$services`",hostname=`"$Hostname`"} $expirySeconds"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# TOP SENDERS/RECIPIENTS (Last 24 hours)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_top_senders_total Top senders by message count (24h)"
|
|
Write-Output "# TYPE exchange_top_senders_total counter"
|
|
try {
|
|
$topSenders = Get-MessageTrackingLog -Start (Get-Date).AddHours(-24) -EventId RECEIVE -ResultSize 10000 -ErrorAction SilentlyContinue |
|
|
Group-Object Sender |
|
|
Sort-Object Count -Descending |
|
|
Select-Object -First 20
|
|
foreach ($sender in $topSenders) {
|
|
$senderAddr = $sender.Name -replace '"', ''
|
|
Write-Output "exchange_top_senders_total{sender=`"$senderAddr`",hostname=`"$Hostname`"} $($sender.Count)"
|
|
}
|
|
} catch {}
|
|
|
|
Write-Output "# HELP exchange_top_recipients_total Top recipients by message count (24h)"
|
|
Write-Output "# TYPE exchange_top_recipients_total counter"
|
|
try {
|
|
$topRecipients = Get-MessageTrackingLog -Start (Get-Date).AddHours(-24) -EventId DELIVER -ResultSize 10000 -ErrorAction SilentlyContinue |
|
|
ForEach-Object { $_.Recipients } |
|
|
Group-Object |
|
|
Sort-Object Count -Descending |
|
|
Select-Object -First 20
|
|
foreach ($recipient in $topRecipients) {
|
|
$recipientAddr = $recipient.Name -replace '"', ''
|
|
Write-Output "exchange_top_recipients_total{recipient=`"$recipientAddr`",hostname=`"$Hostname`"} $($recipient.Count)"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# MESSAGE SIZE STATISTICS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_message_size_bytes Message size statistics"
|
|
Write-Output "# TYPE exchange_message_size_bytes gauge"
|
|
try {
|
|
$messages = Get-MessageTrackingLog -Start (Get-Date).AddHours(-1) -EventId RECEIVE -ResultSize 1000 -ErrorAction SilentlyContinue
|
|
if ($messages -and $messages.Count -gt 0) {
|
|
$sizes = $messages | ForEach-Object { $_.TotalBytes } | Where-Object { $_ -gt 0 }
|
|
if ($sizes -and $sizes.Count -gt 0) {
|
|
$avgSize = [math]::Round(($sizes | Measure-Object -Average).Average)
|
|
$maxSize = ($sizes | Measure-Object -Maximum).Maximum
|
|
$totalSize = ($sizes | Measure-Object -Sum).Sum
|
|
Write-Output "exchange_message_size_bytes{stat=`"avg`",hostname=`"$Hostname`"} $avgSize"
|
|
Write-Output "exchange_message_size_bytes{stat=`"max`",hostname=`"$Hostname`"} $maxSize"
|
|
Write-Output "exchange_message_size_bytes{stat=`"total_1h`",hostname=`"$Hostname`"} $totalSize"
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# EDGE TRANSPORT (if applicable)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_edge_sync_status EdgeSync status"
|
|
Write-Output "# TYPE exchange_edge_sync_status gauge"
|
|
try {
|
|
$edgeSync = Test-EdgeSynchronization -ErrorAction SilentlyContinue
|
|
if ($edgeSync) {
|
|
$syncStatus = if ($edgeSync.SyncStatus -eq 'Normal') { 1 } else { 0 }
|
|
Write-Output "exchange_edge_sync_status{hostname=`"$Hostname`"} $syncStatus"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# TRANSPORT AGENTS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_transport_agent_enabled Transport agent status (1=enabled, 0=disabled)"
|
|
Write-Output "# TYPE exchange_transport_agent_enabled gauge"
|
|
try {
|
|
$agents = Get-TransportAgent -ErrorAction SilentlyContinue
|
|
foreach ($agent in $agents) {
|
|
$agentName = $agent.Identity -replace '\s+', '_' -replace '[^\w_]', ''
|
|
$enabled = if ($agent.Enabled) { 1 } else { 0 }
|
|
Write-Output "exchange_transport_agent_enabled{agent=`"$agentName`",hostname=`"$Hostname`"} $enabled"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# PUBLIC FOLDER STATISTICS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_public_folder_count Number of public folders"
|
|
Write-Output "# TYPE exchange_public_folder_count gauge"
|
|
try {
|
|
$pfCount = (Get-PublicFolder -Recurse -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_public_folder_count{hostname=`"$Hostname`"} $pfCount"
|
|
} catch {
|
|
Write-Output "exchange_public_folder_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# OUTLOOK WEB APP ERRORS (from Event Log)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_owa_errors_total OWA errors from event log (24h)"
|
|
Write-Output "# TYPE exchange_owa_errors_total counter"
|
|
$owaErrors = Get-EventLogCount -LogName "Application" -Source "MSExchange OWA" -Hours 24
|
|
Write-Output "exchange_owa_errors_total{hostname=`"$Hostname`"} $owaErrors"
|
|
|
|
# ============================================================================
|
|
# TRANSPORT ERRORS (from Event Log)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_transport_errors_total Transport errors from event log (24h)"
|
|
Write-Output "# TYPE exchange_transport_errors_total counter"
|
|
$transportErrors = Get-EventLogCount -LogName "Application" -Source "MSExchangeTransport" -Hours 24
|
|
Write-Output "exchange_transport_errors_total{hostname=`"$Hostname`"} $transportErrors"
|
|
|
|
# ============================================================================
|
|
# INFORMATION STORE ERRORS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_store_errors_total Information Store errors from event log (24h)"
|
|
Write-Output "# TYPE exchange_store_errors_total counter"
|
|
$storeErrors = Get-EventLogCount -LogName "Application" -Source "MSExchangeIS" -Hours 24
|
|
Write-Output "exchange_store_errors_total{hostname=`"$Hostname`"} $storeErrors"
|
|
|
|
# ============================================================================
|
|
# REPLICATION HEALTH
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_replication_health Replication health check results"
|
|
Write-Output "# TYPE exchange_replication_health gauge"
|
|
try {
|
|
$replHealth = Test-ReplicationHealth -ErrorAction SilentlyContinue
|
|
foreach ($check in $replHealth) {
|
|
$checkName = $check.Check -replace '\s+', '_'
|
|
$result = if ($check.Result -eq 'Passed') { 1 } else { 0 }
|
|
Write-Output "exchange_replication_health{check=`"$checkName`",hostname=`"$Hostname`"} $result"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# MAIL FLOW TEST
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_mailflow_test_latency_seconds Mail flow test latency"
|
|
Write-Output "# TYPE exchange_mailflow_test_latency_seconds gauge"
|
|
try {
|
|
$mailflow = Test-Mailflow -ErrorAction SilentlyContinue
|
|
if ($mailflow -and $mailflow.TestMailflowResult -eq 'Success') {
|
|
$latency = $mailflow.MessageLatencyTime.TotalSeconds
|
|
Write-Output "exchange_mailflow_test_latency_seconds{hostname=`"$Hostname`"} $([math]::Round($latency, 2))"
|
|
Write-Output "exchange_mailflow_test_success{hostname=`"$Hostname`"} 1"
|
|
} else {
|
|
Write-Output "exchange_mailflow_test_success{hostname=`"$Hostname`"} 0"
|
|
}
|
|
} catch {
|
|
Write-Output "exchange_mailflow_test_success{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# DOMAIN STATISTICS (Send/Receive by Domain)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_domain_messages_total Messages by remote domain (24h)"
|
|
Write-Output "# TYPE exchange_domain_messages_total counter"
|
|
try {
|
|
$domainStats = Get-MessageTrackingLog -Start (Get-Date).AddHours(-24) -EventId SEND -ResultSize 10000 -ErrorAction SilentlyContinue |
|
|
ForEach-Object {
|
|
$_.Recipients | ForEach-Object {
|
|
if ($_ -match '@(.+)$') { $matches[1] }
|
|
}
|
|
} |
|
|
Group-Object |
|
|
Sort-Object Count -Descending |
|
|
Select-Object -First 20
|
|
foreach ($domain in $domainStats) {
|
|
if ($domain.Name) {
|
|
Write-Output "exchange_domain_messages_total{domain=`"$($domain.Name)`",direction=`"outbound`",hostname=`"$Hostname`"} $($domain.Count)"
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# RETENTION POLICY TAGS (Compliance)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_retention_policy_count Number of retention policies"
|
|
Write-Output "# TYPE exchange_retention_policy_count gauge"
|
|
try {
|
|
$retPolicies = (Get-RetentionPolicy -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_retention_policy_count{hostname=`"$Hostname`"} $retPolicies"
|
|
} catch {
|
|
Write-Output "exchange_retention_policy_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# JOURNAL RULES
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_journal_rule_count Number of journal rules"
|
|
Write-Output "# TYPE exchange_journal_rule_count gauge"
|
|
try {
|
|
$journalRules = (Get-JournalRule -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_journal_rule_count{hostname=`"$Hostname`"} $journalRules"
|
|
} catch {
|
|
Write-Output "exchange_journal_rule_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# TRANSPORT RULES
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_transport_rule_count Number of transport rules"
|
|
Write-Output "# TYPE exchange_transport_rule_count gauge"
|
|
try {
|
|
$transportRules = (Get-TransportRule -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_transport_rule_count{hostname=`"$Hostname`"} $transportRules"
|
|
} catch {
|
|
Write-Output "exchange_transport_rule_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# ACCEPTED DOMAINS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_accepted_domain_count Number of accepted domains"
|
|
Write-Output "# TYPE exchange_accepted_domain_count gauge"
|
|
try {
|
|
$acceptedDomains = (Get-AcceptedDomain -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_accepted_domain_count{hostname=`"$Hostname`"} $acceptedDomains"
|
|
} catch {
|
|
Write-Output "exchange_accepted_domain_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# SEND/RECEIVE CONNECTORS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_send_connector_enabled Send connector status"
|
|
Write-Output "# TYPE exchange_send_connector_enabled gauge"
|
|
try {
|
|
$sendConnectors = Get-SendConnector -ErrorAction SilentlyContinue
|
|
foreach ($conn in $sendConnectors) {
|
|
$connName = $conn.Name -replace '\s+', '_' -replace '[^\w_]', ''
|
|
$enabled = if ($conn.Enabled) { 1 } else { 0 }
|
|
Write-Output "exchange_send_connector_enabled{connector=`"$connName`",hostname=`"$Hostname`"} $enabled"
|
|
}
|
|
} catch {}
|
|
|
|
Write-Output "# HELP exchange_receive_connector_enabled Receive connector status"
|
|
Write-Output "# TYPE exchange_receive_connector_enabled gauge"
|
|
try {
|
|
$receiveConnectors = Get-ReceiveConnector -ErrorAction SilentlyContinue
|
|
foreach ($conn in $receiveConnectors) {
|
|
$connName = $conn.Name -replace '\s+', '_' -replace '[^\w_]', ''
|
|
$enabled = if ($conn.Enabled) { 1 } else { 0 }
|
|
Write-Output "exchange_receive_connector_enabled{connector=`"$connName`",hostname=`"$Hostname`"} $enabled"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# EXECUTION TIME
|
|
# ============================================================================
|
|
# ============================================================================
|
|
# AUTODISCOVER
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_autodiscover_requests_per_second Autodiscover requests per second"
|
|
Write-Output "# TYPE exchange_autodiscover_requests_per_second gauge"
|
|
$autodiscoverReq = Get-SafeCounter "\MSExchange Autodiscover\Requests/sec"
|
|
Write-Output "exchange_autodiscover_requests_per_second{hostname=`"$Hostname`"} $autodiscoverReq"
|
|
|
|
Write-Output "# HELP exchange_autodiscover_errors_total Autodiscover errors"
|
|
Write-Output "# TYPE exchange_autodiscover_errors_total counter"
|
|
$autodiscoverErrors = Get-SafeCounter "\MSExchange Autodiscover\Error Responses/sec"
|
|
Write-Output "exchange_autodiscover_errors_total{hostname=`"$Hostname`"} $autodiscoverErrors"
|
|
|
|
# ============================================================================
|
|
# ADDRESS BOOK SERVICE
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_addressbook_requests_per_second Address Book requests per second"
|
|
Write-Output "# TYPE exchange_addressbook_requests_per_second gauge"
|
|
$abRequests = Get-SafeCounter "\MSExchange AddressBook(_total)\Referral RPC Requests/sec"
|
|
Write-Output "exchange_addressbook_requests_per_second{hostname=`"$Hostname`"} $abRequests"
|
|
|
|
Write-Output "# HELP exchange_addressbook_latency_avg Average Address Book latency (ms)"
|
|
Write-Output "# TYPE exchange_addressbook_latency_avg gauge"
|
|
$abLatency = Get-SafeCounter "\MSExchange AddressBook(_total)\RPC Averaged Latency"
|
|
Write-Output "exchange_addressbook_latency_avg{hostname=`"$Hostname`"} $abLatency"
|
|
|
|
# ============================================================================
|
|
# SEARCH / CONTENT INDEXING
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_search_mailboxes_left Mailboxes left to crawl"
|
|
Write-Output "# TYPE exchange_search_mailboxes_left gauge"
|
|
$searchLeft = Get-SafeCounter "\MSExchange Search Indexes(_total)\Mailboxes Left to Crawl"
|
|
Write-Output "exchange_search_mailboxes_left{hostname=`"$Hostname`"} $searchLeft"
|
|
|
|
Write-Output "# HELP exchange_search_documents_indexed Documents indexed per second"
|
|
Write-Output "# TYPE exchange_search_documents_indexed gauge"
|
|
$searchDocs = Get-SafeCounter "\MSExchange Search Indexes(_total)\Average Document Indexing Time"
|
|
Write-Output "exchange_search_documents_indexed{hostname=`"$Hostname`"} $searchDocs"
|
|
|
|
Write-Output "# HELP exchange_search_index_status Search index catalog status"
|
|
Write-Output "# TYPE exchange_search_index_status gauge"
|
|
try {
|
|
$catalogStatus = Get-MailboxDatabaseCopyStatus -ErrorAction SilentlyContinue |
|
|
Select-Object DatabaseName, ContentIndexState
|
|
foreach ($cat in $catalogStatus) {
|
|
$dbName = $cat.DatabaseName -replace '\s+', '_'
|
|
$healthy = if ($cat.ContentIndexState -eq 'Healthy') { 1 } else { 0 }
|
|
Write-Output "exchange_search_index_status{database=`"$dbName`",state=`"$($cat.ContentIndexState)`",hostname=`"$Hostname`"} $healthy"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# THROTTLING POLICY
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_throttling_rejected_total Requests rejected by throttling"
|
|
Write-Output "# TYPE exchange_throttling_rejected_total counter"
|
|
$throttleRejected = Get-SafeCounter "\MSExchange Throttling\Requests Rejected due to Budget Usage"
|
|
Write-Output "exchange_throttling_rejected_total{hostname=`"$Hostname`"} $throttleRejected"
|
|
|
|
Write-Output "# HELP exchange_throttling_delayed_total Requests delayed by throttling"
|
|
Write-Output "# TYPE exchange_throttling_delayed_total counter"
|
|
$throttleDelayed = Get-SafeCounter "\MSExchange Throttling\Requests Submitted to Delayed Execution"
|
|
Write-Output "exchange_throttling_delayed_total{hostname=`"$Hostname`"} $throttleDelayed"
|
|
|
|
# ============================================================================
|
|
# MAILBOX ASSISTANTS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_mailbox_assistant_events Events processed by mailbox assistants"
|
|
Write-Output "# TYPE exchange_mailbox_assistant_events gauge"
|
|
$assistantEvents = Get-SafeCounter "\MSExchange Assistants - Per Assistant(_total)\Events Polled"
|
|
Write-Output "exchange_mailbox_assistant_events{hostname=`"$Hostname`"} $assistantEvents"
|
|
|
|
Write-Output "# HELP exchange_calendar_assistant_requests Calendar assistant requests"
|
|
Write-Output "# TYPE exchange_calendar_assistant_requests gauge"
|
|
$calendarAssist = Get-SafeCounter "\MSExchange Calendar Attendant\Requests"
|
|
Write-Output "exchange_calendar_assistant_requests{hostname=`"$Hostname`"} $calendarAssist"
|
|
|
|
# ============================================================================
|
|
# MAILBOX REPLICATION SERVICE (MRS)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_mrs_active_moves Active mailbox moves"
|
|
Write-Output "# TYPE exchange_mrs_active_moves gauge"
|
|
try {
|
|
$activeMoves = (Get-MoveRequest -MoveStatus InProgress -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_mrs_active_moves{hostname=`"$Hostname`"} $activeMoves"
|
|
} catch {
|
|
Write-Output "exchange_mrs_active_moves{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
Write-Output "# HELP exchange_mrs_queued_moves Queued mailbox moves"
|
|
Write-Output "# TYPE exchange_mrs_queued_moves gauge"
|
|
try {
|
|
$queuedMoves = (Get-MoveRequest -MoveStatus Queued -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_mrs_queued_moves{hostname=`"$Hostname`"} $queuedMoves"
|
|
} catch {
|
|
Write-Output "exchange_mrs_queued_moves{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
Write-Output "# HELP exchange_mrs_failed_moves Failed mailbox moves"
|
|
Write-Output "# TYPE exchange_mrs_failed_moves gauge"
|
|
try {
|
|
$failedMoves = (Get-MoveRequest -MoveStatus Failed -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_mrs_failed_moves{hostname=`"$Hostname`"} $failedMoves"
|
|
} catch {
|
|
Write-Output "exchange_mrs_failed_moves{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# TRANSPORT PIPELINE LATENCY
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_transport_latency_seconds Transport component latency"
|
|
Write-Output "# TYPE exchange_transport_latency_seconds gauge"
|
|
$latencySmtpReceive = Get-SafeCounter "\MSExchangeTransport SmtpReceive(_total)\Average bytes/message"
|
|
$latencyCategor = Get-SafeCounter "\MSExchangeTransport Resolver(_total)\Ambiguous Recipients Rate"
|
|
$latencyDelivery = Get-SafeCounter "\MSExchangeTransport Delivery Failures\Average Delivery Attempts Per Message"
|
|
Write-Output "exchange_transport_latency_seconds{component=`"smtp_receive`",hostname=`"$Hostname`"} $latencySmtpReceive"
|
|
Write-Output "exchange_transport_latency_seconds{component=`"categorizer`",hostname=`"$Hostname`"} $latencyCategor"
|
|
Write-Output "exchange_transport_latency_seconds{component=`"delivery`",hostname=`"$Hostname`"} $latencyDelivery"
|
|
|
|
# ============================================================================
|
|
# PROCESS MEMORY/CPU
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_process_memory_bytes Memory usage by Exchange processes"
|
|
Write-Output "# TYPE exchange_process_memory_bytes gauge"
|
|
Write-Output "# HELP exchange_process_cpu_percent CPU usage by Exchange processes"
|
|
Write-Output "# TYPE exchange_process_cpu_percent gauge"
|
|
|
|
$exchangeProcesses = @(
|
|
'EdgeTransport',
|
|
'Microsoft.Exchange.Store.Worker',
|
|
'MSExchangeTransport',
|
|
'MSExchangeHMWorker',
|
|
'MSExchangeMailboxAssistants',
|
|
'MSExchangeDelivery',
|
|
'MSExchangeSubmission',
|
|
'w3wp' # IIS worker processes
|
|
)
|
|
|
|
foreach ($procName in $exchangeProcesses) {
|
|
try {
|
|
$procs = Get-Process -Name $procName -ErrorAction SilentlyContinue
|
|
if ($procs) {
|
|
$totalMem = ($procs | Measure-Object -Property WorkingSet64 -Sum).Sum
|
|
$procNameClean = $procName -replace '\.', '_'
|
|
Write-Output "exchange_process_memory_bytes{process=`"$procNameClean`",hostname=`"$Hostname`"} $totalMem"
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
# ============================================================================
|
|
# ARCHIVE MAILBOX STATISTICS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_archive_mailbox_count Number of archive mailboxes"
|
|
Write-Output "# TYPE exchange_archive_mailbox_count gauge"
|
|
try {
|
|
$archiveCount = (Get-Mailbox -Archive -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_archive_mailbox_count{hostname=`"$Hostname`"} $archiveCount"
|
|
} catch {
|
|
Write-Output "exchange_archive_mailbox_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# LITIGATION HOLD
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_litigation_hold_count Mailboxes on litigation hold"
|
|
Write-Output "# TYPE exchange_litigation_hold_count gauge"
|
|
try {
|
|
$litigationCount = (Get-Mailbox -ResultSize Unlimited -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.LitigationHoldEnabled -eq $true } | Measure-Object).Count
|
|
Write-Output "exchange_litigation_hold_count{hostname=`"$Hostname`"} $litigationCount"
|
|
} catch {
|
|
Write-Output "exchange_litigation_hold_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# SHARED MAILBOXES
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_shared_mailbox_count Number of shared mailboxes"
|
|
Write-Output "# TYPE exchange_shared_mailbox_count gauge"
|
|
try {
|
|
$sharedCount = (Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_shared_mailbox_count{hostname=`"$Hostname`"} $sharedCount"
|
|
} catch {
|
|
Write-Output "exchange_shared_mailbox_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# ROOM/RESOURCE MAILBOXES
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_resource_mailbox_count Number of resource mailboxes"
|
|
Write-Output "# TYPE exchange_resource_mailbox_count gauge"
|
|
try {
|
|
$roomCount = (Get-Mailbox -RecipientTypeDetails RoomMailbox -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
$equipmentCount = (Get-Mailbox -RecipientTypeDetails EquipmentMailbox -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_resource_mailbox_count{type=`"room`",hostname=`"$Hostname`"} $roomCount"
|
|
Write-Output "exchange_resource_mailbox_count{type=`"equipment`",hostname=`"$Hostname`"} $equipmentCount"
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# DISTRIBUTION GROUPS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_distribution_group_count Number of distribution groups"
|
|
Write-Output "# TYPE exchange_distribution_group_count gauge"
|
|
try {
|
|
$dgCount = (Get-DistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_distribution_group_count{hostname=`"$Hostname`"} $dgCount"
|
|
} catch {
|
|
Write-Output "exchange_distribution_group_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# DYNAMIC DISTRIBUTION GROUPS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_dynamic_distribution_group_count Number of dynamic distribution groups"
|
|
Write-Output "# TYPE exchange_dynamic_distribution_group_count gauge"
|
|
try {
|
|
$ddgCount = (Get-DynamicDistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_dynamic_distribution_group_count{hostname=`"$Hostname`"} $ddgCount"
|
|
} catch {
|
|
Write-Output "exchange_dynamic_distribution_group_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# REMOTE DOMAINS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_remote_domain_count Number of remote domains configured"
|
|
Write-Output "# TYPE exchange_remote_domain_count gauge"
|
|
try {
|
|
$remoteDomains = (Get-RemoteDomain -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_remote_domain_count{hostname=`"$Hostname`"} $remoteDomains"
|
|
} catch {
|
|
Write-Output "exchange_remote_domain_count{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# UNIFIED MESSAGING (if enabled)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_um_calls_current Current UM calls"
|
|
Write-Output "# TYPE exchange_um_calls_current gauge"
|
|
$umCalls = Get-SafeCounter "\MSExchangeUMAvailability\Current Calls"
|
|
Write-Output "exchange_um_calls_current{hostname=`"$Hostname`"} $umCalls"
|
|
|
|
Write-Output "# HELP exchange_um_calls_total Total UM calls"
|
|
Write-Output "# TYPE exchange_um_calls_total counter"
|
|
$umCallsTotal = Get-SafeCounter "\MSExchangeUMAvailability\Total Calls"
|
|
Write-Output "exchange_um_calls_total{hostname=`"$Hostname`"} $umCallsTotal"
|
|
|
|
# ============================================================================
|
|
# SUBMISSION QUEUE STATISTICS (detailed)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_submission_queue_items_expired Items expired from submission queue"
|
|
Write-Output "# TYPE exchange_submission_queue_items_expired counter"
|
|
$subExpired = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Items Expired from Submission Queue"
|
|
Write-Output "exchange_submission_queue_items_expired{hostname=`"$Hostname`"} $subExpired"
|
|
|
|
Write-Output "# HELP exchange_submission_queue_items_submitted Items submitted to queue"
|
|
Write-Output "# TYPE exchange_submission_queue_items_submitted counter"
|
|
$subSubmitted = Get-SafeCounter "\MSExchangeTransport Queues(_total)\Items Submitted Total"
|
|
Write-Output "exchange_submission_queue_items_submitted{hostname=`"$Hostname`"} $subSubmitted"
|
|
|
|
# ============================================================================
|
|
# EXTERNAL RELAY STATISTICS
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_external_relay_messages External relay message count"
|
|
Write-Output "# TYPE exchange_external_relay_messages counter"
|
|
try {
|
|
$externalRelay = Get-MessageTrackingLog -Start (Get-Date).AddHours(-24) -EventId SEND -ResultSize 5000 -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.ConnectorId -notmatch 'Internal' } |
|
|
Measure-Object
|
|
Write-Output "exchange_external_relay_messages{hostname=`"$Hostname`"} $($externalRelay.Count)"
|
|
} catch {
|
|
Write-Output "exchange_external_relay_messages{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# SPAM/MALWARE QUARANTINE
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_quarantine_messages Messages in quarantine"
|
|
Write-Output "# TYPE exchange_quarantine_messages gauge"
|
|
try {
|
|
$quarantineCount = (Get-QuarantineMessage -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
Write-Output "exchange_quarantine_messages{hostname=`"$Hostname`"} $quarantineCount"
|
|
} catch {
|
|
Write-Output "exchange_quarantine_messages{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# HYBRID CONFIGURATION (for O365 hybrid)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_hybrid_configured Hybrid configuration status"
|
|
Write-Output "# TYPE exchange_hybrid_configured gauge"
|
|
try {
|
|
$hybrid = Get-HybridConfiguration -ErrorAction SilentlyContinue
|
|
$isHybrid = if ($hybrid) { 1 } else { 0 }
|
|
Write-Output "exchange_hybrid_configured{hostname=`"$Hostname`"} $isHybrid"
|
|
} catch {
|
|
Write-Output "exchange_hybrid_configured{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# OAUTH TOKENS (for modern auth)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_oauth_token_requests OAuth token requests"
|
|
Write-Output "# TYPE exchange_oauth_token_requests counter"
|
|
$oauthRequests = Get-SafeCounter "\MSExchange OAuth\Inbound: Token Requests"
|
|
Write-Output "exchange_oauth_token_requests{hostname=`"$Hostname`"} $oauthRequests"
|
|
|
|
# ============================================================================
|
|
# LAST FULL BACKUP
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_database_last_backup_seconds Seconds since last full backup"
|
|
Write-Output "# TYPE exchange_database_last_backup_seconds gauge"
|
|
try {
|
|
$databases = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue
|
|
foreach ($db in $databases) {
|
|
$dbName = $db.Name -replace '\s+', '_'
|
|
if ($db.LastFullBackup) {
|
|
$backupAge = [math]::Round(((Get-Date) - $db.LastFullBackup).TotalSeconds)
|
|
Write-Output "exchange_database_last_backup_seconds{database=`"$dbName`",hostname=`"$Hostname`"} $backupAge"
|
|
} else {
|
|
Write-Output "exchange_database_last_backup_seconds{database=`"$dbName`",hostname=`"$Hostname`"} -1"
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# LOG GENERATION RATE
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_log_generation_rate Log generation checkpoint depth"
|
|
Write-Output "# TYPE exchange_log_generation_rate gauge"
|
|
try {
|
|
$databases = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue
|
|
foreach ($db in $databases) {
|
|
$dbName = $db.Name -replace '\s+', '_'
|
|
$counterPath = "\MSExchange Database ==> Instances($dbName)\Log Generation Checkpoint Depth"
|
|
$logDepth = Get-SafeCounter $counterPath
|
|
Write-Output "exchange_log_generation_rate{database=`"$dbName`",hostname=`"$Hostname`"} $logDepth"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# AVERAGE MAILBOX SIZE
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_average_mailbox_size_bytes Average mailbox size"
|
|
Write-Output "# TYPE exchange_average_mailbox_size_bytes gauge"
|
|
try {
|
|
$stats = Get-MailboxStatistics -Server $Hostname -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.TotalItemSize -ne $null }
|
|
if ($stats -and $stats.Count -gt 0) {
|
|
$avgSize = ($stats | ForEach-Object { $_.TotalItemSize.Value.ToBytes() } | Measure-Object -Average).Average
|
|
Write-Output "exchange_average_mailbox_size_bytes{hostname=`"$Hostname`"} $([math]::Round($avgSize))"
|
|
}
|
|
} catch {
|
|
Write-Output "exchange_average_mailbox_size_bytes{hostname=`"$Hostname`"} 0"
|
|
}
|
|
|
|
# ============================================================================
|
|
# LARGEST MAILBOXES (top 10)
|
|
# ============================================================================
|
|
Write-Output "# HELP exchange_largest_mailbox_bytes Largest mailboxes by size"
|
|
Write-Output "# TYPE exchange_largest_mailbox_bytes gauge"
|
|
try {
|
|
$topMailboxes = Get-MailboxStatistics -Server $Hostname -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.TotalItemSize -ne $null } |
|
|
Sort-Object TotalItemSize -Descending |
|
|
Select-Object -First 10
|
|
foreach ($mbx in $topMailboxes) {
|
|
$mbxName = $mbx.DisplayName -replace '["\s]', '_' -replace '[^\w_]', ''
|
|
$size = $mbx.TotalItemSize.Value.ToBytes()
|
|
Write-Output "exchange_largest_mailbox_bytes{mailbox=`"$mbxName`",hostname=`"$Hostname`"} $size"
|
|
}
|
|
} catch {}
|
|
|
|
# ============================================================================
|
|
# EXECUTION TIME
|
|
# ============================================================================
|
|
$EndTime = Get-Date
|
|
$Duration = ($EndTime - $StartTime).TotalSeconds
|
|
|
|
Write-Output "# HELP exchange_collector_duration_seconds Time taken to collect metrics"
|
|
Write-Output "# TYPE exchange_collector_duration_seconds gauge"
|
|
Write-Output "exchange_collector_duration_seconds{hostname=`"$Hostname`"} $([math]::Round($Duration, 2))"
|
|
|
|
Write-Output "# HELP exchange_collector_last_run_timestamp Unix timestamp of last collection"
|
|
Write-Output "# TYPE exchange_collector_last_run_timestamp gauge"
|
|
Write-Output "exchange_collector_last_run_timestamp{hostname=`"$Hostname`"} $([math]::Round((Get-Date -UFormat %s), 0))"
|