diff --git a/Monitor-VeeamJobs.ps1 b/Monitor-VeeamJobs.ps1 index 0959041..6bb4467 100644 --- a/Monitor-VeeamJobs.ps1 +++ b/Monitor-VeeamJobs.ps1 @@ -213,6 +213,35 @@ function ConvertTo-ZabbixDateTime { return $text } +function ConvertTo-ZabbixJobKey { + param( + [Parameter(Mandatory = $true)] + [string]$Name + ) + + $bytes = [System.Text.Encoding]::UTF8.GetBytes($Name) + + return [Convert]::ToBase64String($bytes).TrimEnd('=') -replace '\+', '-' -replace '/', '_' +} + +function ConvertTo-ZabbixText { + param( + [object]$Value + ) + + if ($null -eq $Value) { + return '-' + } + + $text = [string]$Value + + if ([string]::IsNullOrWhiteSpace($text)) { + return '-' + } + + return $text +} + function Get-VeeamJobScheduleEnabled { param( [Parameter(Mandatory = $true)] @@ -501,19 +530,25 @@ function Get-VeeamJobMetrics { $lastResult = Get-VeeamJobLastResult -Job $_ -LastSession $lastSession $lastState = Get-VeeamJobLastState -Job $_ -LastSession $lastSession $restorePoints = $null + $enabledNumeric = if ($scheduleEnabled -eq $false) { 0 } else { 1 } if ($restorePointCounts.ContainsKey($jobName)) { $restorePoints = $restorePointCounts[$jobName] } [ordered]@{ - name = $jobName - enabled = $scheduleEnabled - last_run = Get-VeeamJobLastRun -Job $_ -LastSession $lastSession - next_run = Get-VeeamJobNextRun -Job $_ - last_result = if ($null -eq $lastResult) { $null } else { [string]$lastResult } - last_state = if ($null -eq $lastState) { $null } else { [string]$lastState } - restore_points = $restorePoints + key = ConvertTo-ZabbixJobKey -Name $jobName + name = $jobName + enabled = $scheduleEnabled + enabled_numeric = $enabledNumeric + enabled_text = if ($enabledNumeric -eq 1) { 'yes' } else { 'no' } + last_run = ConvertTo-ZabbixText -Value (Get-VeeamJobLastRun -Job $_ -LastSession $lastSession) + next_run = ConvertTo-ZabbixText -Value (Get-VeeamJobNextRun -Job $_) + last_result = ConvertTo-ZabbixText -Value $lastResult + last_state = ConvertTo-ZabbixText -Value $lastState + result = ConvertTo-ZabbixText -Value $(if ($null -ne $lastResult) { $lastResult } else { $lastState }) + restore_points = $restorePoints + restore_points_text = ConvertTo-ZabbixText -Value $restorePoints } }) $disabledJobs = @($allJobs | Where-Object { @@ -538,6 +573,12 @@ function Get-VeeamJobMetrics { $successfulJobs = @($jobDetails | Where-Object { $_.last_result -in @('Success', 'Succeeded') -or $_.last_state -in @('Success', 'Succeeded') }) + $jobsLld = @($jobDetails | Sort-Object -Property name | ForEach-Object { + [ordered]@{ + '{#VEEAMJOBKEY}' = $_.key + '{#VEEAMJOBNAME}' = $_.name + } + }) $disabledCount = $disabledJobs.Count $disabledExcludedJobs = @() @@ -573,6 +614,9 @@ function Get-VeeamJobMetrics { error_names = Format-ZabbixTextValue -Values @($errorJobs | ForEach-Object { Get-VeeamJobName -Job $_ }) warning_names = Format-ZabbixTextValue -Values @($warningJobs | ForEach-Object { Get-VeeamJobName -Job $_ }) jobs_table = Format-VeeamJobDetailsTable -JobDetails $jobDetails + jobs_lld = [ordered]@{ + data = $jobsLld + } jobs = $jobDetails } } diff --git a/README.md b/README.md index ab0a240..1ed8385 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,28 @@ This repository contains PowerShell scripts for Veeam Backup & Replication: "error_names": "Failed job", "warning_names": "Warning job", "jobs_table": "Name | Enabled | Last run | Next run | Result | Restore points\n...", + "jobs_lld": { + "data": [ + { + "{#VEEAMJOBKEY}": "RGFpbHkgYmFja3Vw", + "{#VEEAMJOBNAME}": "Daily backup" + } + ] + }, "jobs": [ { + "key": "RGFpbHkgYmFja3Vw", "name": "Daily backup", "enabled": true, + "enabled_numeric": 1, + "enabled_text": "yes", "last_run": "2026-07-16 01:00:00", "next_run": "2026-07-17 01:00:00", "last_result": "Success", "last_state": "Stopped", - "restore_points": 14 + "result": "Success", + "restore_points": 14, + "restore_points_text": "14" } ] } @@ -41,6 +54,29 @@ Restore point counting is best effort. The script uses `Get-VBRBackup` and to backups in the current environment, the job remains visible and its restore point count is shown as `-`. +## Discovered per-job items + +The template uses a dependent low-level discovery rule: + +```text +veeam.jobs.discovery +``` + +It creates one set of discovered items per Veeam job: + +```text +veeam.job.enabled[{#VEEAMJOBKEY}] +veeam.job.last_run[{#VEEAMJOBKEY}] +veeam.job.next_run[{#VEEAMJOBKEY}] +veeam.job.result[{#VEEAMJOBKEY}] +veeam.job.restore_points[{#VEEAMJOBKEY}] +``` + +`{#VEEAMJOBNAME}` is stored as a tag named `veeam_job`, so Zabbix views and +dashboard widgets can group or filter the discovered items by job name. The +legacy `veeam.jobs.table` item remains available as a quick text overview, but +the discovered items are the preferred way to build a dashboard. + ## Zabbix dashboard idea Create a dashboard named `Veeam Jobs` for hosts linked to the `Veeam Backup Jobs` @@ -55,10 +91,10 @@ Suggested widgets: | Top row | Item value | `veeam.jobs.disabled` | | Top row | Item value | `veeam.jobs.error` | | Top row | Item value | `veeam.jobs.warning` | -| Middle | Plain text | `veeam.jobs.table` | -| Bottom left | Plain text | `veeam.jobs.error.names` | -| Bottom center | Plain text | `veeam.jobs.warning.names` | -| Bottom right | Plain text | `veeam.jobs.disabled.names` | +| Middle | Top hosts or item list | discovered `veeam.job.*` items | +| Bottom left | Item value or item list | `veeam.jobs.error.names` | +| Bottom center | Item value or item list | `veeam.jobs.warning.names` | +| Bottom right | Item value or item list | `veeam.jobs.disabled.names` | Use a red threshold for `veeam.jobs.error`, yellow for `veeam.jobs.warning`, and keep `veeam.jobs.disabled` aligned with the existing exclusion policy. The diff --git a/zabbix-templete-veeam-jobs.xml b/zabbix-templete-veeam-jobs.xml index 6e97b8a..e6fb775 100644 --- a/zabbix-templete-veeam-jobs.xml +++ b/zabbix-templete-veeam-jobs.xml @@ -365,6 +365,191 @@ + + + 781c7b57c4934bb4af113614ef8959bd + Veeam: Job discovery + DEPENDENT + veeam.jobs.discovery + 0 + + veeam.scripts.monitor + + Discovers Veeam jobs from the JSON returned by Monitor-VeeamJobs.ps1. + + + 07f8608d3a6c40e6bed412afe6c9a410 + Veeam job [{#VEEAMJOBNAME}]: Enabled + DEPENDENT + veeam.job.enabled[{#VEEAMJOBKEY}] + 0 + + veeam.scripts.monitor + + UNSIGNED + Schedule enabled state for Veeam job {#VEEAMJOBNAME}: 1 enabled, 0 disabled. + + + JSONPATH + + $.jobs[?(@.key=="{#VEEAMJOBKEY}")].enabled_numeric.first() + + + + 90d + 365d + + + component + backup + + + veeam_job + {#VEEAMJOBNAME} + + + + + 75e8646b8db84b6f942a5af414c14305 + Veeam job [{#VEEAMJOBNAME}]: Last run + DEPENDENT + veeam.job.last_run[{#VEEAMJOBKEY}] + 0 + + veeam.scripts.monitor + + TEXT + Latest run time for Veeam job {#VEEAMJOBNAME}. + + + JSONPATH + + $.jobs[?(@.key=="{#VEEAMJOBKEY}")].last_run.first() + + + + 90d + 0 + + + component + backup + + + veeam_job + {#VEEAMJOBNAME} + + + + + fdacc145952c4fd592e3a038c7b71888 + Veeam job [{#VEEAMJOBNAME}]: Next run + DEPENDENT + veeam.job.next_run[{#VEEAMJOBKEY}] + 0 + + veeam.scripts.monitor + + TEXT + Next scheduled run time for Veeam job {#VEEAMJOBNAME}. + + + JSONPATH + + $.jobs[?(@.key=="{#VEEAMJOBKEY}")].next_run.first() + + + + 90d + 0 + + + component + backup + + + veeam_job + {#VEEAMJOBNAME} + + + + + 2f3d12d455f249e98bb973d0b3be628a + Veeam job [{#VEEAMJOBNAME}]: Result + DEPENDENT + veeam.job.result[{#VEEAMJOBKEY}] + 0 + + veeam.scripts.monitor + + TEXT + Latest result for Veeam job {#VEEAMJOBNAME}. + + + JSONPATH + + $.jobs[?(@.key=="{#VEEAMJOBKEY}")].result.first() + + + + 90d + 0 + + + component + backup + + + veeam_job + {#VEEAMJOBNAME} + + + + + 7bdca2a712cc460382d3412b391c1c93 + Veeam job [{#VEEAMJOBNAME}]: Restore points + DEPENDENT + veeam.job.restore_points[{#VEEAMJOBKEY}] + 0 + + veeam.scripts.monitor + + TEXT + Restore point count for Veeam job {#VEEAMJOBNAME}; '-' means the count is unavailable. + + + JSONPATH + + $.jobs[?(@.key=="{#VEEAMJOBKEY}")].restore_points_text.first() + + + + 90d + 0 + + + component + backup + + + veeam_job + {#VEEAMJOBNAME} + + + + + + + JSONPATH + + $.jobs_lld + + + + 30d + 0 + +