Skip to content

Instantly share code, notes, and snippets.

@smasterson
Created September 18, 2017 20:33
Show Gist options
  • Save smasterson/4d28600ec135110bb1686690993dd728 to your computer and use it in GitHub Desktop.
Save smasterson/4d28600ec135110bb1686690993dd728 to your computer and use it in GitHub Desktop.
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
# Show VMs with successful backups within time frame ($reportMode)
$showSuccess = $true
# vCenter server(s) - As seen in VBR server
#$vcenters = "vcenter1","vcenter2"
$vcenters = "vcenter.yourdomain.local"
# To Exclude VMs from Missing and Successful Backups report add VM names to be excluded as follows
# $excludevms = @("vm1","vm2","*_replica")
$excludeVMs = @("")
# Exclude VMs from Missing and Successful 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 = $true
# Location of Veeam executable (Veeam.Backup.Shell.exe)
$veeamExePath = "C:\Program Files\Veeam\Backup and Replication\Backup\Veeam.Backup.Shell.exe"
# Location of common dll - Needed for repository function - Get-vPCRepoInfo (Veeam.Backup.Common.dll)
$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 = "[email protected]"
$emailTo = "[email protected]"
# 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
#endregion
#region VersionInfo
$vPCARversion = "1.2"
#
# Version 1.2 - SM
# Added option to show VMs successfully backed up
#
# 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
# http://www.vpowercli.co.uk/2012/01/23/vpowercli-v6-army-report/
# http://pastebin.com/6p3LrWt7
#
# 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 http://www.theagreeablecow.com/2012/09/sysadmin-modular-reporting-samreports.html
# Original Credit - Arne Fokkema http://ict-freak.nl/2011/12/29/powershell-veeam-br-get-total-days-before-the-license-expires/
#
# 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 - http://sightunseen.org/blog/?p=1
# http://www.sightunseen.org/files/vm_backup_status_dev.ps1
#
# 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
#endregion
#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")})
#endregion
#region Functions
function Get-vPCProxyInfo {
[CmdletBinding()]
param (
[Parameter(Position=0)]
[String[]]$Name,
[Parameter(Mandatory=$true, Position=1)]
[PSObject[]]$Sessions
)
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} |
Out-Null
function Build-vPCObj {param ([PsObject]$inputObj)
$ping = new-object system.net.networkinformation.ping
$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 {
$output
}
}
function Get-vPCRepoInfo {
[CmdletBinding()]
param (
[Parameter(Position=0, ValueFromPipeline=$true)]
[PSObject[]]$Repository
)
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 $server.info
$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 {
$outputAry
}
}
function Get-vPCReplicaTarget {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
[PSObject[]]$InputObj
)
BEGIN {
$outputAry = @()
$dsAry = @()
if (($Name -ne $null) -and ($InputObj -eq $null)) {
$InputObj = Get-VBRJob -Name $Name
}
}
PROCESS {
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 {
return
}
}
}
END {
$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
}
else{
$objoutput = New-Object -TypeName PSObject -Property @{
ExpDate = "Failed"
DaysRemain = "Failed"
}
$objoutput
}
}
function Get-VeeamServers {
$vservers=@{}
$outputAry = @()
$vservers.add($($script:vbrserver.realname),"VBRServer")
foreach ($srv in $script:viProxyList) {
If (!$vservers.ContainsKey($srv.Host.Realname)) {
$vservers.Add($srv.Host.Realname,"ProxyServer")
}
}
foreach ($srv in $script:repoList) {
If (!$vservers.ContainsKey($srv.gethost().Realname)) {
$vservers.Add($srv.gethost().Realname,"RepoServer")
}
}
$vservers = $vservers.GetEnumerator() | Sort-Object Name
foreach ($vserver in $vservers) {
$outputAry += $vserver.Name
}
return $outputAry
}
function Get-VeeamServices {
param (
[PSObject]$inputObj)
$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
}
$outputAry
}
function Get-VMsBackupStatus {
param (
[String]$vcenter)
$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($vcenterobj.id) | 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
$vms=@{}
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[$vm.Info.ObjectId][0]=$session.JobName
}
}
}
}
$vms.GetEnumerator() | Sort-Object Value
}
function Get-VMsMissingBackup {
param (
$vms)
$outputary = @()
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
}
function Get-VMsSuccessBackup {
param (
$vms)
$outputary = @()
foreach ($vm in $vms) {
if ($vm.Value[0] -ne "!") {
$objoutput = New-Object -TypeName PSObject -Property @{
Datacenter = $vm.Value[1]
Name = $vm.Value[2]
}
$outputAry += $objoutput
}
}
$outputAry | Select Datacenter, Name | Sort Name
}
#endregion
#region Report
# Get Veeam Version
$VeeamVersion = Get-VeeamVersion
# HTML Stuff
$headerObj = @"
<html>
<head>
<title>$rptTitle</title>
<style>
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;}
</style>
</head>
"@
$bodyTop = @"
<body>
<table cellspacing="0" cellpadding="0">
<tr>
<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>
</tr>
<tr>
<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="http://www.vpowercli.co.uk/2012/01/23/vpowercli-v6-army-report/" target="_blank"><font color="White">vPowerCLI v6 Army Report</font></a></td>
</tr>
</table>
"@
$subHead01 = @"
<table>
<tr>
<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;">
"@
$subHead01err = @"
<table>
<tr>
<td style="height: 35px;background-color: #eeeeee;color: #FF0000;font-size: 16px;font-weight: bold;vertical-align: middle;padding: 5px 0 0 15px;border-top: none;border-bottom: none;">
"@
$subHead02 = @"
</td>
</tr>
</table>
"@
$footerObj = @"
</body>
</html>
"@
If ($VeeamVersion -lt 7) {
Write-Host "Script requires VBR v7 or greater"
exit
}
# 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 VM Backup Status
If ($showMissing -or $showSuccess) {
$vmstatus = @()
foreach ($vcenter in $vcenters) {
$status = Get-VMsBackupStatus $vcenter
$vmstatus += $status
}
}
# Get VMs Missing Backups
$bodyMissing = $null
$missingVMs = @()
If ($showMissing) {
$missingVMs = Get-VMsMissingBackup $vmstatus
If ($missingVMs -ne $null) {
$missingVMs = $missingVMs | Sort Datacenter, Name | ConvertTo-HTML -Fragment
$bodyMissing = $subHead01err + "VMs with no successful backups" + $subHead02 + $missingVMs
}
}
# Get VMs Successfully Backed Up
$bodySuccess = $null
$successVMs = @()
If ($showSuccess) {
$successVMs = Get-VMsSuccessBackup $vmstatus
If ($successVMs -ne $null) {
$successVMs = $successVMs | Sort Datacenter, Name | ConvertTo-HTML -Fragment
$bodySuccess = $subHead01 + "VMs with successful backups" + $subHead02 + $successVMs
}
}
# 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 + $bodySuccess + $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<")
#endregion
#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
$msg.Attachments.Add($attachment)
} Else {
$body = $htmlOutput
$msg.Body = $body
$msg.isBodyhtml = $true
}
$smtp.send($msg)
If ($emailAttach) {
$attachment.dispose()
Remove-Item $tempfile
}
} else {
$htmlOutput | Out-File $file
Invoke-Expression $file
}
#endregion
@doug-mui
Copy link

Hi Shaun,
We are using your powershell script at work to generate the Veeam B&R report.

What an amazing work you have done with the powershell script.

One thing here is we are using Veeam B&R version 12 and some of the things are not kind of working for us. These are things like the linked image insertion for the report header and also the License/Support Renewal Date.
The License says "Failed" even we've renewed the license already.

Is there anyways you can help to update the Ps script to that of Veeam B&R version 12 to fix those issues?

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment