Guide technique ANYSYS · vérifié le 26 octobre 2026

Veeam 13 en PowerShell : rapport quotidien et contrôle du RPO

Modèle d'essai de la machine analytique de Charles Babbage
Modèle d'essai de la machine analytique de Charles BabbageDaderot, Wikimedia Commons, CC0 · Crédits

Le module officiel Veeam.Backup.PowerShell, la connexion, les cmdlets de lecture réellement documentés en version 13 et un script en lecture seule qui produit un rapport CSV et HTML et renvoie un code de sortie non nul quand le RPO n’est pas tenu.

01 / Périmètre

Périmètre et prérequis

La console Veeam montre l’état des tâches ; un rapport automatisé le montre chaque matin à quelqu’un qui n’a pas besoin d’ouvrir la console, et il peut alimenter une supervision. Ce guide clôt la série après l’architecture, les dépôts immuables, les tâches et la restauration.

  • Module : Veeam.Backup.PowerShell, installé par défaut avec la console Veeam Backup & Replication. Contrôle : Get-Module -Name Veeam.Backup.PowerShell -ListAvailable.
  • PowerShell 7.6.3 requis sur la machine qui exécute la session (Windows comme Linux, selon la référence v13).
  • Sur Linux : Rocky Linux 9 ou RHEL 9.6, ASP.NET Core Runtime 8.0, paquet veeam-powershell, module importé depuis /opt/veeam/powershell/Veeam.Backup.PowerShell/Veeam.Backup.PowerShell.psd1.
  • Rôle Backup Administrator requis pour exécuter les cmdlets Veeam.
  • La MFA n’est pas supportée pour PowerShell : utilisez un compte de service dédié pour lequel la MFA est désactivée.

02 / Connexion

Se connecter

Connect-VBRServer ouvre la session. Sans -Server, il se connecte au serveur local ; le port par défaut est 443 et le délai d’attente par défaut de 30 secondes. Les comptes SAML ne sont pas supportés et une session PowerShell ne se connecte qu’à un seul serveur à la fois.

PowerShell (lecture seule)
# Read-only session
Import-Module Veeam.Backup.PowerShell
$cred = Get-Credential -Message 'Veeam service account (DOMAIN\User or UPN)'
Connect-VBRServer -Server 'vbr01.mgmt.local' -Credential $cred
Get-VBRServerSession
# ... read-only cmdlets ...
Disconnect-VBRServer

Le paramètre -ForceAcceptTlsCertificate accepte le certificat TLS du serveur sans contrôle : réservez-le à un laboratoire et installez un certificat de confiance en production. Évitez -User / -Password en clair dans les scripts.

03 / Cmdlets

Cmdlets de lecture

Cmdlets utilisés par le rapport
CmdletCe qu’il renvoie (référence v13)Remarque
Get-VBRJobTâches de sauvegarde, réplication, copie de fichiers, copie de VM, sauvegarde de stockage objet (objet CBackupJob)Obsolète pour les tâches Veeam Agent : utiliser Get-VBRComputerBackupJob
Get-VBRBackupCopyJobTâches de copie de sauvegarde (VBRBackupCopyJob)Paramètres -Name, -Id
Get-VBRComputerBackupJobTâches et politiques Veeam Agent (VBRComputerBackupJob)-Mode ManagedByAgent ou ManagedByBackupServer
Get-VBRBackupSessionSessions des tâches de sauvegarde, réplication, copie, fichiers (VBRBackupSession)Propriétés documentées : CreationTime, EndTime, JobId, Result, State
Get-VBRComputerBackupJobSessionSessions des tâches et politiques Veeam Agent (VBRSession)Mêmes propriétés de résultat et JobId
Get-VBRBackupRepositoryDépôts standards (CBackupRepository) ; -ScaleOut pour les SOBRLes dépôts objet passent par Get-VBRObjectStorageRepository
Get-VBRObjectStorageRepositoryDépôts objet (S3, S3 compatible, Azure Blob, Google Cloud, Wasabi…)Objets S3 et Azure : BackupImmutabilityEnabled, ImmutabilityPeriod

L’énumération VBRSessionResult vaut None, Success, Warning ou Failed. La référence précise qu’elle ne documente pas les classes .NET internes : c’est le cas de CBackupJob et CBackupRepository. Le script lit donc uniquement Name et Id sur ces objets, et tolère leur absence.

04 / Script

Le script de rapport

Le script parcourt les tâches, retrouve pour chacune la dernière session et le dernier succès, calcule l’âge de ce succès et le compare au seuil -RpoHours. Il liste les dépôts et, pour les dépôts objet, l’état d’immuabilité. Il écrit deux CSV (séparateur point-virgule par défaut) et un HTML, puis sort avec le code 0 si tout est dans le RPO, 2 si au moins une tâche le dépasse ou n’a jamais réussi, 1 en cas d’erreur. Rien n’est créé ni modifié sur le serveur Veeam.

Get-VBRDailyReport.ps1PowerShell · 235 lignesTélécharger
<#
.SYNOPSIS
    Read-only daily report for Veeam Backup & Replication 13: last result per job,
    age of the last successful session against an RPO threshold, repositories and
    object storage immutability flags. Exits with code 2 when the RPO is violated.

.DESCRIPTION
    Uses only cmdlets documented in the Veeam Backup & Replication 13 PowerShell
    Reference: Connect-VBRServer, Get-VBRServerSession, Get-VBRJob,
    Get-VBRBackupCopyJob, Get-VBRComputerBackupJob, Get-VBRBackupSession,
    Get-VBRComputerBackupJobSession, Get-VBRBackupRepository,
    Get-VBRObjectStorageRepository, Disconnect-VBRServer.

    Session properties used (JobId, Result, EndTime) are documented for the
    VBRBackupSession and VBRSession objects. Job objects returned by Get-VBRJob
    (CBackupJob) are internal .NET classes: the script only reads Name and Id and
    tolerates their absence. Repository free space is NOT reported because no
    documented cmdlet or property exposes it in the v13 reference.

    Nothing is created, modified, started or deleted on the backup server.

.PARAMETER Server
    Backup server name or address. Default: localhost.

.PARAMETER Port
    Connection port. Default: 443 (Veeam Backup & Replication 13 default).

.PARAMETER Credential
    Optional PSCredential (DOMAIN\User or UPN format). MFA is not supported for
    PowerShell: use a dedicated service account with MFA disabled.

.PARAMETER RpoHours
    Maximum accepted age, in hours, of the last successful session. Default: 24.

.PARAMETER WarningIsSuccess
    Count sessions that ended with Warning as successful. Default: on.

.PARAMETER ExcludeJob
    Job names (wildcards allowed) to leave out of the RPO evaluation.

.PARAMETER OutputDirectory
    Folder for the CSV and HTML files. Default: current folder.

.PARAMETER Delimiter
    CSV delimiter. Default: ';' (Excel with Belgian/French regional settings).

.PARAMETER NoHtml
    Do not write the HTML report.

.EXAMPLE
    .\Get-VBRDailyReport.ps1 -RpoHours 26

.EXAMPLE
    .\Get-VBRDailyReport.ps1 -Server vbr01.mgmt.local -Credential (Get-Credential) -OutputDirectory D:\Reports

.NOTES
    Requires the Veeam.Backup.PowerShell module (installed with the Veeam Backup &
    Replication console, or on Linux under /opt/veeam/powershell) and the Backup
    Administrator role, as stated in the Veeam PowerShell Reference.
    Exit codes: 0 = all evaluated jobs within RPO, 2 = at least one RPO violation,
    1 = script error.
    Source: ANYSYS ICT, https://www.anysys.be/articles/
#>
[CmdletBinding()]
param(
    [string]$Server = 'localhost',
    [int]$Port = 443,
    [System.Management.Automation.PSCredential]$Credential,
    [ValidateRange(1, 8760)]
    [int]$RpoHours = 24,
    [bool]$WarningIsSuccess = $true,
    [string[]]$ExcludeJob = @(),
    [string]$OutputDirectory = (Get-Location).Path,
    [string]$Delimiter = ';',
    [switch]$NoHtml
)

$ErrorActionPreference = 'Stop'

function Get-PropValue {
    # Returns a property value if the object exposes it, otherwise $null.
    param($InputObject, [string]$Name)
    if ($null -eq $InputObject) { return $null }
    $prop = $InputObject.PSObject.Properties[$Name]
    if ($prop) { return $prop.Value }
    return $null
}

function ConvertTo-IdString {
    param($Value)
    if ($null -eq $Value) { return '' }
    return ([string]$Value).ToLowerInvariant()
}

function Test-Excluded {
    param([string]$Name, [string[]]$Pattern)
    foreach ($p in $Pattern) { if ($Name -like $p) { return $true } }
    return $false
}

$connectedHere = $false
$exitCode = 0

try {
    # 1. Load the official module (Windows console install or Linux path).
    if (-not (Get-Module -Name Veeam.Backup.PowerShell)) {
        $linuxPath = '/opt/veeam/powershell/Veeam.Backup.PowerShell/Veeam.Backup.PowerShell.psd1'
        if (Get-Module -Name Veeam.Backup.PowerShell -ListAvailable) {
            Import-Module -Name Veeam.Backup.PowerShell
        }
        elseif (Test-Path -LiteralPath $linuxPath) {
            Import-Module -Name $linuxPath
        }
        else {
            throw 'Module Veeam.Backup.PowerShell not found. Install the Veeam Backup & Replication console first.'
        }
    }

    # 2. Reuse an existing session, otherwise connect (read-only usage).
    if (-not (Get-VBRServerSession)) {
        $connect = @{ Server = $Server; Port = $Port }
        if ($Credential) { $connect.Credential = $Credential }
        Connect-VBRServer @connect
        $connectedHere = $true
    }

    $now = Get-Date

    # 3. Jobs: VM/host jobs, backup copy jobs, agent jobs and policies.
    $jobList = [System.Collections.Generic.List[object]]::new()
    foreach ($j in @(Get-VBRJob)) {
        $jobList.Add([pscustomobject]@{ Name = Get-PropValue $j 'Name'; Id = ConvertTo-IdString (Get-PropValue $j 'Id'); Kind = 'Job' })
    }
    foreach ($j in @(Get-VBRBackupCopyJob)) {
        $jobList.Add([pscustomobject]@{ Name = Get-PropValue $j 'Name'; Id = ConvertTo-IdString (Get-PropValue $j 'Id'); Kind = 'BackupCopy' })
    }
    foreach ($j in @(Get-VBRComputerBackupJob)) {
        $jobList.Add([pscustomobject]@{ Name = Get-PropValue $j 'Name'; Id = ConvertTo-IdString (Get-PropValue $j 'Id'); Kind = 'Agent' })
    }
    $jobs = @($jobList | Where-Object { $_.Id } | Sort-Object -Property Id -Unique)

    # 4. Sessions, grouped by JobId (documented property).
    $sessions = @(Get-VBRBackupSession) + @(Get-VBRComputerBackupJobSession)
    $byJob = @{}
    foreach ($s in $sessions) {
        $key = ConvertTo-IdString (Get-PropValue $s 'JobId')
        if (-not $key) { continue }
        if (-not $byJob.ContainsKey($key)) { $byJob[$key] = [System.Collections.Generic.List[object]]::new() }
        $byJob[$key].Add($s)
    }

    $okResults = @('Success')
    if ($WarningIsSuccess) { $okResults += 'Warning' }

    $jobRows = foreach ($job in $jobs) {
        $list = if ($byJob.ContainsKey($job.Id)) { @($byJob[$job.Id]) } else { @() }
        $ended = @($list | Where-Object { Get-PropValue $_ 'EndTime' } | Sort-Object -Property { Get-PropValue $_ 'EndTime' } -Descending)
        $last = $ended | Select-Object -First 1
        $lastOk = $ended | Where-Object { [string](Get-PropValue $_ 'Result') -in $okResults } | Select-Object -First 1
        $lastOkTime = Get-PropValue $lastOk 'EndTime'
        $ageHours = if ($lastOkTime) { [math]::Round(($now - [datetime]$lastOkTime).TotalHours, 1) } else { $null }
        $excluded = Test-Excluded -Name $job.Name -Pattern $ExcludeJob
        $violated = (-not $excluded) -and (($null -eq $ageHours) -or ($ageHours -gt $RpoHours))

        [pscustomobject]@{
            Job            = $job.Name
            Kind           = $job.Kind
            LastResult     = [string](Get-PropValue $last 'Result')
            LastEnd        = Get-PropValue $last 'EndTime'
            LastSuccessEnd = $lastOkTime
            SuccessAgeH    = $ageHours
            RpoHours       = $RpoHours
            Excluded       = $excluded
            RpoViolated    = $violated
        }
    }
    $jobRows = @($jobRows)

    # 5. Repositories. Free space is not exposed by a documented cmdlet/property.
    $repoRows = [System.Collections.Generic.List[object]]::new()
    foreach ($r in @(Get-VBRBackupRepository)) {
        $repoRows.Add([pscustomobject]@{ Repository = Get-PropValue $r 'Name'; Kind = 'Standard'; ImmutabilityEnabled = 'n/a (not documented)'; ImmutabilityDays = $null })
    }
    foreach ($r in @(Get-VBRBackupRepository -ScaleOut)) {
        $repoRows.Add([pscustomobject]@{ Repository = Get-PropValue $r 'Name'; Kind = 'ScaleOut'; ImmutabilityEnabled = 'see extents'; ImmutabilityDays = $null })
    }
    foreach ($r in @(Get-VBRObjectStorageRepository)) {
        $imm = Get-PropValue $r 'BackupImmutabilityEnabled'
        $repoRows.Add([pscustomobject]@{
                Repository          = Get-PropValue $r 'Name'
                Kind                = 'ObjectStorage:' + $r.GetType().Name
                ImmutabilityEnabled = if ($null -eq $imm) { 'n/a (not documented)' } else { [string]$imm }
                ImmutabilityDays    = Get-PropValue $r 'ImmutabilityPeriod'
            })
    }

    # 6. Output files.
    if (-not (Test-Path -LiteralPath $OutputDirectory)) { throw "Output directory not found: $OutputDirectory" }
    $stamp = $now.ToString('yyyyMMdd-HHmm')
    $jobCsv = Join-Path $OutputDirectory "vbr-jobs-$stamp.csv"
    $repoCsv = Join-Path $OutputDirectory "vbr-repositories-$stamp.csv"
    $jobRows | Export-Csv -LiteralPath $jobCsv -NoTypeInformation -Delimiter $Delimiter -Encoding utf8
    $repoRows | Export-Csv -LiteralPath $repoCsv -NoTypeInformation -Delimiter $Delimiter -Encoding utf8

    $violations = @($jobRows | Where-Object RpoViolated)
    if (-not $NoHtml) {
        $htmlPath = Join-Path $OutputDirectory "vbr-report-$stamp.html"
        $title = "Veeam daily report $($now.ToString('yyyy-MM-dd HH:mm')) - $($violations.Count) RPO violation(s)"
        $body = @(
            "<h1>$([System.Net.WebUtility]::HtmlEncode($title))</h1>"
            '<h2>Jobs</h2>'
            ($jobRows | Sort-Object RpoViolated -Descending | ConvertTo-Html -Fragment)
            '<h2>Repositories</h2>'
            ($repoRows | ConvertTo-Html -Fragment)
        ) -join "`n"
        ConvertTo-Html -Title $title -Body $body | Set-Content -LiteralPath $htmlPath -Encoding utf8
    }

    Write-Host ("Jobs evaluated: {0} | RPO violations: {1} | Repositories: {2}" -f $jobRows.Count, $violations.Count, $repoRows.Count)
    foreach ($v in $violations) {
        Write-Host ("  RPO VIOLATION: {0} (last success age: {1} h)" -f $v.Job, $(if ($null -eq $v.SuccessAgeH) { 'never' } else { $v.SuccessAgeH }))
    }
    Write-Host "CSV: $jobCsv ; $repoCsv"

    if ($violations.Count -gt 0) { $exitCode = 2 }
}
catch {
    Write-Error -Message $_.Exception.Message -ErrorAction Continue
    $exitCode = 1
}
finally {
    if ($connectedHere) { Disconnect-VBRServer }
}

exit $exitCode
Exemple de sortie (illustratif)
Jobs evaluated: 3 | RPO violations: 1 | Repositories: 3
  RPO VIOLATION: VM-Daily (last success age: 30 h)
CSV: D:\Reports\vbr-jobs-20261027-0700.csv ; D:\Reports\vbr-repositories-20261027-0700.csv

05 / Exploitation

Planifier et superviser

Planifiez le script chaque matin, après la fenêtre de sauvegarde, sur un poste d’administration ou le serveur Veeam, sous le compte de service dédié. Le code de sortie est l’interface avec votre supervision : une sonde qui lance le script et alerte sur un code différent de 0 suffit.

  • Fixez -RpoHours un peu au-dessus de l’intervalle réel (par exemple 26 h pour une tâche quotidienne) pour absorber les variations de durée.
  • Excluez explicitement les tâches désactivées ou saisonnières avec -ExcludeJob plutôt que de les laisser en alerte permanente.
  • Archivez les CSV : ils constituent la preuve, mois par mois, que le RPO a été tenu.
  • Envoyez le HTML par la messagerie de votre choix ; le script n’envoie rien lui-même.

06 / Limites

Limites connues

  • Espace libre des dépôts : aucun cmdlet ni aucune propriété documentée de la référence v13 ne l’expose pour les dépôts standards. Le script ne l’affiche donc pas. Suivez-le dans la console, Veeam ONE ou votre supervision du stockage.
  • Immuabilité des dépôts durcis : non exposée par une propriété documentée de CBackupRepository. Le script l’indique « n/a » ; contrôlez-la comme décrit dans le guide 2.
  • Fuseau horaire de EndTime : la référence ne précise pas s’il s’agit d’heure locale ou UTC ; à vérifier sur votre serveur en comparant avec la console.
  • Volume d’historique : Get-VBRBackupSession sans filtre renvoie toutes les sessions. Sur un historique très long, l’exécution peut prendre du temps.
  • Support : Veeam n’écrit pas de scripts sur demande et ne dépanne pas les scripts personnalisés.

07 / Prudence

Avant d’écrire

La référence Veeam le rappelle : une action en PowerShell a la même portée qu’une action dans l’interface. Supprimer une tâche en script la retire de la base sans retour possible. Toute commande Set-, Remove-, Start- ou Stop- doit donc être précédée d’une lecture qui affiche exactement les objets ciblés, et validée sur un environnement de test.

PowerShell
# 1. Read-only: show exactly what would be targeted
Get-VBRBackupRepository -Name 'Hardened-*' | Select-Object Name

# 2. CONFIGURATION CHANGE (only after review, with four-eyes approval if enabled)
# Set-VBRBackupRepository -Repository $repo -EnableBackupImmutability -ImmutabilityPeriod 30

Avec l’autorisation à quatre yeux activée, la désactivation de l’immuabilité ou la réduction de sa période exige l’approbation d’un second administrateur, que l’opération vienne de la console ou d’ailleurs.

08 / Contrôle

Vérification

  1. 01

    Sur le poste d’exécution : Get-Module -Name Veeam.Backup.PowerShell -ListAvailable et $PSVersionTable.PSVersion.

  2. 02

    Lancez le script avec -RpoHours 26 et comparez chaque ligne avec la vue Home de la console.

  3. 03

    Provoquez un cas d’échec maîtrisé (tâche de test désactivée depuis plus de 26 h) et vérifiez le code de sortie 2.

  4. 04

    Vérifiez que la sonde de supervision remonte bien l’alerte.

  5. 05

    Consignez la version du module et le build Veeam dans la documentation d’exploitation.

PowerShell (lecture seule)
.\Get-VBRDailyReport.ps1 -RpoHours 26 -OutputDirectory D:\Reports
"Exit code: $LASTEXITCODE"

09 / Sources

Sources