On VBR 13 a job's ScheduleOptions property is a stale snapshot: NextRun
is empty (and its LatestRunLocal years old), so every Veeam job showed
no next run and could never be flagged Overdue. GetScheduleOptions() /
Get-VBRJobScheduleOptions return the live schedule ("2026-10-09
12:15:00 AM"). Use GetScheduleOptions(), falling back to the property.
Immediate-mode backup copy jobs (IsContinuous) have a live NextRun too,
but it's a stale date months in the past, which would show as Overdue;
keep reporting no next run for them.
Parsing tries the current culture, then invariant; VBR 13's format
parsed correctly under en-CA, en-US, fr-CA and en-GB. Mock updated to
VBR 13's schedule shape.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
94 lines
5.2 KiB
PowerShell
94 lines
5.2 KiB
PowerShell
# Stand-ins for the Veeam cmdlets, shaped like the real objects' properties the collector reads.
|
||
# Dot-source this before running the collector to try it without a VBR server:
|
||
#
|
||
# . ./tests/MockVeeam.ps1
|
||
# ./Send-VeeamBackupStatus.ps1 -ConfigPath ./tests/config.json -DryRun
|
||
|
||
# Fixed to the top of the hour, and IDs derived from names/times, so repeated runs see the same
|
||
# jobs and sessions, like a real server
|
||
$now = Get-Date -Minute 0 -Second 0 -Millisecond 0
|
||
function New-StableGuid([string]$Seed) {
|
||
[guid]::new([Security.Cryptography.MD5]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($Seed)))
|
||
}
|
||
$notSet = Get-Date '1900-01-01'
|
||
|
||
# As on VBR 13: the ScheduleOptions property is a stale snapshot with an empty NextRun, and the live
|
||
# schedule (NextRun as a "2026-10-09 12:15:00 AM" string) comes from GetScheduleOptions().
|
||
# -Continuous: an immediate-mode copy job, whose live NextRun is a stale date in the past.
|
||
function New-MockJob($Name, $Type, $NextRun, [bool]$Enabled = $true, [switch]$Continuous) {
|
||
$job = [pscustomobject]@{
|
||
Id = New-StableGuid $Name; Name = $Name; JobType = $Type
|
||
IsScheduleEnabled = $Enabled
|
||
IsContinuous = [bool]$Continuous
|
||
ScheduleOptions = [pscustomobject]@{ NextRun = '' }
|
||
LiveNextRun = $NextRun
|
||
}
|
||
$job | Add-Member ScriptMethod GetScheduleOptions { [pscustomobject]@{ NextRun = $this.LiveNextRun } }
|
||
$job
|
||
}
|
||
function Format-VbrTime([datetime]$Value) {
|
||
$Value.ToString('yyyy-MM-dd h:mm:ss tt', [Globalization.CultureInfo]::InvariantCulture)
|
||
}
|
||
|
||
function New-MockSession($Job, $Start, $End, $State, $Result, $Reason = '', $Tasks = @(), $LogRecords = @(), $Size = 0) {
|
||
$s = [pscustomobject]@{
|
||
Id = New-StableGuid "$($Job.Name)|$($Start.Ticks)"; JobId = $Job.Id; JobName = $Job.Name
|
||
CreationTime = $Start; EndTime = $End; State = $State; Result = $Result
|
||
IsRetryMode = $false
|
||
Info = [pscustomobject]@{ Reason = $Reason }
|
||
BackupStats = [pscustomobject]@{ BackupSize = $Size; DataSize = $Size * 3 }
|
||
Logger = [pscustomobject]@{ Records = $LogRecords }
|
||
Tasks = $Tasks
|
||
}
|
||
$s.Logger | Add-Member ScriptMethod GetLog { [pscustomobject]@{ UpdatedRecords = $this.Records } }
|
||
$s | Add-Member ScriptMethod GetTaskSessions { $this.Tasks }
|
||
$s
|
||
}
|
||
|
||
function New-MockTask($Name, $Status, $Reason = '') {
|
||
[pscustomobject]@{ Name = $Name; Status = $Status; Info = [pscustomobject]@{ Reason = $Reason } }
|
||
}
|
||
|
||
$global:MockJobs = @(
|
||
(New-MockJob 'File Server – Nightly' 'Backup' (Format-VbrTime $now.Date.AddDays(1).AddMinutes(15))),
|
||
# Some versions return NextRun as a DateTime
|
||
(New-MockJob 'SQL01 – Logs' 'Backup' $now.AddMinutes(40)),
|
||
(New-MockJob 'Offsite Copy' 'BackupSync' $null),
|
||
(New-MockJob 'Old Exchange' 'Backup' $now.AddDays(1) $false),
|
||
# VBR 12+ backup copy job (immediate mode, no schedule): the "policy" job Get-VBRJob returns
|
||
(New-MockJob 'Cloud Copy' 'SimpleBackupCopyPolicy' (Format-VbrTime $now.AddMonths(-6)) -Continuous)
|
||
)
|
||
$fs, $sql, $copy, $old, $cloud = $global:MockJobs
|
||
|
||
# Its hidden worker job, one per source job. Get-VBRJob doesn't return it, and Get-VBRBackupSession
|
||
# returns only the worker's sessions. Shape taken from a real VBR 13 server.
|
||
$cloudWorker = [pscustomobject]@{
|
||
Id = New-StableGuid 'Cloud Copy\File Server – Nightly'; Name = 'Cloud Copy\File Server – Nightly'
|
||
JobType = 'SimpleBackupCopyWorker'; PolicyJobId = $cloud.Id
|
||
}
|
||
function New-MockWorkerSession($Start, $End, $Result) {
|
||
$s = New-MockSession $cloudWorker $Start $End 'Stopped' $Result -Size 40GB
|
||
$s | Add-Member ScriptMethod GetJob { $cloudWorker }
|
||
$s
|
||
}
|
||
|
||
$global:MockSessions = @(
|
||
(New-MockSession $fs $now.AddDays(-1).AddHours(-2) $now.AddDays(-1).AddHours(-1) 'Stopped' 'Success' -Size 128GB -Tasks @(
|
||
(New-MockTask 'FS01' 'Success'), (New-MockTask 'FS02' 'Success'))),
|
||
(New-MockSession $fs $now.AddMinutes(-20) $notSet 'Working' 'None' -Tasks @((New-MockTask 'FS01' 'InProgress'))),
|
||
(New-MockSession $sql $now.AddHours(-1) $now.AddMinutes(-55) 'Stopped' 'Warning' -Size 2GB `
|
||
-Tasks @((New-MockTask 'SQL01' 'Warning' 'Failed to truncate transaction logs for database "ERP_PROD"'))),
|
||
(New-MockSession $copy $now.AddHours(-5) $now.AddHours(-4) 'Stopped' 'Failed' 'Error: Unable to connect to repository NAS02' `
|
||
-LogRecords @(
|
||
[pscustomobject]@{ Title = 'Preparing backup chain'; Status = 'ESucceeded' },
|
||
[pscustomobject]@{ Title = 'Network path not found (0x80070035)'; Status = 'EFailed' })),
|
||
(New-MockSession $old $now.AddDays(-10) $now.AddDays(-10).AddHours(1) 'Stopped' 'Success'), # outside 48h lookback
|
||
(New-MockWorkerSession $now.AddDays(-1).AddMinutes(-55) $now.AddDays(-1).AddMinutes(-30) 'Success'),
|
||
(New-MockWorkerSession $now.AddMinutes(-55) $now.AddMinutes(-35) 'Warning'),
|
||
# A session from a job the collector doesn't know (e.g. deleted): must be ignored, not crash
|
||
(New-MockSession ([pscustomobject]@{ Id = New-StableGuid 'gone'; Name = 'Deleted job' }) $now.AddHours(-2) $now.AddHours(-1) 'Stopped' 'Success')
|
||
)
|
||
|
||
function Get-VBRJob { [CmdletBinding()] param() $global:MockJobs }
|
||
function Get-VBRBackupSession { [CmdletBinding()] param() $global:MockSessions }
|