<# .SYNOPSIS Installs the MARS (Azure Backup agent) collector as a SYSTEM scheduled task. .DESCRIPTION - Copies Send-MarsBackupStatus.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), which the agent's MSOnlineBackup module targets. Safe to re-run to change settings or update the script. Can sit alongside the Veeam collector. .EXAMPLE .\Install-MarsCollector.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, # Name shown on the dashboard; defaults to " - Azure Backup" [string]$JobName, [string]$SourceSystem = 'Azure Backup (MARS)', [ValidateRange(5, 1440)][int]$IntervalMinutes = 60, [ValidateRange(1, 720)][int]$LookbackHours = 48, [string]$InstallDir = (Join-Path $env:ProgramFiles 'BackupDashboard') ) $ErrorActionPreference = 'Stop' $TaskName = 'Backup Dashboard - MARS collector' $DataDir = Join-Path $env:ProgramData 'BackupDashboard' $ConfigPath = Join-Path $DataDir 'mars-collector.json' $WindowsPowerShell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' # Check the agent is here before changing anything $agentModule = Join-Path $env:ProgramFiles 'Microsoft Azure Recovery Services Agent\bin\Modules\MSOnlineBackup' if (-not (Get-Module -ListAvailable -Name MSOnlineBackup) -and -not (Test-Path $agentModule)) { throw 'The MARS agent (MSOnlineBackup PowerShell module) is not installed on this server.' } 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 )' $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 Tenant = $Tenant SourceSystem = $SourceSystem LookbackHours = $LookbackHours } if ($JobName) { $config.JobName = $JobName } $config | ConvertTo-Json | Set-Content -Path $ConfigPath -Encoding UTF8 New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null $scriptPath = Join-Path $InstallDir 'Send-MarsBackupStatus.ps1' Copy-Item -Path (Join-Path $PSScriptRoot 'Send-MarsBackupStatus.ps1') -Destination $scriptPath -Force Unblock-File -Path $scriptPath $action = New-ScheduledTaskAction -Execute $WindowsPowerShell ` -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 Azure Backup (MARS) results to the Backup Dashboard' -Force | Out-Null Write-Host "Installed. Config: $ConfigPath" Write-Host "Task '$TaskName' runs every $IntervalMinutes minutes as SYSTEM, using Windows PowerShell." Write-Host '' Write-Host 'Next steps:' Write-Host " 1. Preview what will be sent: & '$WindowsPowerShell' -File '$scriptPath' -DryRun" Write-Host " 2. Backfill 30 days of history: & '$WindowsPowerShell' -File '$scriptPath' -LookbackHours 720" Write-Host " 3. Check the log: Get-Content '$DataDir\mars-collector.log' -Tail 20"