Src/Private/Get-AbrVbrBackupServerInfo.ps1


function Get-AbrVbrBackupServerInfo {
    <#
    .SYNOPSIS
    Used by As Built Report to retrieve Veeam VBR Backup Server Information
    .DESCRIPTION
        Documents the configuration of Veeam VBR in Word/HTML/Text formats using PScribo.
    .NOTES
        Version: 0.8.6
        Author: Jonathan Colon
        Twitter: @jcolonfzenpr
        Github: rebelinux
        Credits: Iain Brighton (@iainbrighton) - PScribo module
 
    .LINK
        https://github.com/AsBuiltReport/AsBuiltReport.Veeam.VBR
    #>

    [CmdletBinding()]
    param (

    )

    begin {
        Write-PScriboMessage "Discovering Veeam V&R Server information from $System."
    }

    process {
        try {
            $script:BackupServers = Get-VBRServer -Type Local
            if (($BackupServers).count -gt 0) {
                Section -Style Heading3 'Backup Server' {
                    $OutObj = @()
                    try {
                        foreach ($BackupServer in $BackupServers) {
                            $CimSession = New-CimSession $BackupServer.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication
                            $PssSession = New-PSSession $BackupServer.Name -Credential $Credential -Authentication $Options.PSDefaultAuthentication
                            $SecurityOptions = Get-VBRSecurityOptions
                            try { $DomainJoined = Get-CimInstance -Class Win32_ComputerSystem -Property PartOfDomain -CimSession $CimSession } catch { 'Unknown' }
                            Write-PScriboMessage "Collecting Backup Server information from $($BackupServer.Name)."
                            try {
                                $script:VeeamVersion = Invoke-Command -Session $PssSession -ErrorAction SilentlyContinue -ScriptBlock { Get-ChildItem -Recurse HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty | Where-Object { $_.DisplayName -match 'Veeam Backup & Replication Server' } | Select-Object -Property DisplayVersion }
                            } catch { Write-PScriboMessage -IsWarning "Backup Server Inkoke-Command Section: $($_.Exception.Message)" }
                            try {
                                $VeeamInfo = Invoke-Command -Session $PssSession -ErrorAction SilentlyContinue -ScriptBlock { Get-ItemProperty -Path 'HKLM:\SOFTWARE\Veeam\Veeam Backup and Replication' }
                            } catch { Write-PScriboMessage -IsWarning "Backup Server Invoke-Command Section: $($_.Exception.Message)" }
                            try {
                                $VeeamDBFlavor = Invoke-Command -Session $PssSession -ErrorAction SilentlyContinue -ScriptBlock { Get-ItemProperty -Path 'HKLM:\SOFTWARE\Veeam\Veeam Backup and Replication\DatabaseConfigurations' }
                            } catch { Write-PScriboMessage -IsWarning "Backup Server Invoke-Command Section: $($_.Exception.Message)" }
                            try {
                                $VeeamDBInfo = Invoke-Command -Session $PssSession -ErrorAction SilentlyContinue -ScriptBlock { Get-ItemProperty -Path "HKLM:\SOFTWARE\Veeam\Veeam Backup and Replication\DatabaseConfigurations\$(($Using:VeeamDBFlavor).SqlActiveConfiguration)" }
                            } catch { Write-PScriboMessage -IsWarning "Backup Server Invoke-Command Section: $($_.Exception.Message)" }
                            Write-PScriboMessage "Discovered $BackupServer Server."
                            $inObj = [ordered] @{
                                'Server Name' = $BackupServer.Name
                                'Is Domain Joined?' = ConvertTo-TextYN $DomainJoined.PartOfDomain
                                'Version' = Switch (($VeeamVersion).count) {
                                    0 { "--" }
                                    default { $VeeamVersion.DisplayVersion }
                                }
                                'Database Server' = Switch ([string]::IsNullOrEmpty($VeeamDBInfo.SqlServerName)) {
                                    $true { "--" }
                                    $false { $VeeamDBInfo.SqlServerName }
                                    default { 'Unknown' }
                                }
                                'Database Instance' = Switch ([string]::IsNullOrEmpty($VeeamDBInfo.SqlInstanceName)) {
                                    $true { "--" }
                                    $false { $VeeamDBInfo.SqlInstanceName }
                                    default { 'Unknown' }
                                }
                                'Database Name' = Switch ([string]::IsNullOrEmpty($VeeamDBInfo.SqlDatabaseName)) {
                                    $true { "--" }
                                    $false { $VeeamDBInfo.SqlDatabaseName }
                                    default { 'Unknown' }
                                }
                                'Connection Ports' = Switch (($VeeamInfo.BackupServerPort).count) {
                                    0 { "--" }
                                    default { "Backup Server Port: $($VeeamInfo.BackupServerPort)`r`nSecure Connections Port: $($VeeamInfo.SecureConnectionsPort)`r`nCloud Server Port: $($VeeamInfo.CloudServerPort)`r`nCloud Service Port: $($VeeamInfo.CloudSvcPort)" }
                                }
                                'Install Path' = Switch (($VeeamInfo.CorePath).count) {
                                    0 { "--" }
                                    default { $VeeamInfo.CorePath }
                                }
                                'Audit Logs Path' = $SecurityOptions.AuditLogsPath
                                'Compress Old Audit Logs' = ConvertTo-TextYN $SecurityOptions.CompressOldAuditLogs
                                'Fips Compliant Mode' = Switch ($SecurityOptions.FipsCompliantModeEnabled) {
                                    'True' { "Enabled" }
                                    'False' { "Disabled" }
                                }
                                'Linux host authentication' = Switch ($SecurityOptions.HostPolicy.Type) {
                                    'All' { "Add all discovered host to the list automatically" }
                                    'KnownHosts' { "Add unknown host to the list manually" }
                                }
                                'Logging Level' = $VeeamInfo.LoggingLevel

                            }

                            if ($Null -notlike $VeeamInfo.LogDirectory) {
                                $inObj.add('Log Directory', ($VeeamInfo.LogDirectory))
                            }

                            $OutObj += [pscustomobject]$inobj
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "Backup Server Section: $($_.Exception.Message)"
                    }

                    if ($HealthCheck.Infrastructure.BackupServer) {
                        $OutObj | Where-Object { $_.'Logging Level' -gt 4 } | Set-Style -Style Warning -Property 'Logging Level'
                        $OutObj | Where-Object { $_.'Is Domain Joined?' -eq 'Yes' } | Set-Style -Style Warning -Property 'Is Domain Joined?'
                    }

                    $TableParams = @{
                        Name = "Backup Server - $($BackupServer.Name.Split(".")[0])"
                        List = $true
                        ColumnWidths = 40, 60
                    }
                    if ($Report.ShowTableCaptions) {
                        $TableParams['Caption'] = "- $($TableParams.Name)"
                    }
                    $OutObj | Table @TableParams
                    if ($HealthCheck.Infrastructure.BestPractice) {
                        if ($OutObj | Where-Object { $_.'Is Domain Joined?' -eq 'Yes' }) {
                            Paragraph "Health Check:" -Bold -Underline
                            BlankLine
                            Paragraph {
                                Text 'Best Practice:' -Bold

                                Text 'For the most secure deployment, Veeam recommend three options:'
                            }
                            BlankLine
                            Paragraph '1. Add the Veeam components to a management domain that resides in a separate Active Directory Forest and protect the administrative accounts with two-factor authentication mechanics.'

                            Paragraph '2. Add the Veeam components to a separate workgroup and place the components on a separate network where applicable.'

                            Paragraph '3. Add the Veeam components to the production domain but make sure the accounts with administrative privileges are protected with two-factor authentication.'
                            BlankLine
                            Paragraph {
                                Text 'Reference:' -Bold
                                Text 'https://bp.veeam.com/vbr/Security/Security_domains.html'
                            }
                            BlankLine
                        }
                    }
                    #---------------------------------------------------------------------------------------------#
                    # Backup Server Inventory & Software Summary Section #
                    #---------------------------------------------------------------------------------------------#
                    try {
                        Write-PScriboMessage "Hardware Inventory Status set as $($Options.EnableHardwareInventory)."
                        if ($Options.EnableHardwareInventory) {
                            $BackupServer = Get-VBRServer -Type Local
                            Write-PScriboMessage "Collecting Backup Server Inventory Summary from $($BackupServer.Name)."
                            $HW = Invoke-Command -Session $PssSession -ScriptBlock { Get-ComputerInfo }
                            $License = Get-CimInstance -Query 'Select * from SoftwareLicensingProduct' -CimSession $CimSession | Where-Object { $_.LicenseStatus -eq 1 }
                            $HWCPU = Get-CimInstance -Class Win32_Processor -CimSession $CimSession
                            $HWBIOS = Get-CimInstance -Class Win32_Bios -CimSession $CimSession
                            if ($HW) {
                                Section -Style Heading4 'Hardware & Software Inventory' {
                                    $OutObj = @()
                                    $inObj = [ordered] @{
                                        'Name' = $HW.CsDNSHostName
                                        'Windows Product Name' = $HW.WindowsProductName
                                        'Windows Current Version' = $HW.WindowsCurrentVersion
                                        'Windows Build Number' = $HW.OsVersion
                                        'Windows Install Type' = $HW.WindowsInstallationType
                                        'Active Directory Domain' = $HW.CsDomain
                                        'Windows Installation Date' = $HW.OsInstallDate
                                        'Time Zone' = $HW.TimeZone
                                        'License Type' = $License.ProductKeyChannel
                                        'Partial Product Key' = $License.PartialProductKey
                                        'Manufacturer' = $HW.CsManufacturer
                                        'Model' = $HW.CsModel
                                        'Serial Number' = $HWBIOS.SerialNumber
                                        'Bios Type' = $HW.BiosFirmwareType
                                        'BIOS Version' = $HWBIOS.Version
                                        'Processor Manufacturer' = $HWCPU[0].Manufacturer
                                        'Processor Model' = $HWCPU[0].Name
                                        'Number of CPU Cores' = ($HWCPU.NumberOfCores | Measure-Object -Sum).Sum
                                        'Number of Logical Cores' = ($HWCPU.NumberOfLogicalProcessors | Measure-Object -Sum).Sum
                                        'Physical Memory (GB)' = ConvertTo-FileSizeString $HW.CsTotalPhysicalMemory
                                    }
                                    $OutObj += [pscustomobject]$inobj

                                    if ($HealthCheck.Infrastructure.Server) {
                                        $OutObj | Where-Object { $_.'Number of CPU Cores' -lt 2 } | Set-Style -Style Warning -Property 'Number of CPU Cores'
                                        if ([int]([regex]::Matches($OutObj.'Physical Memory (GB)', "\d+(?!.*\d+)").value) -lt 8) { $OutObj | Set-Style -Style Warning -Property 'Physical Memory (GB)' }
                                    }

                                    $TableParams = @{
                                        Name = "Backup Server Inventory - $($BackupServer.Name.Split(".")[0])"
                                        List = $true
                                        ColumnWidths = 40, 60
                                    }
                                    if ($Report.ShowTableCaptions) {
                                        $TableParams['Caption'] = "- $($TableParams.Name)"
                                    }
                                    $OutObj | Table @TableParams
                                    if ($HealthCheck.Infrastructure.BestPractice) {
                                        if (([int]([regex]::Matches($OutObj.'Physical Memory (GB)', "\d+(?!.*\d+)").value) -lt 8) -or ($OutObj | Where-Object { $_.'Number of CPU Cores' -lt 2 })) {
                                            Paragraph "Health Check:" -Bold -Underline
                                            BlankLine
                                            Paragraph {
                                                Text "Best Practice:" -Bold
                                                Text "Recommended Veeam Backup Server minimum configuration is two CPU cores and 8GB of RAM."
                                            }
                                            BlankLine
                                        }
                                    }
                                    #---------------------------------------------------------------------------------------------#
                                    # Backup Server Local Disk Inventory Section #
                                    #---------------------------------------------------------------------------------------------#
                                    if ($InfoLevel.Infrastructure.BackupServer -ge 3) {
                                        try {
                                            $HostDisks = Invoke-Command -Session $PssSession -ScriptBlock { Get-Disk | Where-Object { $_.BusType -ne "iSCSI" -and $_.BusType -ne "Fibre Channel" } }
                                            if ($HostDisks) {
                                                Section -Style NOTOCHeading5 -ExcludeFromTOC 'Local Disks' {
                                                    $LocalDiskReport = @()
                                                    ForEach ($Disk in $HostDisks) {
                                                        try {
                                                            $TempLocalDiskReport = [PSCustomObject]@{
                                                                'Disk Number' = $Disk.Number
                                                                'Model' = $Disk.Model
                                                                'Serial Number' = $Disk.SerialNumber
                                                                'Partition Style' = $Disk.PartitionStyle
                                                                'Disk Size' = "$([Math]::Round($Disk.Size / 1Gb)) GB"
                                                            }
                                                            $LocalDiskReport += $TempLocalDiskReport
                                                        } catch {
                                                            Write-PScriboMessage -IsWarning "Backup Server Local Disk $($Disk.Number) Section: $($_.Exception.Message)"
                                                        }
                                                    }
                                                    $TableParams = @{
                                                        Name = "Backup Server - Local Disks"
                                                        List = $false
                                                        ColumnWidths = 20, 20, 20, 20, 20
                                                    }
                                                    if ($Report.ShowTableCaptions) {
                                                        $TableParams['Caption'] = "- $($TableParams.Name)"
                                                    }
                                                    $LocalDiskReport | Sort-Object -Property 'Disk Number' | Table @TableParams
                                                }
                                            }
                                        } catch {
                                            Write-PScriboMessage -IsWarning "Backup Server Local Disk Section: $($_.Exception.Message)"
                                        }
                                        #---------------------------------------------------------------------------------------------#
                                        # Backup Server SAN Disk Inventory Section #
                                        #---------------------------------------------------------------------------------------------#
                                        try {
                                            $SanDisks = Invoke-Command -Session $PssSession -ScriptBlock { Get-Disk | Where-Object { $_.BusType -Eq "iSCSI" -or $_.BusType -Eq "Fibre Channel" } }
                                            if ($SanDisks) {
                                                Section -Style NOTOCHeading5 -ExcludeFromTOC 'SAN Disks' {
                                                    $SanDiskReport = @()
                                                    ForEach ($Disk in $SanDisks) {
                                                        try {
                                                            $TempSanDiskReport = [PSCustomObject]@{
                                                                'Disk Number' = $Disk.Number
                                                                'Model' = $Disk.Model
                                                                'Serial Number' = $Disk.SerialNumber
                                                                'Partition Style' = $Disk.PartitionStyle
                                                                'Disk Size' = "$([Math]::Round($Disk.Size / 1Gb)) GB"
                                                            }
                                                            $SanDiskReport += $TempSanDiskReport
                                                        } catch {
                                                            Write-PScriboMessage -IsWarning "Backup Server SAN Disk $($Disk.Number) Section: $($_.Exception.Message)"
                                                        }
                                                    }
                                                    $TableParams = @{
                                                        Name = "Backup Server - SAN Disks"
                                                        List = $false
                                                        ColumnWidths = 20, 20, 20, 20, 20
                                                    }
                                                    if ($Report.ShowTableCaptions) {
                                                        $TableParams['Caption'] = "- $($TableParams.Name)"
                                                    }
                                                    $SanDiskReport | Sort-Object -Property 'Disk Number' | Table @TableParams
                                                }
                                            }
                                        } catch {
                                            Write-PScriboMessage -IsWarning "Backup Server SAN Disk Section: $($_.Exception.Message)"
                                        }
                                    }
                                    #---------------------------------------------------------------------------------------------#
                                    # Backup Server Volume Inventory Section #
                                    #---------------------------------------------------------------------------------------------#
                                    try {
                                        $HostVolumes = Invoke-Command -Session $PssSession -ScriptBlock { Get-Volume | Where-Object { $_.DriveType -ne "CD-ROM" -and $NUll -ne $_.DriveLetter } }
                                        if ($HostVolumes) {
                                            Section -Style NOTOCHeading5 -ExcludeFromTOC 'Host Volumes' {
                                                $HostVolumeReport = @()
                                                ForEach ($HostVolume in $HostVolumes) {
                                                    try {
                                                        $TempHostVolumeReport = [PSCustomObject]@{
                                                            'Drive Letter' = $HostVolume.DriveLetter
                                                            'File System Label' = $HostVolume.FileSystemLabel
                                                            'File System' = $HostVolume.FileSystem
                                                            'Size' = "$([Math]::Round($HostVolume.Size / 1gb)) GB"
                                                            'Free Space' = "$([Math]::Round($HostVolume.SizeRemaining / 1gb)) GB"
                                                            'Health Status' = $HostVolume.HealthStatus
                                                        }
                                                        $HostVolumeReport += $TempHostVolumeReport
                                                    } catch {
                                                        Write-PScriboMessage -IsWarning "Backup Server Host Volume $($HostVolume.DriveLetter) Section: $($_.Exception.Message)"
                                                    }
                                                }
                                                $TableParams = @{
                                                    Name = "Backup Server - Volumes"
                                                    List = $false
                                                    ColumnWidths = 15, 15, 15, 20, 20, 15
                                                }
                                                if ($Report.ShowTableCaptions) {
                                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                                }
                                                $HostVolumeReport | Sort-Object -Property 'Drive Letter' | Table @TableParams
                                            }
                                        }
                                    } catch {
                                        Write-PScriboMessage -IsWarning "Backup Server Host Volume Section: $($_.Exception.Message)"
                                    }
                                    #---------------------------------------------------------------------------------------------#
                                    # Backup Server Network Inventory Section #
                                    #---------------------------------------------------------------------------------------------#
                                    if ($InfoLevel.Infrastructure.BackupServer -ge 2) {
                                        try {
                                            $HostAdapters = Invoke-Command -Session $PssSession { Get-NetAdapter }
                                            if ($HostAdapters) {
                                                Section -Style NOTOCHeading5 -ExcludeFromTOC 'Network Adapters' {
                                                    $HostAdaptersReport = @()
                                                    ForEach ($HostAdapter in $HostAdapters) {
                                                        try {
                                                            $TempHostAdaptersReport = [PSCustomObject]@{
                                                                'Adapter Name' = $HostAdapter.Name
                                                                'Adapter Description' = $HostAdapter.InterfaceDescription
                                                                'Mac Address' = $HostAdapter.MacAddress
                                                                'Link Speed' = $HostAdapter.LinkSpeed
                                                            }
                                                            $HostAdaptersReport += $TempHostAdaptersReport
                                                        } catch {
                                                            Write-PScriboMessage -IsWarning "Backup Server Host Volume $($HostAdapter.Name) Section: $($_.Exception.Message)"
                                                        }
                                                    }
                                                    $TableParams = @{
                                                        Name = "Backup Server - Network Adapters"
                                                        List = $false
                                                        ColumnWidths = 30, 35, 20, 15
                                                    }
                                                    if ($Report.ShowTableCaptions) {
                                                        $TableParams['Caption'] = "- $($TableParams.Name)"
                                                    }
                                                    $HostAdaptersReport | Sort-Object -Property 'Adapter Name' | Table @TableParams
                                                }
                                            }
                                        } catch {
                                            Write-PScriboMessage -IsWarning "Backup Server Host Volume Section: $($_.Exception.Message)"
                                        }
                                        try {
                                            $NetIPs = Invoke-Command -Session $PssSession { Get-NetIPConfiguration | Where-Object -FilterScript { ($_.NetAdapter.Status -Eq "Up") } }
                                            if ($NetIPs) {
                                                Section -Style NOTOCHeading5 -ExcludeFromTOC 'IP Address' {
                                                    $NetIpsReport = @()
                                                    ForEach ($NetIp in $NetIps) {
                                                        try {
                                                            $TempNetIpsReport = [PSCustomObject]@{
                                                                'Interface Name' = $NetIp.InterfaceAlias
                                                                'Interface Description' = $NetIp.InterfaceDescription
                                                                'IPv4 Addresses' = $NetIp.IPv4Address.IPAddress -Join ","
                                                                'Subnet Mask' = $NetIp.IPv4Address[0].PrefixLength
                                                                'IPv4 Gateway' = $NetIp.IPv4DefaultGateway.NextHop
                                                            }
                                                            $NetIpsReport += $TempNetIpsReport
                                                        } catch {
                                                            Write-PScriboMessage -IsWarning "Backup Server Host Volume $($NetIp.InterfaceAlias) Section: $($_.Exception.Message)"
                                                        }
                                                    }
                                                    $TableParams = @{
                                                        Name = "Backup Server - IP Address"
                                                        List = $false
                                                        ColumnWidths = 25, 25, 20, 10, 20
                                                    }
                                                    if ($Report.ShowTableCaptions) {
                                                        $TableParams['Caption'] = "- $($TableParams.Name)"
                                                    }
                                                    $NetIpsReport | Sort-Object -Property 'Interface Name' | Table @TableParams
                                                }
                                            }
                                        } catch {
                                            Write-PScriboMessage -IsWarning "Backup Server Host Volume Section: $($_.Exception.Message)"
                                        }
                                    }
                                }
                            }
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "Backup Server Inventory Summary Section: $($_.Exception.Message)"
                    }
                    try {
                        Write-PScriboMessage "Infrastructure Backup Server InfoLevel set at $($InfoLevel.Infrastructure.BackupServer)."
                        if ($InfoLevel.Infrastructure.BackupServer -ge 3) {
                            $VeeamInfo = Invoke-Command -Session $PssSession -ErrorAction SilentlyContinue -ScriptBlock { Get-ItemProperty -Path 'HKLM:\SOFTWARE\Veeam\Veeam Backup and Replication' }
                            $DefaultRegistryHash = @{
                                "AgentLogging" = "1"
                                "AgentLogOptions" = "flush"
                                "LoggingLevel" = "4"
                                "VNXBlockNaviSECCliPath" = "C:\Program Files\Veeam\Backup and Replication\Backup\EMC Navisphere CLI\NaviSECCli.exe"
                                "VNXeUemcliPath" = "C:\Program Files\Veeam\Backup and Replication\Backup\EMC Unisphere CLI\3.0.1\uemcli.exe"
                                "SqlLockInfo" = ""
                                "CloudServerPort" = "10003"
                                "SqlDatabaseName" = "VeeamBackup"
                                "SqlInstanceName" = "VEEAMSQL2016"
                                "SqlServerName" = ""
                                "SqlLogin" = ""
                                "CorePath" = "C:\Program Files\Veeam\Backup and Replication\Backup\"
                                "BackupServerPort" = "9392"
                                "SecureConnectionsPort" = "9401"
                                "VddkReadBufferSize" = "0"
                                "EndPointServerPort" = "10001"
                                "SqlSecuredPassword" = ""
                                "IsComponentsUpdateRequired" = "0"
                                "LicenseAutoUpdate" = "1"
                                "CloudSvcPort" = "6169"
                                "VBRServiceRestartNeeded" = "0"
                                "ImportServers" = "0"
                                "MaxLogCount" = "10"
                                "MaxLogSize" = "10240"
                                "RunspaceId" = "0000"
                                "ProviderCredentialsId" = ""
                                "ProviderInfo" = ""
                                "ProviderId" = ""
                            }
                            if ($VeeamInfo) {
                                $OutObj = @()
                                $Hashtable = $VeeamInfo | ForEach-Object {
                                    foreach ($prop in $_.psobject.Properties.Where({ $_.Name -notlike 'PS*' })) {
                                        [pscustomobject] @{
                                            Key = $prop.Name
                                            Value = $prop.Value
                                        }
                                    }
                                }
                                foreach ($Registry in $Hashtable) {
                                    if ($Registry.Key -notin $DefaultRegistryHash.Keys) {
                                        $inObj = [ordered] @{
                                            'Registry Key' = $Registry.Key
                                            'Registry Value' = Switch (($Registry.Value).count) {
                                                0 { '--' }
                                                1 { $Registry.Value }
                                                default { $Registry.Value -Join ', ' }

                                            }
                                        }
                                        $OutObj += [pscustomobject]$inobj
                                    }
                                }

                                $TableParams = @{
                                    Name = "Non-Default Registry Keys - $($BackupServer.Name.Split(".")[0])"
                                    List = $false
                                    ColumnWidths = 50, 50
                                }
                                if ($Report.ShowTableCaptions) {
                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                }
                            }
                            if ($OutObj) {
                                Section -Style Heading4 "Non-Default Registry Keys" {
                                    $OutObj | Sort-Object -Property 'Registry Key' | Table @TableParams
                                }
                            }
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "Backup Server Non-Default Registry Keys Section: $($_.Exception.Message)"
                    }
                    #---------------------------------------------------------------------------------------------#
                    # Backup Server Services Information Section #
                    #---------------------------------------------------------------------------------------------#
                    if ($HealthCheck.Infrastructure.Server) {
                        $BackupServer = Get-VBRServer -Type Local
                        try {
                            Write-PScriboMessage "Infrastructure Backup Server InfoLevel set at $($InfoLevel.Infrastructure.BackupServer)."
                            if ($InfoLevel.Infrastructure.BackupServer -ge 2) {
                                $Available = Invoke-Command -Session $PssSession -ScriptBlock { Get-Service "W32Time" | Select-Object DisplayName, Name, Status }
                                Write-PScriboMessage "Collecting Backup Server Service Summary from $($BackupServer.Name)."
                                $Services = Invoke-Command -Session $PssSession -ScriptBlock { Get-Service Veeam* }
                                if ($Available) {
                                    Section -Style Heading4 "HealthCheck - Services Status" {
                                        $OutObj = @()
                                        foreach ($Service in $Services) {
                                            Write-PScriboMessage "Collecting '$($Service.DisplayName)' status on $($BackupServer.Name)."
                                            $inObj = [ordered] @{
                                                'Display Name' = $Service.DisplayName
                                                'Short Name' = $Service.Name
                                                'Status' = $Service.Status
                                            }
                                            $OutObj += [pscustomobject]$inobj
                                        }

                                        if ($HealthCheck.Infrastructure.Server) {
                                            $OutObj | Where-Object { $_.'Status' -notlike 'Running' } | Set-Style -Style Warning -Property 'Status'
                                        }

                                        $TableParams = @{
                                            Name = "HealthCheck - Services Status - $($BackupServer.Name.Split(".")[0])"
                                            List = $false
                                            ColumnWidths = 45, 35, 20
                                        }
                                        if ($Report.ShowTableCaptions) {
                                            $TableParams['Caption'] = "- $($TableParams.Name)"
                                        }
                                        $OutObj | Table @TableParams
                                    }
                                }
                            }
                        } catch {
                            Write-PScriboMessage -IsWarning "Backup Server Service Status Section: $($_.Exception.Message)"
                        }
                    }
                    if ($HealthCheck.Infrastructure.BestPractice) {
                        try {
                            $UpdObj = @()
                            $Updates = Invoke-Command -Session $PssSession -ScriptBlock { (New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsHidden=0 and IsInstalled=0").Updates | Select-Object Title, KBArticleIDs }
                            $UpdObj += if ($Updates) {
                                $OutObj = @()
                                foreach ($Update in $Updates) {
                                    try {
                                        $inObj = [ordered] @{
                                            'KB Article' = "KB$($Update.KBArticleIDs)"
                                            'Name' = $Update.Title
                                        }
                                        $OutObj += [pscustomobject]$inobj

                                        if ($HealthCheck.OperatingSystem.Updates) {
                                            $OutObj | Set-Style -Style Warning
                                        }
                                    } catch {
                                        Write-PScriboMessage -IsWarning $_.Exception.Message
                                    }
                                }

                                $OutObj | Set-Style -Style Warning

                                $TableParams = @{
                                    Name = "Missing Windows Updates - $($BackupServer.Name.Split(".")[0])"
                                    List = $false
                                    ColumnWidths = 40, 60
                                }
                                if ($Report.ShowTableCaptions) {
                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                }
                                $OutObj | Sort-Object -Property 'Name' | Table @TableParams
                            }
                            if ($UpdObj) {
                                Section -Style Heading4 'Missing Windows Updates' {
                                    Paragraph "The following table provides a summary of the backup server pending/missing windows updates."
                                    BlankLine
                                    $UpdObj
                                }
                                Paragraph "Health Check:" -Bold -Underline
                                BlankLine
                                Paragraph {
                                    Text "Security Best Practices:" -Bold
                                    Text "Patch operating systems, software, and firmware on Veeam components. Most hacks succeed because there is already vulnerable software in use which is not up-to-date with current patch levels. So make sure all software and hardware where Veeam components are running are up-to-date. One of the most possible causes of a credential theft are missing guest OS updates and use of outdated authentication protocols."
                                }
                            }
                        } catch {
                            Write-PScriboMessage -IsWarning $_.Exception.Message
                        }
                    }
                }
            }
        } catch {
            Write-PScriboMessage -IsWarning "Backup Server Section: $($_.Exception.Message)"
        }
    }
    end {
        if ($PssSession) { Remove-PSSession -Session $PssSession }
        if ($CimSession) { Remove-CimSession $CimSession }
    }

}