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:
@@ -0,0 +1,105 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installs the Veeam Backup for Microsoft 365 collector as a SYSTEM scheduled task.
|
||||
|
||||
.DESCRIPTION
|
||||
- Copies Send-Vb365BackupStatus.ps1 to Program Files (only admins can modify it, which
|
||||
matters because it runs as SYSTEM).
|
||||
- Writes the config to ProgramData\BackupDashboard and restricts that folder to SYSTEM and
|
||||
Administrators, since it contains the API key.
|
||||
- Registers a scheduled task that runs every IntervalMinutes, under Windows PowerShell 5.1
|
||||
(powershell.exe) unless -PwshPath points at PowerShell 7.
|
||||
Safe to re-run to change settings or update the script. Can sit alongside the other collectors.
|
||||
|
||||
Dashboard company per job: -OrganizationTenants maps Microsoft 365 organization names to
|
||||
companies; jobs of unmapped organizations use -Tenant, or the organization's name if no -Tenant.
|
||||
|
||||
.EXAMPLE
|
||||
.\Install-Vb365Collector.ps1 -ApiUrl https://backups.example.com -Tenant "Acme Corp"
|
||||
One client per VB365 server. Prompts for the API key.
|
||||
.EXAMPLE
|
||||
.\Install-Vb365Collector.ps1 -ApiUrl https://backups.example.com -OrganizationTenants @{
|
||||
'acme.onmicrosoft.com' = 'Acme Corp'; 'bluewater.onmicrosoft.com' = 'Bluewater Dental' }
|
||||
A shared VB365 server protecting several organizations.
|
||||
#>
|
||||
#Requires -RunAsAdministrator
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ApiUrl,
|
||||
[string]$Tenant,
|
||||
[hashtable]$OrganizationTenants = @{},
|
||||
[string]$SourceSystem = 'Veeam Backup for M365',
|
||||
[ValidateRange(5, 1440)][int]$IntervalMinutes = 60,
|
||||
[ValidateRange(1, 720)][int]$LookbackHours = 48,
|
||||
[string]$InstallDir = (Join-Path $env:ProgramFiles 'BackupDashboard'),
|
||||
# Run under PowerShell 7 instead of Windows PowerShell, e.g. 'C:\Program Files\PowerShell\7\pwsh.exe'
|
||||
[string]$PwshPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$TaskName = 'Backup Dashboard - VB365 collector'
|
||||
$DataDir = Join-Path $env:ProgramData 'BackupDashboard'
|
||||
$ConfigPath = Join-Path $DataDir 'vb365-collector.json'
|
||||
$Shell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
||||
if ($PwshPath) {
|
||||
if (-not (Test-Path $PwshPath)) { throw "PowerShell 7 not found at '$PwshPath'." }
|
||||
$Shell = $PwshPath
|
||||
}
|
||||
|
||||
# Check VB365 is here before changing anything
|
||||
$moduleFile = Join-Path $env:ProgramFiles 'Veeam\Backup365\Veeam.Archiver.PowerShell\Veeam.Archiver.PowerShell.psd1'
|
||||
if (-not (Get-Module -ListAvailable -Name Veeam.Archiver.PowerShell) -and -not (Test-Path $moduleFile)) {
|
||||
throw 'The Veeam Backup for Microsoft 365 PowerShell module (Veeam.Archiver.PowerShell) is not installed on this server.'
|
||||
}
|
||||
if (-not $Tenant -and $OrganizationTenants.Count -eq 0) {
|
||||
Write-Warning 'No -Tenant or -OrganizationTenants: jobs will be listed under their Microsoft 365 organization name.'
|
||||
}
|
||||
|
||||
if ($ApiUrl -notmatch '^https://') {
|
||||
Write-Warning 'ApiUrl is not HTTPS: the API key would be sent in clear text.'
|
||||
}
|
||||
|
||||
# Read the key as a SecureString so it isn't echoed to the console or kept in shell history
|
||||
$secure = Read-Host -AsSecureString 'API key (from: python manage.py create-key <name>)'
|
||||
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
|
||||
try { $apiKey = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
|
||||
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
|
||||
if (-not $apiKey.StartsWith('bdk_')) { throw "That doesn't look like a dashboard API key (expected 'bdk_...')." }
|
||||
|
||||
# Config folder: SYSTEM + Administrators only (SIDs so this works on non-English Windows)
|
||||
New-Item -ItemType Directory -Path $DataDir -Force | Out-Null
|
||||
& icacls.exe $DataDir /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' | Out-Null
|
||||
if ($LASTEXITCODE) { throw "icacls failed on $DataDir" }
|
||||
|
||||
$config = [ordered]@{
|
||||
ApiUrl = $ApiUrl.TrimEnd('/')
|
||||
ApiKey = $apiKey
|
||||
SourceSystem = $SourceSystem
|
||||
LookbackHours = $LookbackHours
|
||||
OrganizationTenants = $OrganizationTenants
|
||||
}
|
||||
if ($Tenant) { $config.Tenant = $Tenant }
|
||||
$config | ConvertTo-Json | Set-Content -Path $ConfigPath -Encoding UTF8
|
||||
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
$scriptPath = Join-Path $InstallDir 'Send-Vb365BackupStatus.ps1'
|
||||
Copy-Item -Path (Join-Path $PSScriptRoot 'Send-Vb365BackupStatus.ps1') -Destination $scriptPath -Force
|
||||
Unblock-File -Path $scriptPath
|
||||
|
||||
$action = New-ScheduledTaskAction -Execute $Shell `
|
||||
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`""
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) `
|
||||
-RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes)
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'S-1-5-18' -LogonType ServiceAccount -RunLevel Highest
|
||||
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 30) `
|
||||
-MultipleInstances IgnoreNew -StartWhenAvailable
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal `
|
||||
-Settings $settings -Description 'Sends Veeam Backup for Microsoft 365 results to the Backup Dashboard' -Force | Out-Null
|
||||
|
||||
Write-Host "Installed. Config: $ConfigPath"
|
||||
Write-Host "Task '$TaskName' runs every $IntervalMinutes minutes as SYSTEM, using $Shell."
|
||||
Write-Host ''
|
||||
Write-Host 'Next steps:'
|
||||
Write-Host " 1. Preview what will be sent: & '$Shell' -File '$scriptPath' -DryRun"
|
||||
Write-Host " 2. Backfill 30 days of history: & '$Shell' -File '$scriptPath' -LookbackHours 720"
|
||||
Write-Host " 3. Check the log: Get-Content '$DataDir\vb365-collector.log' -Tail 20"
|
||||
Reference in New Issue
Block a user