Extend Veeam job monitoring
This commit is contained in:
583
Monitor-VeeamJobs.ps1
Normal file
583
Monitor-VeeamJobs.ps1
Normal file
@@ -0,0 +1,583 @@
|
||||
<#
|
||||
Monitors Veeam Backup & Replication jobs for Zabbix.
|
||||
|
||||
Default mode prints one JSON object for a Zabbix Agent 2 UserParameter:
|
||||
UserParameter=veeam.scripts.monitor,powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Program Files\Zabbix Agent 2\scripts\Monitor-VeeamJobs.ps1"
|
||||
|
||||
Sender mode pushes the same metrics with zabbix_sender, useful for Task Scheduler:
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Program Files\Zabbix Agent 2\scripts\Monitor-VeeamJobs.ps1" -Mode Sender
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('Json', 'Sender')]
|
||||
[string]$Mode = 'Json',
|
||||
|
||||
[string]$ScriptDir = $PSScriptRoot,
|
||||
[string]$JobExclusionFilterFile,
|
||||
|
||||
[string]$ZabbixSender = 'C:\Program Files\Zabbix Agent 2\zabbix_sender.exe',
|
||||
[string]$ZabbixConfig = 'C:\Program Files\Zabbix Agent 2\zabbix_agent2.conf',
|
||||
[string]$ZabbixServer,
|
||||
[string]$ZabbixHost
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ScriptDir)) {
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($JobExclusionFilterFile)) {
|
||||
$JobExclusionFilterFile = Join-Path $ScriptDir 'NotifyOfDisabledJobs_EXCLUSIONS.IN'
|
||||
}
|
||||
|
||||
function Import-VeeamPowerShell {
|
||||
Add-PSSnapin -Name VeeamPSSnapIn -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not (Get-Command Get-VBRJob -ErrorAction SilentlyContinue) -and
|
||||
-not (Get-Command Get-VBRComputerBackupJob -ErrorAction SilentlyContinue)) {
|
||||
Import-Module Veeam.Backup.PowerShell -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (-not (Get-Command Get-VBRJob -ErrorAction SilentlyContinue) -and
|
||||
-not (Get-Command Get-VBRComputerBackupJob -ErrorAction SilentlyContinue)) {
|
||||
throw 'Veeam PowerShell commands are not available. Run the script on the Veeam server or install/load the Veeam PowerShell module.'
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ZabbixAgentConfigValue {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name
|
||||
)
|
||||
|
||||
if (-not (Test-Path -Path $Path)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$line = Get-Content -Path $Path |
|
||||
Where-Object { $_ -match "^\s*$([regex]::Escape($Name))\s*=" -and $_ -notmatch '^\s*#' } |
|
||||
Select-Object -Last 1
|
||||
|
||||
if (-not $line) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return (($line -split '=', 2)[1]).Trim()
|
||||
}
|
||||
|
||||
function Get-ZabbixServerEndpoints {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Servers
|
||||
)
|
||||
|
||||
$endpoints = @()
|
||||
|
||||
foreach ($serverEndpoint in ($Servers -split ',')) {
|
||||
$serverEndpoint = $serverEndpoint.Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($serverEndpoint)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$serverHost = $serverEndpoint
|
||||
$serverPort = $null
|
||||
|
||||
if ($serverEndpoint -match '^(?<host>.+):(?<port>\d+)$') {
|
||||
$serverHost = $Matches.host
|
||||
$serverPort = $Matches.port
|
||||
}
|
||||
|
||||
$endpoints += [pscustomobject]@{
|
||||
Host = $serverHost
|
||||
Port = $serverPort
|
||||
}
|
||||
}
|
||||
|
||||
return $endpoints
|
||||
}
|
||||
|
||||
function Get-ObjectValue {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$InputObject,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$Paths
|
||||
)
|
||||
|
||||
foreach ($path in $Paths) {
|
||||
$current = $InputObject
|
||||
|
||||
foreach ($part in ($path -split '\.')) {
|
||||
if ($null -eq $current) {
|
||||
break
|
||||
}
|
||||
|
||||
$property = $current.PSObject.Properties[$part]
|
||||
|
||||
if ($null -eq $property) {
|
||||
$current = $null
|
||||
break
|
||||
}
|
||||
|
||||
$current = $property.Value
|
||||
}
|
||||
|
||||
if ($null -ne $current) {
|
||||
return $current
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-VeeamJobName {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Job
|
||||
)
|
||||
|
||||
$name = Get-ObjectValue -InputObject $Job -Paths @('Name', 'Info.Name')
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($name)) {
|
||||
return '<unknown>'
|
||||
}
|
||||
|
||||
return $name
|
||||
}
|
||||
|
||||
function ConvertTo-ZabbixDateTime {
|
||||
param(
|
||||
[object]$Value
|
||||
)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($Value -is [datetime]) {
|
||||
if ($Value -eq [datetime]::MinValue -or $Value -eq [datetime]::MaxValue) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $Value.ToString('yyyy-MM-dd HH:mm:ss')
|
||||
}
|
||||
|
||||
$text = [string]$Value
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $text
|
||||
}
|
||||
|
||||
function Get-VeeamJobScheduleEnabled {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Job
|
||||
)
|
||||
|
||||
return Get-ObjectValue -InputObject $Job -Paths @(
|
||||
'Info.IsScheduleEnabled',
|
||||
'IsScheduleEnabled',
|
||||
'ScheduleEnabled',
|
||||
'ScheduleOptions.Enabled',
|
||||
'JobScheduleOptions.Enabled'
|
||||
)
|
||||
}
|
||||
|
||||
function Get-VeeamJobLastResult {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Job
|
||||
)
|
||||
|
||||
return Get-ObjectValue -InputObject $Job -Paths @(
|
||||
'Info.LastResult',
|
||||
'LastResult',
|
||||
'Info.LatestRunResult',
|
||||
'LatestRunResult'
|
||||
)
|
||||
}
|
||||
|
||||
function Get-VeeamJobLastState {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Job
|
||||
)
|
||||
|
||||
return Get-ObjectValue -InputObject $Job -Paths @(
|
||||
'Info.LastState',
|
||||
'LastState',
|
||||
'Info.LatestRunState',
|
||||
'LatestRunState'
|
||||
)
|
||||
}
|
||||
|
||||
function Get-VeeamJobLastRun {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Job
|
||||
)
|
||||
|
||||
$value = Get-ObjectValue -InputObject $Job -Paths @(
|
||||
'Info.LatestRunLocal',
|
||||
'LatestRunLocal',
|
||||
'Info.LastRunLocal',
|
||||
'LastRunLocal',
|
||||
'Info.LastRun',
|
||||
'LastRun',
|
||||
'Info.LastStartTime',
|
||||
'LastStartTime',
|
||||
'Info.LastEndTime',
|
||||
'LastEndTime'
|
||||
)
|
||||
|
||||
return ConvertTo-ZabbixDateTime -Value $value
|
||||
}
|
||||
|
||||
function Get-VeeamJobNextRun {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Job
|
||||
)
|
||||
|
||||
$value = Get-ObjectValue -InputObject $Job -Paths @(
|
||||
'Info.NextRun',
|
||||
'NextRun',
|
||||
'ScheduleOptions.NextRun',
|
||||
'JobScheduleOptions.NextRun',
|
||||
'ScheduleOptions.NextRunLocal',
|
||||
'JobScheduleOptions.NextRunLocal'
|
||||
)
|
||||
|
||||
return ConvertTo-ZabbixDateTime -Value $value
|
||||
}
|
||||
|
||||
function Get-VeeamBackupJobName {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Backup
|
||||
)
|
||||
|
||||
return Get-ObjectValue -InputObject $Backup -Paths @(
|
||||
'JobName',
|
||||
'Info.JobName',
|
||||
'Job.Name',
|
||||
'Name'
|
||||
)
|
||||
}
|
||||
|
||||
function Get-VeeamRestorePointCounts {
|
||||
$counts = @{}
|
||||
|
||||
if (-not (Get-Command Get-VBRBackup -ErrorAction SilentlyContinue) -or
|
||||
-not (Get-Command Get-VBRRestorePoint -ErrorAction SilentlyContinue)) {
|
||||
return $counts
|
||||
}
|
||||
|
||||
foreach ($backup in @(Get-VBRBackup -WarningAction SilentlyContinue)) {
|
||||
$jobName = Get-VeeamBackupJobName -Backup $backup
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($jobName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$restorePointCount = @(Get-VBRRestorePoint -Backup $backup -WarningAction SilentlyContinue).Count
|
||||
|
||||
if (-not $counts.ContainsKey($jobName)) {
|
||||
$counts[$jobName] = 0
|
||||
}
|
||||
|
||||
$counts[$jobName] += $restorePointCount
|
||||
}
|
||||
catch {
|
||||
if (-not $counts.ContainsKey($jobName)) {
|
||||
$counts[$jobName] = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $counts
|
||||
}
|
||||
|
||||
function Format-ZabbixTextValue {
|
||||
param(
|
||||
[object[]]$Values
|
||||
)
|
||||
|
||||
$text = @($Values |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
||||
Sort-Object -Unique) -join '; '
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return $text
|
||||
}
|
||||
|
||||
function Format-VeeamJobDetailsTable {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object[]]$JobDetails
|
||||
)
|
||||
|
||||
if ($JobDetails.Count -eq 0) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
$lines = @(
|
||||
'Name | Enabled | Last run | Next run | Result | Restore points'
|
||||
'--- | --- | --- | --- | --- | ---'
|
||||
)
|
||||
|
||||
foreach ($job in ($JobDetails | Sort-Object -Property name)) {
|
||||
$enabled = if ($job.enabled -eq $true) { 'yes' } elseif ($job.enabled -eq $false) { 'no' } else { 'unknown' }
|
||||
$lastRun = if ([string]::IsNullOrWhiteSpace($job.last_run)) { '-' } else { $job.last_run }
|
||||
$nextRun = if ([string]::IsNullOrWhiteSpace($job.next_run)) { '-' } else { $job.next_run }
|
||||
$result = if ([string]::IsNullOrWhiteSpace($job.last_result)) { $job.last_state } else { $job.last_result }
|
||||
$restorePoints = if ($null -eq $job.restore_points) { '-' } else { $job.restore_points }
|
||||
|
||||
$lines += ('{0} | {1} | {2} | {3} | {4} | {5}' -f $job.name, $enabled, $lastRun, $nextRun, $result, $restorePoints)
|
||||
}
|
||||
|
||||
return ($lines -join [Environment]::NewLine)
|
||||
}
|
||||
|
||||
function Format-ZabbixSenderValue {
|
||||
param(
|
||||
[object]$Value
|
||||
)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
$text = [string]$Value
|
||||
$text = $text -replace "(`r`n|`n|`r)", ' / '
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return $text
|
||||
}
|
||||
|
||||
function Get-VeeamJobs {
|
||||
Import-VeeamPowerShell
|
||||
|
||||
$jobs = @()
|
||||
|
||||
if (Get-Command Get-VBRJob -ErrorAction SilentlyContinue) {
|
||||
$jobs += @(Get-VBRJob -WarningAction SilentlyContinue)
|
||||
}
|
||||
|
||||
if (Get-Command Get-VBRComputerBackupJob -ErrorAction SilentlyContinue) {
|
||||
$jobs += @(Get-VBRComputerBackupJob -WarningAction SilentlyContinue)
|
||||
}
|
||||
|
||||
return $jobs
|
||||
}
|
||||
|
||||
function Get-VeeamJobMetrics {
|
||||
$allJobs = @(Get-VeeamJobs)
|
||||
$restorePointCounts = Get-VeeamRestorePointCounts
|
||||
$jobDetails = @($allJobs | ForEach-Object {
|
||||
$jobName = Get-VeeamJobName -Job $_
|
||||
$scheduleEnabled = Get-VeeamJobScheduleEnabled -Job $_
|
||||
$lastResult = Get-VeeamJobLastResult -Job $_
|
||||
$lastState = Get-VeeamJobLastState -Job $_
|
||||
$restorePoints = $null
|
||||
|
||||
if ($restorePointCounts.ContainsKey($jobName)) {
|
||||
$restorePoints = $restorePointCounts[$jobName]
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
name = $jobName
|
||||
enabled = $scheduleEnabled
|
||||
last_run = Get-VeeamJobLastRun -Job $_
|
||||
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
|
||||
}
|
||||
})
|
||||
$disabledJobs = @($allJobs | Where-Object {
|
||||
$scheduleEnabled = Get-VeeamJobScheduleEnabled -Job $_
|
||||
|
||||
$scheduleEnabled -eq $false
|
||||
})
|
||||
$errorJobs = @($allJobs | Where-Object {
|
||||
$lastState = Get-VeeamJobLastState -Job $_
|
||||
$lastResult = Get-VeeamJobLastResult -Job $_
|
||||
|
||||
$lastState -eq 'Failed' -or $lastResult -eq 'Failed'
|
||||
})
|
||||
$warningJobs = @($allJobs | Where-Object {
|
||||
$lastState = Get-VeeamJobLastState -Job $_
|
||||
$lastResult = Get-VeeamJobLastResult -Job $_
|
||||
|
||||
$lastState -eq 'Warning' -or $lastResult -eq 'Warning'
|
||||
})
|
||||
$successfulJobs = @($allJobs | Where-Object {
|
||||
$lastResult = Get-VeeamJobLastResult -Job $_
|
||||
|
||||
$lastResult -eq 'Success'
|
||||
})
|
||||
|
||||
$disabledCount = $disabledJobs.Count
|
||||
$disabledExcludedJobs = @()
|
||||
|
||||
if (Test-Path -Path $JobExclusionFilterFile) {
|
||||
$jobsToExclude = @(Get-Content -Path $JobExclusionFilterFile |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
||||
ForEach-Object { $_.Trim() })
|
||||
|
||||
if ($jobsToExclude.Count -gt 0) {
|
||||
$disabledExcludedJobs = @($disabledJobs | Where-Object {
|
||||
$jobName = Get-VeeamJobName -Job $_
|
||||
$jobsToExclude | Where-Object { $jobName -like "*$_*" }
|
||||
})
|
||||
|
||||
$disabledJobsFiltered = @($disabledJobs | Where-Object {
|
||||
$jobName = Get-VeeamJobName -Job $_
|
||||
-not ($jobsToExclude | Where-Object { $jobName -like "*$_*" })
|
||||
})
|
||||
$disabledJobs = $disabledJobsFiltered
|
||||
$disabledCount = $disabledJobsFiltered.Count
|
||||
}
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
total = $allJobs.Count
|
||||
disabled = $disabledCount
|
||||
successful = $successfulJobs.Count
|
||||
error = $errorJobs.Count
|
||||
warning = $warningJobs.Count
|
||||
disabled_names = Format-ZabbixTextValue -Values @($disabledJobs | ForEach-Object { Get-VeeamJobName -Job $_ })
|
||||
disabled_excluded_names = Format-ZabbixTextValue -Values @($disabledExcludedJobs | ForEach-Object { Get-VeeamJobName -Job $_ })
|
||||
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 = $jobDetails
|
||||
}
|
||||
}
|
||||
|
||||
function Send-ZabbixMetrics {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$Metrics
|
||||
)
|
||||
|
||||
if (-not (Test-Path -Path $ZabbixSender)) {
|
||||
throw "zabbix_sender.exe was not found: $ZabbixSender"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ZabbixServer)) {
|
||||
$ZabbixServer = Get-ZabbixAgentConfigValue -Path $ZabbixConfig -Name 'ServerActive'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ZabbixServer)) {
|
||||
$ZabbixServer = Get-ZabbixAgentConfigValue -Path $ZabbixConfig -Name 'Server'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ZabbixHost)) {
|
||||
$ZabbixHost = Get-ZabbixAgentConfigValue -Path $ZabbixConfig -Name 'Hostname'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ZabbixHost)) {
|
||||
$hostnameItem = Get-ZabbixAgentConfigValue -Path $ZabbixConfig -Name 'HostnameItem'
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($hostnameItem) -or $hostnameItem -eq 'system.hostname') {
|
||||
$ZabbixHost = $env:COMPUTERNAME
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ZabbixServer)) {
|
||||
throw "Zabbix server was not provided and was not found in $ZabbixConfig"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ZabbixHost)) {
|
||||
throw "Zabbix hostname was not provided, was not found in $ZabbixConfig, and COMPUTERNAME is empty. Pass -ZabbixHost with the exact Zabbix host name."
|
||||
}
|
||||
|
||||
$serverEndpoints = @(Get-ZabbixServerEndpoints -Servers $ZabbixServer)
|
||||
|
||||
if ($serverEndpoints.Count -eq 0) {
|
||||
throw "No usable Zabbix server endpoints were found in: $ZabbixServer"
|
||||
}
|
||||
|
||||
$senderInput = New-TemporaryFile
|
||||
|
||||
try {
|
||||
Write-Host "Sending as host: $ZabbixHost"
|
||||
|
||||
@(
|
||||
"`"$ZabbixHost`" veeam.jobs.total $($Metrics.total)"
|
||||
"`"$ZabbixHost`" veeam.jobs.disabled $($Metrics.disabled)"
|
||||
"`"$ZabbixHost`" veeam.jobs.successful $($Metrics.successful)"
|
||||
"`"$ZabbixHost`" veeam.jobs.error $($Metrics.error)"
|
||||
"`"$ZabbixHost`" veeam.jobs.warning $($Metrics.warning)"
|
||||
"`"$ZabbixHost`" veeam.jobs.disabled.names $(Format-ZabbixSenderValue -Value $Metrics.disabled_names)"
|
||||
"`"$ZabbixHost`" veeam.jobs.disabled.excluded.names $(Format-ZabbixSenderValue -Value $Metrics.disabled_excluded_names)"
|
||||
"`"$ZabbixHost`" veeam.jobs.error.names $(Format-ZabbixSenderValue -Value $Metrics.error_names)"
|
||||
"`"$ZabbixHost`" veeam.jobs.warning.names $(Format-ZabbixSenderValue -Value $Metrics.warning_names)"
|
||||
"`"$ZabbixHost`" veeam.jobs.table $(Format-ZabbixSenderValue -Value $Metrics.jobs_table)"
|
||||
) | Set-Content -Path $senderInput -Encoding ASCII
|
||||
|
||||
$failedEndpoints = @()
|
||||
|
||||
foreach ($serverEndpoint in $serverEndpoints) {
|
||||
$senderArgs = @('-z', $serverEndpoint.Host, '-i', $senderInput)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($serverEndpoint.Port)) {
|
||||
$senderArgs += @('-p', $serverEndpoint.Port)
|
||||
}
|
||||
|
||||
$displayEndpoint = $serverEndpoint.Host
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($serverEndpoint.Port)) {
|
||||
$displayEndpoint = "$displayEndpoint`:$($serverEndpoint.Port)"
|
||||
}
|
||||
|
||||
Write-Host "Sending to Zabbix endpoint: $displayEndpoint"
|
||||
& $ZabbixSender @senderArgs
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
return
|
||||
}
|
||||
|
||||
$failedEndpoints += "$displayEndpoint (exit code $LASTEXITCODE)"
|
||||
}
|
||||
|
||||
throw "zabbix_sender failed for all configured endpoints: $($failedEndpoints -join '; ')"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -Path $senderInput -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$metrics = Get-VeeamJobMetrics
|
||||
|
||||
if ($Mode -eq 'Sender') {
|
||||
Send-ZabbixMetrics -Metrics $metrics
|
||||
}
|
||||
else {
|
||||
$metrics | ConvertTo-Json -Depth 5 -Compress
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error $_.Exception.Message
|
||||
exit 1
|
||||
}
|
||||
63
README.md
Normal file
63
README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Veeam Backup Jobs for Zabbix
|
||||
|
||||
This repository contains a PowerShell collector and a Zabbix 7.0 template for
|
||||
monitoring Veeam Backup & Replication jobs.
|
||||
|
||||
## Collector output
|
||||
|
||||
`Monitor-VeeamJobs.ps1` prints one compressed JSON object by default:
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 10,
|
||||
"disabled": 1,
|
||||
"successful": 7,
|
||||
"error": 1,
|
||||
"warning": 1,
|
||||
"disabled_names": "Disabled job",
|
||||
"disabled_excluded_names": "Policy disabled by design",
|
||||
"error_names": "Failed job",
|
||||
"warning_names": "Warning job",
|
||||
"jobs_table": "Name | Enabled | Last run | Next run | Result | Restore points\n...",
|
||||
"jobs": [
|
||||
{
|
||||
"name": "Daily backup",
|
||||
"enabled": true,
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Restore point counting is best effort. The script uses `Get-VBRBackup` and
|
||||
`Get-VBRRestorePoint` when both cmdlets are available; if Veeam cannot map a job
|
||||
to backups in the current environment, the job remains visible and its restore
|
||||
point count is shown as `-`.
|
||||
|
||||
## Zabbix dashboard idea
|
||||
|
||||
Create a dashboard named `Veeam Jobs` for hosts linked to the `Veeam Backup Jobs`
|
||||
template.
|
||||
|
||||
Suggested widgets:
|
||||
|
||||
| Area | Widget | Items |
|
||||
| --- | --- | --- |
|
||||
| Top row | Item value | `veeam.jobs.total` |
|
||||
| Top row | Item value | `veeam.jobs.successful` |
|
||||
| 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` |
|
||||
|
||||
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
|
||||
template intentionally keeps `veeam.jobs.disabled.excluded.names`, because some
|
||||
disabled policies are expected.
|
||||
370
zabbix-templete-veeam-jobs.xml
Normal file
370
zabbix-templete-veeam-jobs.xml
Normal file
@@ -0,0 +1,370 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<zabbix_export>
|
||||
<version>7.0</version>
|
||||
<template_groups>
|
||||
<template_group>
|
||||
<uuid>6822b9e565e14f159eb87e271cc98988</uuid>
|
||||
<name>Templates/Backup</name>
|
||||
</template_group>
|
||||
</template_groups>
|
||||
<templates>
|
||||
<template>
|
||||
<uuid>aba24cc8492744808e216c4a3d9ff0ea</uuid>
|
||||
<template>Veeam Backup Jobs</template>
|
||||
<name>Veeam Backup Jobs</name>
|
||||
<description>Monitors Veeam Backup & Replication job states via Zabbix Agent 2 UserParameter.</description>
|
||||
<groups>
|
||||
<group>
|
||||
<name>Templates/Backup</name>
|
||||
</group>
|
||||
</groups>
|
||||
<items>
|
||||
<item>
|
||||
<uuid>30c699d3e87e4c2d9354837cc202ed5f</uuid>
|
||||
<name>Veeam: Monitor</name>
|
||||
<type>ZABBIX_PASSIVE</type>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
<delay>15m</delay>
|
||||
<value_type>TEXT</value_type>
|
||||
<description>Raw JSON from Monitor-VeeamJobs.ps1 via UserParameter.</description>
|
||||
<history>0</history>
|
||||
<trends>0</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>107474afe229462b8f3642e2637a89d9</uuid>
|
||||
<name>Veeam: Total jobs</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.total</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>UNSIGNED</value_type>
|
||||
<description>Total number of Veeam backup jobs.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.total</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>365d</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>1836afb72c2c47248497f83116154cae</uuid>
|
||||
<name>Veeam: Disabled jobs</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.disabled</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>UNSIGNED</value_type>
|
||||
<description>Number of disabled Veeam jobs (after exclusion filter).</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.disabled</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>365d</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
<triggers>
|
||||
<trigger>
|
||||
<uuid>ec3eca9a55924ca4985a7c848c49663e</uuid>
|
||||
<expression>last(/Veeam Backup Jobs/veeam.jobs.disabled)>0 and last(/Veeam Backup Jobs/veeam.jobs.disabled.names)<>"" and last(/Veeam Backup Jobs/veeam.jobs.disabled.names)<>"-" and last(/Veeam Backup Jobs/veeam.jobs.disabled.excluded.names)<>""</expression>
|
||||
<name>Disabled Veeam jobs detected: {ITEM.LASTVALUE2}</name>
|
||||
<priority>HIGH</priority>
|
||||
<description>There are disabled Veeam backup jobs. Excluded disabled jobs: {ITEM.LASTVALUE3}</description>
|
||||
<status>ENABLED</status>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</trigger>
|
||||
</triggers>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>bcef9c99992e4fb28737740c9d8a2c0f</uuid>
|
||||
<name>Veeam: Error jobs</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.error</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>UNSIGNED</value_type>
|
||||
<description>Number of Veeam jobs with errors.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.error</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>365d</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
<triggers>
|
||||
<trigger>
|
||||
<uuid>b7cc8e4cba7e42ca912acd03eabc3073</uuid>
|
||||
<expression>last(/Veeam Backup Jobs/veeam.jobs.error)>5 and last(/Veeam Backup Jobs/veeam.jobs.error.names)<>"" and last(/Veeam Backup Jobs/veeam.jobs.error.names)<>"-"</expression>
|
||||
<name>More than 5 Veeam jobs in error state: {ITEM.LASTVALUE2}</name>
|
||||
<priority>HIGH</priority>
|
||||
<description>More than 5 Veeam backup jobs have failed.</description>
|
||||
<status>ENABLED</status>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</trigger>
|
||||
</triggers>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>56a111fea6794fa5ada55b86b8cf39f5</uuid>
|
||||
<name>Veeam: Successful jobs</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.successful</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>UNSIGNED</value_type>
|
||||
<description>Number of Veeam jobs whose latest run completed successfully.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.successful</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>365d</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>f872f71af61948bfb397167793a783de</uuid>
|
||||
<name>Veeam: Disabled job names</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.disabled.names</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>TEXT</value_type>
|
||||
<description>Names of disabled Veeam jobs (after exclusion filter).</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.disabled_names</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>0</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>91be94066325497dba8d7ff16bcbd014</uuid>
|
||||
<name>Veeam: Excluded disabled job names</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.disabled.excluded.names</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>TEXT</value_type>
|
||||
<description>Names of disabled Veeam jobs ignored by the exclusion filter.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.disabled_excluded_names</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>0</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>e4cd149f583249608d4d5fe65eaae780</uuid>
|
||||
<name>Veeam: Error job names</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.error.names</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>TEXT</value_type>
|
||||
<description>Names of Veeam jobs with errors.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.error_names</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>0</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>e167e78b395d4c40ae7ddaba4e60c118</uuid>
|
||||
<name>Veeam: Warning jobs</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.warning</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>UNSIGNED</value_type>
|
||||
<description>Number of Veeam jobs with warnings.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.warning</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>365d</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
<triggers>
|
||||
<trigger>
|
||||
<uuid>447f923c2377452698f3964e063aa8e7</uuid>
|
||||
<expression>last(/Veeam Backup Jobs/veeam.jobs.warning)>0 and last(/Veeam Backup Jobs/veeam.jobs.warning.names)<>"" and last(/Veeam Backup Jobs/veeam.jobs.warning.names)<>"-"</expression>
|
||||
<name>Veeam jobs in warning state: {ITEM.LASTVALUE2}</name>
|
||||
<priority>WARNING</priority>
|
||||
<description>One or more Veeam backup jobs have warnings.</description>
|
||||
<status>ENABLED</status>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</trigger>
|
||||
</triggers>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>80d706f12efa4b98b051d3fcf35ccab1</uuid>
|
||||
<name>Veeam: Warning job names</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.warning.names</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>TEXT</value_type>
|
||||
<description>Names of Veeam jobs with warnings.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.warning_names</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>0</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
<item>
|
||||
<uuid>3925f767110e4f0aa74d6b996ff1f1c9</uuid>
|
||||
<name>Veeam: Job details table</name>
|
||||
<type>DEPENDENT</type>
|
||||
<key>veeam.jobs.table</key>
|
||||
<delay>0</delay>
|
||||
<master_item>
|
||||
<key>veeam.scripts.monitor</key>
|
||||
</master_item>
|
||||
<value_type>TEXT</value_type>
|
||||
<description>Text table with all Veeam jobs: name, enabled state, last run, next run, latest result, and restore point count.</description>
|
||||
<preprocessing>
|
||||
<step>
|
||||
<type>JSONPATH</type>
|
||||
<parameters>
|
||||
<parameter>$.jobs_table</parameter>
|
||||
</parameters>
|
||||
</step>
|
||||
</preprocessing>
|
||||
<history>90d</history>
|
||||
<trends>0</trends>
|
||||
<tags>
|
||||
<tag>
|
||||
<tag>component</tag>
|
||||
<value>backup</value>
|
||||
</tag>
|
||||
</tags>
|
||||
</item>
|
||||
</items>
|
||||
</template>
|
||||
</templates>
|
||||
</zabbix_export>
|
||||
Reference in New Issue
Block a user