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.
