Skip to main content

Creating a daily veeam backup email using enterprise manager API

  • September 15, 2026
  • 1 comment
  • 10 views

Hi everyone, sharing this, perhaps it might be useful to others, becasue it uses variables it should just bolt on to anyones’ setup witht he appropriate credentials and smtp relay details.
 

$ErrorActionPreference = 'Stop'

#region Configuration

$Script:EnterpriseServer = 'https://xxxxxxxxxxxxxx:9398'
$Script:User             = 'xxxxxxxxxxxxxxxx'
$Script:Password         = 'xxxxxxxxxxxxxxxx'
$Script:Cc               = 'xxxxxxxxxxx@domain.com'
$Script:ContactsPath     = 'C:\Active-Scripts\veeam-contacts.csv'
$Script:FromAddress     = 'sender@domain.com'
$Script:SmtpServer       = 'xxxxxxxxxxxx’
$Script:SmtpPort         = 587
$Script:SmtpUser         = 'xxxxxxxxxxxxxxxxxxxxxx'
$Script:SmtpCredential   = New-Object System.Management.Automation.PSCredential(
    $Script:SmtpUser,
    (ConvertTo-SecureString 'xxxxxxxxxxxxxxxxxxxxxxxxx’ -AsPlainText -Force)
)

#endregion

function Get-VeeamEmailStyle {
    @"
<style>
body {
    font-family: Segoe UI, Arial, sans-serif;
    font-size: 10pt;
}

table {
    border-collapse: collapse;
}

th {
    background-color: #0078D4;
    color: white;
    padding: 8px 12px;
    text-align: left;
}

td {
    padding: 8px 12px;
    border: 1px solid #d0d0d0;
}

tr:nth-child(even) {
    background-color: #f5f5f5;
}
</style>
"@
}

function Get-ContactLookup {
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    $lookup = @{}
    Import-Csv $Path | ForEach-Object {
        $lookup[$_.Name] = $_
    }
    $lookup
}

function Get-VeeamBasicAuthHeader {
    param(
        [Parameter(Mandatory)]
        [string]$User,

        [Parameter(Mandatory)]
        [string]$Password
    )

    $encoded = [Convert]::ToBase64String(
        [Text.Encoding]::UTF8.GetBytes("${User}:${Password}")
    )
    "Basic $encoded"
}

function Connect-VeeamEnterpriseManager {
    param(
        [Parameter(Mandatory)]
        [string]$BaseUrl,

        [Parameter(Mandatory)]
        [string]$AuthorizationHeader
    )

    Write-Host 'Logging into Veeam Enterprise Manager...' -ForegroundColor Cyan

    $login = Invoke-WebRequest `
        -Method Post `
        -Uri "$BaseUrl/api/sessionMngr/?v=latest" `
        -Headers @{
            Authorization = $AuthorizationHeader
            Accept        = 'application/json'
        } `
        -SkipCertificateCheck

    $sessionId = $login.Headers['X-RestSvcSessionId'] | Select-Object -First 1

    if ([string]::IsNullOrWhiteSpace($sessionId)) {
        Write-Host 'Response headers:' -ForegroundColor Yellow
        $login.Headers | Format-List
        throw 'Login completed but X-RestSvcSessionId was not returned.'
    }

    Write-Host "Session acquired: $($sessionId.Substring(0, [Math]::Min(8, $sessionId.Length)))..." `
        -ForegroundColor Green

    @{
        SessionId = $sessionId
        Headers   = @{
            'X-RestSvcSessionId' = $sessionId
            Accept               = 'application/json'
        }
    }
}

function Get-VeeamBackupJobSessions {
    param(
        [Parameter(Mandatory)]
        [string]$BaseUrl,

        [Parameter(Mandatory)]
        [hashtable]$Headers,

        [int]$PageSize = 1000
    )

    Write-Host 'Retrieving backup job sessions...' -ForegroundColor Cyan

    Invoke-RestMethod `
        -Method Get `
        -Uri "$BaseUrl/api/query?type=BackupJobSession&format=Entities&pageSize=$PageSize" `
        -Headers $Headers `
        -TimeoutSec 30 `
        -SkipCertificateCheck
}

function Get-VeeamLatestSessionResults {
    param(
        [Parameter(Mandatory)]
        [array]$Sessions
    )

    $Sessions |
        Group-Object jobUid |
        ForEach-Object {
            $latest = $_.Group |
                Sort-Object creationTimeUTC -Descending |
                Select-Object -First 1

            $serverName = $latest.links |
                Where-Object { $_.rel -eq 'Up' -and $_.type -eq 'BackupServerReference' } |
                Select-Object -ExpandProperty name -First 1

            [PSCustomObject]@{
                ServerName     = $serverName
                JobName        = $latest.jobName
                JobType        = $latest.jobType
                Result         = $latest.result
                State          = $latest.state
                CreationTime   = $latest.creationTimeUTC
                EndTime        = $latest.endTimeUTC
                FailureMessage = $latest.failureMessage
            }
        }
}

function Get-VeeamReportSubject {
    param(
        [Parameter(Mandatory)]
        [string]$Description,

        [Parameter(Mandatory)]
        [array]$ServerResults
    )

    $failed  = @($ServerResults | Where-Object Result -eq 'Failed').Count
    $warning = @($ServerResults | Where-Object Result -eq 'Warning').Count
    $success = @($ServerResults | Where-Object Result -eq 'Success').Count

    if ($failed -gt 0) {
        "[ACTION REQUIRED] $Description - $failed failed backup job(s)"
    }
    elseif ($warning -gt 0) {
        "[ATTENTION] $Description - $warning backup warning(s)"
    }
    else {
        "[OK] $Description - All $success backup job(s) successful"
    }
}

function New-VeeamReportHtmlBody {
    param(
        [Parameter(Mandatory)]
        [string]$Style,

        [Parameter(Mandatory)]
        [string]$Description,

        [Parameter(Mandatory)]
        [string]$ServerName,

        [Parameter(Mandatory)]
        [array]$ServerResults
    )

    $failed  = @($ServerResults | Where-Object Result -eq 'Failed').Count
    $warning = @($ServerResults | Where-Object Result -eq 'Warning').Count
    $success = @($ServerResults | Where-Object Result -eq 'Success').Count

    $emailTable = $ServerResults |
        Sort-Object JobName |
        Select-Object @{
            Name       = 'Backup Job'
            Expression = { $_.JobName }
        }, @{
            Name       = 'Type'
            Expression = { $_.JobType }
        }, @{
            Name       = 'Result'
            Expression = { $_.Result }
        }, @{
            Name       = 'Started'
            Expression = { $_.CreationTime.ToString('dd/MM/yyyy HH:mm') }
        }, @{
            Name       = 'Finished'
            Expression = {
                if ($null -ne $_.EndTime) {
                    $_.EndTime.ToString('dd/MM/yyyy HH:mm')
                }
            }
        } |
        ConvertTo-Html -Fragment

    @"
<html>
<head>
$Style
</head>
<body>

<h2>Daily Veeam Backup Report</h2>

<p>
<strong>Location:</strong> $Description<br>
<strong>Backup Server:</strong> $ServerName
</p>

<p>
<strong>Successful:</strong> $success<br>
<strong>Warnings:</strong> $warning<br>
<strong>Failed:</strong> $failed
</p>

$emailTable

</body>
</html>

The CSV with contacts is formatted:
Server Description,Manager

With Manager being the recipient.  The cc recipient is the higher level supervisore who only receives the cc if there is a warning or failure.

1 comment

  • Author
  • New Here
  • September 15, 2026

my apologies the CSV headings are:

Name,Server Description,Manager

Name = server name as listed in Enterprise Manager
Description = Description from Enterprise Manager
Manager = recipient Email Address