• Skip to main content
  • Skip to secondary menu
  • Skip to primary sidebar
The Blog of Jorge de la Cruz

The Blog of Jorge de la Cruz

Everything about VMware, Veeam, InfluxData, Grafana, Zimbra, etc.

  • Home
  • VMWARE
  • VEEAM
    • Veeam Content Recap 2021
    • Veeam v11a
      • Veeam Backup and Replication v11a
    • Veeam Backup for AWS
      • Veeam Backup for AWS v4
    • Veeam Backup for Azure
      • Veeam Backup for Azure v3
    • VeeamON 2021
      • Veeam Announces Support for Red Hat Enterprise Virtualization (RHEV/KVM)
      • Veeam announces enhancements for new versions of Veeam Backup for AWS v4/Azure v3/GVP v2
      • VBO v6 – Self-Service Portal and Native Integration with Azure Archive and AWS S3 Glacier
  • Grafana
    • Part I (Installing InfluxDB, Telegraf and Grafana on Ubuntu 20.04 LTS)
    • Part VIII (Monitoring Veeam using Veeam Enterprise Manager)
    • Part XII (Native Telegraf Plugin for vSphere)
    • Part XIII – Veeam Backup for Microsoft Office 365 v4
    • Part XIV – Veeam Availability Console
    • Part XV – IPMI Monitoring of our ESXi Hosts
    • Part XVI – Performance and Advanced Security of Veeam Backup for Microsoft Office 365
    • Part XVII – Showing Dashboards on Two Monitors Using Raspberry Pi 4
    • Part XIX (Monitoring Veeam with Enterprise Manager) Shell Script
    • Part XXII (Monitoring Cloudflare, include beautiful Maps)
    • Part XXIII (Monitoring WordPress with Jetpack RESTful API)
    • Part XXIV (Monitoring Veeam Backup for Microsoft Azure)
    • Part XXV (Monitoring Power Consumption)
    • Part XXVI (Monitoring Veeam Backup for Nutanix)
    • Part XXVII (Monitoring ReFS and XFS (block-cloning and reflink)
    • Part XXVIII (Monitoring HPE StoreOnce)
    • Part XXIX (Monitoring Pi-hole)
    • Part XXXI (Monitoring Unifi Protect)
    • Part XXXII (Monitoring Veeam ONE – experimental)
    • Part XXXIII (Monitoring NetApp ONTAP)
    • Part XXXIV (Monitoring Runecast)
  • Nutanix
  • ZIMBRA
  • PRTG
  • LINUX
  • MICROSOFT

Veeam: Veeam Backup for AWS – Workload Protection History Report

8th March 2024 - Written in: veeam

Greetings friends, over the years, I have been creating all sort of scripts, dashboards, or tiny applications to solve Customers problems, or Community requests.

A few weeks ago I got an interesting use-case. When you are running thousands of workloads in AWS and you want to audit, or submit to compliance the task can become a bit manual, going through your VB Appliance, etc.The specific use-case was that sometimes the team receives requests about data protection with an existing random vm names, with random dates to audit. That example right there is great, but there is no report that can cover that.

But, of course we have the powerful REST API for Veeam Backup for AWS:

  • https://helpcenter.veeam.com/docs/vbaws/rest/rest_api_reference.html?ver=70
  • https://www.veeam.com/veeam_backup_aws_7_0_rest_api_reference_map_ot.pdf

Custom script in PowerShell

When working with It professionals, I found much simpler to work with PowerShell, which by the way can handle perfectly fine querying API endpoints and operating with them right after. The only tricky part perhaps is to combine everything into an HTML later on, but more on that later, let’s dissect a bit the Script.

  • Download the Script from here: https://raw.githubusercontent.com/jorgedlcruz/veeam-html-reporting/main/veeam-backup-for-aws/workload-protection-history/Veeam_Backup_AWS_WorkloadProtectionHistory.ps1

After all ran successfully, you should have something similar to this:

1.- Disable SSL verification

I know I know, you might think this is insecure, etc. But it is very common, and much more nowadays, that appliances that comes from vendors, like VB AWS, to just trust the SSL that comes with and omit the headache of maintaining a SSL. But if you want to resolve this, I got you covered with the next blog post using Let’s Encrypt.

Add-Type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

2.- Variables for the script

This is where you will spend the most time right. Probably once for the user/pass, but coming often to edit the name of the VMs, and dates you want to audit on:

# Variables for API connection
$veeamUsername = "YOURUSER"
$veeamPassword = "YOURPASS"
$veeamBackupAWSServer = "https://YOURVBIP"
$veeamBackupAWSPort = "11005"
$apiVersion = "1.4-rev0" # Make sure you use the API version according to your appliance https://helpcenter.veeam.com/docs/vbaws/rest/versioning.html?ver=70
$apiUrl = "$veeamBackupAWSServer`:$veeamBackupAWSPort/api/v1"
$vmNames = @('MYVMANEM1','MYVMNAME2')
$startDate = '2024-01-01T00:00:01'
$endDate = '2024-02-21T23:59:59'

3.- Functions

Bunch of functions so we can call them on the gran finale!

3.1.1.- Get-ApiToken

A variable that connects to the auth endpoint, uses user/pass and gets an auth token for later:

# Function to get API token - Public API
Function Get-ApiToken {
    Write-Host "Requesting API token..."
    $body = @{
        username = $veeamUsername
        password = $veeamPassword
        grant_type = 'password'
    }
    $headers = @{
        "Content-Type" = "application/x-www-form-urlencoded"
        "Accept" = "application/json"
        "x-api-version" = $apiVersion
    }
    $uri = "$apiUrl/token"
    
    # Using Invoke-RestMethod for API call
    $response = Invoke-RestMethod -Uri $uri -Method Post -Body $body -Headers $headers -ContentType 'application/x-www-form-urlencoded'
    Write-Host "API token received."
    return $response.access_token
}
$token = Get-ApiToken

3.1.2.- Get-NewApiToken

You are not going to believe it, but we needed an additional endpoint that is not on public API, but still exists on the private one. The one where the UI is built on:

# Function to get API token - Private API
Function Get-NewApiToken {
    Write-Host "Requesting new API token..."

    # Define the request URI and body
    $uri = "$veeamBackupAWSServer/api/oauth2/token"
    $body = "username=$veeamUsername&password=$veeamPassword&rememberMe=false&use_short_term_refresh=true&grant_type=password"

    # Define headers for the request
    $headers = @{
        "Content-Type" = "application/x-www-form-urlencoded"
        "Accept" = "application/json"
    }

    # Make the request and extract the token
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body
        Write-Host "New API token received."
        return $response.access_token
    } catch {
        Write-Host "Error obtaining new API token: $($_.Exception.Message)"
    }
}
$newToken = Get-NewApiToken

3.1.3. – GetVMIds

As we are introducing the VM names we need to audit, this function will help us to retrieve the VMIDs we need for future calls:

# Function to get VM IDs by name
Function Get-VMIds {
    Param (
        [string]$vmName
    )
    Write-Host "Retrieving VM IDs for $vmName..."
    $headers = @{
        'Authorization' = "Bearer $token"
        'x-api-version' = $apiVersion
    }
    $response = Invoke-RestMethod -Uri "$apiUrl/virtualMachines?SearchPattern=$vmName" -Headers $headers
    $vmIds = $response.results.id
    Write-Host "VM IDs for $vmName retrieved: $($vmIds -join ', ')"
    return $vmIds
}

3.1.4. – GetPoliciesByVMid

A function that will query the policies per VMId, usually should be one, but sometimes you can have multiple policies for the same workload, perhaps with just snapshots, or just replicas, etc.

# Function to get policies by VM ID
Function Get-PoliciesByVMId {
    Param (
        [string]$vmId
    )
    Write-Host "Retrieving policies for VM ID: $vmId..."
    $headers = @{
        'Authorization' = "Bearer $token"
        'x-api-version' = $apiVersion
    }
    $policies = @()  # Initialize an empty array to store policy objects
    try {
        $response = Invoke-RestMethod -Uri "$apiUrl/virtualMachines/policies?VirtualMachineId=$vmId" -Headers $headers
        if ($response.totalCount -gt 0) {
            foreach ($policy in $response.results) {
                $policyObj = [PSCustomObject]@{
                    'ID' = $policy.id
                    'Name' = $policy.name
                }
                $policies += $policyObj
            }
        } else {
            Write-Host "No policies found for VM ID: $vmId"
        }
    } catch {
        Write-Host "Error retrieving policies for VM ID: $vmId - $($_.Exception.Message)"
    }
    return $policies
}

3.1.5.- GetPolicyInstanceSessions

This is the part that is not supported currently on public API, so we ended calling the internal API to get something very simple, the policy sessions per policy ID and VM ID:

# Function to get policies sessions by Policy Id and VM ID
Function Get-PolicyInstanceSessions {
    Param (
        [string]$policyId,
        [string]$vmId
    )
    Write-Host "Retrieving policy instance sessions for Policy ID: $policyId and VM ID: $vmId..."

    $headers = @{
        "Authorization" = "Bearer $newToken"
        "Content-Type" = "application/json"
    }
    
    $body = @{
        "skip" = 0
        "count" = 200
        "orderAsc" = $false
        "orderColumn" = 'startTimeUtc'
        "policyId" = $policyId
        "instanceId" = $vmId
        "filters" = @{
            "timePeriod" = ''
            "statuses" = @()
            "sessionTypes" = @()
        }
    } | ConvertTo-Json
    
    try {
        $response = Invoke-RestMethod -Uri "$veeamBackupAWSServer/api/v1/reports/policyInstanceSessions" -Method "POST" -Headers $headers -ContentType "application/json" -Body $body
        $filteredSessions = @()
        foreach ($session in $response.data) {
            $sessionStopTimeStr = $session.stopTime -split '\.' | Select-Object -First 1
            $sessionStopTime = [datetime]::ParseExact($sessionStopTimeStr, 'yyyy-MM-ddTHH:mm:ss', $null)
            if ($sessionStopTime -ge $startDate -and $sessionStopTime -le $endDate) {
                $filteredSessions += $session
            }
        }
        Write-Host "Filtered policy instance sessions retrieved successfully."
        return $filteredSessions
    } catch {
        Write-Host "Error retrieving policy instance sessions: $($_.Exception.Message)"
        return @()
    }
}

4.- The grand finale! Putting all the functions to work

This is the script, now we are going to run through all the functions, get the token, get the VM Ids, check the policies where the VMs are, and finally check the sessions for those policies within the timeframe selected on topn:

# Initialize an empty array for all unique policy session objects
$policySessionObjects = @()

# Main script execution part
foreach ($vmName in $vmNames) {
    Write-Host "Processing VM: $vmName"
    $vmIds = Get-VMIds -vmName $vmName
    foreach ($vmId in $vmIds) {
        Write-Host "Retrieving policies for VM ID: $vmId..."
        $policies = Get-PoliciesByVMId -vmId $vmId
        foreach ($policy in $policies) {
            $policyId = $policy.ID
            $policyName = $policy.Name
            Write-Host "Retrieving sessions for Policy: '$policyName' (ID: $policyId) and VM ID: $vmId..."
            $sessions = Get-PolicyInstanceSessions -policyId $policyId -vmId $vmId
            foreach ($session in $sessions) {
                $sessionObj = [PSCustomObject]@{
                    'VM Name'       = $vmName
                    'Policy ID'     = $policyId
                    'Policy Name'   = $policyName
                    'Session Type'  = $session.sessionType
                    'Result'        = $session.result
                    'Session ID'    = $session.sessionId
                    'Start Time'    = $session.startTime
                    'Stop Time'     = $session.stopTime
                    'Duration (s)'  = $session.duration
                }
                $policySessionObjects += $sessionObj
            }
        }
    }
}

5.- Preparing the HTML/CSV and Export

An script is not an script without a proper and valid output. In this case I thought it could be cool to export as HTML, and CSV. Using the current time when the script was ran:

if ($policySessionObjects.Count -gt 0) {

$htmlHeader = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Policy Session Report</title>
    <style>
        body { font-family: 'Open-Sans', sans-serif; padding: 10px; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #dddddd; text-align: left; padding: 8px; }
        th { background-color: #f2f2f2; }
        .success { background-color: #28a745; color: white; font-weight: bold; }
        .failed { background-color: #dc3545; color: white; font-weight: bold; }
        .warning { background-color: #ffc107; color: black; font-weight: bold; }
    </style>
</head>
<body>
    <h1>Policy Session Report</h1>
    <table>
        <tr>
            <th>VM Name</th>
            <th>Policy Name</th>
            <th>Session Type</th>
            <th>Result</th>
            <th>Session ID</th>
            <th>Start Time</th>
            <th>Stop Time</th>
            <th>Duration (s)</th>
        </tr>
"@

$htmlFooter = @"
    </table>
</body>
</html>
"@

# Construct the HTML for each row based on the policy session objects
$rowsHtml = $policySessionObjects | ForEach-Object {
    $rowData = $_
    $resultStyle = switch ($rowData.'Result') {
        'Success' { 'class="success"' }
        'Failed'  { 'class="failed"' }
        'Warning' { 'class="warning"' }
        Default   { '' } # Default, no additional class
    }
    $rowHtml = "<tr>"
    $rowHtml += "<td>$($rowData.'VM Name')</td>"
    $rowHtml += "<td>$($rowData.'Policy Name')</td>"
    $rowHtml += "<td>$($rowData.'Session Type')</td>"
    $rowHtml += "<td $resultStyle>$($rowData.'Result')</td>" # Apply conditional formatting here
    $rowHtml += "<td>$($rowData.'Session ID')</td>"
    $rowHtml += "<td>$($rowData.'Start Time')</td>"
    $rowHtml += "<td>$($rowData.'Stop Time')</td>"
    $rowHtml += "<td>$($rowData.'Duration (s)')</td>"
    $rowHtml += "</tr>"
    $rowHtml
}



# Building the Output
$currentDate = Get-Date -Format "yyyy-MM-dd"

# Define file names with the current date
$htmlFileName = "$currentDate-PolicySessionReport.html"
$csvFileName = "$currentDate-PolicySessionReport.csv"

# Define file paths relative to the current script's directory
$htmlFilePath = Join-Path -Path $PSScriptRoot -ChildPath $htmlFileName
$csvFilePath = Join-Path -Path $PSScriptRoot -ChildPath $csvFileName

# Combine all parts of the HTML
$htmlContent = $htmlHeader + $rowsHtml + $htmlFooter

# Output to HTML file
$htmlContent | Out-File -FilePath $htmlFilePath
Write-Host "HTML report saved to $htmlFilePath"

# Export to CSV file
$policySessionObjects | Export-Csv -Path $csvFilePath -NoTypeInformation
Write-Host "CSV report saved to $csvFilePath"

} else {
    Write-Host "No policy session information was found for any VMs."
}

And that’s it. Most likely this logic, and the use-case will be added to Veeam ONE in the future, but meanwhile, solving real-use case scenarios with API, always makes me happy.

I hope this is useful for you, give it a try and let me know.

Filed Under: veeam Tagged With: veeam audit, veeam cloud report, veeam cloud reporting, veeam reporting

Reader Interactions

Comments

  1. Wesley Martins says

    14th March 2024 at 2:47 pm

    Great post, Jorge.
    I had a few customer asking for that and even to troubleshooting this is good to check when an instance failed or not.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Primary Sidebar

  • E-mail
  • GitHub
  • LinkedIn
  • RSS
  • Twitter
  • YouTube

Posts Calendar

March 2024
M T W T F S S
 123
45678910
11121314151617
18192021222324
25262728293031
« Feb   Sep »

Disclaimer

All opinions expressed on this site are my own and do not represent the opinions of any company I have worked with, am working with, or will be working with.

Copyright © 2025 · The Blog of Jorge de la Cruz