<# .SYNOPSIS Installs the Veeam collector on a VBR server as a SYSTEM scheduled task. .DESCRIPTION - Copies Send-VeeamBackupStatus.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 PowerShell 7 (pwsh.exe), which the Veeam module on VBR 13 expects. Install PowerShell 7 machine-wide (MSI or winget) first: Microsoft Store installs can't be run by SYSTEM. Safe to re-run to change settings or update the script. .EXAMPLE .\Install-VeeamCollector.ps1 -ApiUrl https://backups.example.com -Tenant "Acme Corp" (prompts for the API key) #> #Requires -RunAsAdministrator [CmdletBinding()] param( [Parameter(Mandatory)][string]$ApiUrl, [Parameter(Mandatory)][string]$Tenant, [string]$SourceSystem = 'Veeam B&R', [ValidateRange(5, 1440)][int]$IntervalMinutes = 60, [ValidateRange(1, 720)][int]$LookbackHours = 48, [string]$InstallDir = (Join-Path $env:ProgramFiles 'BackupDashboard'), # Defaults to the machine-wide PowerShell 7 install [string]$PwshPath ) $ErrorActionPreference = 'Stop' # Find PowerShell 7 before changing anything, so a missing install fails cleanly if (-not $PwshPath) { $PwshPath = Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe' if (-not (Test-Path $PwshPath)) { $onPath = Get-Command pwsh.exe -ErrorAction SilentlyContinue if ($onPath) { $PwshPath = $onPath.Source } } } if (-not (Test-Path $PwshPath)) { throw "PowerShell 7 not found at '$PwshPath'. Install it machine-wide (e.g. winget install Microsoft.PowerShell) or pass -PwshPath." } if ($PwshPath -match '\\WindowsApps\\') { throw "'$PwshPath' is a Microsoft Store install, which the SYSTEM account can't run. Install the MSI or winget version instead." } $pwshVersion = & $PwshPath -NoProfile -NonInteractive -Command '$PSVersionTable.PSVersion.ToString()' $pwshMajor = & $PwshPath -NoProfile -NonInteractive -Command '$PSVersionTable.PSVersion.Major' if ($LASTEXITCODE -or ($pwshMajor -as [int]) -lt 7) { throw "'$PwshPath' isn't a working PowerShell 7 (reported '$pwshVersion')." } # Shared installer code: in the repo it's in ..\common, so copy the whole collectors folder $sharedModule = @( (Join-Path $PSScriptRoot 'BackupDashboard.Collector.psm1'), (Join-Path (Join-Path (Join-Path $PSScriptRoot '..') 'common') 'BackupDashboard.Collector.psm1') ) | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $sharedModule) { throw "BackupDashboard.Collector.psm1 not found. Copy the whole collectors folder, including common." } Import-Module $sharedModule -Force $settings = [ordered]@{ Tenant = $Tenant; SourceSystem = $SourceSystem; LookbackHours = $LookbackHours } Install-CollectorTask -Name 'veeam' -ScriptPath (Join-Path $PSScriptRoot 'Send-VeeamBackupStatus.ps1') ` -TaskName 'Backup Dashboard - Veeam collector' ` -TaskDescription 'Sends Veeam job results to the Backup Dashboard' ` -Shell $PwshPath -ApiUrl $ApiUrl -IntervalMinutes $IntervalMinutes -InstallDir $InstallDir -Settings $settings