My Veeam Report

Find the latest version here.

Here’s another case of taking a script that someone has shared and putting your personal tweaks into it.

If there was an area in which I wish Veeam would improve, it would be reporting. Though their answer is VeeamOne, it’s a bit much if you just want the additional backup reports. Fortunately this can be overcome with some creative Powershell. I started with a report called vPowerCLI v6 Army Report which can be found here. I had used this report for quite a while until v7 came along and broke a few items. I decided to take the opportunity to not only fix the issues (where I could) but also add some information to the report. I had also been looking at the SAMReports found here and decided to combine a few bits. Lastly, Tom Sightler had a post here that added the ability to report back VMs that had not been backed up within the given time frame – very nice. I can’t say enough about the original authors, without which, I never could have come up with such a report in the end.

Report info includes

  • Job/Session details over period of time (12,24,Week,Month)
  • VMs with no successful backups
  • Running sessions
  • Sessions with warnings or failures with session level details
  • Repository Details
  • Proxy Details
  • Veeam Services Details
  • License/Support Renewal Date

Update the User-Variables section to suit your needs.

asnp "VeeamPSSnapIn" -ErrorAction SilentlyContinue
#region User-Variables
# Report Title
$rptTitle = "My Veeam Report"
# Report mode – valid modes: any number of hours, Weekly or Monthly
# 24, 48, "Weekly", "Monthly"
$reportMode = 24
# File Output path and filename
# When $sendEmail is set to $false, file is created and opened in default browser
$file = "E:\MyVeeamReport.htm"
# Show VMs with no successful backups within time frame ($reportMode)
$showMissing = $true
# vCenter server(s) – As seen in VBR server
#$vcenters = "vcenter1","vcenter2"
$vcenters = "vcenter.yourdomain.local"
# To Exclude VMs from Missing Backups report add VM names to be excluded as follows
# $excludevms = @("vm1","vm2","*_replica")
$excludeVMs = @("")
# Exclude VMs from Missing Backups report in the following (vcenter) folder
$folderExclude = ""
# Show jobs w/warnings or errors
$jlines = $true
# Show running jobs
$rlines = $true
# Show running Services
$runningSvc = $false
# Location of Veeam executable (Veeam.Backup.Manager.exe)
$veeamExePath = "C:\Program Files\Veeam\Backup and Replication\Backup\Veeam.Backup.Shell.exe"
# Location of common dll – Needed for repository function – Get-vPCRepoInfo
$veeamDllPath = "C:\Program Files\Veeam\Backup and Replication\Backup\Veeam.Backup.Common.dll"
# Email configuration
# Send Email – $true or $false – if true, email is sent, if false, output is saved to $file
$sendEmail = $false
$emailHost = "smtprelay.yourdomain.local"
$emailUser = ""
$emailPass = ""
$emailFrom = "MyVeeamReport@yourdomain.local"
$emailTo = "you@yourdomain.local"
# Send report as attachment – $true or $false
$emailAttach = $false
# Highlighting Thresholds
# Repository Free Space Remaining %
$repoCritical = 10
$repoWarn = 20
# Replica Target Free Space Remaining %
$replicaCritical = 10
$replicaWarn = 20
# License Days Remaining
$licenseCritical = 30
$licenseWarn = 90
#region VersionInfo
$vPCARversion = "1.1.4"
# Version 1.1.4 – SM
# Misc tweaks/bug fixes
# Reconfigured HTML a bit to help with certain email clients
# Added cell coloring to highlight status
# Added $rptTitle variable to hold report title
# Added ability to send report via email as attachment
# Version 1.1.3 – SM
# Added Details to Sessions with Warnings or Failures
# Version 1.1.2 – SM
# Minor tweaks/updates
# Added Veeam version info to header
# Version 1.1.1 – Shawn Masterson
# Based on vPowerCLI v6 Army Report (v1.1) by Thomas McConnell
# Tweaked HTML header (color, title)
# Changed report width to 1024px
# Moved hard-coded path to exe/dll files to user declared variables ($veeamExePath/$veeamDllPath)
# Adjusted sorting on all objects
# Modified info group/counts
# Modified – Total Jobs = Job Runs
# Added – Read (GB)
# Added – Transferred (GB)
# Modified – Warning = Warnings
# Modified – Failed = Failures
# Added – Failed (last session)
# Added – Running (currently running sessions)
# Modified job lines
# Renamed Header – Sessions with Warnings or Failures
# Fixed Write (GB) – Broke with v7
# Added support license renewal
# Credit – Gavin Townsend
# Original Credit – Arne Fokkema
# Modified Proxy section
# Removed Read/Write/Util – Broke in v7 – Workaround unknown
# Modified Services section
# Added – $runningSvc variable to toggle displaying services that are running
# Added – Ability to hide section if no results returned (all services are running)
# Added – Scans proxies and repositories as well as the VBR server for services
# Added VMs Not Backed Up section
# Credit – Tom Sightler –
# Modified $reportMode
# Added ability to run with any number of hours (8,12,72 etc)
# Added bits to allow for zero sessions (semi-gracefully)
# Added Running Jobs section
# Added ability to toggle displaying running jobs
# Added catch to ensure running v7 or greater
# Version 1.1
# Added job lines as per a request on the website
# Version 1.0
# Clean up for release
# Version 0.9
# More cmdlet rewrite to improve perfomace, credit to @SethBartlett
# for practically writing the Get-vPCRepoInfo
# Version 0.8
# Added Read/Write stats for proxies at requests of @bsousapt
# Performance improvement of proxy tear down due to rewrite of cmdlet
# Replaced 2 other functions
# Added Warning counter, .00 to all storage returns and fetch credentials for
# remote WinLocal repos
# Version 0.7
# Added Utilisation(Get-vPCDailyProxyUsage) and Modes 24, 48, Weekly, and Monthly
# Minor performance tweaks
#region NonUser-Variables
# Get the B&R Server
$vbrServer = Get-VBRLocalHost
# Get all the VI proxies in your army
$viProxyList = Get-VBRViProxy
# Get all the backup repositories
$repoList = Get-VBRBackupRepository
# Get unique repo servers
$uniqueRepos = @()
$repolist | %{$uniqueRepos += $($_.gethost().realname)}
$uniqueRepos = $uniqueRepos | Sort -unique
# Get all sessions to determine working jobs
$allSesh = Get-VBRBackupSession
# Get all the backup sessions for mode
if ($reportMode -eq "Monthly") {
$HourstoCheck = 720
} elseif ($reportMode -eq "Weekly") {
$HourstoCheck = 168
} else {
$HourstoCheck = $reportMode
$seshList = $allSesh | ?{($_.CreationTime -ge (Get-Date).AddHours(-$HourstoCheck)) -and ($_.State -ne "Working")}
if (($reportMode -ne "Weekly") -And ($reportMode -ne "Monthly")) {
$rptTitle = "$rptTitle (Last $reportMode Hrs)"
} else {
$rptTitle = "$rptTitle ($reportMode)"
#Get replica jobs
$repList = Get-VBRJob | ?{$_.IsReplica}
# Get the job counts
$totalxfer = $null
$totalRead = $null
$seshList | %{$totalxfer += $([Math]::Round([Decimal]$_.Progress.TransferedSize/1GB, 2))}
$seshList | %{$totalRead += $([Math]::Round([Decimal]$_.Progress.ReadSize/1GB, 2))}
$succesSessions = @($seshList | ?{$_.Result -eq "Success"})
$warningSessions = @($seshList | ?{$_.Result -eq "Warning"})
$failsSessions = @($seshList | ?{$_.Result -eq "Failed"})
$totalSessions = @($seshList | ?{$_.Result -eq "Failed" -Or $_.Result -eq "Success" -Or $_.Result -eq "Warning"})
$runningSessions = @($allSesh | ?{$_.State -eq "Working"})
$failedSessions = @($seshList | ?{($_.Result -eq "Failed") -and ($_.WillBeRetried -ne "True")})
#region Functions
function Get-vPCProxyInfo {
param (
[Parameter(Mandatory=$true, Position=1)]
Begin {
$vPCObjAry = @()
[Int]$script:jobcount = 0
[Int]$totalObjInJob = 0
$cleanSessions = $Sessions | ?{($_.Result -ne "Failed") -and
($_.State -eq "Stopped") -and ($_.JobType -ne "Copy")}
$cleanSessions | %{$totalObjInJob = $totalObjInJob + $_.Progress.TotalObjects} |
function Build-vPCObj {param ([PsObject]$inputObj)
$ping = new-object
$pinginfo = $ping.send("$($inputObj.Host.RealName)")
if ($pinginfo.Status -eq "Success") {
$hostAlive = "Alive"
} else {
$hostAlive = "Dead"
$vPCFuncObject = New-Object PSObject -Property @{
ProxyName = $inputObj.Name
RealName = $inputObj.Host.RealName.ToLower()
Disabled = $inputObj.IsDisabled
Status = $hostAlive
IP = $pinginfo.Address
Responce = $pinginfo.RoundtripTime
ReadData = [Decimal]0.00
WriteData = [Decimal]0.00
UsageCount = 0
ProxiesInSessions = $($totalObjInJob*2)
return $vPCFuncObject
function Get-vPCProxyName {param ([String]$title)
$titleAry = @($title.Split())
$ai = 3
$bi = $titleAry.count – 1
while ($ai -ne $bi) {
$proxyString = $proxyString + " " + $titleAry[$ai]
$ai ++
return $proxyString.Trim()
function Get-vPCProxyRole {param ([String]$title)
$titleAry = @($title.Split())
$roleString = $titleAry[1]
return $roleString
Get-VBRViProxy | %{$vPCObjAry = $vPCObjAry + $(Build-vPCObj $_)}
Process {
if ($Name -ne $null) {
$output = $vPCObjAry | ?{$Name -like $_.ProxyName}
else {
$output = $vPCObjAry
End {
function Get-vPCRepoInfo {
param (
[Parameter(Position=0, ValueFromPipeline=$true)]
Begin {
$outputAry = @()
[Reflection.Assembly]::LoadFile($veeamDllPath) | Out-Null
function Build-Object {param($name, $path, $free, $total)
$repoObj = New-Object -TypeName PSObject -Property @{
Target = $name
Storepath = $path
StorageFree = [Math]::Round([Decimal]$free/1GB,2)
StorageTotal = [Math]::Round([Decimal]$total/1GB,2)
FreePercentage = [Math]::Round(($free/$total)*100)
return $repoObj | Select Target, Storepath, StorageFree, StorageTotal, FreePercentage
Process {
foreach ($r in $Repository) {
if ($r.GetType().Name -eq [String]) {
$r = Get-VBRBackupRepository -Name $r
if ($r.Type -eq "WinLocal") {
$Server = $r.GetHost()
$FileCommander = [Veeam.Backup.Core.CRemoteWinFileCommander]::Create($Server.Info)
$storage = $FileCommander.GetDrives([ref]$null) | ?{$_.Name -eq $r.Path.Substring(0,3)}
$outputObj = Build-Object $r.Name $r.Path $storage.FreeSpace $storage.TotalSpace
elseif ($r.Type -eq "LinuxLocal") {
$Server = $r.GetHost()
$FileCommander = new-object Veeam.Backup.Core.CSshFileCommander $
$storage = $FileCommander.FindDirInfo($r.Path)
$outputObj = Build-Object $r.Name $r.Path $storage.FreeSpace $storage.TotalSize
elseif ($r.Type -eq "CifsShare") {
$fso = New-Object -Com Scripting.FileSystemObject
$storage = $fso.GetDrive($r.Path)
$outputObj = Build-Object $r.Name $r.Path $storage.AvailableSpace $storage.TotalSize
$outputAry = $outputAry + $outputObj
End {
function Get-vPCReplicaTarget {
$outputAry = @()
$dsAry = @()
if (($Name -ne $null) -and ($InputObj -eq $null)) {
$InputObj = Get-VBRJob -Name $Name
foreach ($obj in $InputObj) {
if (($dsAry -contains $obj.ViReplicaTargetOptions.DatastoreName) -eq $false) {
$esxi = $obj.GetTargetHost()
$dtstr = $esxi | Find-VBRViDatastore -Name $obj.ViReplicaTargetOptions.DatastoreName
$objoutput = New-Object -TypeName PSObject -Property @{
Target = $esxi.Name
Datastore = $obj.ViReplicaTargetOptions.DatastoreName
StorageFree = [Math]::Round([Decimal]$dtstr.FreeSpace/1GB,2)
StorageTotal = [Math]::Round([Decimal]$dtstr.Capacity/1GB,2)
FreePercentage = [Math]::Round(($dtstr.FreeSpace/$dtstr.Capacity)*100)
$dsAry = $dsAry + $obj.ViReplicaTargetOptions.DatastoreName
$outputAry = $outputAry + $objoutput
else {
$outputAry | Select Target, Datastore, StorageFree, StorageTotal, FreePercentage
function Get-VeeamVersion {
$veeamExe = Get-Item $veeamExePath
$VeeamVersion = $veeamExe.VersionInfo.ProductVersion
Return $VeeamVersion
function Get-VeeamSupportDate {
#Get version and license info
$regBinary = (Get-Item 'HKLM:\SOFTWARE\Veeam\Veeam Backup and Replication\license').GetValue('Lic1')
$veeamLicInfo = [string]::Join($null, ($regBinary | % { [char][int]$_; }))
if($script:VeeamVersion -like "6*"){
$pattern = "Expiration date\=\d{1,2}\/\d{1,2}\/\d{1,4}"
elseif($script:VeeamVersion -like "5*"){
$pattern = "EXPIRATION DATE\=\d{1,2}\/\d{1,2}\/\d{1,4}"
elseif($script:VeeamVersion -like "7*"){
$pattern = "expiration date\=\d{1,2}\/\d{1,2}\/\d{1,4}"
# Convert Binary key
if($script:VeeamVersion -like "5*" -OR $script:VeeamVersion -like "6*" -OR $script:VeeamVersion -like "7*"){
$expirationDate = [regex]::matches($VeeamLicInfo, $pattern)[0].Value.Split("=")[1]
$datearray = $expirationDate -split '/'
$datearray = $datearray[1],$datearray[0],$datearray[2]
$expirationDate = $datearray -join '/'
$totalDaysLeft = ((Get-Date $expirationDate) – (get-date)).Totaldays.toString().split(",")[0]
$totalDaysLeft = [int]$totalDaysLeft
$objoutput = New-Object -TypeName PSObject -Property @{
ExpDate = $expirationDate
DaysRemain = $totalDaysLeft
$objoutput = New-Object -TypeName PSObject -Property @{
ExpDate = "Failed"
DaysRemain = "Failed"
function Get-VeeamServers {
$outputAry = @()
foreach ($srv in $script:viProxyList) {
If (!$vservers.ContainsKey($srv.Host.Realname)) {
foreach ($srv in $script:repoList) {
If (!$vservers.ContainsKey($srv.gethost().Realname)) {
$vservers = $vservers.GetEnumerator() | Sort-Object Name
foreach ($vserver in $vservers) {
$outputAry += $vserver.Name
return $outputAry
function Get-VeeamServices {
param (
$outputAry = @()
foreach ($obj in $InputObj) {
$output = Get-Service -computername $obj -Name "*Veeam*" -exclude "SQLAgent*" |
Select @{Name="Server Name"; Expression = {$obj}}, @{Name="Service Name"; Expression = {$_.DisplayName}}, Status
$outputAry = $outputAry + $output
function Get-VMsMissingBackup {
param (
$outputary = @()
$vcenterobj = Get-VBRServer -Name $vcenter
$vmobjs = Find-VBRObject -Server $vcenterobj | Where-Object {$_.Type -eq "VirtualMachine" -and $_.VMFolderName -notlike $script:folderExclude}
$jobobjids = [Veeam.Backup.Core.CHierarchyObj]::GetObjectsOnHost($ | Where-Object {$_.GetItem().Type -eq "Vm"}
# Convert exclusion list to simple regular expression
$excludevms_regex = (‘(?i)^(‘ + (($script:excludevms | ForEach {[regex]::escape($_)}) –join “|”) + ‘)$’) -replace "\\\*", ".*"
foreach ($vm in $vmobjs) {
$jobobjid = ($jobobjids | Where-Object {$_.ObjectId -eq $vm.Id}).Id
if (!$jobobjid) {
$jobobjid = $vm.FindParent("Datacenter").Id + "\" + $vm.Id
$vm | Add-Member -MemberType NoteProperty "JobObjId" -Value $jobobjid
# Get a list of all VMs from vCenter and add to hash table, assume Unprotected
foreach ($vm in ($vmobjs | where {$_.Name -notmatch $excludevms_regex})) {
if(!$vms.ContainsKey($vm.JobObjId)) {
$vms.Add($vm.JobObjId, @("!", [string]$vm.GetParent("Datacenter"), $vm.Name))
# Find all backup job sessions that have ended in the last x hours
$vbrjobs = Get-VBRJob | Where-Object {$_.JobType -eq "Backup"}
$vbrsessions = Get-VBRBackupSession | Where-Object {$_.JobType -eq "Backup" -and $_.EndTime -ge (Get-Date).addhours(-$script:HourstoCheck)}
# Find all successfully backed up VMs in selected sessions (i.e. VMs not ending in failure) and update status to "Protected"
if ($vbrsessions) {
foreach ($session in $vbrsessions) {
foreach ($vm in ($session.gettasksessions() | Where-Object {$_.Status -ne "Failed"} | ForEach-Object { $_ })) {
if($vms.ContainsKey($vm.Info.ObjectId)) {
$vms = $vms.GetEnumerator() | Sort-Object Value
foreach ($vm in $vms) {
if ($vm.Value[0] -eq "!") {
$objoutput = New-Object -TypeName PSObject -Property @{
Datacenter = $vm.Value[1]
Name = $vm.Value[2]
$outputAry += $objoutput
$outputAry | Select Datacenter, Name | Sort Name
#region Report
# Get Veeam Version
$VeeamVersion = Get-VeeamVersion
# HTML Stuff
$headerObj = @"
body {font-family: Tahoma; background-color:#fff;width: 1024px;}
table {font-family: Tahoma;width: 1024px;font-size: 12px;border-collapse:collapse;}
<!– table tr:nth-child(odd) td {background: #e2e2e2;} –>
th {background-color: #cccc99;border: 1px solid #a7a9ac;border-bottom: none;}
td {background-color: #ffffff;border: 1px solid #a7a9ac;padding: 2px 3px 2px 3px;vertical-align: top;}
$bodyTop = @"
<table cellspacing="0" cellpadding="0">
<td style="width: 80%;height: 45px;border: none;background-color: #003366;color: White;font-size: 24px;vertical-align: bottom;padding: 0px 0px 0px 15px;">$rptTitle</td>
<td style="width: 30%;height: 45px;border: none;background-color: #003366;color: White;font-size: 12px;vertical-align:text-top;text-align:right;padding: 2px 3px 2px 3px;">v$vPCARversion<br>Veeam v$VeeamVersion</td>
<td style="width: 80%;height: 35px;border: none;background-color: #003366;color: White;font-size: 10px;vertical-align: bottom;padding: 0px 0px 2px 3px;">Report generated: $(Get-Date -format g)</font></td>
<td style="width: 30%;height: 35px;border: none;background-color: #003366;color: White;font-size: 10px;vertical-align:bottom;text-align:right;padding: 2px 3px 2px 3px;">Based on:<br><a href="" target="_blank"><font color="White">vPowerCLI v6 Army Report</font></a></td>
$subHead01 = @"
<td style="height: 35px;background-color: #eeeeee;color: #003366;font-size: 16px;font-weight: bold;vertical-align: middle;padding: 5px 0 0 15px;border-top: none;border-bottom: none;">
$subHead02 = @"
$footerObj = @"
If ($VeeamVersion -lt 7) {
Write-Host "Script requires VBR v7 or greater"
# Get Summary Info
$vbrMasterHash = @{"Coordinator" = "$((gc env:computername).ToLower())"; "Failed" = $failedSessions.Count; "Sessions" = $totalSessions.Count;
"Read" = $totalRead; "Transferred" = $totalXfer; "Successfull" = $succesSessions.Count; "Warning" = $warningSessions.Count;
"Fails" = $failsSessions.Count; "Running" = $runningSessions.Count;}
$vbrMasterObj = New-Object -TypeName PSObject -Property $vbrMasterHash
$bodyMasterT = $vbrMasterObj | Select Coordinator, @{Name="Job Runs"; Expression = {$_.Sessions}},
@{Name="Read (GB)"; Expression = {$_.Read}}, @{Name="Transferred (GB)"; Expression = {$_.Transferred}},
@{Name="Running"; Expression = {$_.Running}}, @{Name="Successfull"; Expression = {$_.Successfull}},
@{Name="Warnings"; Expression = {$_.Warning}}, @{Name="Failures"; Expression = {$_.Fails}},
@{Name="Failed"; Expression = {$_.Failed}} | ConvertTo-HTML -Fragment
# Get VMs Missing Backups
$bodyMissing = $null
If ($showMissing) {
$missingVMs = @()
foreach ($vcenter in $vcenters) {
$missing = Get-VMsMissingBackup $vcenter | Select Datacenter, Name
$missingVMs += $missing
If ($missingVMs -ne $null) {
$missingVMs = $missingVMs | Sort Datacenter, Name | ConvertTo-HTML -Fragment
$bodyMissing = $subHead01 + "VMs with no successful backups" + $subHead02 + $missingVMs
# Get Running Jobs
$bodyrLines = $null
if ($rlines -eq $true) {
if ($runningSessions.count -gt 0) {
$bodyrLines = $runningSessions | Sort Creationtime | Select @{Name="Job Name"; Expression = {$_.Name}},
@{Name="Start Date"; Expression = {$_.Progress.StartTime.ToShortDateString()}},
@{Name="Start Time"; Expression = {$_.Progress.StartTime.ToShortTimeString()}},
@{Name="Duration"; Expression = {Get-Date "$($_.Progress.Duration)" -f HH:mm:ss}},
@{Name="Read (GB)"; Expression = {[Math]::Round([Decimal]$_.Progress.ReadSize/1GB, 2)}},
@{Name="Write (GB)"; Expression = {[Math]::Round([Decimal]$_.Progress.TransferedSize/1GB, 2)}} | ConvertTo-HTML -Fragment
$bodyrLines = $subHead01 + "Running Jobs" + $subHead02 + $bodyrLines
# Get Jobs with Failures or Warnings
$bodyjLines = $null
if ($jlines -eq $true) {
$counter = @($seshList | ?{($_.Result -eq "Warning") -or ($_.Result -eq "Failed")})
if ($counter.count -gt 0) {
$bodyjLines = $seshList | ?{($_.Result -eq "Warning") -or ($_.Result -eq "Failed")} | Sort Creationtime | Select @{Name="Job Name"; Expression = {$_.Name}},
@{Name="Start Date"; Expression = {$_.Progress.StartTime.ToShortDateString()}},
@{Name="Start Time"; Expression = {$_.Progress.StartTime.ToShortTimeString()}},
@{Name="Stop Time"; Expression = {$_.Progress.StopTime.ToShortTimeString()}},
@{Name="Details"; Expression = {($_.GetDetails()).Replace("<br />"," – ")}},Result | ConvertTo-HTML -Fragment
$bodyjLines = $subHead01 + "Sessions with Warnings or Failures" + $subHead02 + $bodyjLines
# Get Proxy Info
$bodyProxy = $null
if ($viProxyList -ne $null -And $seshList -ne $null) {
$bodyProxy = Get-vPCProxyInfo -Sessions $seshList | Select @{Name="Proxy Host"; Expression = {$_.RealName}},
Disabled, @{Name="IP Address"; Expression = {$_.IP}}, @{Name="RT (ms)"; Expression = {$_.Responce}}, Status |
Sort "Proxy Host" | ConvertTo-HTML -Fragment
$bodyProxy = $subHead01 + "Proxy Details" + $subHead02 + $bodyProxy
# Get Repository Info
$bodyRepo = $null
if ($repoList -ne $null) {
$bodyRepo = $repoList | Get-vPCRepoInfo | Select @{Name="Repository Name"; Expression = {$_.Target}},
@{Name="Path"; Expression = {$_.Storepath}}, @{Name="Free (GB)"; Expression = {$_.StorageFree}},
@{Name="Total (GB)"; Expression = {$_.StorageTotal}}, @{Name="Free (%)"; Expression = {$_.FreePercentage}},
@{Name="Status"; Expression = {If ($_.FreePercentage -lt $repoCritical) {"Critical"} ElseIf ($_.FreePercentage -lt $repoWarn) {"Warning"} Else {"OK"}}} | `
Sort "Repository Name" | ConvertTo-HTML -Fragment
$bodyRepo = $subHead01 + "Repository Details" + $subHead02 + $bodyRepo
# Get Replica Target Info
$bodyReplica = $null
if ($repList -ne $null) {
$bodyReplica = $repList | Get-vPCReplicaTarget | Select @{Name="Replica Target"; Expression = {$_.Target}}, Datastore,
@{Name="Free (GB)"; Expression = {$_.StorageFree}}, @{Name="Total (GB)"; Expression = {$_.StorageTotal}},
@{Name="Free (%)"; Expression = {$_.FreePercentage}},
@{Name="Status"; Expression = {If ($_.FreePercentage -lt $replicaCritical) {"Critical"} ElseIf ($_.FreePercentage -lt $replicaWarn) {"Warning"} Else {"OK"}}} | `
Sort "Replica Target" | ConvertTo-HTML -Fragment
$bodyReplica = $subHead01 + "Replica Details" + $subHead02 + $bodyReplica
# Get Veeam Services Info
$bodyServices = Get-VeeamServers
$bodyServices = Get-VeeamServices $bodyServices
If ($runningSvc -ne $true) {$bodyServices = $bodyServices | ?{$_.Status -ne "Running"}}
If ($bodyServices -ne $null) {
$bodyServices = $bodyServices | Select "Server Name", "Service Name", Status | Sort "Server Name", "Service Name" | ConvertTo-HTML -Fragment
$bodyServices = $subHead01 + "Services" + $subHead02 + $bodyServices
# Get License Info
$bodyLicense = Get-VeeamSupportDate | Select @{Name="Expiry Date"; Expression = {$_.ExpDate}}, @{Name="Days Remaining"; Expression = {$_.DaysRemain}}, `
@{Name="Status"; Expression = {If ($_.DaysRemain -lt $licenseCritical) {"Critical"} ElseIf ($_.DaysRemain -lt $licenseWarn) {"Warning"} Else {"OK"}}} | `
ConvertTo-HTML -Fragment
$bodyLicense = $subHead01 + "License/Support Renewal Date" + $subHead02 + $bodyLicense
# Combine HTML Output
$htmlOutput = $headerObj + $bodyTop + $bodyMasterT + $bodyMissing + $bodyrLines + $bodyjLines + $bodyRepo + $bodyProxy + $bodyReplica + $bodyServices + $bodyLicense + $footerObj
# Add color to output depending on results
$htmlOutput = $htmlOutput.Replace("<td>Running<","<td style=""background-color: Green;color: White;"">Running<")
$htmlOutput = $htmlOutput.Replace("<td>OK<","<td style=""background-color: Green;color: White;"">OK<")
$htmlOutput = $htmlOutput.Replace("<td>Alive<","<td style=""background-color: Green;color: White;"">Alive<")
$htmlOutput = $htmlOutput.Replace("<td>Warning<","<td style=""background-color: Yellow;"">Warning<")
$htmlOutput = $htmlOutput.Replace("<td>Stopped<","<td style=""background-color: Red;color: White;"">Stopped<")
$htmlOutput = $htmlOutput.Replace("<td>Failed<","<td style=""background-color: Red;color: White;"">Failed<")
$htmlOutput = $htmlOutput.Replace("<td>Critical<","<td style=""background-color: Red;color: White;"">Critical<")
$htmlOutput = $htmlOutput.Replace("<td>Dead<","<td style=""background-color: Red;color: White;"">Dead<")
$htmlOutput = $htmlOutput.Replace("<th>Datacenter</th><th>","<th style=""background-color: Red;"">Datacenter<th style=""background-color: Red;"">")
#region Output
if ($sendEmail) {
$emailSubject = $rptTitle
$smtp = New-Object System.Net.Mail.SmtpClient $emailHost
$smtp.Credentials = New-Object System.Net.NetworkCredential($emailUser, $emailPass);
$msg = New-Object System.Net.Mail.MailMessage($emailFrom, $emailTo)
$msg.Subject = $emailSubject
If ($emailAttach) {
$body = "Veeam Report Attached"
$msg.Body = $body
$tempfile = "$env:TEMP\$rptTitle.htm"
$htmlOutput | Out-File $tempfile
$attachment = new-object System.Net.Mail.Attachment $tempfile
} Else {
$body = $htmlOutput
$msg.Body = $body
$msg.isBodyhtml = $true
If ($emailAttach) {
Remove-Item $tempfile
} else {
$htmlOutput | Out-File $file
Invoke-Expression $file

Update 03/25/2014 – Version 1.1.4

  • Misc tweaks/bug fixes
  • Reconfigured HTML a bit to help with certain email clients
  • Added cell coloring to highlight status
  • Added $rptTitle variable to hold report title
  • Added ability to send report via email as attachment

56 thoughts on “My Veeam Report

    1. smasterson Post author

      Hi Ajeje
      The code was chewed up a bit and some of the html markup was stripped. I’ve switched how the code is displayed and should it be easier to work with now.
      Let me know if you still have issues.

      1. Ajeje

        Whould be nice to have a link with the full powershell script.

        – Able to set a title from config variable
        – Able to have a different e-mail subject if there are jobs with Warning/Errors. So it is possible to filter mails on the client side…
        – HTML colors according to success, warning, errors
        – Able to send e-mail to different recpient according to the results of the jobs. Ex: If success: mail1; if warning: mail1,mail2; if error: mail1,mail2,supervisor-mail

  1. smasterson Post author

    Ajeje – You can hit a link to the Gist at the bottom of the script – this makes it easier to download the entire script. Unfortunately, WordPress does not allow for file hosting (only images), I think a gist is the next best thing.
    I’ve also added a few new tweaks including a title variable and HTML coloring of some of the cells to highlight status.
    Let me know what you think

  2. MMK

    Hello I am very very new to power shell. I have saved your code as .pS1 and when I run it using power shell on the Veeam server I get bunch of errors and the an html out put showing the version a job table with no information in it.
    Is all the red in your script suppose to be replace with current relevant information- I am need of guidance thanks.

  3. MMK

    Hello Shawn, thank you for you response. I have installed the Add-in and adjusted the script from 3-54 and I am getting all information except for VM with no successful backups section ( is this section for identifying which machine in on ESXi are not part of any backup job?) -I really need this feature since we have over 200 VM in our environment and I don’t know what is not added to Veeam jobs.
    Thank you for all your help.

    1. smasterson Post author

      The missing VM section is highly dependent on ensuring your vcenter name listed on line 16 is exactly how it is shown within Veeam (Backup Infrastructure > Managed Servers > VMware vSphere > vCenter Servers)

  4. MMK

    Hello Shaw,sorry to bother you again please I am I have confirmed that I am using the VMware server same as Veeam software. When rung the script on the Veeam server I get the following error and then an HTML with only:
    Repository Details
    Repository Name
    Proxy Details
    License/Support Renewal Date
    Find-VBRObject : Cannot validate argument on parameter ‘Server’. The argument i
    s null. Supply a non-null argument and try the command again.
    At C:\Users\administrator\Desktop\Veeam Report.ps1:443 char:34
    + $vmobjs = Find-VBRObject -Server <<<< $vcenterobj | Where-Object {$_.Typ
    e -eq "VirtualMachine" -and $_.VMFolderName -notlike $script:folderExclude}
    + CategoryInfo : InvalidData: (:) [Find-VBRObject], ParameterBind
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Veeam.Backup.Po

    Cannot convert argument "0", with value: "", for "GetObjectsOnHost" to type "Sy
    stem.Guid": "Cannot convert null to type "System.Guid"."
    At C:\Users\administrator\Desktop\Veeam Report.ps1:444 char:66
    + $jobobjids = [Veeam.Backup.Core.CHierarchyObj]::GetObjectsOnHost <<<< ($v | Where-Object {$_.GetItem().Type -eq "Vm"}
    + CategoryInfo : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

    You cannot call a method on a null-valued expression.
    At C:\Users\administrator\Desktop\p\Veeam Report.ps1:453 char:30
    + $jobobjid = $vm.FindParent <<<< ("Datacenter").Id + "\" + $vm.Id
    + CategoryInfo : InvalidOperation: (FindParent:String) [], Runtim
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Add-Member : Cannot bind argument to parameter 'InputObject' because it is null
    At CC:\Users\administrator\Desktop\\Veeam Report.ps1:455 char:19
    + $vm | Add-Member <<<< -MemberType NoteProperty "JobObjId" -Value $jo
    + CategoryInfo : InvalidData: (:) [Add-Member], ParameterBindingV
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,M

    Cannot index into a null array.

  5. Hem

    I have one question , if i more than one veeam backup server how script knows about ◦VMs with no successful backups for that particular Server .. or I will query vcenter server server and give me list of all VMS which are not included for particular veeam backup server..

  6. smasterson Post author

    Hi Hem,
    Unfortunately this is going to be a limitation of the script as it assumes one backup server is protecting the entire vCenter environment (or multiple vCenters).


    1. ericzba

      I think I figured it out. I modified this line in the script:

      $seshList = $allSesh | ?{($_.CreationTime -ge (Get-Date).AddHours(-$HourstoCheck)) -and ($_.State -ne “Working”) -and ($_.JobType -ne “BackupSync”) -and ($_.Name -ne “Lab Backup”)}

      I added the -and ($_.JobType -ne “BackupSync”) -and ($_.Name -ne “Lab Backup”). This excludes Backup Copy Jobs and a specific job I have with the name Lab Backup. Works great now!

  7. Rollins

    Hi Shawn

    Great script.

    For the life of me I can’t get it to email multiple recipients have tried @(“recipient1@domain”, “recipient2@domain”) does not seem to like it. Will only email the last recipient in the list. Any ideas ?


  8. Stoner

    I am getting also an error. Perhaps you could check.

    #region Report
    # Get Veeam Version
    $VeeamVersion = Get-VeeamVersion
    # HTML Stuff
    $headerObj = @”” im Ausdruck oder in der Anweisung.
    Bei C:\Users\USERNAME\Desktop\MyVeeamReport.ps1:503 Zeichen:16
    + $excludevms_re <<<< gex = (â?~(?i)^(â?~ + (($script:excludevms | ForEach {[regex]::escape($_)}) â?"join â?o|â??)
    + â?~)$â?T) -replace "\\\*", ".*"
    + CategoryInfo : ParserError: (â?"join â?o|â??…$headerObj = @":String) [], ParseException
    + FullyQualifiedErrorId : UnexpectedToken

  9. Stoner

    Never mind. I found it.
    Wrong charset -> $excludevms_regex = (‘(?i)^(‘ + (($script:excludevms | ForEach {[regex]::escape($_)}) –join “|”) + ‘)$’) -replace “\\\*”, “.*”

  10. Stoner

    Hello again,

    Another issue:

    Get-Date : Cannot bind parameter ‘Date’. Cannot convert value “10/13/2014” to type “System.DateTime”. Error: “String was not recognized as a valid DateTime.”
    At C:\Users\NAME\Desktop\VeeamReport.ps1:386 char:30
    + $totalDaysLeft = ((Get-Date <<<< $expirationDate) – (get-date)).Totaldays.toString().split(",")[0]
    + CategoryInfo : InvalidArgument: (:) [Get-Date], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.GetDateCommand

    Tried to figure it out myself. But without success.


    1. smasterson Post author

      Quick guess has me thinking your date format is different than mine (USA vs World…)

      Try swapping the order in line 384
      $datearray = $datearray[1],$datearray[0],$datearray[2]
      change to
      $datearray = $datearray[0],$datearray[1],$datearray[2]

      This should switch the expiration date retrieved from month/day/year to day/month/year
      I haven’t tested the script at all with different locales…this seems to be the issues you are encountering.


  11. Georges

    Hi. Thank you for this nice piece of code!

    BTW, I made some modifications to print the list of successful backups (I like having a list of what really happened, so I can be sure that no VM has been forgotten in the batch): let me know if you want me to send my version.


    1. smasterson Post author

      That’s a great addition. I like the idea of being able to toggle the option on or off as it would get cumbersome in large environments. You can send to smasterson via gmail if you’d like.


    1. smasterson Post author

      Glad you like 🙂
      I don’t recall exactly what the issue was but v7 had broken the proxy utilization. I should prolly swing back around and check it out as the functionality was nice.

  12. Marko

    Nice repport script. question, is it possible to extract the vm’s from a job?
    I mean we have all the vm’s now in a folder that Veeam takes backup and inside of thet folder is all the vm’s that i need to get the times.

  13. Pingback: My Veeam Report v1.2 | Shawn Masterson's Blog

  14. Pingback: My Veeam Report v1.3 with VBR v8 Support | Shawn Masterson's Blog

  15. martijnmoret

    Thanks for this, I really love this script.

    I have just tried it on veeam B&R 8 Patch 1 with VMware 5.1 and see a few errors.
    Can you please take a look?

    WARNING: This cmdlet is obsolete, use Find-VBRViEntity instead
    Method invocation failed because [Veeam.Backup.Core.CHierarchyObj] does not contain a method named ‘GetItem’.
    At E:\MyVeeamReport.ps1:444 char:99
    + … Where-Object {$_.GetItem().Type -eq “Vm”}
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

    Unable to find type [Veeam.Backup.Core.CRemoteWinFileCommander]. Make sure that the assembly that contains this type is loaded.
    At E:\MyVeeamReport.ps1:297 char:33
    + $FileCommander = [Veeam.Backup.Core.CRemoteWinFi …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (Veeam.Backup.Co…inFileCommander:TypeName) [], RuntimeException
    + FullyQualifiedErrorId : TypeNotFound

    You cannot call a method on a null-valued expression.
    At E:\MyVeeamReport.ps1:298 char:33
    + $storage = $FileCommander.GetDrives([ref]$null) …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Attempted to divide by zero.
    At E:\MyVeeamReport.ps1:280 char:25
    + $repoObj = New-Object -TypeName PSObject -Property @{
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException


  16. Roy

    Unexpected token ‘â?”join â?o|â??) + â?~)$â?T) -replace “\\\*”, “.*”
    foreach ($vm in $vmobjs) {
    $jobobjid = ($jobobjids | Where-Object {$_.ObjectId -eq $vm.Id})
    if (!$jobobjid) {
    $jobobjid = $vm.FindParent(“Datacenter”).Id + “\” + $vm.Id

    all i did was replace the server and paths as required….any ideas?

    1. smasterson Post author

      Hi Rob
      Did you happen to use the ‘raw view’ or download option on the Gist? For some reason they are both changing characters on line 461. Try copying from the Gist window itself.
      line 461 (ish depending on version) should read:
      $excludevms_regex = (‘(?i)^(‘ + (($script:excludevms | ForEach {[regex]::escape($_)}) –join “|”) + ‘)$’) -replace “\\\*”, “.*”

      1. Roy

        thanks, that was exactly the issue. I’m fairly new to powershell scripting, is there an easy way to only collect the last session for failed jobs instead of seeing the failure plus all 3 retries? We have over 400 vm’s being backed up by about 100 jobs, and 20 of them were failing…it was pretty impossible to read :/

        1. smasterson Post author

          Hey Roy
          The ‘only show last session per job’ has come up a few times now – I’ll see if I can incorporate it in the next version.


  17. TheAgreeableCow

    I came looking for some Veeam V8 Updates and found this original post (and your V8 updates).

    I think it’s great that you were able to get started from some my scripts and I’m able to streamline some updates based on your work 🙂

    Thanks for the credit and link – I’ll ensure to do the same.


  18. Michael

    Hi guys,

    always appears the errormessage

    Attempted to divide by zero.
    At C:\Batch\Veeam\VeeamReport\veeamreport.ps1:293 char:25
    + $repoObj = New-Object -TypeName PSObject -Property @{
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException


      1. Michael


        I have running the latest version of Reports (1.3) and the latest version of VBR (

        Thanks and Greetings

        1. smasterson Post author

          It appears as though the script is unable to determine the free space on your repo(s).
          What types of repositories are you running?
          Is there any info returned in the repo section? If so, are any of your repo’s missing?

      2. Michael


        OK. Ich habe den Fehler gefunden. Es gab noch alte repos welche nicht mehr existieren.

        Thanke für die Hilfe.


      3. Michael

        And now in English 🙂


        OK. I have found the error. There were old repos which no longer exist.

        Thanke for help.


  19. Michael


    New questions or suggestions :-):

    1. Is it possible to exclude whole VmWare Date Center?
    We have a vCenter with several small Datacenter (ESXi hosts max. 2) running in the branch office. This will not backup at the central site.

    2. Is it possible to exclude multiple folders?

    3. Is it possible to exclude certain backup jobs?
    We have several daily jobs with certain virtual machines and several weekly jobs with certain virtual machines.
    In the daily report the machines are always displayed under “VMs with no successful backups”, which are only backed up weekly.
    If I can exclude backup jobs, then I can work with two reports. A daily and a weekly with different settings.

    Thanke very much and greetings

    1. smasterson Post author

      Thanks a bunch for the feedback – always welcomed.

      I’m in process of polishing v1.4 and went ahead and added suggestions #1 and #2.

      Regarding #3 – it could get a bit hairy. I think you hit it on the head with having multiple reports. Configure one for daily and another for weekly with each having different VM/Folder/DC exclusions.

  20. Michael


    sehr sehr schön Skript
    Vielen vielen Danke für die Änderungen

    Beste Grüße aus Südtirol

    1. Michael


      very very nice script
      Many many thanks for the changes

      Best regards from South Tyrol

  21. Chanda Smith

    Hello Shawn!

    Thank you so much for the script. I am learning how to use power shell to create a report that says what the job is, what vmdk was backed up, if the job was successful or not, and what is the repository. This has been a struggle for me. I tried your script and i’m not sure if I plugged in my specific information correctly. I saw that another person got the same error as me and I’m not clear on the fix for:

    Warning: This cmdlet is Obsolete, use Find-VBRiEntity instead.

    1. smasterson Post author

      Hi Chanda – that warning can be disregarded, it is just telling us that the command may not exist in the future, but works fine for now.

      1. Chanda Smith

        Thank for replying. Unfortunately, I don’t see an output of the report. I thought it was incomplete.

        1. smasterson Post author

          Are there any errors generated or other odd output in the console?
          Be sure you are running the latest version – currently 1.2 for VBR v7 and 1.4 for VBR v8.
          Could also try setting to save to file as opposed to send email.

  22. Brant

    I’m interested in getting a simple (I think) tabular report of the Veeam Session Report, preferably in a CSV format so that I can then import to Excel for graphing trends. Towards that end, should I just be able to trim everything but the “Job/Session details over period of time (12,24,Week,Month)”?

    I’m very new to PS scripts, and have been mostly successful in modding ’em for my needs, but I’m under a time constraint with this endeavor, so whatever time you could spare to assist would be very much appreciated!

  23. Simon Jackson

    This is a great powershell report!!!

    A couple of minor fixes i’d like to see:
    1) Use your ‘exclude list’ within the Successful Sessions section too.
    2) Allow custom colour codes/html headers for SUCCESS/FAILED/Warning/Error/Other; giving the administrator more control – it’s a little difficult to find/change without affecting a large block of the report
    3) In Replica Details, please include the VM name
    4) In Successful Sessions, please include the Job Type (backup/replication) AND sort by type then Job Name (basically group Backup Jobs in order; then Recover jobs in order and finally Replication jobs in order).


  24. Vaibhav Shah

    Hi I am trying to run 9.0.3 first time. After making necessary changes when try to test the script it fails. It gives me error on shell..
    PS D:\Veeam_Report> D:\Veeam_Report\MyVeeamReport.ps1
    Get-VBRServerSession : Access to the registry key ‘HKEY_LOCAL_MACHINE\SOFTWARE\Veeam\Veeam Backup and Replication’ is denied.
    At D:\Veeam_Report\MyVeeamReport.ps1:544 char:20
    + $OpenConnection = (Get-VBRServerSession).Server
    + ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], UnauthorizedAccessException
    + FullyQualifiedErrorId : System.UnauthorizedAccessException

    Disconnect-VBRServer : Access to the registry key ‘HKEY_LOCAL_MACHINE\SOFTWARE\Veeam\Veeam Backup and Replication’ is denied.
    At D:\Veeam_Report\MyVeeamReport.ps1:546 char:3
    + Disconnect-VBRServer
    + ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], UnauthorizedAccessException
    + FullyQualifiedErrorId : System.UnauthorizedAccessException

    How can i fix this. Do i need to give any permission on the script?


Leave a Reply

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