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 months ago I got an interesting use-case. When you are running thousands of workloads in Microsoft Azure 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 Azure:
- https://helpcenter.veeam.com/docs/vbazure/guide/overview.html?ver=8
- https://www.veeam.com/veeam_backup_azure_8_rest_api_reference_map_ot.pdf
Why this is great for Administrators
Managing thousands of workloads across Azure can quickly become overwhelming — especially when dealing with ad-hoc audit requests or compliance checks. Often, backup admins are asked to validate protection for a handful of VM names with completely random time ranges. There’s no built-in report in the UI that can handle that level of granularity or flexibility.
That’s where this script shines.
With just a few lines of PowerShell, we can:
- Instantly query Veeam Backup for Azure’s REST API for any workload
- Filter results by workload name(s) and time window
- Export the results into clean, human-readable HTML and CSV formats
- Save hours of manual digging through policy history
- Confidently respond to compliance, audit, or license optimization requests
And best of all? It’s API-driven, scriptable, and 100% under your control.
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://github.com/jorgedlcruz/veeam-html-reporting/tree/main/veeam-backup-for-azure/workload-protection-history
After all ran successfully, you should have something similar to this:
It also has a nice output in the console:
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 Azure, 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.
# Ignore SSL certificate validation errors (Not recommended for production) 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:
# PARAMETERS $veeamAzureServer = "https://YOURVBAZURE" $veeamUsername = "YOURUSERNAME" $veeamPassword = 'YOURPASS' $apiVersion = "v8" $vmNames = @('VMNAME1','VMNAME2','VMNAME3') $startDate = Get-Date "2025-06-01" $endDate = Get-Date "2025-12-31"
3.- 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:
# OUTPUT if ($policySessionObjects.Count -gt 0) { $htmlHeader = @" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Schedule-Based Policies Session Report</title> <style> body { font-family: 'Open-Sans', sans-serif; padding: 10px; } table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #ddd; 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>Schedule-Based Policies 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> "@ $rowsHtml = $policySessionObjects | ForEach-Object { $resultStyle = switch ($_.Result) { "Success" { 'class="success"' } "Failed" { 'class="failed"' } "Warning" { 'class="warning"' } Default { '' } } "<tr><td>$($_.'VM Name')</td><td>$($_.'Policy Name')</td><td>$($_.'Session Type')</td><td $resultStyle>$($_.Result)</td><td>$($_.'Session ID')</td><td>$($_.'Start Time')</td><td>$($_.'Stop Time')</td><td>$($_.'Duration (s)')</td></tr>" } # Write files $currentDate = Get-Date -Format "yyyy-MM-dd" $htmlPath = Join-Path $PSScriptRoot "$currentDate-PolicySessionReport.html" $csvPath = Join-Path $PSScriptRoot "$currentDate-PolicySessionReport.csv" ($htmlHeader + ($rowsHtml -join "`n") + $htmlFooter) | Out-File -FilePath $htmlPath -Encoding UTF8 $policySessionObjects | Export-Csv -Path $csvPath -NoTypeInformation Write-Host "`nHTML report saved to: $htmlPath" -ForegroundColor Cyan Write-Host "CSV report saved to: $csvPath" -ForegroundColor Cyan } else { Write-Host "`nNo policy session information found in the selected date range." -ForegroundColor Yellow }
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.
Leave a Reply