Add Veeam Backup for Microsoft 365 collector

PowerShell script for the VB365 server, run hourly as a SYSTEM scheduled
task (Windows PowerShell 5.1 by default, -PwshPath for PowerShell 7).
One dashboard job per VB365 backup job and backup copy job.

- Sessions from Get-VBOJobSession (and Get-VBOCopyJobSession where
  available) within the lookback window; status from session Status,
  message from the session log's warning/error lines, size from
  Statistics.TransferredData ("1.5 GB" -> bytes)
- One VB365 server can protect several Microsoft 365 organizations, so
  the dashboard company is chosen per job: OrganizationTenants mapping,
  else Tenant, else the organization's name
- Next run from the job's NextRun; empty for disabled jobs
- Get-Vb365CollectorDiagnostics.ps1 dumps jobs and sessions, showing
  each DateTime's Kind (local vs UTC); installer adapted from MARS

Not yet run against a real server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-08 10:31:43 -06:00
co-authored by Claude Opus 5.5
parent dd3e2fe885
commit 3f407ba76e
6 changed files with 735 additions and 1 deletions
+71
View File
@@ -0,0 +1,71 @@
# Stand-ins for the Veeam Backup for Microsoft 365 cmdlets. Object shapes are best guesses until
# checked against Get-Vb365CollectorDiagnostics.ps1 output from a real server.
#
# . ./tests/MockVb365.ps1
# ./Send-Vb365BackupStatus.ps1 -ConfigPath ./my-test-config.json -DryRun
# Fixed to the top of the hour, and IDs derived from names/times, so repeated runs see the same data
$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)))
}
$acme = [pscustomobject]@{ Name = 'acme.onmicrosoft.com'; Type = 'Office365' }
$blue = [pscustomobject]@{ Name = 'bluewater.onmicrosoft.com'; Type = 'Office365' }
function New-MockJob($Name, $Org, $NextRun, [bool]$Enabled = $true) {
[pscustomobject]@{
Id = New-StableGuid $Name; Name = $Name; Organization = $Org
IsEnabled = $Enabled; NextRun = $NextRun; LastStatus = 'Success'
}
}
function New-MockSession($Job, $Start, $End, $Status, $Transferred = '0 B', [int]$Objects = 0, $Log = @()) {
[pscustomobject]@{
Id = New-StableGuid "$($Job.Name)|$($Start.Ticks)"; JobId = $Job.Id; JobName = $Job.Name
CreationTime = $Start; EndTime = $(if ($End) { $End } else { [datetime]::MinValue })
Status = $Status
Statistics = [pscustomobject]@{ ProcessedObjects = $Objects; TransferredData = $Transferred }
Log = $Log
}
}
function New-LogEntry($Title, $Status = 'Success') { [pscustomobject]@{ Title = $Title; Status = $Status } }
$global:MockVboJobs = @(
(New-MockJob 'Acme - Exchange & OneDrive' $acme $now.AddHours(3)),
(New-MockJob 'Acme - SharePoint & Teams' $acme $now.AddHours(5)),
(New-MockJob 'Bluewater - Full Org' $blue $now.AddMinutes(30)),
(New-MockJob 'Bluewater - Old Mailboxes' $blue $now.AddDays(1) $false) # disabled
)
$exo, $spo, $bw, $old = $global:MockVboJobs
$global:MockVboSessions = @(
(New-MockSession $exo $now.AddHours(-21) $now.AddHours(-20.5) 'Success' '1.5 GB' 412 @(
(New-LogEntry 'Processing mailbox [email protected]'), (New-LogEntry 'Job finished'))),
(New-MockSession $exo $now.AddHours(-1) $null 'Running' '0 B' 10),
(New-MockSession $spo $now.AddHours(-19) $now.AddHours(-18) 'Warning' '512 MB' 38 @(
(New-LogEntry 'Processing site Marketing'),
(New-LogEntry 'Item "Budget 2026.xlsx" was skipped: file is locked' 'Warning'))),
(New-MockSession $bw $now.AddHours(-6) $now.AddHours(-5.9) 'Failed' '0 B' 0 @(
(New-LogEntry 'Failed to connect to Exchange Online: (401) Unauthorized' 'Failed'))),
(New-MockSession $bw $now.AddHours(-30) $now.AddHours(-29.5) 'Stopped' '2 GB' 120), # cancelled
(New-MockSession $old $now.AddDays(-5) $now.AddDays(-5).AddHours(1) 'Success') # outside 48h lookback
)
# A backup copy job (to object storage) for Acme's mailbox job
$global:MockVboCopyJobs = @(
[pscustomobject]@{ Id = New-StableGuid 'acme-copy'; Name = 'Acme - Exchange copy to Wasabi'; BackupJob = $exo; IsEnabled = $true; NextRun = $null }
)
$global:MockVboCopySessions = @(
[pscustomobject]@{
Id = New-StableGuid 'acme-copy-s1'; JobId = $global:MockVboCopyJobs[0].Id; CreationTime = $now.AddHours(-20); EndTime = $now.AddHours(-19.8)
Status = 'Success'; Statistics = [pscustomobject]@{ ProcessedObjects = 412; TransferredData = '1.4 GB' }; Log = @()
}
)
function Get-VBOJob { $global:MockVboJobs }
function Get-VBOJobSession { param($Job) $global:MockVboSessions | Where-Object { -not $Job -or $_.JobId -eq $Job.Id } }
function Get-VBOOrganization { $acme, $blue }
function Get-VBOCopyJob { $global:MockVboCopyJobs }
function Get-VBOCopyJobSession { $global:MockVboCopySessions }