VMware.CloudFoundation.Reporting.psm1

# Copyright 2022-2023 VMware, Inc.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# Note:
# This PowerShell module should be considered entirely experimental. It is still in development and not tested beyond lab
# scenarios. It is recommended you don't use it for any production environment without testing extensively!

# Allow communication with self-signed certificates when using Powershell Core. If you require all communications to be
# secure and do not wish to allow communication with self-signed certificates, remove lines 15-41 before importing the
# module.

if ($PSEdition -eq 'Core') {
    $PSDefaultParameterValues.Add("Invoke-RestMethod:SkipCertificateCheck", $true)
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
    Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
}

if ($PSEdition -eq 'Desktop') {
    # Allow communication with self-signed certificates when using Windows PowerShell
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
    Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null

    if ("TrustAllCertificatePolicy" -as [type]) {} else {
        Add-Type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertificatePolicy : ICertificatePolicy {
        public TrustAllCertificatePolicy() {}
        public bool CheckValidationResult(
            ServicePoint sPoint, X509Certificate certificate,
            WebRequest wRequest, int certificateProblem) {
            return true;
        }
    }
"@

        [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertificatePolicy
    }
}

#######################################################################################################################
##################################### J S O N O U T P U T V A R I A B L E S #####################################

Set-Variable -Name "backupJsonSuffix" -value "backup-status.json" -scope global
Set-Variable -Name "nsxtTransportJsonSuffix" -value "nsxttransportnode-status.json" -scope global
Set-Variable -Name "nsxttntunnelJsonSuffix" -value "nsxttntunnel-status.json" -scope global
Set-Variable -Name "nsxttier0bgpJsonSuffix" -value "nsxttier0bgp-status.json" -scope global
Set-Variable -Name "snapshotJsonSuffix" -value "snapshot-status.json" -scope global
Set-Variable -Name "localuserexpiryJsonSuffix" -value "localuserexpiry-status.json" -scope global
Set-Variable -Name "storageCapacityHealthJsonSuffix" -value "storagecapacityhealth-status.json" -scope global
Set-Variable -Name "componentConnectivityHealthNonSOSJsonSuffix" -value "componentconnectivityhealthnonsos-status.json" -scope global
Set-Variable -Name "nsxtCombinedHealthNonSOSJsonSuffix" -value "nsxtcombinedhealthnonsos-status.json" -scope global

############################## E N D O F J S O N O U T P U T V A R I A B L E S ##############################
#######################################################################################################################

#######################################################################################################################
################################## C O M P O N E N T S I Z E V A R I A B L E S ##################################

Set-Variable -Name "vcsaSize" -Value '[
    {
        "name": "tiny",
        "resources": {
            "cpu": 2,
            "memory": 12
        },
        "maximum": {
            "hosts": 10,
            "vms": 100
        }
    },
    {
        "name": "small",
        "resources": {
            "cpu": 4,
            "memory": 19
        },
        "maximum": {
            "hosts": 100,
            "vms": 1000
        }
    },
    {
        "name": "medium",
        "resources": {
            "cpu": 8,
            "memory": 28
        },
        "maximum": {
            "hosts": 400,
            "vms": 4000
        }
    },
    {
        "name": "large",
        "resources": {
            "cpu": 16,
            "memory": 37
        },
        "maximum": {
            "hosts": 1000,
            "vms": 10000
        }
    },
    {
        "name": "extra-large",
        "resources": {
            "cpu": 24,
            "memory": 56
        },
        "maximum": {
            "hosts": 3000,
            "vms": 30000
        }
    }
]'
 -Scope Global
Set-Variable -Name 'nsxLocalManagerSize' -Value '[
    {
        "name": "extra-small",
        "resources": {
            "cpu": 2,
            "memory": 8
        }
    },
    {
        "name": "small",
        "resources": {
            "cpu": 4,
            "memory": 16
        }
    },
    {
        "name": "medium",
        "resources": {
            "cpu": 6,
            "memory": 24
        }
    },
    {
        "name": "large",
        "resources": {
            "cpu": 12,
            "memory": 48
        }
    }
]'
 -Scope Global

Set-Variable -Name 'nsxEdgeSize' -Value '[
    {
        "name": "small",
        "resources": {
            "cpu": 2,
            "memory": 4
        }
    },
    {
        "name": "medium",
        "resources": {
            "cpu": 4,
            "memory": 8
        }
    },
    {
        "name": "large",
        "resources": {
            "cpu": 8,
            "memory": 32
        }
    },
    {
        "name": "extra-large",
        "resources": {
            "cpu": 16,
            "memory": 64
        }
    }
]'
 -Scope Global

########################### E N D O F C O M P O N E N T S I Z E V A R I A B L E S ###########################
#######################################################################################################################

#######################################################################################################################
############################# C O M B I N E D O P E R A T I O N S F U N C T I O N S ############################

Function Invoke-VcfHealthReport {
    <#
        .SYNOPSIS
        Perform health checks

        .DESCRIPTION
        The Invoke-VcfHealthReport provides a single cmdlet to perform health checks across a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfHealthReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -sddcManagerLocalUser vcf -sddcManagerLocalPass VMw@re1! -reportPath F:\Reporting -allDomains
        This example runs a health check across a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfHealthReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -sddcManagerLocalUser vcf -sddcManagerLocalPass VMw@re1! -reportPath F:\Reporting -workloadDomain sfo-w01
        This example runs a health check for a specific workload domain within a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfHealthReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -sddcManagerLocalUser vcf -sddcManagerLocalPass VMw@re1! -reportPath F:\Reporting -allDomains -failureOnly
        This example runs a health check across a VMware Cloud Foundation instance but only ouputs issues to the HTML report.

        .PARAMETER sddcManagerFqdn
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER sddcManagerUser
        The username to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerPass
        The password to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerLocalUser
        The username to authenticate to the SDDC Manager appliance.

        .PARAMETER sddcManagerLocalPass
        The password to authenticate to the SDDC Manager appliance.

        .PARAMETER reportPath
        The path to save the policy report.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER darkMode
        Switch to enable dark mode for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerLocalUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerLocalPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$reportPath,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$darkMode
    )

    Try {
        Clear-Host; Write-Host ""

        if (Test-VCFConnection -server $sddcManagerFqdn) {
            if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {
                $defaultReport = Start-CreateReportDirectory -path $reportPath -sddcManagerFqdn $sddcManagerFqdn -reportType health # Setup Report Location and Report File
                if (!(Test-Path -Path $reportPath)) {Write-Warning "Unable to locate report path $reportPath, enter a valid path and try again"; Write-Host ""; Break }
                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    $reportname = $defaultReport.Split('.')[0] + "-" + $sddcManagerFqdn.Split(".")[0] + ".htm"
                    $reportData = "<h1>SDDC Manager: $sddcManagerFqdn</h1>"
                    $workflowMessage = "VMware Cloud Foundation instance ($sddcManagerFqdn)"
                    $commandSwitch = "-allDomains"
                } else {
                    $reportname = $defaultReport.Split('.')[0] + "-" + $workloadDomain + ".htm"
                    $reportData = "<h1>Workload Domain: $workloadDomain</h1>"
                    $workflowMessage = "Workload Domain ($workloadDomain)"
                    $commandSwitch = "-workloadDomain $workloadDomain"
                }
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    $failureOnlySwitch = " -failureOnly"
                }

                $vcfVersion = ((Get-VCFManager).version).Split('-')[0]

                Start-SetupLogFile -Path $reportPath -ScriptName $MyInvocation.MyCommand.Name # Setup Log Location and Log File
                Write-LogMessage -Type INFO -Message "Starting the process of creating a Health Report for $workflowMessage." -Colour Yellow
                Write-LogMessage -Type INFO -Message "Setting up the log file to path $logfile."
                Write-LogMessage -Type INFO -Message "Setting up report folder and report $reportName."
                Write-LogMessage -Type INFO -Message "Running an SoS Health Check for $workflowMessage, process takes time."
                $jsonFilePath = Invoke-Expression "Request-SoSHealthJson -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass -reportPath $reportFolder $($commandSwitch)"

                # Generating the Service Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the Service Health Report using the SoS output for $workflowMessage."
                $serviceHtml = Invoke-Expression "Publish-ServiceHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $serviceHtml

                # Generating the Version Health Data Using the SoS Data
                if ($vcfVersion -ge "4.5.0") {
                    Write-LogMessage -Type INFO -Message "Generating the Version Health Report using the SoS output for $workflowMessage."
                    $versionHtml = Invoke-Expression "Publish-VersionHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $versionHtml
                }

                # Generating the Hardware Compatibility Health Data Using the SoS Data
                Write-LogMessage -type INFO -Message "Generating the Hardware Compatibility Health Report using the SoS output for $workflowMessage."
                $hardwareCompatibilityHtml = Invoke-Expression "Publish-HardwareCompatibilityHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $hardwareCompatibilityHtml

                # Generating the Connectivity Health Data Using SoS Data and Supplemental PowerShell Request Functions
                Write-LogMessage -Type INFO -Message "Generating the Connectivity Health Report using the SoS output for $workflowMessage."
                $componentConnectivityHtml = Invoke-Expression "Publish-ComponentConnectivityHealth -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass -json $jsonFilePath $($commandSwitch) $($failureOnlySwitch)"; $reportData += $componentConnectivityHtml

                # Generating the Password Expiry Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the Password Expiry Report for $workflowMessage."
                $passwordHtml = Invoke-Expression "Publish-PasswordHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $passwordHtml

                # Generating the Certificate Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the Certificate Health Report using the SoS output for $workflowMessage."
                $certificateHtml = Invoke-Expression "Publish-CertificateHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $certificateHtml

                # Generating the Backup Status Health Data Using PowerShell Request Functions
                Write-LogMessage -Type INFO -Message "Generating the Backup Status Report for $workflowMessage."
                $backupStatusHtml = Invoke-Expression "Publish-BackupStatus -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $backupStatusHtml

                # Generating the Snapshot Status Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the Snapshots Report for $workflowMessage."
                $snapshotStatusHtml = Invoke-Expression "Publish-SnapshotStatus -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $snapshotStatusHtml

                # Generating the DNS Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the DNS Health Report using the SoS output for $workflowMessage."
                $dnsHtml = Invoke-Expression "Publish-DnsHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $dnsHtml

                # Generating the NTP Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the NTP Health Report using the SoS output for $workflowMessage."
                $ntpHtml = Invoke-Expression "Publish-NtpHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $ntpHtml

                # Generating the vCenter Server Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the vCenter Server Health Report using the SoS output for $workflowMessage."
                $vcenterHtml = Invoke-Expression "Publish-VcenterHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $vcenterHtml

                # Generating the ESXi Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the ESXi Health Report using the SoS output for $workflowMessage."
                $esxiHtml = Invoke-Expression "Publish-EsxiHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $esxiHtml

                # Generating the ESXi Connection Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the ESXi Connection Health Data report for $workflowMessage."
                $esxiConnectionHtml = Invoke-Expression "Publish-EsxiConnectionHealth -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $esxiConnectionHtml

                # Generating the Free Pool Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the SDDC Manager Free Pool Health for $workflowMessage."
                $freePoolHtml = Invoke-Expression "Publish-SddcManagerFreePool -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($failureOnlySwitch)"; $reportData += $freePoolHtml

                # Generating the vSAN Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the vSAN Health Report using the SoS output for $workflowMessage."
                $vsanHtml = Invoke-Expression "Publish-VsanHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $vsanHtml

                # Generating the vSAN Storage Policy Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the vSAN Storage Policy Health Report using the SoS output for $workflowMessage."
                $vsanPolicyHtml = Invoke-Expression "Publish-VsanStoragePolicy -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $vsanPolicyHtml

                # Generating the NSX Manager Health Data Using SoS output and Supplemental PowerShell Request Functions
                Write-LogMessage -Type INFO -Message "Generating the NSX Health Report using the SoS output for $workflowMessage."
                $nsxtHtml = Invoke-Expression "Publish-NsxtCombinedHealth -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass -json $jsonFilePath $($commandSwitch) $($failureOnlySwitch)"; $reportData += $nsxtHtml

                # Generating the NSX Edge Cluster Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the NSX Edge Cluster Health Report using the SoS output for $workflowMessage."
                $nsxtEdgeClusterHtml = Invoke-Expression "Publish-NsxtEdgeClusterHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $nsxtEdgeClusterHtml

                # Generating the NSX Edge Node Health Data Using the SoS Data
                Write-LogMessage -Type INFO -Message "Generating the NSX Edge Node Health Report using the SoS output for $workflowMessage."
                $nsxtEdgeNodeHtml = Invoke-Expression "Publish-NsxtEdgeNodeHealth -json $jsonFilePath -html $($failureOnlySwitch)"; $reportData += $nsxtEdgeNodeHtml

                # Generating the NSX Transport Node Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the NSX Transport Node Report for $workflowMessage."
                $nsxTransportNodeHtml = Invoke-Expression "Publish-NsxtTransportNodeStatus -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $nsxTransportNodeHtml

                # Generating the NSX Transport Node Tunnel Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the NSX Transport Node Tunnel Report for $workflowMessage."
                $nsxTransportNodeTunnelHtml = Invoke-Expression "Publish-NsxtTransportNodeTunnelStatus -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $nsxTransportNodeTunnelHtml

                # Generating the NSX Tier-0 Gateway BGP Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the NSX Tier-0 Gateway BGP Report for $workflowMessage."
                $nsxTier0BgpHtml = Invoke-Expression "Publish-NsxtTier0BgpStatus -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $nsxTier0BgpHtml

                # Generating the Disk Capacity Health Data Using PowerShell Request Functions
                Write-LogMessage -Type INFO -Message "Generating the Disk Capacity Report for $workflowMessage.'"
                $storageCapacityHealthHtml = Invoke-Expression "Publish-StorageCapacityHealth -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass -localUser $sddcManagerLocalUser -localPass $sddcManagerLocalPass $($commandSwitch) $($failureOnlySwitch)"; $reportData += $storageCapacityHealthHtml

                # Generating the Virtual Machines with Connected CD-ROM Health Data Using PowerShell Request Functions
                Write-LogMessage -type INFO -Message "Generating the Virtual Machines with Connected CD-ROM Report for $workflowMessage."
                $vmConnectedCdromHtml = Invoke-Expression "Publish-VmConnectedCdrom -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $vmConnectedCdromHtml

                if ($PsBoundParameters.ContainsKey("darkMode")) { $reportHeader = Get-ClarityReportHeader -dark } else { $reportHeader = Get-ClarityReportHeader }
                $reportNavigation = Get-ClarityReportNavigation -reportType health
                $reportFooter = Get-ClarityReportFooter
                $report = $reportHeader
                $report += $reportNavigation
                $report += $reportData
                $report += $reportFooter

                # Generate the report to an HTML file and then open it in the default browser
                Write-LogMessage -Type INFO -Message "Generating the final report and saving to ($reportName)."
                $report | Out-File $reportName
                if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -ne "Linux") {
                    Invoke-Item $reportName
                } elseif ($PSEdition -eq "Desktop") {
                    Invoke-Item $reportName
                }
            }
        }
    } Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Invoke-VcfHealthReport

Function Invoke-VcfAlertReport {
    <#
        .SYNOPSIS
        Generates the alert report for a VMware Cloud Foundation instance.

        .DESCRIPTION
        The Invoke-VcfAlertReport provides a single cmdlet to generates the alert report for a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfAlertReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -allDomains
        This example generates the alert report across a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfAlertReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -allDomains -failureOnly
        This example generates the alert report across a VMware Cloud Foundation instance but for only failed items.

        .EXAMPLE
        Invoke-VcfAlertReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -workloadDomain sfo-w01
        This example generates the alert report for a specific workload domain in a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfAlertReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -workloadDomain sfo-w01 -failureOnly
        This example generates the alert report for a specific workload domain in a VMware Cloud Foundation instance but for only failed items.

        .PARAMETER sddcManagerFqdn
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER sddcManagerUser
        The username to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerPass
        The password to authenticate to the SDDC Manager.

        .PARAMETER reportPath
        The path to save the policy report.

        .PARAMETER allDomains
        Switch to run against all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER darkMode
        Switch to enable dark mode for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$reportPath,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$darkMode
    )

    Try {
        Clear-Host; Write-Host ""

        if (Test-VCFConnection -server $sddcManagerFqdn) {
            if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {
                $defaultReport = Start-CreateReportDirectory -path $reportPath -sddcManagerFqdn $sddcManagerFqdn -reportType alert # Setup Report Location and Report File
                if (!(Test-Path -Path $reportPath)) {Write-Warning "Unable to locate report path $reportPath, enter a valid path and try again"; Write-Host ""; Break }
                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    $reportname = $defaultReport.Split('.')[0] + "-" + $sddcManagerFqdn.Split(".")[0] + ".htm"
                    $reportData = "<h1>SDDC Manager: $sddcManagerFqdn</h1>"
                    $workflowMessage = "VMware Cloud Foundation instance ($sddcManagerFqdn)"
                    $commandSwitch = "-allDomains"
                } else {
                    $reportname = $defaultReport.Split('.')[0] + "-" + $workloadDomain + ".htm"
                    $reportData = "<h1>Workload Domain: $workloadDomain</h1>"
                    $workflowMessage = "Workload Domain ($workloadDomain)"
                    $commandSwitch = "-workloadDomain $workloadDomain"
                }
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    $commandSwitch = $commandSwitch + " -failureOnly"
                }

                Start-SetupLogFile -Path $reportPath -ScriptName $MyInvocation.MyCommand.Name # Setup Log Location and Log File
                Write-LogMessage -Type INFO -Message "Starting the process of creating an Alert Report for $workflowMessage." -Colour Yellow
                Write-LogMessage -Type INFO -Message "Setting up the log file to path $logfile."
                Write-LogMessage -Type INFO -Message "Setting up report folder and report $reportName."

                # Generate vCenter Server Alerts Using PowerShell Function
                Write-LogMessage -Type INFO -Message "Generating the vCenter Server alerts for $workflowMessage."
                $vCenterAlertHtml = Invoke-Expression "Publish-VcenterAlert -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $vCenterAlertHtml

                # Generate ESXi Alerts Using PowerShell Function
                Write-LogMessage -type INFO -Message "Generating the ESXi host alerts for $workflowMessage."
                $esxiAlertHtml = Invoke-Expression "Publish-EsxiAlert -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $esxiAlertHtml

                # Generate vSAN Alerts Using PowerShell Function
                Write-LogMessage -type INFO -Message "Generating the vSAN alerts for $workflowMessage."
                $vsanAlertHtml = Invoke-Expression "Publish-VsanAlert -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $vsanAlertHtml

                # Generate NSX Alerts Using PowerShell Function
                Write-LogMessage -type INFO -Message "Generating the NSX alerts for $workflowMessage."
                $nsxtAlertHtml = Invoke-Expression "Publish-NsxtAlert -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $nsxtAlertHtml

                if ($PsBoundParameters.ContainsKey("darkMode")) {
                    $reportHeader = Get-ClarityReportHeader -dark
                } else {
                    $reportHeader = Get-ClarityReportHeader
                }
                $reportNavigation = Get-ClarityReportNavigation -reportType alert
                $reportFooter = Get-ClarityReportFooter
                $report = $reportHeader
                $report += $reportNavigation
                $report += $reportData
                $report += $reportFooter

                # Generate the report to an HTML file and then open it in the default browser
                Write-LogMessage -Type INFO -Message "Generating the final report and saving to ($reportName)."
                $report | Out-File $reportName
                if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -ne "Linux") {
                    Invoke-Item $reportName
                } elseif ($PSEdition -eq "Desktop") {
                    Invoke-Item $reportName
                }
            }
        }
    } Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Invoke-VcfAlertReport

Function Invoke-VcfConfigReport {
    <#
        .SYNOPSIS
        Generates the configuration report

        .DESCRIPTION
        The Invoke-VcfConfigReport provides a single cmdlet to generates a configuration report for a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfConfigReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -allDomains
        This example generates the configuration report across a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfConfigReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -workloadDomain sfo-w01
        This example generates the configuration report for a specific workload domain within a VMware Cloud Foundation instance.

        .PARAMETER sddcManagerFqdn
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER sddcManagerUser
        The username to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerPass
        The password to authenticate to the SDDC Manager.

        .PARAMETER reportPath
        The path to save the policy report.

        .PARAMETER allDomains
        Switch to run against all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER darkMode
        Switch to enable dark mode for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$reportPath,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$darkMode
    )

    Try {
        Clear-Host; Write-Host ""

        if (Test-VCFConnection -server $sddcManagerFqdn) {
            if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {
                $defaultReport = Start-CreateReportDirectory -path $reportPath -sddcManagerFqdn $sddcManagerFqdn -reportType config # Setup Report Location and Report File
                if (!(Test-Path -Path $reportPath)) {Write-Warning "Unable to locate report path $reportPath, enter a valid path and try again"; Write-Host ""; Break }
                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    $reportname = $defaultReport.Split('.')[0] + "-" + $sddcManagerFqdn.Split(".")[0] + ".htm"
                    $reportData = "<h1>SDDC Manager: $sddcManagerFqdn</h1>"
                    $workflowMessage = "VMware Cloud Foundation instance ($sddcManagerFqdn)"
                    $commandSwitch = "-allDomains"
                } else {
                    $reportname = $defaultReport.Split('.')[0] + "-" + $workloadDomain + ".htm"
                    $reportData = "<h1>Workload Domain: $workloadDomain</h1>"
                    $workflowMessage = "Workload Domain ($workloadDomain)"
                    $commandSwitch = "-workloadDomain $workloadDomain"
                }
                Start-SetupLogFile -Path $reportPath -ScriptName $MyInvocation.MyCommand.Name # Setup Log Location and Log File
                Write-LogMessage -Type INFO -Message "Starting the Process of Creating a Configuration Report for $workflowMessage." -Colour Yellow
                Write-LogMessage -Type INFO -Message "Setting up the log file to path $logfile."
                Write-LogMessage -Type INFO -Message "Setting up report folder and report $reportName."

                # Collecting Cluster Configuration Using PowerShell Functions
                Write-LogMessage -Type INFO -Message "Generating the Cluster Configuration for $workflowMessage."
                $clusterConfigHtml = Invoke-Expression "Publish-ClusterConfiguration -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $clusterConfigHtml

                # Collecting Cluster DRS Rules Using PowerShell Functions
                Write-LogMessage -Type INFO -Message "Generating the DRS Rule Configuration for $workflowMessage."
                $clusterDrsRuleHtml = Invoke-Expression "Publish-ClusterDrsRule -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $clusterDrsRuleHtml

                # Collecting Resource Pool Details Using PowerShell Functions
                Write-LogMessage -Type INFO -Message "Generating the Resouce Pool Configuration for $workflowMessage."
                $resourcePoolsHtml = Invoke-Expression "Publish-ResourcePool -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $resourcePoolsHtml

                # Collecting VM Overrides Using PowerShell Functions
                Write-LogMessage -Type INFO -Message "Generating the VM Override Configuration for $workflowMessage."
                $vmOverridesHtml = Invoke-Expression "Publish-VmOverride -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $vmOverridesHtml

                # Collecting Virtual Networking Using PowerShell Functions
                Write-LogMessage -Type INFO -Message "Generating the Virtual Networking Configuration for $workflowMessage."
                $virtualNetworkHtml = Invoke-Expression "Publish-VirtualNetwork -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $virtualNetworkHtml

                # Collecting ESXi Security Configuration Using PowerShell Functions
                Write-LogMessage -Type INFO -Message "Generating the ESXi Security Configuration for $workflowMessage."
                $esxiSecurityHtml = Invoke-Expression "Publish-EsxiSecurityConfiguration -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass $($commandSwitch)"; $reportData += $esxiSecurityHtml

                if ($PsBoundParameters.ContainsKey("darkMode")) {
                    $reportHeader = Get-ClarityReportHeader -dark
                } else {
                    $reportHeader = Get-ClarityReportHeader
                }
                $reportNavigation = Get-ClarityReportNavigation -reportType config
                $reportFooter = Get-ClarityReportFooter
                $report = $reportHeader
                $report += $reportNavigation
                $report += $reportData
                $report += $reportFooter

                # Generate the report to an HTML file and then open it in the default browser
                Write-LogMessage -Type INFO -Message "Generating the Final Report and Saving to ($reportName)."
                $report | Out-File $reportName
                if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -ne "Linux") {
                    Invoke-Item $reportName
                } elseif ($PSEdition -eq "Desktop") {
                    Invoke-Item $reportName
                }
            }
        }
    } Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Invoke-VcfConfigReport

Function Invoke-VcfUpgradePrecheck {
    <#
        .SYNOPSIS
        Perform upgrade precheck

        .DESCRIPTION
        The Invoke-VcfUpgradePrecheck runs an upgrade precheck for a workload domain

        .EXAMPLE
        Invoke-VcfUpgradePrecheck -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -workloadDomain sfo-w01
        This example runs a health check for a specific workload domain within an SDDC Manager instance.

        .PARAMETER sddcManagerFqdn
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER sddcManagerUser
        The username to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerPass
        The password to authenticate to the SDDC Manager.

        .PARAMETER reportPath
        The path to save the policy report.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER darkMode
        Switch to enable dark mode for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$reportPath,
        [Parameter (ParameterSetName = 'Specific--WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$darkMode
    )

    Try {

        Clear-Host; Write-Host ""

        if (Test-VCFConnection -server $sddcManagerFqdn) {
            if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {
                $defaultReport = Start-CreateReportDirectory -path $reportPath -sddcManagerFqdn $sddcManagerFqdn -reportType upgrade # Setup Report Location and Report File
                if (!(Test-Path -Path $reportPath)) {Write-Warning "Unable to locate report path $reportPath, enter a valid path and try again"; Write-Host ""; Break }
                $reportname = $defaultReport.Split('.')[0] + "-" + $workloadDomain + ".htm"
                $workflowMessage = "Workload Domain ($workloadDomain)"
                Start-SetupLogFile -Path $reportPath -ScriptName $MyInvocation.MyCommand.Name # Setup Log Location and Log File
                Write-LogMessage -Type INFO -Message "Starting the Process of Running an Upgrade Precheck for $workflowMessage." -Colour Yellow
                Write-LogMessage -Type INFO -Message "Setting up the log file to path $logfile."
                Write-LogMessage -Type INFO -Message "Setting up report folder and report $reportName."

                if (Test-VCFConnection -server $sddcManagerFqdn) {
                    if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {
                        $jsonSpec = '{ "resources" : [ { "resourceId" : "'+ (Get-VCFWorkloadDomain | Where-Object {$_.name -eq $workloadDomain}).id+'", "type" : "DOMAIN" } ] }'
                        $task = Start-VCFSystemPrecheck -json $jsonSpec
                        Write-LogMessage -Type INFO -Message "Waiting for Upgrade Precheck Task ($($task.name)) with Id ($($task.id)) to Complete."
                        Do { $status = Get-VCFSystemPrecheckTask -id $task.id } While ($status.status -eq "IN_PROGRESS")
                        Write-LogMessage -Type INFO -Message "Task ($($task.name)) with Task Id ($($task.id)) completed with status ($($status.status))."
                        $allChecksObject = New-Object System.Collections.ArrayList
                        foreach ($subTask in $status.subTasks) {
                            $elementObject = New-Object -TypeName psobject
                            $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $subTask.resources.type
                            if ($subTask.resources.type -eq "ESX") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFHost -id $subTask.resources.resourceId).fqdn
                            }
                            elseif ($subTask.resources.type -eq "VCENTER") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFvCenter -id $subTask.resources.resourceId).fqdn
                            }
                            elseif ($subTask.resources.type -eq "CLUSTER") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFCluster -id $subTask.resources.resourceId).name
                            }
                            elseif ($subTask.resources.type -eq "VSAN") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFCluster -id $subTask.resources.resourceId).primaryDatastoreName
                            }
                            elseif ($subTask.resources.type -eq "DEPLOYMENT_CONFIGURATION") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFManager -id $subTask.resources.resourceId).fqdn
                            }
                            elseif ($subTask.resources.type -eq "VRSLCM") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFvRSLCM -id $subTask.resources.resourceId).fqdn
                            }
                            elseif ($subTask.resources.type -eq "VROPS") {
                                $id = $subTask.resources.resourceId + ":vrops"
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFvROPS | Where-Object {$_.id -eq $id}).loadBalancerFqdn
                            }
                            elseif ($subTask.resources.type -eq "VRLI") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFvRLI -id $subTask.resources.resourceId).loadBalancerFqdn
                            }
                            elseif ($subTask.resources.type -eq "VRA") {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue (Get-VCFvRA -id $subTask.resources.resourceId).loadBalancerFqdn
                            }
                            else {
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $subTask.resources.resourceId
                            }
                            $elementObject | Add-Member -NotePropertyName 'Precheck Task' -NotePropertyValue $subTask.name
                            if ($subTask.status -eq "SUCCESSFUL") {
                                $alert = "GREEN"
                            } elseif ($subTask.status -eq "WARNING") {
                                $alert = "YELLOW"
                            } elseif ($subTask.status -eq "FAILED") {
                                $alert = "RED"
                            }
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                            $allChecksObject += $elementObject
                        }
                        $allChecksObject = $allChecksObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="upgrade-precheck"></a><h3>Upgrade Precheck</h3>' -As Table
                        $allChecksObject = Convert-CssClass -htmldata $allChecksObject
                    }
                }

                # Combine all information gathered into a single HTML report
                $reportData = "<h1>Workload Domain: $workloadDomain</h1>"
                $reportData += $allChecksObject

                if ($PsBoundParameters.ContainsKey("darkMode")) {
                    $reportHeader = Get-ClarityReportHeader -dark
                } else {
                    $reportHeader = Get-ClarityReportHeader
                }
                $reportNavigation = Get-ClarityReportNavigation -reportType upgrade
                $reportFooter = Get-ClarityReportFooter
                $report = $reportHeader
                $report += $reportNavigation
                $report += $reportData
                $report += $reportFooter

                # Generate the report to an HTML file and then open it in the default browser
                Write-LogMessage -Type INFO -Message "Generating the Final Report and Saving to ($reportName)."
                $report | Out-File $reportName
                if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -ne "Linux") {
                    Invoke-Item $reportName
                } elseif ($PSEdition -eq "Desktop") {
                    Invoke-Item $reportName
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Invoke-VcfUpgradePrecheck

Function Invoke-VcfOverviewReport {
    <#
        .SYNOPSIS
        Generates the system overview report

        .DESCRIPTION
        The Invoke-VcfOverviewReport provides a single cmdlet to generates a system overview report for a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfOverviewReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting
        This example generates the system overview report for a VMware Cloud Foundation instance.

        .EXAMPLE
        Invoke-VcfOverviewReport -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1! -reportPath F:\Reporting -anonymized
        This example generates the system overview report for a VMware Cloud Foundation instance, but will anonymize the output.

        .PARAMETER sddcManagerFqdn
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER sddcManagerUser
        The username to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerPass
        The password to authenticate to the SDDC Manager.

        .PARAMETER reportPath
        The path to save the policy report.

        .PARAMETER darkMode
        Switch to enable dark mode for the report.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$reportPath,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$darkMode,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        Clear-Host; Write-Host ""

        if (Test-VCFConnection -server $sddcManagerFqdn) {
            if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {
                $defaultReport = Start-CreateReportDirectory -path $reportPath -sddcManagerFqdn $sddcManagerFqdn -reportType overview # Setup Report Location and Report File
                if (!(Test-Path -Path $reportPath)) {Write-Warning "Unable to locate report path $reportPath, enter a valid path and try again"; Write-Host ""; Break }
                $reportname = $defaultReport.Split('.')[0] + "-" + $sddcManagerFqdn.Split(".")[0] + ".htm"
                $workflowMessage = "VMware Cloud Foundation instance ($sddcManagerFqdn)"
                Start-SetupLogFile -Path $reportPath -ScriptName $MyInvocation.MyCommand.Name # Setup Log Location and Log File
                Write-LogMessage -Type INFO -Message "Starting the Process of Creating a System Overview Report for $workflowMessage." -Colour Yellow
                Write-LogMessage -Type INFO -Message "Setting up the log file to path $logfile."
                Write-LogMessage -Type INFO -Message "Setting up report folder and report $reportName."

                if ($PsBoundParameters.ContainsKey("anonymized")) {
                    Write-LogMessage -Type INFO -Message "Generating Anonymized System Overview Report for $workflowMessage."
                    $vcfOverviewHtml = Publish-VcfSystemOverview -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass -anonymized
                } else {
                    Write-LogMessage -Type INFO -Message "Generating System Overview Report for $workflowMessage."
                    $vcfOverviewHtml = Publish-VcfSystemOverview -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass
                }

                $reportData += $vcfOverviewHtml

                if ($PsBoundParameters.ContainsKey("darkMode")) {
                    $reportHeader = Get-ClarityReportHeader -dark
                } else {
                    $reportHeader = Get-ClarityReportHeader
                }
                $reportNavigation = Get-ClarityReportNavigation -reportType overview
                $reportFooter = Get-ClarityReportFooter
                $report = $reportHeader
                $report += $reportNavigation
                $report += $reportData
                $report += $reportFooter

                # Generate the report to an HTML file and then open it in the default browser
                Write-LogMessage -Type INFO -Message "Generating the Final Report and Saving to ($reportName)."
                $report | Out-File $reportName
                if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -ne "Linux") {
                    Invoke-Item $reportName
                } elseif ($PSEdition -eq "Desktop") {
                    Invoke-Item $reportName
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Invoke-VcfOverviewReport

########################################## E N D O F F U N C T I O N S ##########################################
#######################################################################################################################


#######################################################################################################################
############################# S O S J S O N E X T R A C T I O N F U N C T I O N S ############################

Function Request-SoSHealthJson {
    <#
        .SYNOPSIS
        Run SoS and save the JSON output.

        .DESCRIPTION
        The Request-SoSHealthJson cmdlet connects to SDDC Manager, runs an SoS Health collection to JSON, and saves the
        JSON file to the local file system.

        .EXAMPLE
        Request-SoSHealthJson -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -reportPath F:\Reporting\HealthReports -allDomains
        This example runs an SoS Health collection for all domains in the SDDC and saves the JSON output to the local file system.

        .EXAMPLE
        Request-SoSHealthJson -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -reportPath F:\Reporting\HealthReports -workloadDomain sfo-w01
        This example runs an SoS Health collection for a workload domain in the SDDC and saves the JSON output to the local file system.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER reportPath
        The path to save the policy report.

        .PARAMETER allDomains
        Switch to run against all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$reportPath,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    # Request VCF Token
    Request-VCFToken -fqdn $server -Username $user -Password $pass -skipCertificateCheck -ErrorAction SilentlyContinue -ErrorVariable ErrMsg | Out-Null
    $vcfVersion = ((Get-VCFManager).version).Split('-')[0]

    # Build Default Health Summary Check POST payload
    $healthChecksPayload = New-Object -TypeName psobject
    $healthChecksPayload | Add-Member -notepropertyname 'certificateHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'composabilityHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'computeHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'connectivityHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'dnsHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'generalHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'hardwareCompatibilityHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'ntpHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'passwordHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'servicesHealth' -notepropertyvalue $true
    $healthChecksPayload | Add-Member -notepropertyname 'storageHealth' -notepropertyvalue $true
    if ($vcfVersion -ge "4.5.0") { $healthChecksPayload | Add-Member -notepropertyname 'versionHealth' -notepropertyvalue $true }
    $optionsConfigPayload = New-Object -TypeName psobject
    $optionsConfigPayload | Add-Member -notepropertyname 'force' -notepropertyvalue $false
    $optionsConfigPayload | Add-Member -notepropertyname 'skipKnownHostCheck' -notepropertyvalue $true
    $optionsIncludePayload = New-Object -TypeName psobject
    if ($vcfVersion -ge "4.5.0") { $optionsIncludePayload | Add-Member -notepropertyname 'precheckReport' -notepropertyvalue $false }
    $optionsIncludePayload | Add-Member -notepropertyname 'summaryReport' -notepropertyvalue $false
    $optionsPayload = New-Object -TypeName psobject
    $optionsPayload | Add-Member -notepropertyname 'config' -notepropertyvalue $optionsConfigPayload
    $optionsPayload | Add-Member -notepropertyname 'include' -notepropertyvalue $optionsIncludePayload
    $scopeDomainsPayload = New-Object -TypeName psobject
    $scopeDomainsPayload | Add-Member -notepropertyname 'clusterNames' -notepropertyvalue @()
    $scopeDomainsPayload | Add-Member -notepropertyname 'domainName' -notepropertyvalue ""
    $scopeDomainsPayload.clusterNames += @("")
    $domainsArray = @()
    $domainsArray += $scopeDomainsPayload
    $scopePayload = New-Object -TypeName psobject
    $scopePayload | Add-Member -notepropertyname 'domains' -notepropertyvalue @()
    $scopePayload | Add-Member -notepropertyname 'includeAllDomains' -notepropertyvalue $false
    $scopePayload | Add-Member -notepropertyname 'includeFreeHosts' -notepropertyvalue $false
    $scopePayload.domains += $domainsArray
    $healthSummarySpec = New-Object -TypeName psobject
    $healthSummarySpec | Add-Member -notepropertyname 'healthChecks' -notepropertyvalue $healthChecksPayload
    $healthSummarySpec | Add-Member -notepropertyname 'options' -notepropertyvalue $optionsPayload
    $healthSummarySpec | Add-Member -notepropertyname 'scope' -notepropertyvalue $scopePayload

    Try {
        if ($PsBoundParameters.ContainsKey("allDomains")) {
            $healthSummarySpec.scope.includeAllDomains = $true
            if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -eq "Linux") {
                $reportDestination = ($reportDestination = ($reportPath + "\" + $server.Split(".")[0] + "-all-health-results.json")).split('\') -join '/' | Split-Path -NoQualifier
            } else {
                $reportDestination = ($reportPath + "\" + $server.Split(".")[0] + "-all-health-results.json")
            }
        } elseif ($PsBoundParameters.ContainsKey("workloadDomain")) {
            $healthSummarySpec.scope.domains[0].domainName = $workloadDomain
            if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -eq "Linux") {
                $reportDestination = ($reportDestination = ($reportPath + "\" + $workloadDomain + "-all-health-results.json")).split('\') -join '/' | Split-Path -NoQualifier
            } else {
                $reportDestination = ($reportPath + "\" + $workloadDomain + "-all-health-results.json")
            }
        }
        if (Test-VCFConnection -server $server) {
            # Create a temporary directory under reportDirectory
            $createPathCounter = 0
            for ($createPathCounter -lt 4) {
                $randomOutput = -join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 6 |%{[char]$_})
                $outFilePath = Join-Path -Path $reportPath -childPath $randomOutput
                if (!(Test-Path -Path $outFilePath) -and (Test-Path -Path $reportPath)) {
                    Break
                } else {
                    if ($createPathCounter -eq 3) {
                        Write-Error "Unable to write to $reportPath."
                    }
                    $createPathCounter++
                }
            }
            New-Item -Path $outFilePath -ItemType Directory | Out-NULL

            # Use REST API method to request the health summary.
            $healthSummaryPayloadJson = Join-Path -Path $outFilePath -childPath "healthSummaryPayload.json"
            ConvertTo-JSON $healthSummarySpec -depth 10 | Out-File $healthSummaryPayloadJson
            $response = Start-VCFHealthSummary -json $healthSummaryPayloadJson
            if ($response.id -eq "") {
                Write-Error "The Health Summary request encountered an issue. Please try again."
                Return $false
            }
            $requestID = $response.id

            # Retrieve the request status.
            $response = Get-VCFHealthSummaryTask -id $requestID
            $escapeCounter = 0
            While (($escapeCounter -lt 30) -and !($response.status -match "COMPLETED")) {
                sleep(30)
                $escapeCounter++
                $response = Get-VCFHealthSummaryTask -id $requestID
            }
            if ($escapeCounter -eq 20) {
                Write-Error " The Health Summary request is taking an unusual amount of time to complete."
                Return $false
            }

            # Download the health summary bundle file to a temporary directory.
            Request-VCFHealthSummaryBundle -id $requestID
            $outFile = Join-Path -Path $outFilePath  -childPath "health-summary.tar.gz"
            $savedFile = "health-summary-"+$requestID+".tar"
            if (Test-Path -Path $savedFile) {
                Copy-Item $savedFile $outFile | Out-NULL
                Remove-Item -Force $savedFile  | Out-NULL
            } else {
                Write-Error "An error was encountered downloading the health summary bundle."
                Return $false
            }

            # Untar the tar.gz file and extract health-results.json file.
            tar -xzf $outFile -C $outFilePath | Out-NULL
            $healthSummaryPath = gci -recurse -filter "health-results.json" -Path $outFilePath
            $healthSummaryFile = Join-Path -Path $healthSummaryPath.DirectoryName -childPath "health-results.json"
            Copy-Item $healthSummaryFile $reportDestination  | Out-NULL

            # Remove the temporary directory.
            Remove-Item -Recurse -Force $outFilePath  | Out-NULL

            # Convert to JSON.
            $temp = Get-Content -Path $reportDestination; $temp = $temp -replace '""', '"-"'; $temp | Out-File $reportDestination
            Return $reportDestination
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-SoSHealthJson

Function Publish-CertificateHealth {
    <#
        .SYNOPSIS
        Formats the Certificate Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-CertificateHealth cmdlet formats the Certificate Health data from the SoS JSON output and publishes
        it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-CertificateHealth -json <file-name>
        This example extracts and formats the Certificate Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-CertificateHealth -json <file-name> -html
        This example extracts and formats the Certificate Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-CertificateHealth -json <file-name> -failureOnly
        This example extracts and formats the Certificate Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        $customObject = New-Object System.Collections.ArrayList
        $jsonInputData = $targetContent.'Certificates'.'Certificate Status' # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'Certificate Status data not found in the JSON file: SKIPPED'
        } else {
            foreach ($component in $jsonInputData.PsObject.Properties.Value) {
                foreach ($element in $component.PsObject.Properties.Value) {
                    $elementObject = New-Object -TypeName psobject
                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue ($element.area -Split (':'))[0].Trim()
                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                    if ($element.title -and $element.title -notcontains "-") {
                        $current = [DateTime]::Now
                        $expiry = [DateTime]::ParseExact(($element.title[2] -replace '\s+', ' '), "MMM d HH:mm:ss yyyy 'GMT'", $null).ToUniversalTime()
                        $expires_in = ($expiry - $current).Days
                            $alert = "GREEN"
                            if ([int]$expires_in -le 15) {
                                $alert = "RED"
                            } elseif ([int]$expires_in -le 30) {
                                $alert = "YELLOW"
                            }
                        $elementObject | Add-Member -NotePropertyName 'Expires In (Days)' -NotePropertyValue $expires_in
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                    } else {
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                    }
                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($element.status -eq 'FAILED')) {
                            $customObject += $elementObject
                        }
                    } else {
                        $customObject += $elementObject
                    }
                }
            }
            $outputObject += $customObject
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="security-certificate"></a><h3>Certificate Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="security-certificate"></a><h3>Certificate Health Status</h3>' -As Table
                }
                $outputObject = Convert-CssClass -htmlData $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-ntp"></a><h3>NTP Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Certificate Status data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-CertificateHealth

Function Publish-ConnectivityHealth {
    <#
        .SYNOPSIS
        Formats the Connectivity Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-ConnectivityHealth cmdlet formats the Connectivity Health data from the SoS JSON output and
        publishes it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-ConnectivityHealth -json <file-name>
        This example extracts and formats the Connectivity Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-ConnectivityHealth -json <file-name> -html
        This example extracts and formats the Connectivity Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-ConnectivityHealth -json <file-name> -failureOnly
        This example extracts and formats the Connectivity Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        $customObject = New-Object System.Collections.ArrayList
        $jsonInputCheck = $targetContent.Connectivity.'Connectivity Status' # Extract Data from the provided SOS JSON
        if (($jsonInputCheck | Measure-Object).Count -lt 1) {
            Write-Warning 'Connectivity Status data not found in the JSON file: SKIPPED'
        } else {

            # ESXi SSH Status
            $jsonInputData = $targetContent.Connectivity.'Connectivity Status'.'ESXi SSH Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # ESXi API Status
            $jsonInputData = $targetContent.Connectivity.'Connectivity Status'.'ESXi API Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Additional Items Status
            $jsonInputData = $targetContent.Connectivity.'Connectivity Status' # Extract Data from the provided SOS JSON
            $jsonInputData.PSObject.Properties.Remove('ESXi SSH Status')
            $jsonInputData.PSObject.Properties.Remove('ESXi API Status')
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -As Table
                }
                $customObject = Convert-CssClass -htmldata $customObject
            } else {
                $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Connectivity Status data not found.</p>' -As Table
            }
            $customObject
        }
        else {
            $customObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ConnectivityHealth

Function Publish-PingConnectivityHealth {
    <#
        .SYNOPSIS
        Formats the Ping Connectivity Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-PingConnectivityHealth cmdlet formats the Ping Connectivity Health data from the SoS JSON output and
        publishes it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-PingConnectivityHealth -json <file-name>
        This example extracts and formats the Ping Connectivity Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-PingConnectivityHealth -json <file-name> -html
        This example extracts and formats the Ping Connectivity Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-PingConnectivityHealth -json <file-name> -failureOnly
        This example extracts and formats the Ping Connectivity Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        $customObject = New-Object System.Collections.ArrayList
        $jsonInputData = $targetContent.Connectivity.'Ping Status'
        $pingStatusProperties = @('area', 'title', 'state', 'timestamp', 'message', 'status', 'alert')

        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'Ping Status data not found in the JSON file: SKIPPED'
        } else {
            $esxiHosts = Get-VCFHost
            foreach ($esxiHost in $esxiHosts) {
                $fqdn = $esxiHost.fqdn
                $propertiesName = $jsonInputData.$fqdn.PSObject.Properties.Name
                if (($pingStatusProperties.contains($propertiesName))) {
                    # do nothing
                } else {
                    $jsonInputData.$fqdn = $jsonInputData.$fqdn.$propertiesName
                }
            }

            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData
            }
            $customObject += $outputObject
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-ping-connectivity"></a><h3>Ping Connectivity Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-ping-connectivity"></a><h3>Ping Connectivity Health Status</h3>' -As Table
                }
                $customObject = Convert-CssClass -htmldata $customObject
            } else {
                $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-ping-connectivity"></a><h3>Ping Connectivity Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Ping Status data not found.</p>' -As Table
            }
            $customObject
        } else {
            $customObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-PingConnectivityHealth

Function Publish-DnsHealth {
    <#
        .SYNOPSIS
        Formats the DNS Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-DnsHealth cmdlet formats the DNS Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-DnsHealth -json <file-name>
        This example extracts and formats the DNS Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-DnsHealth -json <file-name> -html
        This example extracts and formats the DNS Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-DnsHealth -json <file-name> -failureOnly
        This example extracts and formats the DNS Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # Forward Lookup Health Status
        $allForwardLookupObject = New-Object System.Collections.ArrayList
        $jsonForwardLookupInput = $targetContent.'DNS lookup Status'.'Forward lookup Status' # Extract Data from the provided SOS JSON
        if (($jsonForwardLookupInput | Measure-Object).Count -lt 1) {
            Write-Warning 'Forward Lookup Status not found in the JSON file: SKIPPED'
        } else {
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $allForwardLookupObject = Read-JsonElement -inputData $jsonForwardLookupInput -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $allForwardLookupObject = Read-JsonElement -inputData $jsonForwardLookupInput # Call Function to Structure the Data for Report Output
            }
        }

        # Reverse Lookup Health Status
        $allReverseLookupObject = New-Object System.Collections.ArrayList
        $jsonReverseLookupInput = $targetContent.'DNS lookup Status'.'Reverse lookup Status' # Extract Data from the provided SOS JSON
        if (($jsonReverseLookupInput | Measure-Object).Count -lt 1) {
            Write-Warning 'Reverse Lookup Status not found in the JSON file: SKIPPED'
        } else {
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $allReverseLookupObject = Read-JsonElement -inputData $jsonReverseLookupInput -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $allReverseLookupObject = Read-JsonElement -inputData $jsonReverseLookupInput # Call Function to Structure the Data for Report Output
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey("html")) {
            if (($jsonForwardLookupInput | Measure-Object).Count -gt 0) {
                if ($allForwardLookupObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allForwardLookupObject = $allForwardLookupObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-dns-forward"></a><h3>DNS Forward Lookup Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allForwardLookupObject = $allForwardLookupObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-dns-forward"></a><h3>DNS Forward Lookup Health Status</h3>' -As Table
                }
                $allForwardLookupObject = Convert-CssClass -htmldata $allForwardLookupObject
            } else {
                $allForwardLookupObject = $allForwardLookupObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-dns-forward"></a><h3>DNS Forward Lookup Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Forward Lookup Status data not found.</p>' -As Table
            }
            $allForwardLookupObject

            if (($jsonReverseLookupInput | Measure-Object).Count -gt 0) {
                if ($allReverseLookupObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allReverseLookupObject = $allReverseLookupObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-dns-reverse"></a><h3>DNS Reverse Lookup Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allReverseLookupObject = $allReverseLookupObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-dns-reverse"></a><h3>DNS Reverse Lookup Health Status</h3>' -As Table
                }
                $allReverseLookupObject = Convert-CssClass -htmldata $allReverseLookupObject
            } else {
                $allReverseLookupObject = $allReverseLookupObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-dns-reverse"></a><h3>DNS Reverse Lookup Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Reverse Lookup Status data not found.</p>' -As Table
            }
            $allReverseLookupObject
        } else {
            $allForwardLookupObject | Sort-Object Component, Resource
            $allReverseLookupObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-DnsHealth

Function Publish-EsxiHealth {
    <#
        .SYNOPSIS
        Formats the ESXi Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-EsxiHealth cmdlet formats the ESXi Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-EsxiHealth -json <file-name>
        This example extracts and formats the ESXi Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-EsxiHealth -json <file-name> -html
        This example extracts and formats the ESXi Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-EsxiHealth -json <file-name> -failureOnly
        This example extracts and formats the ESXi Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # ESXi Overall Health Status
        $allOverallHealthObject = New-Object System.Collections.ArrayList

        $jsonGeneralCheck = $targetContent.General # Extract Data from the provided SOS JSON
        if (($jsonGeneralCheck | Measure-Object).Count -lt 1) {
            Write-Warning 'General data not found in the JSON file: SKIPPED'
        } else {
            # ESXi Core Dump Status
            $allCoreDumpObject = New-Object System.Collections.ArrayList
            $jsonInputData = $targetContent.General.'ESXi Core Dump Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $allCoreDumpObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $allCoreDumpObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Rep
            }
        }

        $jsonComputeCheck = $targetContent.Compute # Extract Data from the provided SOS JSON
        if (($jsonComputeCheck | Measure-Object).Count -lt 1) {
            Write-Warning 'Compute data not found in the JSON file: SKIPPED'
        } else {
            # ESXi Overall Health Status
            $jsonInputData = $targetContent.Compute.'ESXi Overall Health' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $allOverallHealthObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $allOverallHealthObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }

            # ESXi License Status
            $allLicenseObject = New-Object System.Collections.ArrayList
            $jsonInputData = $targetContent.Compute.'ESXi License Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $allLicenseObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $allLicenseObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }

            # ESXi Disk Status
            $allDiskObject = New-Object System.Collections.ArrayList
            $jsonInputData = $targetContent.Compute.'ESXi Disk Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $allDiskObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $allDiskObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonGeneralCheck | Measure-Object).Count -gt 0) {
                if ($allCoreDumpObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allCoreDumpObject = $allCoreDumpObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-coredump"></a><h3>ESXi Core Dump Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allCoreDumpObject = $allCoreDumpObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-coredump"></a><h3>ESXi Core Dump Health Status</h3>' -As Table
                }
                $allCoreDumpObject = Convert-CssClass -htmldata $allCoreDumpObject
            } else {
                $allCoreDumpObject = $allCoreDumpObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-coredump"></a><h3>ESXi Core Dump Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: ESXi Core Dump data not found.</p>' -As Table
            }
            $allCoreDumpObject

            if (($jsonComputeCheck | Measure-Object).Count -gt 0) {
                if ($allOverallHealthObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allOverallHealthObject = $allOverallHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-overall"></a><h3>ESXi Overall Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allOverallHealthObject = $allOverallHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-overall"></a><h3>ESXi Overall Health Status</h3>' -As Table
                }
                $allOverallHealthObject = Convert-CssClass -htmldata $allOverallHealthObject
            } else {
                $allOverallHealthObject = $allOverallHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-overall"></a><h3>ESXi Overall Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: ESXi Overall Health data not found.</p>' -As Table
            }
            $allOverallHealthObject

            if (($jsonComputeCheck | Measure-Object).Count -gt 0) {
                if ($allLicenseObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allLicenseObject = $allLicenseObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-license"></a><h3>ESXi License Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allLicenseObject = $allLicenseObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-license"></a><h3>ESXi License Health Status</h3>' -As Table
                }
                $allLicenseObject = Convert-CssClass -htmldata $allLicenseObject
            } else {
                $allLicenseObject = $allLicenseObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-license"></a><h3>ESXi License Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: ESXi License data not found.</p>' -As Table
            }
            $allLicenseObject

            if (($jsonComputeCheck | Measure-Object).Count -gt 0) {
                if ($allDiskObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allDiskObject = $allDiskObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-disk"></a><h3>ESXi Disk Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allDiskObject = $allDiskObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-disk"></a><h3>ESXi Disk Health Status</h3>' -As Table
                }
                $allDiskObject = Convert-CssClass -htmldata $allDiskObject
            } else {
                $allDiskObject = $allDiskObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="esxi-disk"></a><h3>ESXi Disk Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: ESXi Disk data not found.</p>' -As Table
            }
            $allDiskObject
        } else {
            $allCoreDumpObject | Sort-Object Component, Resource
            $allOverallHealthbject | Sort-Object Component, Resource
            $allLicenseObject | Sort-Object Component, Resource
            $allDiskObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-EsxiHealth

Function Publish-NsxtHealth {
    <#
        .SYNOPSIS
        Formats the NSX Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-NsxtHealth cmdlet formats the NSX Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-NsxtHealth -json <file-name>
        This example extracts and formats the NSX Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-NsxtHealth -json <file-name> -html
        This example extracts and formats the NSX Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-NsxtHealth -json <file-name> -failureOnly
        This example extracts and formats the NSX Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        $customObject = New-Object System.Collections.ArrayList
        $jsonInputCheck = $targetContent.General.'NSX Health' # Extract Data from the provided SOS JSON
        if (($jsonInputCheck | Measure-Object).Count -lt 1) {
            Write-Warning 'NSX Health data not found in the JSON file: SKIPPED'
        } else {

            # NSX Manager Health
            $component = 'NSX Manager'
            $inputData = $targetContent.General.'NSX Health'.'NSX Manager'

            foreach ($element in $inputData.PsObject.Properties.Value) {
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($element.status -eq 'FAILED')) {
                        $customObject += $elementObject
                    }
                } else {
                    $customObject += $elementObject
                }
            }

            # NSX Container Cluster Health Status
            $component = 'NSX Container Cluster'
            $inputData = $targetContent.General.'NSX Health'.'NSX Container Cluster Health Status'
            foreach ($element in $inputData.PsObject.Properties.Value) {
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($element.status -eq 'FAILED')) {
                        $customObject += $elementObject
                    }
                } else {
                    $customObject += $elementObject
                }
            }
            # NSX Cluster Status
            $component = 'NSX Cluster Status'
            $inputData = $targetContent.General.'NSX Health'.'NSX Cluster Status'
            foreach ($resource in $inputData.PsObject.Properties.Value) {
                foreach ($element in $resource.PsObject.Properties.Value) {
                    $elementObject = New-Object -TypeName psobject
                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                    $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($element.status -eq 'FAILED')) {
                            $customObject += $elementObject
                        }
                    } else {
                        $customObject += $elementObject
                    }
                }
            }

            # NSX Controllers Health
            $component = 'NSX Controllers'
            $inputData = $targetContent.General.'NSX Health'.'NSX Controllers'
            foreach ($resource in $inputData.PsObject.Properties.Value) {
                foreach ($element in $resource.PsObject.Properties.Value) {
                    $elementObject = New-Object -TypeName psobject
                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                    $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($element.status -eq 'FAILED')) {
                            $customObject += $elementObject
                        }
                    } else {
                        $customObject += $elementObject
                    }
                }
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($customObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $customObject = $customObject | Sort-Object Resource, Component | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $customObject = $customObject | Sort-Object Resource, Component | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status</h3>' -As Table
                }
                $customObject = Convert-CssClass -htmldata $customObject
            } else {
                $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: NSX Health data not found.</p>' -As Table
            }
            $customObject
        } else {
            $customObject | Sort-Object Resource, Component
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtHealth

Function Publish-NsxtEdgeNodeHealth {
    <#
        .SYNOPSIS
        Formats the NSX Edge Node Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-NsxtEdgeNodeHealth cmdlet formats the NSX Edge Node Health data from the SoS JSON output and
        publishes it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-NsxtEdgeNodeHealth -json <file-name>
        This example extracts and formats the NSX Edge Node Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-NsxtEdgeNodeHealth -json <file-name> -html
        This example extracts and formats the NSX Edge Node Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-NsxtEdgeNodeHealth -json <file-name> -failureOnly
        This example extracts and formats the NSX Edge Node Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # NSX Edge Node Health
        $customObject = New-Object System.Collections.ArrayList
        $jsonInputData = $targetContent.General.'NSX Health'.'NSX Edge'
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning "NSX Health data for NSX Edges not found in the JSON file: SKIPPED"
        } else {
            $nsxtClusters = Get-VCFNsxtCluster
            foreach ($nsxtVip in $nsxtClusters.vipFqdn) {
                $jsonInputData.PSObject.Properties.Remove($nsxtVip)
            }
            $jsonInputData = $jsonInputData | Where-Object {$_ -ne ""}
            foreach ($element in $jsonInputData.PsObject.Properties.Value) {
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue 'NSX Edge'
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue ($element.message -Split ('Following are the individual health stats'))[0]
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($element.status -eq 'FAILED')) {
                        $customObject += $elementObject
                    }
                } else {
                    $customObject += $elementObject
                }
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($customObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-edge"></a><h3>NSX Edge Node Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-edge"></a><h3>NSX Edge Node Health Status</h3>' -As Table
                }
                $customObject = Convert-CssClass -htmldata $customObject
            } else {
                $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-edge"></a><h3>NSX Edge Node Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: NSX Health data for NSX Edges not found. This warning is safe to ignore if NSX Edges are not managed by SDDC Manager.</p>' -As Table
            }
            $customObject
        } else {
            $customObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtEdgeNodeHealth

Function Publish-NsxtEdgeClusterHealth {
    <#
        .SYNOPSIS
        Formats the NSX Edge Cluster Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-NsxtEdgeClusterHealth cmdlet formats the NSX Edge Cluster Health data from the SoS JSON output and
        publishes it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-NsxtEdgeClusterHealth -json <file-name>
        This example extracts and formats the NSX Edge Cluster Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-NsxtEdgeClusterHealth -json <file-name> -html
        This example extracts and formats the NSX Edge Cluster Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-NsxtEdgeClusterHealth -json <file-name> -failureOnly
        This example extracts and formats the NSX Edge Cluster Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # NSX Edge Cluster Health
        $customObject = New-Object System.Collections.ArrayList
        $jsonInputData = $targetContent.General.'NSX Health'.'NSX Edge'
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning "NSX Health data for NSX Edge Cluster not found in the JSON file: SKIPPED"
        } else {
            $nsxtEdgeClusters = Get-VCFEdgeCluster
            foreach ($nsxtEdgeNodes in $nsxtEdgeClusters.edgeNodes.hostname) {
                $jsonInputData.PSObject.Properties.Remove($nsxtEdgeNodes)
            }
            if ($null -eq $nsxtEdgeClusters) {
                $nsxtClusters = Get-VCFNsxtCluster
                foreach ($nsxtCluster in $nsxtClusters) {
                    $jsonInputData.PSObject.Properties.Remove($nsxtCluster.vipFqdn)
                }
            }
            $jsonInputData = $jsonInputData | Where-Object {$_ -ne ""}
            foreach ($element in $jsonInputData.PsObject.Properties.Value) {
                foreach ($cluster in $element.PsObject.Properties.Value) {
                    $elementObject = New-Object -TypeName psobject
                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue 'NSX Edge Cluster'
                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($cluster.area -Split (':'))[-1].Trim()
                    $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $cluster.alert
                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $cluster.message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($element.status -eq 'FAILED')) {
                            $customObject += $elementObject
                        }
                    } else {
                        $customObject += $elementObject
                    }
                }
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($customObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-edge-cluster"></a><h3>NSX Edge Cluster Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-edge-cluster"></a><h3>NSX Edge Cluster Health Status</h3>' -As Table
                }
                $customObject = Convert-CssClass -htmldata $customObject
            } else {
                $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-edge-cluster"></a><h3>NSX Edge Cluster Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: NSX Health data for Edge Cluster not found. This warning is safe to ignore if NSX Edges are not managed by SDDC Manager.</p>' -As Table
            }
            $customObject
        } else {
            $customObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtEdgeClusterHealth

Function Publish-NtpHealth {
    <#
        .SYNOPSIS
        Formats the NTP Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-NtpHealth cmdlet formats the NTP Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-NtpHealth -json <file-name>
        This example extracts and formats the NTP Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-NtpHealth -json <file-name> -html
        This example extracts and formats the NTP Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-NtpHealth -json <file-name> -failureOnly
        This example extracts and formats the NTP Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # NTP Health Status
        $jsonInputData = $targetContent.'NTP' # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'NTP data not found in the JSON file: SKIPPED'
        } else {
            $jsonInputData.PSObject.Properties.Remove('ESXi HW Time')
            $jsonInputData.PSObject.Properties.Remove('ESXi Time')

            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey("html")) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-ntp"></a><h3>NTP Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-ntp"></a><h3>NTP Health Status</h3>' -As Table
                }
                $outputObject = Convert-CssClass -htmldata $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="infra-ntp"></a><h3>NTP Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: NTP data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NtpHealth

Function Publish-PasswordHealth {
    <#
        .SYNOPSIS
        Formats the Password Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-PasswordHealth cmdlet formats the Password Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-PasswordHealth -json <file-name>
        This example extracts and formats the Password Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-PasswordHealth -json <file-name> -html
        This example extracts and formats the Password Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-PasswordHealth -json <file-name> -failureOnly
        This example extracts and formats the Password Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # Password Expiry Health
        $jsonInputData = $targetContent.'Password Expiry Status' # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'Password Expiry Status not found in the JSON file: SKIPPED'
        } else {
            $outputObject = New-Object System.Collections.ArrayList
            foreach ($element in $jsonInputData.PsObject.Properties.Value) {
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue ($element.area -Split (':'))[0].Trim()
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                $elementObject | Add-Member -NotePropertyName 'User' -NotePropertyValue $element.title.User
                $elementObject | Add-Member -NotePropertyName 'Expires In (Days)' -NotePropertyValue $element.title.expires_in
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($element.status -eq 'FAILED')) {
                        $outputObject += $elementObject
                    }
                } else {
                    $outputObject += $elementObject
                }
            }

        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey("html")) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="security-password"></a><h3>Password Expiry Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="security-password"></a><h3>Password Expiry Health Status</h3>' -As Table
                }
                $outputObject = Convert-CssClass -htmldata $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="security-password"></a><h3>Password Expiry Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Password Expiry Status data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-PasswordHealth

Function Publish-VersionHealth {
    <#
        .SYNOPSIS
        Formats the Version Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-VersionHealth cmdlet formats the Version Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-VersionHealth -json <file-name>
        This example extracts and formats the Version Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-VersionHealth -json <file-name> -html
        This example extracts and formats the Version Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-VersionHealth -json <file-name> -failureOnly
        This example extracts and formats the Version Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # Version Health
        $jsonInputData = $targetContent.'Version Check Status' # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'Version Check Status not found in the JSON file: SKIPPED'
        } else {
            $outputObject = New-Object System.Collections.ArrayList
            foreach ($element in $jsonInputData.PsObject.Properties.Value) {
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue ($element.area -Split (':'))[0].Trim()
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                $elementObject | Add-Member -NotePropertyName 'Version' -NotePropertyValue $element.title[0]
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($element.status -eq 'FAILED')) {
                        $outputObject += $elementObject
                    }
                } else {
                    $outputObject += $elementObject
                }
            }
        }

        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-version"></a><h3>Version Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-version"></a><h3>Version Health Status</h3>' -As Table
                }
                $outputObject = Convert-CssClass -htmldata $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-version"></a><h3>Version Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Version data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VersionHealth

Function Publish-HardwareCompatibilityHealth {
    <#
        .SYNOPSIS
        Formats the Hardware Compatibility data from the SoS JSON output.

        .DESCRIPTION
        The Publish-HardwareCompatibilityHealth cmdlet formats the Hardware Compatibility data from the SoS JSON output and publishes
        it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-HardwareCompatibilityHealth -json <file-name>
        This example extracts and formats the Hardware Compatibility data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-HardwareCompatibilityHealth -json <file-name> -html
        This example extracts and formats the Hardware Compatibility data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-HardwareCompatibilityHealth -json <file-name> -failureOnly
        This example extracts and formats the Hardware Compatibility data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # Hardware Compatibility
        $jsonInputData = $targetContent.'Hardware Compatibility' # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'Hardware Compatibility not found in the JSON file: SKIPPED'
        } else {
            $outputObject = New-Object System.Collections.ArrayList
            foreach ($component in $jsonInputData.PsObject.Properties.Value) {
                foreach ($element in $component.PsObject.Properties.Value) {
                    if ($element.title -ne '-' -and $element.alert -ne '-') {
                        $elementObject = New-Object -TypeName psobject
                        $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue ($element.area -Split (':'))[0].Trim()
                        $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert

                        $message = $element.message
                        $fixupMessage = $message.Replace('is Successul', 'is successful')

                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $fixupMessage
                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                            if (($element.status -eq 'FAILED')) {
                                $outputObject += $elementObject
                            }
                        } else {
                            $outputObject += $elementObject
                        }
                    }
                }
            }
        }

        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) {
                    $addNoIssues = $true
                }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-hardware"></a><h3>Hardware Compatibility Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-hardware"></a><h3>Hardware Compatibility Health Status</h3>' -As Table
                }
                $outputObject = Convert-CssClass -htmlData $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-hardware"></a><h3>Hardware Compatibility Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Hardware compatibility data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-HardwareCompatibilityHealth

Function Publish-ServiceHealth {
    <#
        .SYNOPSIS
        Formats the Service Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-ServiceHealth cmdlet formats the Service Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-ServiceHealth -json <file-name>
        This example extracts and formats the Service Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-ServiceHealth -json <file-name> -html
        This example extracts and formats the Service Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-ServiceHealth -json <file-name> -failureOnly
        This example extracts and formats the Service Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        $outputObject = New-Object System.Collections.ArrayList
        $inputData = $targetContent.'Services' # Extract Data from the provided SOS JSON
        if (($inputData | Measure-Object).Count -lt 1) {
            Write-Warning 'Services data not found in the JSON file: SKIPPED'
        } else {
            foreach ($component in $inputData) {
                foreach ($element in $component.PsObject.Properties.Value) {
                    $elementObject = New-Object -TypeName psobject
                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue ($element.area -Split (':'))[0].Trim()
                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                    $elementObject | Add-Member -NotePropertyName 'Service Name' -NotePropertyValue $element.title.ToUpper()
                    $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($element.status -eq 'FAILED')) {
                            $outputObject += $elementObject
                        }
                    } else {
                        $outputObject += $elementObject
                    }
                }
            }
        }

        if ($PsBoundParameters.ContainsKey('html')) {
            if (($inputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-service"></a><h3>Service Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-service"></a><h3>Service Health Status</h3>' -As Table
                }
                $outputObject = Convert-CssClass -htmldata $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-service"></a><h3>Service Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: Services data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ServiceHealth

Function Publish-VcenterHealth {
    <#
        .SYNOPSIS
        Formats the vCenter Server Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-VcenterHealth cmdlet formats the vCenter Server Health data from the SoS JSON output and publishes
        it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-VcenterHealth -json <file-name>
        This example extracts and formats the vCenter Server Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-VcenterHealth -json <file-name> -html
        This example extracts and formats the vCenter Server Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-VcenterHealth -json <file-name> -failureOnly
        This example extracts and formats the vCenter Server Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # vCenter Server Overall Health
        $jsonInputData = $targetContent.Compute.'vCenter Overall Health' # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning "vCenter Server Overall Health data not found in the JSON file: SKIPPED"
        } else {
            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                $vcenterOverall = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $vcenterOverall = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }

            # Ring Topology Health
            $ringTopologyHealth = New-Object System.Collections.ArrayList
            $vcfVersion = ((Get-VCFManager).version -Split ('\.\d{1}\-\d{8}')) -split '\s+' -match '\S'
            if ($vcfVersion -eq "4.2.1") {
                $jsonInputData = $targetContent.Connectivity.'Vcenter Ring Topology Status'.'Vcenter Ring Topology Status' # Extract Data from the provided SOS JSON
            } else {
                $jsonInputData = $targetContent.General.'Vcenter Ring Topology Status'.'Vcenter Ring Topology Status' # Extract Data from the provided SOS JSON
            }
            $elementObject = New-Object -TypeName psobject
            $elementObject | Add-Member -notepropertyname 'Component' -notepropertyvalue ($jsonInputData.area -SPlit  ("SDDC:"))[-1].Trim()
            $elementObject | Add-Member -notepropertyname 'Alert' -notepropertyvalue $jsonInputData.alert
            $elementObject | Add-Member -notepropertyname 'Message' -notepropertyvalue $jsonInputData.message
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                if (($jsonInputData.status -eq "FAILED")) {
                    $ringTopologyHealth += $elementObject
                }
            } else {
                $ringTopologyHealth += $elementObject
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey("html")) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($vcenterOverall.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $vcenterOverall = $vcenterOverall | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vcenter-overall"></a><h3>vCenter Server Overall Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $vcenterOverall = $vcenterOverall | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vcenter-overall"></a><h3>vCenter Server Overall Health Status</h3>' -As Table
                }
                $vcenterOverall = Convert-CssClass -htmldata $vcenterOverall
            } else {
                $vcenterOverall = $vcenterOverall | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vcenter-overall"></a><h3>vCenter Server Overall Health Status</h3>' -PostContent '<p><p><strong>WARNING</strong>: vCenter Server Overall Health data not found.</p>' -As Table
            }
            $vcenterOverall
        } else {
            $vcenterOverall | Sort-Object Component, Resource
        }

        if ($PsBoundParameters.ContainsKey('html')) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
            if (@($ringTopologyHealth).Count -lt 1) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $ringTopologyHealth = $ringTopologyHealth | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vcenter-ring-topology"></a><h3>vCenter Server Ring Topology Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $ringTopologyHealth = $ringTopologyHealth | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vcenter-ring-topology"></a><h3>vCenter Server Ring Topology Health Status</h3>' -As Table
                }
                $ringTopologyHealth = Convert-CssClass -htmldata $ringTopologyHealth
            } else {
                $ringTopologyHealth = $ringTopologyHealth | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vcenter-ring-topology"></a><h3>vCenter Server Ring Topology Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: vCenter Server Overall Health data not found.</p>' -As Table
            }
            $ringTopologyHealth
        } else {
            $ringTopologyHealth | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VcenterHealth

Function Publish-VsanHealth {
    <#
        .SYNOPSIS
        Formats the vSAN Health data from the SoS JSON output.

        .DESCRIPTION
        The Publish-VsanHealth cmdlet formats the vSAN Health data from the SoS JSON output and publishes it as
        either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-VsanHealth -json <file-name>
        This example extracts and formats the vSAN Health data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-VsanHealth -json <file-name> -html
        This example extracts and formats the vSAN Health data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-VsanHealth -json <file-name> -failureOnly
        This example extracts and formats the vSAN Health data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        $customObject = New-Object System.Collections.ArrayList
        $jsonInputCheck = $targetContent.vSAN # Extract Data from the provided SOS JSON
        if (($jsonInputCheck | Measure-Object).Count -lt 1) {
            Write-Warning 'vSAN data not found in the JSON file: SKIPPED'
        } else {

            # Cluster Health Status
            $jsonInputData = $targetContent.vSAN.'Cluster vSAN Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Cluster Disk Status
            $jsonInputData = $targetContent.vSAN.'Cluster Disk Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Cluster Capacity Utilization
            $jsonInputData = $targetContent.vSAN.'vSAN Capacity Utilization' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Cluster Active ReSync Objects
            $jsonInputData = $targetContent.vSAN.'Active ReSync Objects' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Cluster Data Compression Status
            $jsonInputData = $targetContent.vSAN.'Cluster Data Compression Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Cluster Data Encryption Status
            $jsonInputData = $targetContent.vSAN.'Cluster Data Encryption Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Cluster Data Deduplication Status
            $jsonInputData = $targetContent.vSAN.'Cluster Data Deduplication Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Stretched Cluster Status
            $jsonInputData = $targetContent.vSAN.'Stretched Cluster Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Stretched Cluster Health Status
            $jsonInputData = $targetContent.vSAN.'Stretched Cluster Health Status' # Extract Data from the provided SOS JSON
            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                $outputObject = Read-JsonElement -inputData $jsonInputData -failureOnly # Call Function to Structure the Data for Report Output
            } else {
                $outputObject = Read-JsonElement -inputData $jsonInputData # Call Function to Structure the Data for Report Output
            }
            $customObject += $outputObject # Adding individual component to main customObject

            # Stretched Cluster Tests
            $jsonInputData = $targetContent.vSAN.'Stretched Cluster Tests' # Extract Data from the provided SOS JSON
            foreach ($component in $jsonInputData.PsObject.Properties.Value) {
                foreach ($element in $component.PsObject.Properties.Value) {
                    $outputObject = New-Object -TypeName psobject
                    $outputObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue ($element.area -Split (':'))[0].Trim()
                    $outputObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.area -Split (':'))[-1].Trim()
                    $outputObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                    $outputObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $element.message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($element.status -eq 'FAILED')) {
                            $customObject += $outputObject
                        }
                    } else {
                        $customObject += $outputObject # Adding individual component to main customObject
                    }
                }
            }

        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey("html")) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($customObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vsan-overall"></a><h3>vSAN Overall Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $customObject = $customObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vsan-overall"></a><h3>vSAN Overall Health Status</h3>' -As Table
                }
                $customObject = Convert-CssClass -htmldata $customObject
            } else {
                $customObject = $customObject | ConvertTo-Html -Fragment -PreContent '<a id="vsan-overall"></a><h3>vSAN Overall Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: vSAN data not found.</p>'
            }
            $customObject
        } else {
            $customObject | Sort-Object Component, Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VsanHealth

Function Publish-VsanStoragePolicy {
    <#
        .SYNOPSIS
        Formats the vSAN Storage Policy for virtual machines from the SoS JSON output.

        .DESCRIPTION
        The Publish-VsanStoragePolicy cmdlet formats the vSAN Storage Policy data from the SoS JSON output and
        publishes it as either a standard PowerShell object or an HTML object.

        .EXAMPLE
        Publish-VsanStoragePolicy -json <file-name>
        This example extracts and formats the vSAN Storage Policy data as a PowerShell object from the JSON file.

        .EXAMPLE
        Publish-VsanStoragePolicy -json <file-name> -html
        This example extracts and formats the vSAN Storage Policy data as an HTML object from the JSON file.

        .EXAMPLE
        Publish-VsanStoragePolicy -json <file-name> -failureOnly
        This example extracts and formats the vSAN Storage Policy data as a PowerShell object from the JSON file for only the failed items.

        .PARAMETER json
        The path to the JSON file containing the SoS Health Summary data.

        .PARAMETER html
        Specifies that the output should be formatted as an HTML object.

        .PARAMETER failureOnly
        Specifies that the output should only contain failed items.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (!(Test-Path -Path $json)) {
            Write-Error "Unable to find JSON file at location ($json)" -ErrorAction Stop
        } else {
            $targetContent = Get-Content $json | ConvertFrom-Json
        }

        # VSAN Storage Policy
        $jsonInputData = $targetContent.vSAN # Extract Data from the provided SOS JSON
        if (($jsonInputData | Measure-Object).Count -lt 1) {
            Write-Warning 'vSAN data not found in the JSON file: SKIPPED'
        } else {
            $jsonInputData.PSObject.Properties.Remove('Host vSAN Status')
            $jsonInputData.PSObject.Properties.Remove('Host Disk Status')
            $jsonInputData.PSObject.Properties.Remove('Cluster vSAN Status')
            $jsonInputData.PSObject.Properties.Remove('Cluster Disk Status')
            $jsonInputData.PSObject.Properties.Remove('vCenter HCL Status')
            $jsonInputData.PSObject.Properties.Remove('Cluster Data Compression Status')
            $jsonInputData.PSObject.Properties.Remove('Cluster Data Encryption Status')
            $jsonInputData.PSObject.Properties.Remove('Cluster Data Deduplication Status')
            $jsonInputData.PSObject.Properties.Remove('Stretched Cluster Status')
            $jsonInputData.PSObject.Properties.Remove('Stretched Cluster Health Status')
            $jsonInputData.PSObject.Properties.Remove('Stretched Cluster Tests')

            $outputObject = New-Object System.Collections.ArrayList
            foreach ($element in $jsonInputData.PsObject.Properties.Value) {
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue "Virtual Machine"
                $elementObject | Add-Member -NotePropertyName 'vCenter Server' -NotePropertyValue (($element.area -Split (' : '))[-1] -Split (' VM '))[0].Trim()
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue ($element.Message -Split (" "),2)[0].Trim()
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $element.alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue ($element.Message -Split (" "),2)[-1].Trim()
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($element.status -eq 'FAILED')) {
                        $outputObject += $elementObject
                    }
                } else {
                    $outputObject += $elementObject
                }
            }
        }

        # Return the structured data to the console or format using HTML CSS Styles
        if ($PsBoundParameters.ContainsKey("html")) {
            if (($jsonInputData | Measure-Object).Count -gt 0) {
                if ($outputObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $outputObject = $outputObject | Sort-Object Component, 'vCenter Server', Resource | ConvertTo-Html -Fragment -PreContent '<a id="vsan-spbm"></a><h3>vSAN Storage Policy Health Status</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $outputObject = $outputObject | Sort-Object Component, 'vCenter Server', Resource | ConvertTo-Html -Fragment -PreContent '<a id="vsan-spbm"></a><h3>vSAN Storage Policy Health Status</h3>' -As Table
                }
            $outputObject = Convert-CssClass -htmldata $outputObject
            } else {
                $outputObject = $outputObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="vsan-spbm"></a><h3>vSAN Storage Policy Health Status</h3>' -PostContent '<p><strong>WARNING</strong>: vSAN data not found.</p>' -As Table
            }
            $outputObject
        } else {
            $outputObject | Sort-Object Component, 'vCenter Server', Resource
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VsanStoragePolicy

########################################## E N D O F F U N C T I O N S ##########################################
#######################################################################################################################


#######################################################################################################################
#################################### H E A L T H C H E C K F U N C T I O N S ###################################

Function Publish-BackupStatus {
    <#
        .SYNOPSIS
        Request and publish the backup status.

        .DESCRIPTION
        The Publish-BackupStatus cmdlet checks the backup status for SDDC Manager, vCenter Server instances, and NSX
        Local Manager clusters in a VMware Cloud Foundation instance and prepares the data to be published to an HTML
        report. The cmdlet connects to SDDC Manager using the -server, -user, and password values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs checks on the backup status and outputs the results
        - outputJson parameter takes in name of the folder to save the json. Filename is autogenerated.

        .EXAMPLE
        Publish-BackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will publish the backup status for the SDDC Manager, vCenter Server instances, and NSX Local Manager clusters in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-BackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will publish the backup status for the SDDC Manager, vCenter Server instances, and NSX Local Manager clusters in a VMware Cloud Foundation instance but only reports issues.

        .EXAMPLE
        Publish-BackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will publish the backup status for the vCenter Server instances, and NSX Local Manager clusters in workload domain sfo-w01.

        .EXAMPLE
        Publish-BackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -outputJson F:\Reporting
        This example will generate a json for the backup status for the vCenter Server instances, and NSX Local Manager clusters in a VMware Cloud Foundation instance
        and saves it under F:\Reporting with filename <timestamp>-backup-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $singleWorkloadDomain = Get-VCFWorkloadDomain | Where-Object {$_.name -eq $workloadDomain}
                $allBackupStatusObject = New-Object System.Collections.ArrayList

                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey("allDomains")) {
                        $sddcManagerBackupStatus = Request-SddcManagerBackupStatus -server $server -user $user -pass $pass -failureOnly; $allBackupStatusObject += $sddcManagerBackupStatus
                        foreach ($domain in $allWorkloadDomains ) {
                            $vcenterBackupStatus = Request-vCenterBackupStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allBackupStatusObject += $vcenterBackupStatus
                            $nsxtManagerBackupStatus = Request-NsxtManagerBackupStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allBackupStatusObject += $nsxtManagerBackupStatus
                        }
                    } else {
                        if ($singleWorkloadDomain.type -eq "MANAGEMENT") {
                            $sddcManagerBackupStatus = Request-SddcManagerBackupStatus -server $server -user $user -pass $pass -failureOnly; $allBackupStatusObject += $sddcManagerBackupStatus
                        }
                        $vcenterBackupStatus = Request-vCenterBackupStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allBackupStatusObject += $vcenterBackupStatus
                        $nsxtManagerBackupStatus = Request-NsxtManagerBackupStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allBackupStatusObject += $nsxtManagerBackupStatus
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey("allDomains")) {
                        $sddcManagerBackupStatus = Request-SddcManagerBackupStatus -server $server -user $user -pass $pass; $allBackupStatusObject += $sddcManagerBackupStatus
                        foreach ($domain in $allWorkloadDomains ) {
                            $vcenterBackupStatus = Request-vCenterBackupStatus -server $server -user $user -pass $pass -domain $domain.name; $allBackupStatusObject += $vcenterBackupStatus
                            $nsxtManagerBackupStatus = Request-NsxtManagerBackupStatus -server $server -user $user -pass $pass -domain $domain.name; $allBackupStatusObject += $nsxtManagerBackupStatus
                        }
                    } else {
                        if ($singleWorkloadDomain.type -eq "MANAGEMENT") {
                            $sddcManagerBackupStatus = Request-SddcManagerBackupStatus -server $server -user $user -pass $pass; $allBackupStatusObject += $sddcManagerBackupStatus
                        }
                        $vcenterBackupStatus = Request-VcenterBackupStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allBackupStatusObject += $vcenterBackupStatus
                        $nsxtManagerBackupStatus = Request-NsxtManagerBackupStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allBackupStatusObject += $nsxtManagerBackupStatus
                    }
                }

                if ($PsBoundParameters.ContainsKey('outputJson')) {
                    $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $backupJsonSuffix
                    $allBackupStatusObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
                    Write-Output "JSON Created at $json"
                } else {
                    if ($allBackupStatusObject.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $allBackupStatusObject = $allBackupStatusObject | Sort-Object Component, Resource, Element | ConvertTo-Html -Fragment -PreContent '<a id="infra-backup"></a><h3>Backups Status</h3>' -PostContent "<p>No issues found.</p><p>Please verify that each successful file-based backup exists on the destination.</p>"
                    } else {
                        $allBackupStatusObject = $allBackupStatusObject | Sort-Object Component, Resource, Element | ConvertTo-Html -Fragment -PreContent '<a id="infra-backup"></a><h3>Backups Status</h3>' -PostContent "<p>Please verify that each successful file-based backup exists on the destination.</p>" -As Table
                    }
                    $allBackupStatusObject = Convert-CssClass -htmldata $allBackupStatusObject
                    $allBackupStatusObject
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-BackupStatus

Function Publish-NsxtTransportNodeStatus {
    <#
        .SYNOPSIS
        Request and publish the status of NSX transport nodes managed by an NSX Manager cluster.

        .DESCRIPTION
        The Publish-NsxtTransportNodeStatus cmdlet checks the status NSX transport nodes managed by an NSX Manager cluster
        and prepares the data to be published to an HTML report. The cmdlet connects to SDDC Manager using the
        -server, -user, and password values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs checks on the NSX transport node status and outputs the results

        .EXAMPLE
        Publish-NsxtTransportNodeStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will publish the status of all NSX transport nodes in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtTransportNodeStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will publish thestatus of all NSX transport nodes in a VMware Cloud Foundation instance but only reports issues.

        .EXAMPLE
        Publish-NsxtTransportNodeStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will publish the BGP status for the NSX transport nodes in a VMware Cloud Foundation instance for a workload domain named sfo-w01.

        .EXAMPLE
        Publish-NsxtTransportNodeStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -outputJson F:\Reporting
        This example will generate a json for the status of all NSX transport nodes in a VMware Cloud Foundation instance.
        and saves it under F:\Reporting with filename <timestamp>-nsxttransportnode-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>

    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson

    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allNsxtTransportNodeStatusObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtTransportNodeStatus = Request-NsxtTransportNodeStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtTransportNodeStatusObject += $nsxtTransportNodeStatus
                        }
                    } else {
                        $nsxtTransportNodeStatus = Request-NsxtTransportNodeStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtTransportNodeStatusObject += $nsxtTransportNodeStatus
                    }
                }
                else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtTransportNodeStatus = Request-NsxtTransportNodeStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtTransportNodeStatusObject += $nsxtTransportNodeStatus
                        }
                    } else {
                        $nsxtTransportNodeStatus = Request-NsxtTransportNodeStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtTransportNodeStatusObject += $nsxtTransportNodeStatus
                    }
                }

                if ($PsBoundParameters.ContainsKey('outputJson')) {
                    $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $nsxtTransportJsonSuffix
                    $allNsxtTransportNodeStatusObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
                    Write-Output "JSON Created at $json"
                } else {
                    if ($allNsxtTransportNodeStatusObject.Count -eq 0) {
                        $addNoIssues = $true
                    }
                    if ($addNoIssues) {
                        $allNsxtTransportNodeStatusObject = $allNsxtTransportNodeStatusObject | Sort-Object Domain, Resource, Element | ConvertTo-Html -Fragment -PreContent '<a id="nsx-tn"></a><h3>NSX Transport Node Status</h3>' -PostContent '<p>No issues found.</p>'
                    } else {
                        $allNsxtTransportNodeStatusObject = $allNsxtTransportNodeStatusObject | Sort-Object Domain, Resource, Element  | ConvertTo-Html -Fragment -PreContent '<a id="nsx-tn"></a><h3>NSX Transport Node Status</h3>' -As Table
                    }
                    $allNsxtTransportNodeStatusObject = Convert-CssClass -htmldata $allNsxtTransportNodeStatusObject
                    $allNsxtTransportNodeStatusObject
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtTransportNodeStatus

Function Publish-NsxtTransportNodeTunnelStatus {
    <#
        .SYNOPSIS
        Request and publish the status of NSX transport node tunnels.

        .DESCRIPTION
        The Publish-NsxtTransportNodeStatus cmdlet checks the status NSX transport node tunnels and prepares the data
        to be published to an HTML report. The cmdlet connects to SDDC Manager using the -server, -user, and password
        values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs checks on the NSX transport node tunnel status and outputs the results

        .EXAMPLE
        Publish-NsxtTransportNodeTunnelStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will publish the status of all NSX transport node tunnels in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtTransportNodeTunnelStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will publish thestatus of all NSX transport node tunnels in a VMware Cloud Foundation instance but only reports issues.

        .EXAMPLE
        Publish-NsxtTransportNodeTunnelStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will publish the BGP status for the NSX transport node tunnels in a VMware Cloud Foundation instance for a workload domain named sfo-w01.

        .EXAMPLE
        Publish-NsxtTransportNodeTunnelStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -outputJson F:\Reporting
        This example will generate a json for the status of all NSX transport node tunnels in a VMware Cloud Foundation instance
        and saves it under F:\Reporting with filename <timestamp>-nsxttntunnel-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>

    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson

    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allNsxtTransportNodeTunnelStatusObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtTransportNodeTunnelStatus = Request-NsxtTransportNodeTunnelStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtTransportNodeTunnelStatusObject += $nsxtTransportNodeTunnelStatus
                        }
                    } else {
                        $nsxtTransportNodeTunnelStatus = Request-NsxtTransportNodeTunnelStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtTransportNodeTunnelStatusObject += $nsxtTransportNodeTunnelStatus
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtTransportNodeTunnelStatus = Request-NsxtTransportNodeTunnelStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtTransportNodeTunnelStatusObject += $nsxtTransportNodeTunnelStatus
                        }
                    } else {
                        $nsxtTransportNodeTunnelStatus = Request-NsxtTransportNodeTunnelStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtTransportNodeTunnelStatusObject += $nsxtTransportNodeTunnelStatus
                    }
                }
                if ($PsBoundParameters.ContainsKey('outputJson')) {
                    $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $nsxttntunnelJsonSuffix
                    $allNsxtTransportNodeTunnelStatusObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
                    Write-Output "JSON Created at $json"
                } else {
                    if ($allNsxtTransportNodeTunnelStatusObject.Count -eq 0) { $addNoIssues = $true }
                    if ($allNsxtTransportNodeTunnelStatusObject.Count -ne 0) {
                        if ($addNoIssues) {
                            $allNsxtTransportNodeTunnelStatusObject = $allNsxtTransportNodeTunnelStatusObject | Sort-Object Domain, Resource, Element | ConvertTo-Html -Fragment -PreContent '<a id="nsx-tn-tunnel"></a><h3>NSX Transport Node Tunnel Status</h3>' -PostContent '<p>No issues found.</p>'
                        } else {
                            $allNsxtTransportNodeTunnelStatusObject = $allNsxtTransportNodeTunnelStatusObject | Sort-Object Domain, Resource, Element  | ConvertTo-Html -Fragment -PreContent '<a id="nsx-tn-tunnel"></a><h3>NSX Transport Node Tunnel Status</h3>' -As Table
                        }
                        $allNsxtTransportNodeTunnelStatusObject = Convert-CssClass -htmlData $allNsxtTransportNodeTunnelStatusObject
                    } else {
                        $allNsxtTransportNodeTunnelStatusObject = $allNsxtTransportNodeTunnelStatusObject | ConvertTo-Html -Fragment -PreContent '<a id="nsx-tn-tunnel"></a><h3>NSX Transport Node Tunnel Status</h3>' -PostContent '<p>No NSX Transport Node Tunnels found.</p>'
                    }
                    $allNsxtTransportNodeTunnelStatusObject
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtTransportNodeTunnelStatus

Function Publish-NsxtTier0BgpStatus {
    <#
        .SYNOPSIS
        Request and publish the BGP status for the NSX Tier-0 gateways.

        .DESCRIPTION
        The Publish-NsxtTier0BgpStatus cmdlet checks the BGP status for the NSX Tier-0 gateways in a VMware Cloud
        Foundation instance and prepares the data to be published to an HTML report. The cmdlet connects to SDDC
        Manager using the -server, -user, and password values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs checks on the BGP status and outputs the results

        .EXAMPLE
        Publish-NsxtTier0BgpStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will publish the BGP status for all NSX Tier-0 gateways in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtTier0BgpStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will publish the BGP status for all NSX Tier-0 gateways in a VMware Cloud Foundation instance but only for the failed items.

        .EXAMPLE
        Publish-NsxtTier0BgpStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will publish the BGP status for the NSX Tier-0 gateways in a VMware Cloud Foundation instance for a workload domain named sfo-w01.

        .EXAMPLE
        Publish-NsxtTier0BgpStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -outputJson F:\Reporting
        This example will generate a json for the BGP status for all NSX Tier-0 gateways in a VMware Cloud Foundation instance.
        and saves it under F:\Reporting with filename <timestamp>-nsxttier0bgp-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>

    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allNsxtTier0BgpStatusObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtTier0BgpStatus = Request-NsxtTier0BgpStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtTier0BgpStatusObject += $nsxtTier0BgpStatus
                        }
                    } else {
                        $nsxtTier0BgpStatus = Request-NsxtTier0BgpStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtTier0BgpStatusObject += $nsxtTier0BgpStatus
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtTier0BgpStatus = Request-NsxtTier0BgpStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtTier0BgpStatusObject += $nsxtTier0BgpStatus
                        }
                    } else {
                        $nsxtTier0BgpStatus = Request-NsxtTier0BgpStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtTier0BgpStatusObject += $nsxtTier0BgpStatus
                    }
                }
                if ($PsBoundParameters.ContainsKey('outputJson')) {
                    $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $nsxttier0bgpJsonSuffix
                    $allNsxtTier0BgpStatusObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
                    Write-Output "JSON Created at $json"
                } else {
                    if ($allNsxtTier0BgpStatusObject.Count -eq 0) { $addNoIssues = $true }
                    if ($nsxtTier0BgpStatus.Count -gt 0) {
                        if ($addNoIssues) {
                            $allNsxtTier0BgpStatusObject = $allNsxtTier0BgpStatusObject | Sort-Object 'NSX Manager', 'Domain', 'Tier-0 ID', 'Source Address' | ConvertTo-Html -Fragment -PreContent '<a id="nsx-t0-bgp"></a><h3>NSX Tier-0 Gateway BGP Status</h3>' -PostContent '<p>No issues found.</p>'
                        } else {
                            $allNsxtTier0BgpStatusObject = $allNsxtTier0BgpStatusObject | Sort-Object 'NSX Manager', 'Domain', 'Tier-0 ID', 'Source Address' | ConvertTo-Html -Fragment -PreContent '<a id="nsx-t0-bgp"></a><h3>NSX Tier-0 Gateway BGP Status</h3>' -As Table
                        }
                        $allNsxtTier0BgpStatusObject = Convert-CssClass -htmldata $allNsxtTier0BgpStatusObject
                    } else {
                        $allNsxtTier0BgpStatusObject = $allNsxtTier0BgpStatusObject | ConvertTo-Html -Fragment -PreContent '<a id="nsx-t0-bgp"></a><h3>NSX Tier-0 Gateway BGP Status</h3>' -PostContent '<p>No BGP configuration found on NSX Tier-0 Gateway(s).</p>' -As Table
                        $allNsxtTier0BgpStatusObject = Convert-CssClass -htmldata $allNsxtTier0BgpStatusObject
                    }
                    $allNsxtTier0BgpStatusObject
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtTier0BgpStatus

Function Publish-SnapshotStatus {
    <#
        .SYNOPSIS
        Request and publish the snapshot status for the SDDC Manager, vCenter Server instances, and NSX Edge nodes
        managed by SDDC Manager.

        .DESCRIPTION
        The Publish-SnapshotStatus cmdlet checks the snapshot status for SDDC Manager, vCenter Server instances,
        and NSX Edge nodes in a VMware Cloud Foundation instance and prepares the data to be published
        to an HTML report. The cmdlet connects to SDDC Manager using the -server, -user, and password values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs checks on the snapshot status and outputs the results

        .NOTES
        The cmdlet will not publish the snapshot status for NSX Local Manager cluster appliances managed by SDDC Manager.
        Snapshots are not recommended for NSX Manager cluster appliances and are disabled by default.

        .EXAMPLE
        Publish-SnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will publish the snapshot status for the SDDC Manager, vCenter Server instances, and NSX Edge nodes managed by SDDC Manager.

        .EXAMPLE
        Publish-SnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will publish the snapshot status for the SDDC Manager, vCenter Server instances, and NSX Edge nodes managed by SDDC Manager but only failed items

        .EXAMPLE
        Publish-SnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will publish the snapshot status for the SDDC Manager, vCenter Server instance, and NSX Edge nodes managed by SDDC Manager for a workload domain named sfo-w01.

        .EXAMPLE
        Publish-SnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -outputJson F:\Reporting
        This example will generate a json for the snapshot status for the SDDC Manager, vCenter Server instances, and NSX Edge nodes managed by SDDC Manager.
        and saves it under F:\Reporting with filename <timestamp>-snapshot-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allSnapshotStatusObject = New-Object System.Collections.ArrayList
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $singleWorkloadDomain = Get-VCFWorkloadDomain | Where-Object {$_.name -eq $workloadDomain}
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        $sddcManagerSnapshotStatus = Request-SddcManagerSnapshotStatus -server $server -user $user -pass $pass -failureOnly; $allSnapshotStatusObject += $sddcManagerSnapshotStatus
                        foreach ($domain in $allWorkloadDomains ) {
                            $vcenterSnapshotStatus = Request-VcenterSnapshotStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allSnapshotStatusObject += $vcenterSnapshotStatus
                            $nsxtEdgeSnapshotStatus = Request-NsxtEdgeSnapshotStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allSnapshotStatusObject += $nsxtEdgeSnapshotStatus
                        }
                    } else {
                        if ($singleWorkloadDomain.type -eq "MANAGEMENT") {
                            $sddcManagerSnapshotStatus = Request-SddcManagerSnapshotStatus -server $server -user $user -pass $pass -failureOnly; $allSnapshotStatusObject += $sddcManagerSnapshotStatus
                        }
                        $vcenterSnapshotStatus = Request-VcenterSnapshotStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allSnapshotStatusObject += $vcenterSnapshotStatus
                        $nsxtEdgeSnapshotStatus = Request-NsxtEdgeSnapshotStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allSnapshotStatusObject += $nsxtEdgeSnapshotStatus
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        $sddcManagerSnapshotStatus = Request-SddcManagerSnapshotStatus -server $server -user $user -pass $pass; $allSnapshotStatusObject += $sddcManagerSnapshotStatus
                        foreach ($domain in $allWorkloadDomains ) {
                            $vcenterSnapshotStatus = Request-VcenterSnapshotStatus -server $server -user $user -pass $pass -domain $domain.name; $allSnapshotStatusObject += $vcenterSnapshotStatus
                            $nsxtEdgeSnapshotStatus = Request-NsxtEdgeSnapshotStatus -server $server -user $user -pass $pass -domain $domain.name; $allSnapshotStatusObject += $nsxtEdgeSnapshotStatus
                        }
                    } else {
                        if ($singleWorkloadDomain.type -eq "MANAGEMENT") {
                            $sddcManagerSnapshotStatus = Request-SddcManagerSnapshotStatus -server $server -user $user -pass $pass; $allSnapshotStatusObject += $sddcManagerSnapshotStatus
                        }
                        $vcenterSnapshotStatus = Request-VcenterSnapshotStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allSnapshotStatusObject += $vcenterSnapshotStatus
                        $nsxtEdgeSnapshotStatus = Request-NsxtEdgeSnapshotStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allSnapshotStatusObject += $nsxtEdgeSnapshotStatus
                    }
                }

                if ($PsBoundParameters.ContainsKey('outputJson')) {
                    $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $snapshotJsonSuffix
                    $allSnapshotStatusObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
                    Write-Output "JSON Created at $json"
                } else {

                    if ($allSnapshotStatusObject.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $allSnapshotStatusObject = $allSnapshotStatusObject | Sort-Object Component, Resource, Element | ConvertTo-Html -Fragment -PreContent '<a id="infra-snapshot"></a><h3>Snapshot Status</h3>' -PostContent '<p>No issues found.</p>'
                    } else {
                        $allSnapshotStatusObject = $allSnapshotStatusObject | Sort-Object Component, Resource, Element | ConvertTo-Html -Fragment -PreContent '<a id="infra-snapshot"></a><h3>Snapshot Status</h3>' -PostContent '<p>Only checks snapshots for SDDC Manager, vCenter Server instances, and NSX Edge nodes managed by SDDC Manager. By default, snapshots for NSX Local Manager cluster appliances are disabled and are not recommended.</p>' -As Table
                    }
                    $allSnapshotStatusObject = Convert-CssClass -htmldata $allSnapshotStatusObject
                    $allSnapshotStatusObject
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-SnapshotStatus

Function Publish-NsxtHealthNonSOS {
    <#
        .SYNOPSIS
        Publish NSX Manager Health only for health checks which are not a part of SOS Utility NSX health. Data obtained is a subset of Publish-NsxtCombinedHealth cmdlet.

        .DESCRIPTION
        The Publish-NsxtHealthNonSOS cmdlet performs additional checks outside of SOS Utility to get the health of NSX Manager on the VMware Cloud Foundation instance
        and prepares the data to be published to an HTML report. Data obtained is subset of Publish-NsxtCombinedHealth cmdlet. The cmdlet connects to SDDC Manager using the
        -server, -user, and password values:
        - Validates that network connectivity and autehentication is available to SDDC Manager
        - Validates that network connectivity and autehentication is available to NSX Manager
        - Performs health checks and outputs the results

        .EXAMPLE
        Publish-NsxtHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example checks NSX Manager health outside SOS Utility for all workload domains across the VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example checks NSX Manager health outside SOS Utility for a single workload domain in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example checks NSX Manager health outside SOS Utility for all workload domains across the VMware Cloud Foundation instance but only reports issues.

        .EXAMPLE
        Publish-NsxtHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -outputJson F:\Reporting
        This example checks NSX Manager health outside SOS Utility for all workload domains across the VMware Cloud Foundation instance and
        and saves it as JSON under F:\Reporting with filename <timestamp>-nsxtcombinedhealthnonsos-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson
    )

        Try {
        $allNsxtHealthObject = New-Object System.Collections.ArrayList
        $allWorkloadDomains = Get-VCFWorkloadDomain
        if ($PsBoundParameters.ContainsKey("allDomains") -and $PsBoundParameters.ContainsKey("failureOnly")) {
            foreach ($domain in $allWorkloadDomains ) {
                $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtHealthObject += $nsxtVidmStatus
                $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtHealthObject += $nsxtComputeManagerStatus
            }
        } elseif ($PsBoundParameters.ContainsKey("allDomains")) {
            foreach ($domain in $allWorkloadDomains ) {
                $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtHealthObject += $nsxtVidmStatus
                $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtHealthObject += $nsxtComputeManagerStatus
            }
        }

        if ($PsBoundParameters.ContainsKey("workloadDomain") -and $PsBoundParameters.ContainsKey("failureOnly")) {
            $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtHealthObject += $nsxtVidmStatus
            $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtHealthObject += $nsxtComputeManagerStatus
        } elseif ($PsBoundParameters.ContainsKey("workloadDomain")) {
            $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtHealthObject += $nsxtVidmStatus
            $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtHealthObject += $nsxtComputeManagerStatus
        }

        if ($PsBoundParameters.ContainsKey("outputJson")) {
            $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $nsxtCombinedHealthNonSOSJsonSuffix
            $allNsxtHealthObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
            Write-Output "JSON Created at $json"
        } else {
            if ($allNsxtHealthObject.Count -eq 0) { $addNoIssues = $true }
            if ($addNoIssues) {
                $allNsxtHealthObject = $allNsxtHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status - Non SOS</h3>' -PostContent '<p>No issues found.</p>'
            } else {
                $allNsxtHealthObject = $allNsxtHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status - Non SOS</h3>' -As Table
            }
            $allNsxtHealthObject = Convert-CssClass -htmldata $allNsxtHealthObject
            $allNsxtHealthObject
        }

    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtHealthNonSOS

Function Publish-NsxtCombinedHealth {
    <#
        .SYNOPSIS
        Request and publish NSX Manager Health.

        .DESCRIPTION
        The Publish-NsxtCombinedHealth cmdlet checks the health of NSX Manager on the VMware Cloud Foundation instance
        and prepares the data to be published to an HTML report. The cmdlet connects to SDDC Manager using the
        -server, -user, and password values:
        - Validates that network connectivity and autehentication is available to SDDC Manager
        - Validates that network connectivity and autehentication is available to NSX Manager
        - Performs health checks and outputs the results

        .EXAMPLE
        Publish-NsxtCombinedHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -json <json-file> -allDomains
        This example checks NSX Manager health for all workload domains across the VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtCombinedHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -json <json-file> -workloadDomain sfo-w01
        This example checks NSX Manager health for a single workload domain in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-NsxtCombinedHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -json <json-file> -allDomains -failureOnly
        This example checks NSX Manager health for all workload domains across the VMware Cloud Foundation instance but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        $allNsxtHealthObject = New-Object System.Collections.ArrayList
        $allWorkloadDomains = Get-VCFWorkloadDomain
        if ($PsBoundParameters.ContainsKey("allDomains") -and $PsBoundParameters.ContainsKey("failureOnly")) {
            foreach ($domain in $allWorkloadDomains ) {
                $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtHealthObject += $nsxtVidmStatus
                $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allNsxtHealthObject += $nsxtComputeManagerStatus
            }
            $nsxtHtml = Publish-NsxtHealth -json $json -failureOnly; $allNsxtHealthObject += $nsxtHtml
        } elseif ($PsBoundParameters.ContainsKey("allDomains")) {
            foreach ($domain in $allWorkloadDomains ) {
                $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtHealthObject += $nsxtVidmStatus
                $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $domain.name; $allNsxtHealthObject += $nsxtComputeManagerStatus
            }
            $nsxtHtml = Publish-NsxtHealth -json $json; $allNsxtHealthObject += $nsxtHtml
        }

        if ($PsBoundParameters.ContainsKey("workloadDomain") -and $PsBoundParameters.ContainsKey("failureOnly")) {
            $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtHealthObject += $nsxtVidmStatus
            $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allNsxtHealthObject += $nsxtComputeManagerStatus
            $nsxtHtml = Publish-NsxtHealth -json $json -failureOnly; $allNsxtHealthObject += $nsxtHtml
        } elseif ($PsBoundParameters.ContainsKey("workloadDomain")) {
            $nsxtVidmStatus = Request-NsxtVidmStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtHealthObject += $nsxtVidmStatus
            $nsxtComputeManagerStatus = Request-NsxtComputeManagerStatus -server $server -user $user -pass $pass -domain $workloadDomain; $allNsxtHealthObject += $nsxtComputeManagerStatus
            $nsxtHtml = Publish-NsxtHealth -json $json; $allNsxtHealthObject += $nsxtHtml
        }

        if ($allNsxtHealthObject.Count -eq 0) { $addNoIssues = $true }
        if ($addNoIssues) {
            $allNsxtHealthObject = $allNsxtHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status</h3>' -PostContent '<p>No issues found.</p>'
        } else {
            $allNsxtHealthObject = $allNsxtHealthObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="nsx-local-manager"></a><h3>NSX Manager Health Status</h3>' -As Table
        }
        $allNsxtHealthObject = Convert-CssClass -htmldata $allNsxtHealthObject
        $allNsxtHealthObject
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtCombinedHealth

Function Publish-StorageCapacityHealth {
    <#
        .SYNOPSIS
        Request and publish the storage capacity status.

        .DESCRIPTION
        The Publish-StorageCapacityHealth cmdlet checks the storage usage status for SDDC Manager, vCenter Server,
        Datastores and ESXi hosts, in a VMware Cloud Foundation instance and prepares the data to be published
        to an HTML report or plain text to console. The cmdlet connects to SDDC Manager using the -server, -user, -pass, -localUser, and -localPass values:
        - Validates the network connectivity and authantication to the SDDC Manager instance
        - Performs checks on the storage usage status and outputs the results

        .EXAMPLE
        Publish-StorageCapacityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -localUser vcf -localPass VMw@re1! -allDomains
        This example will publish storage usage status for SDDC Manager, vCenter Server instances, ESXi hosts, and datastores in a VMware Cloud Foundation instance

        .EXAMPLE
        Publish-StorageCapacityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -localUser vcf -localPass VMw@re1! -allDomains -failureOnly
        This example will publish storage usage status for SDDC Manager, vCenter Server instances, ESXi hosts, and datastores in a VMware Cloud Foundation instance but only for the failed items.

        .EXAMPLE
        Publish-StorageCapacityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -localUser vcf -localPass VMw@re1! -workloadDomain sfo-w01
        This example will publish storage usage status for a specific workload domain in a VMware Cloud Foundation instance

        .EXAMPLE
        Publish-StorageCapacityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -localUser vcf -localPass VMw@re1! -workloadDomain sfo-w01 -outputJson F:\Reporting
        This example will publish storage usage status for a specific workload domain in a VMware Cloud Foundation instance
        and saves it as JSON under F:\Reporting with filename <timestamp>-storagecapacityhealth-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER localUser
        The username to authenticate to the SDDC Manager appliance as a local user.

        .PARAMETER localPass
        The password to authenticate to the SDDC Manager appliance as a local user.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$localUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$localPass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $singleWorkloadDomain = Get-VCFWorkloadDomain | Where-Object {$_.name -eq $workloadDomain}
                $allStorageCapacityHealth = New-Object System.Collections.ArrayList
                $allVcenterStorageHealth = New-Object System.Collections.ArrayList
                $allEsxiStorageCapacity = New-Object System.Collections.ArrayList
                $allDatastoreStorageCapacity = New-Object System.Collections.ArrayList

                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    if ($PsBoundParameters.ContainsKey("failureOnly")) {
                        $sddcManagerStorageHealth = Request-SddcManagerStorageHealth -server $server -user $user -pass $pass -localUser $localUser -localPass $localPass -failureOnly;
                        foreach ($domain in $allWorkloadDomains ) {
                            $vCenterStorageHealth = Request-VcenterStorageHealth -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allVcenterStorageHealth += $vCenterStorageHealth
                            $esxiStorageCapacity = Request-EsxiStorageCapacity -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allEsxiStorageCapacity += $esxiStorageCapacity
                            $datastoreStorageCapacity = Request-DatastoreStorageCapacity -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allDatastoreStorageCapacity += $datastoreStorageCapacity
                        }
                    } else {
                        $sddcManagerStorageHealth = Request-SddcManagerStorageHealth -server $server -user $user -pass $pass -localUser $localUser -localPass $localPass
                        foreach ($domain in $allWorkloadDomains ) {
                            $vCenterStorageHealth = Request-VcenterStorageHealth -server $server -user $user -pass $pass -domain $domain.name; $allVcenterStorageHealth += $vCenterStorageHealth
                            $esxiStorageCapacity = Request-EsxiStorageCapacity -server $server -user $user -pass $pass -domain $domain.name; $allEsxiStorageCapacity += $esxiStorageCapacity
                            $datastoreStorageCapacity = Request-DatastoreStorageCapacity -server $server -user $user -pass $pass -domain $domain.name; $allDatastoreStorageCapacity += $datastoreStorageCapacity
                        }
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey("failureOnly")) {
                        if ($singleWorkloadDomain.type -eq "MANAGEMENT") {
                            $sddcManagerStorageHealth = Request-SddcManagerStorageHealth -server $server -user $user -pass $pass -localUser $localUser -localPass $localPass -failureOnly
                        }
                        $vCenterStorageHealth = Request-VcenterStorageHealth -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allVcenterStorageHealth += $vCenterStorageHealth
                        $esxiStorageCapacity = Request-EsxiStorageCapacity -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allEsxiStorageCapacity += $esxiStorageCapacity
                        $datastoreStorageCapacity = Request-DatastoreStorageCapacity -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allDatastoreStorageCapacity += $datastoreStorageCapacity
                    } else {
                        if ($singleWorkloadDomain.type -eq "MANAGEMENT") {
                            $sddcManagerStorageHealth = Request-SddcManagerStorageHealth -server $server -user $user -pass $pass -localUser $localUser -localPass $localPass
                        }
                        $vCenterStorageHealth = Request-VcenterStorageHealth -server $server -user $user -pass $pass -domain $workloadDomain; $allVcenterStorageHealth += $vCenterStorageHealth
                        $esxiStorageCapacity = Request-EsxiStorageCapacity -server $server -user $user -pass $pass -domain $workloadDomain; $allEsxiStorageCapacity += $esxiStorageCapacity
                        $datastoreStorageCapacity = Request-DatastoreStorageCapacity -server $server -user $user -pass $pass -domain $workloadDomain; $allDatastoreStorageCapacity += $datastoreStorageCapacity
                    }
                }

                if ($PsBoundParameters.ContainsKey('outputJson')) {
                    $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $storageCapacityHealthJsonSuffix
                    $combinedjson = @{
                        "sddc-manager" = $sddcManagerStorageHealth
                        "vcenter" = $allVcenterStorageHealth
                        "esxi" = $allEsxiStorageCapacity
                        "datastore" = $allDatastoreStorageCapacity
                    }
                    $combinedJson | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
                    Write-Output "JSON Created at $json"

                } else {
                    if ($sddcManagerStorageHealth.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $sddcManagerStorageHealth = $sddcManagerStorageHealth | ConvertTo-Html -Fragment -PreContent '<a id="storage-sddcmanager"></a><h3>SDDC Manager Disk Health Status</h3>' -PostContent '<p>No Issues Found.</p>'
                    } else {
                        $sddcManagerStorageHealth = $sddcManagerStorageHealth | ConvertTo-Html -Fragment -PreContent '<a id="storage-sddcmanager"></a><h3>SDDC Manager Disk Health Status</h3>' -As Table
                    }
                    $sddcManagerStorageHealth = Convert-CssClass -htmldata $sddcManagerStorageHealth

                    if ($allVcenterStorageHealth.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $allVcenterStorageHealth = $allVcenterStorageHealth | Sort-Object FQDN, Filesystem | ConvertTo-Html -Fragment -PreContent '<a id="storage-vcenter"></a><h3>vCenter Server Disk Health</h3>' -PostContent '<p>No Issues Found.</p>'
                    } else {
                        $allVcenterStorageHealth = $allVcenterStorageHealth | Sort-Object  FQDN, Filesystem | ConvertTo-Html -Fragment -PreContent '<a id="storage-vcenter"></a><h3>vCenter Server Disk Health</h3>' -As Table
                    }
                    $allVcenterStorageHealth = Convert-CssClass -htmldata $allVcenterStorageHealth

                    if ($allEsxiStorageCapacity.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $allEsxiStorageCapacity = $allEsxiStorageCapacity | Sort-Object Domain, 'ESXi FQDN', 'Volume Name' | ConvertTo-Html -Fragment -PreContent '<a id="storage-esxi"></a><h3>ESXi Host Local Volume Capacity</h3>' -PostContent '<p>No Issues Found.</p>'
                    } else {
                        $allEsxiStorageCapacity = $allEsxiStorageCapacity | Sort-Object Domain, 'ESXi FQDN', 'Volume Name' | ConvertTo-Html -Fragment -PreContent '<a id="storage-esxi"></a><h3>ESXi Host Local Volume Capacity</h3>' -As Table
                    }
                    $allEsxiStorageCapacity = Convert-CssClass -htmldata $allEsxiStorageCapacity

                    if ($allDatastoreStorageCapacity.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $allDatastoreStorageCapacity = $allDatastoreStorageCapacity | Sort-Object 'vCenter Server', 'Datastore Name' | ConvertTo-Html -Fragment -PreContent '<a id="storage-datastore"></a><h3>Datastore Space Usage Report</h3>' -PostContent '<p>No Issues Found.</p>'
                    } else {
                        $allDatastoreStorageCapacity = $allDatastoreStorageCapacity | Sort-Object 'vCenter Server', 'Datastore Name' | ConvertTo-Html -Fragment -PreContent '<a id="storage-datastore"></a><h3>Datastore Space Usage Report</h3>' -As Table
                    }
                    $allDatastoreStorageCapacity = Convert-CssClass -htmldata $allDatastoreStorageCapacity

                    $allStorageCapacityHealth += $sddcManagerStorageHealth
                    $allStorageCapacityHealth += $allVcenterStorageHealth
                    $allStorageCapacityHealth += $allEsxiStorageCapacity
                    $allStorageCapacityHealth += $allDatastoreStorageCapacity
                    $allStorageCapacityHealth
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-StorageCapacityHealth

Function Request-NsxtVidmStatus {
    <#
        .SYNOPSIS
        Returns the status of the Identity Manager integration for an NSX Manager cluster.

        .DESCRIPTION
        The Request-NsxtVidmStatus cmdlet returns the status of the Identity Manager integration for an NSX Manager cluster.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instanc
        - Gathers the details for the NSX Manager cluster from the SDDC Manager
        - Validates network connectivity and authentication to the NSX Local Manager cluster
        - Collects the Identity Manager integration status details

        .EXAMPLE
        Request-NsxtVidmStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the status of the Identity Manager integration for an NSX Manager cluster managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-NsxtVidmStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the status of the Identity Manager integration for an NSX Manager cluster managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -first 1) -pass ($vcfNsxDetails.adminPass | Select-Object -first 1)) {
                            $customObject = New-Object System.Collections.ArrayList
                            $component = 'Identity Manager Integration' # Define the component name
                            $resource = $vcfNsxDetails.fqdn # Define the resource name
                            $integration = Get-NsxtVidmStatus

                            # Set the alert and message based on the status of the integration
                            if ($integration.vidm_enable -eq $true) {
                                $alert = 'GREEN' # Ok; enabled
                                $message = 'Integration is enabled ' # Set the status message
                            } else {
                                $alert = '-' # Notice; not enabled
                                $message = 'Integration is not enabled. ' # Critical; failure
                            }

                            # Set the alert and message based on the status of the runtime state
                            if ($integration.runtime_state -eq 'ALL_OK') {
                                $alert = 'GREEN' # Ok; integration status is OK
                                $messageState = 'and healthy.' # Set the alert message
                            } elseif ($integration.vidm_enable -eq $true -and $integration.runtime_state -ne 'ALL_OK') {
                                $alert = 'RED' # Critical; integration status is has failed
                                $messageState = 'but unhealthy.' # Set the alert message
                            } elseif ($integration.vidm_enable -eq $false -and $integration.runtime_state -ne 'ALL_OK') {
                                $alert = '-' # Notice; integration is not enabled
                            }

                            $message += $messageState # Combine the alert message

                            # Add the properties to the element object
                            $elementObject = New-Object -TypeName psobject
                            $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                            $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                            $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                    $customObject += $elementObject
                                }
                            } else {
                                $customObject += $elementObject
                            }
                        }
                        $customObject | Sort-Object Component, Resource
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtVidmStatus

Function Request-NsxtComputeManagerStatus {
    <#
        .SYNOPSIS
        Returns the status of the compute managers attached to an NSX Manager cluster.

        .DESCRIPTION
        The Request-NsxtComputeManagerStatus cmdlet returns the status of the compute managers attached to an NSX Manager cluster.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the details for the NSX Manager cluster from the SDDC Manager
        - Validates network connectivity and authentication to the NSX Local Manager cluster
        - Collects the status of the compute managers

        .EXAMPLE
        Request-NsxtComputeManagerStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the status of the compute managers attached to an NSX Manager cluster managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-NsxtComputeManagerStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the status of the compute managers attached to an NSX Manager cluster managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -First 1) -pass ($vcfNsxDetails.adminPass | Select-Object -First 1)) {
                            $computeManagers = (Get-NsxtComputeManager)
                            foreach ($computeManager in $computeManagers) {
                                $customObject = New-Object System.Collections.ArrayList
                                $component = 'Compute Manager' # Define the component name
                                $resource = $vcfNsxDetails.fqdn # Define the resource name

                                # Set the alert and message based on the status of the compute manager
                                if ($computeManager.server -notin (( Get-VCFWorkloadDomain | Where-Object { $_.nsxtCluster.vipFqdn -eq $vcfNsxDetails.fqdn }).vcenters.fqdn) ) {
                                    $alert = 'RED' # Critical; rogue addition detected
                                    $message = "$($computeManager.server) has been detected as a rogue addition." # Critical; rogue addition detected
                                } else {
                                    $status = (Get-NsxtComputeManagerStatus -id $computeManager.id)
                                    if ($status.registration_status -eq 'REGISTERED' -and $status.connection_status -eq 'UP') {
                                        $alert = 'GREEN' # Ok; registered and up
                                        $message = "$($computeManager.server) is registered and healthy." # Ok; registered and up
                                    } elseif ($status.registration_status -eq 'REGISTERED' -and $status.connection_status -ne 'UP') {
                                        $alert = 'RED' # Critical; registered and not up
                                        $message = "$($computeManager.server) is registered but unhealthy." # Critical; registered and not up
                                    } else {
                                        $alert = 'RED' # Critical; not registered
                                        $message = "$($computeManager.server) is not registered." # Critical; not registered
                                    }
                                }

                                # Add the properties to the element object
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if ($elementObject.alert -eq 'RED') {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                                $outputObject += $customObject
                            }
                        }
                        $outputObject | Sort-Object Component, Resource
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtComputeManagerStatus

Function Request-SddcManagerSnapshotStatus {
    <#
        .SYNOPSIS
        Request the snapshot status for the SDDC Manager.

        .DESCRIPTION
        The Request-SddcManagerSnapshotStatus cmdlet checks the snapshot status for SDDC Manager.
        The cmdlet connects to SDDC Manager using the -server, -user, and password values:
        - Validates network connectivity and authenticaton to the SDDC Manager instance
        - Gathers the details for the vCenter Server instance from the SDDC Manager
        - Validates network connectivity and authentication to the vCenter Server instance
        - Performs checks on the snapshot status and outputs the results

        .EXAMPLE
        Request-SddcManagerSnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will publish the snapshot status for the SDDC Manager in a VMware Cloud Foundation instance.

        .EXAMPLE
        Request-SddcManagerSnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -failureOnly
        This example will publish the snapshot status for the SDDC Manager in a VMware Cloud Foundation instance, but for only failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                    if (Test-VsphereConnection -server $vcfVcenterDetails.fqdn) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $customObject = New-Object System.Collections.ArrayList
                            $component = 'SDDC Manager'
                            $resource = 'SDDC Manager Snapshot'
                            $domain = (Get-VCFWorkloadDomain | Sort-Object -Property type, name).name -join ','
                            $snapshotStatus = Get-SnapshotStatus -vm ($server.Split('.')[0])
                            $snapshotCount = ($snapshotStatus | Measure-Object).count
                            $snapshotLast = $snapshotStatus.Created | select -Last 1
                            $snapshotAge = [math]::Ceiling(((Get-Date) - ([DateTime]$snapshotLast)).TotalDays)

                            # Set the alert color based on the age of the snapshot
                            if ($snapshotCount -eq 0) {
                                $alert = 'GREEN' # Ok; = 0 snapshots
                                $message = 'No snapshots exist.'
                            } elseif ($snapshotAge -le 1) {
                                $alert = 'GREEN' # OK; <= 1 days
                                $message = 'Latest snapshot is less than 1 day old. '
                            } elseif ($snapshotAge -gt 1 -and $snapshotAge -le 3) {
                                $alert = 'YELLOW' # Warning; > 1 days and <= 3 days
                                $message = 'Latest snapshot is greater than 1 day old. '
                            } elseif ($snapshotAge -gt 3) {
                                $alert = 'RED' # Critical; >= 7 days
                                $message = 'Latest snapshot is greater than 3 days old. '
                            }

                            # Set the alert color based on the number of snapshots.
                            if ($snapshotCount -eq 1) {
                                $messageCount = 'A single snapshot exists. '
                            } elseif ($snapshotCount -gt 1) {
                                $messageCount = 'More than 1 snapshot exist. '
                            }
                            $message += $messageCount # Combine the alert message

                            # Set the alert message based on the snapshot consolidation status.
                            if (Get-SnapshotConsolidation -vm ($server.Split('.')[0])) {
                                $alert = 'RED' # Critical; Consolidation is required
                                $consolidationRequired = $true
                                $messageConsolidation += 'Snapshot consolidation is required.'
                            } else {
                                $consolidationRequired = $false
                            }
                            $message += $messageConsolidation

                            $elementObject = New-Object -TypeName psobject
                            # Add the snapshot details to the PSObject
                            $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                            $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource
                            $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $server
                            $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                            $elementObject | Add-Member -NotePropertyName 'Snapshots' -NotePropertyValue $snapshotCount
                            $elementObject | Add-Member -NotePropertyName 'Latest' -NotePropertyValue $snapshotLast
                            $elementObject | Add-Member -NotePropertyName 'Consolidation Required' -NotePropertyValue $consolidationRequired
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                            $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                    $customObject += $elementObject
                                }
                            } else {
                                $customObject += $elementObject
                            }
                            $customObject | Sort-Object Component, Resource, Element
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-SddcManagerSnapshotStatus

Function Request-VcenterSnapshotStatus {
    <#
        .SYNOPSIS
        Request the snapshot status for the vCenter Server instance.

        .DESCRIPTION
        The Request-VcenterSnapshotStatus cmdlet checks the snapshot status for vCenter Server instance.
        The cmdlet connects to SDDC Manager using the -server, -user, and password values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the details for the vCenter Server instance from the SDDC Manager
        - Validates network connectivity and authentication to the vCenter Server instance
        - Performs checks on the snapshot status and outputs the results

        .EXAMPLE
        Request-VcenterSnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will publish the snapshot status for a vCenter Server instance for a specific workload domain.

        .EXAMPLE
        Request-VcenterSnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will publish the snapshot status for a vCenter Server instance for a specific workload domain, but only failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $vcenter = (Get-VCFWorkloadDomain | Where-Object { $_.name -eq $domain }).vcenters
                $vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT
                if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        $customObject = New-Object System.Collections.ArrayList
                        $component = 'vCenter Server'
                        $resource = 'vCenter Server Snapshot'
                        $domain = (Get-VCFWorkloadDomain | Where-Object { $_.vcenters.fqdn -eq $vcenter.fqdn }).name
                        $snapshotStatus = Get-SnapshotStatus -vm ($vcenter.fqdn.Split('.')[0])
                        $snapshotCount = ($snapshotStatus | Measure-Object).count
                        $snapshotLast = $snapshotStatus.Created | select -Last 1
                        $snapshotAge = [math]::Ceiling(((Get-Date) - ([DateTime]$snapshotLast)).TotalDays)

                        # Set the alert color based on the age of the snapshot
                        if ($snapshotCount -eq 0) {
                            $alert = 'GREEN' # Ok; = 0 snapshots
                            $message = 'No snapshots exist.'
                        } elseif ($snapshotAge -le 1) {
                            $alert = 'GREEN' # OK; <= 1 days
                            $message = 'Latest snapshot is less than 1 day old. '
                        } elseif ($snapshotAge -gt 1 -and $snapshotAge -le 3) {
                            $alert = 'YELLOW' # Warning; > 1 days and <= 3 days
                            $message = 'Latest snapshot is greater than 1 day old. '
                        } elseif ($snapshotAge -gt 3) {
                            $alert = 'RED' # Critical; >= 7 days
                            $message = 'Latest snapshot is greater than 3 days old. '
                        }

                        # Set the alert message based on the number of snapshots.
                        if ($snapshotCount -eq 1) {
                            $messageCount = 'A single snapshot exists. '
                        } elseif ($snapshotCount -gt 1) {
                            $messageCount = 'More than 1 snapshot exist. '
                        }
                        $message += $messageCount # Combine the alert message

                        # Set the alert message based on the snapshot consolidation status.
                        if (Get-SnapshotConsolidation -vm ($vcenter.fqdn.Split('.')[0])) {
                            $alert = 'RED' # Critical; Consolidation is required
                            $consolidationRequired = $true
                            $messageConsolidation += 'Snapshot consolidation is required.'
                        } else {
                            $consolidationRequired = $false
                        }
                        $message += $messageConsolidation

                        $elementObject = New-Object -TypeName psobject
                        # Add the snapshot details to the PSObject
                        $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                        $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource
                        $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $vcenter.fqdn
                        $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                        $elementObject | Add-Member -NotePropertyName 'Snapshots' -NotePropertyValue $snapshotCount
                        $elementObject | Add-Member -NotePropertyName 'Latest' -NotePropertyValue $snapshotLast
                        $elementObject | Add-Member -NotePropertyName 'Consolidation Required' -NotePropertyValue $consolidationRequired
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message

                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                            if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                $customObject += $elementObject
                            }
                        } else {
                            $customObject += $elementObject
                        }
                        $outputObject += $customObject # Add the custom object to the output object
                    }
                    Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                }
                $outputObject | Sort-Object Component, Resource, Element
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VcenterSnapshotStatus

Function Request-NsxtEdgeSnapshotStatus {
    <#
        .SYNOPSIS
        Request the snapshot status for NSX Edge nodes.

        .DESCRIPTION
        The Request-NsxtEdgeSnapshotStatus cmdlet checks the snapshot status for NSX Edge nodes.
        The cmdlet connects to SDDC Manager using the -server, -user, and password values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the NSX Manager and NSX Edge node details from the SDDC Manager
        - Validates network connectivity and authentication to the vCenter Server instance
        - Performs checks on the snapshot status and outputs the results

        .EXAMPLE
        Request-NsxtEdgeSnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will publish the snapshot status for all NSX Edge nodes managed by SDDC Manager for a specific workload domain.

        .EXAMPLE
        Request-NsxtEdgeSnapshotStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will publish the snapshot status for all NSX Edge nodes managed by SDDC Manager for a specific workload domain. but only failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $nsxtManager = Get-VCFNsxtCluster | Where-Object { $_.domains.name -eq $domain }
                if ($nsxtEdgeDetails = Get-VCFEdgeCluster | Where-Object { $_.nsxtCluster.vipfqdn -eq $nsxtManager.vipFqdn }) {
                    foreach ($nsxtEdgeNode in $nsxtEdgeDetails.edgeNodes) {
                        if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                            if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                                if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                                    $customObject = New-Object System.Collections.ArrayList
                                    $message = ''
                                    $component = 'NSX'
                                    $resource = 'NSX Edge Node Snapshot'
                                    $domain = $domain
                                    $snapshotStatus = Get-SnapshotStatus -vm ($nsxtEdgeNode.hostName.Split('.')[0])
                                    $snapshotCount = ($snapshotStatus | Measure-Object).count
                                    $snapshotLast = $snapshotStatus.Created | select -Last 1
                                    $snapshotAge = [math]::Ceiling(((Get-Date) - ([DateTime]$snapshotLast)).TotalDays)

                                    # Set the alert color based on the age of the snapshot
                                    if ($snapshotCount -eq 0) {
                                        $alert = 'GREEN' # Ok; = 0 snapshots
                                        $message = 'No snapshots exist.'
                                    } elseif ($snapshotAge -le 1) {
                                        $alert = 'GREEN' # OK; <= 1 days
                                        $message = 'Latest snapshot is less than 1 day old. '
                                    } elseif ($snapshotAge -gt 1 -and $snapshotAge -le 3) {
                                        $alert = 'YELLOW' # Warning; > 1 days and <= 3 days
                                        $message = 'Latest snapshot is greater than 1 day old. '
                                    } elseif ($snapshotAge -gt 3) {
                                        $alert = 'RED' # Critical; >= 7 days
                                        $message = 'Latest snapshot is greater than 3 days old. '
                                    }

                                    # Set the alert message based on the number of snapshots.
                                    if ($snapshotCount -eq 1) {
                                        $messageCount = 'A single snapshot exists. '
                                    } elseif ($snapshotCount -gt 1) {
                                        $messageCount = 'More than 1 snapshot exist. '
                                    }
                                    $message += $messageCount # Combine the alert message

                                    # Set the alert message based on snapshots consolidation status.
                                    if (Get-SnapshotConsolidation -vm ($nsxtEdgeNode.hostName.Split('.')[0])) {
                                        $alert = 'RED' # Critical; Consolidation is required
                                        $consolidationRequired = $true
                                        $messageConsolidation += 'Snapshot consolidation is required.'
                                    } else {
                                        $consolidationRequired = $false
                                    }
                                    $message += $messageConsolidation

                                    $elementObject = New-Object -TypeName psobject
                                    # Add the snapshot details to the PSObject
                                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource
                                    $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $nsxtEdgeNode.hostName
                                    $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                                    $elementObject | Add-Member -NotePropertyName 'Snapshots' -NotePropertyValue $snapshotCount
                                    $elementObject | Add-Member -NotePropertyName 'Latest' -NotePropertyValue $snapshotLast
                                    $elementObject | Add-Member -NotePropertyName 'Consolidation Required' -NotePropertyValue $consolidationRequired
                                    $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message

                                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                        if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                            $customObject += $elementObject
                                        }
                                    } else {
                                        $customObject += $elementObject
                                    }
                                }
                                $outputObject += $customObject # Add the custom object to the output object
                            }
                            Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                }
            }
        }
        $outputObject | Sort-Object Component, Resource, Element
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtEdgeSnapshotStatus

Function Request-SddcManagerBackupStatus {
    <#
        .SYNOPSIS
        Returns the status of the file-level latest backup task in an SDDC Manager instance.

        .DESCRIPTION
        The Request-SddcManagerBackupStatus cmdlet returns the status of the latest file-level backup task in an SDDC
        Manager instance. The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Collects the latest file-level backup status details

        .EXAMPLE
        Request-SddcManagerBackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return the status of the latest file-level backup task in an SDDC Manager instance.

        .EXAMPLE
        Request-SddcManagerBackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -failureOnly
        This example will return the status of the latest file-level backup task in an SDDC Manager instance but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $customObject = New-Object System.Collections.ArrayList
                $component = 'SDDC Manager' # Define the component name
                $resource = 'SDDC Manager Backup Operation' # Define the resource name
                $domain = (Get-VCFWorkloadDomain | Sort-Object -Property type, name).name -join ',' # Define the domain(s)
                $backupTask = Get-VCFTask | Where-Object { $_.type -eq 'SDDCMANAGER_BACKUP' } | Select-Object -First 1
                if ($backupTask) {
                    if ($PSEdition -eq 'Core') {
                        $date = $backupTask.creationTimestamp
                    } else {
                        $date = [DateTime]::ParseExact($backupTask.creationTimestamp, 'yyyy-MM-ddTHH:mm:ss.fffZ', [System.Globalization.CultureInfo]::InvariantCulture) # Define the date
                    }
                    $backupAge = [math]::Ceiling(((Get-Date) - ([DateTime]$date)).TotalDays) # Calculate the number of days since the backup was created

                    # Set the status for the backup task
                    if ($backupTask.status -eq 'Successful') {
                        $alert = "GREEN" # Ok; success
                    } else {
                        $alert = "RED" # Critical; failure
                    }

                    # Set the message for the backup task
                    if ([string]::IsNullOrEmpty($errors)) {
                        $message = "The backup completed without errors. " # Ok; success
                    } else {
                        $message = "The backup failed with errors. Please investigate before proceeding. " # Critical; failure
                    }

                    # Set the alert and message for the backup task based on the age of the backup
                    if ($backupAge -ge 3) {
                        $alert = "RED" # Critical; >= 3 days
                        $messageBackupAge = "Backup is more than 3 days old." # Set the alert message
                    } elseif ($backupAge -gt 1) {
                        $alert = "YELLOW" # Warning; > 1 days
                        $messageBackupAge = "Backup is more than 1 days old." # Set the alert message
                    } else {
                        $alert = "GREEN" # Ok; <= 1 days
                        $messageBackupAge = "Backup is less than 1 day old." # Set the alert message
                    }

                    $message += $messageBackupAge # Combine the alert message

                    # Set the alert and message if the backup is located on the SDDC Manager
                    $backupServer = (Get-VCFBackupConfiguration).server # Get the backup server

                    if ($backupServer -eq (Get-VCFManager).fqdn -or $backupServer -eq (Get-VCFManager).ipAddress) {
                        $alert = "RED" # Critical; backup server is located on the SDDC Manager
                        $messageBackupServer = "Backup is located on the SDDC Manager ($server). Reconfigure backups to use another location." # Set the alert message
                        $message = $messageBackupServer # Override the message
                    }
                } else {
                    $alert = "RED" # Critical; backup is not configured
                    $message = "Backup is not configured." # Set the alert message
                }

                # Add Backup Status Properties to the element object
                $elementObject = New-Object -TypeName psobject
                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the name
                $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $server # Set the element name
                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain(s)
                $elementObject | Add-Member -NotePropertyName 'Date' -NotePropertyValue $date # Set the timestamp
                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                        $customObject += $elementObject
                    }
                } else {
                    $customObject += $elementObject
                }
                $customObject | Sort-Object Component, Resource, Element
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-SddcManagerBackupStatus

Function Request-NsxtManagerBackupStatus {
    <#
        .SYNOPSIS
        Returns the status of the latest file-level backup of an NSX Manager cluster.

        .DESCRIPTION
        The Request-NsxtManagerBackupStatus cmdlet returns the status of the latest backup of an NSX Manager cluster.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the details for the NSX Manager cluster from the SDDC Manager
        - Validates network connectivity and authentication to the NSX Manager cluster
        - Collects the file-level backup status details

        .EXAMPLE
        Request-NsxtManagerBackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the status of the latest file-level backup of an NSX Manager cluster managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-NsxtManagerBackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the status of the latest file-level backup of an NSX Manager cluster managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -first 1) -pass ($vcfNsxDetails.adminPass | Select-Object -first 1)) {
                            $backupTask = Get-NsxtBackupHistory -fqdn $vcfNsxDetails.fqdn
                            $customObject = New-Object System.Collections.ArrayList

                            # NSX Node Backup
                            $component = 'NSX Manager' # Define the component name
                            $resource = 'Node Backup Operation' # Define the resource name

                            foreach ($element in $backupTask.node_backup_statuses) {
                                $timestamp = [DateTimeOffset]::FromUnixTimeMilliseconds($backupTask.node_backup_statuses.end_time).DateTime
                                $backupAge = [math]::Ceiling(((Get-Date) - ([DateTime]$timestamp)).TotalDays) # Calculate the number of days since the backup was created

                                # Set the alert and message based on the status of the backup
                                if ($backupTask.node_backup_statuses.success -eq $true) {
                                    $alert = "GREEN" # Ok; success
                                    $message = 'The backup completed without errors. ' # Set the backup status message
                                } else {
                                    $alert = "RED" # Critical; failure
                                    $message = "The backup failed with errors. Please investigate before proceeding. " # Critical; failure
                                }

                                # Set the alert and message update for the backup task based on the age of the backup
                                if ($backupAge -ge 3) {
                                    $alert = 'RED' # Critical; >= 3 days
                                    $messageBackupAge = 'Backup is more than 3 days old.' # Set the alert message
                                } elseif ($backupAge -gt 1) {
                                    $alert = 'YELLOW' # Warning; > 1 days
                                    $messageBackupAge = 'Backup is more than 1 days old.' # Set the alert message
                                } else {
                                    $alert = 'GREEN' # Ok; <= 1 days
                                    $messageBackupAge = 'Backup is less than 1 day old.' # Set the alert message
                                }

                                $message += $messageBackupAge # Combine the alert message

                                # Set the alert and message if the backup is located on the SDDC Manager.
                                $backupServer = (Get-NsxtBackupConfiguration -fqdn $vcfNsxDetails.fqdn).remote_file_server.server # Get the backup server

                                if ($backupServer -eq (Get-VCFManager).fqdn -or $backupServer -eq (Get-VCFManager).ipAddress) {
                                    $alert = 'RED' # Critical; backup server is located on the SDDC Manager.
                                    $messageBackupServer = "Backup is located on the SDDC Manager ($server). Reconfigure backups to use another location." # Set the alert message
                                    $message = $messageBackupServer # Override the message
                                }

                                # Add Backup Status Properties to the element object
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                                $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $vcfNsxDetails.fqdn # Set the element name
                                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain
                                $elementObject | Add-Member -NotePropertyName 'Date' -NotePropertyValue $timestamp # Set the end timestamp
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }

                            $outputObject += $customObject # Add the custom object to the output object

                            # NSX Cluster Backup
                            $component = 'NSX Manager' # Define the component name
                            $resource = 'Cluster Backup Operation' # Define the resource name
                            foreach ($element in $backupTask.cluster_backup_statuses) {
                                $timestamp = [DateTimeOffset]::FromUnixTimeMilliseconds($backupTask.cluster_backup_statuses.end_time).DateTime
                                $backupAge = [math]::Ceiling(((Get-Date) - ([DateTime]$timestamp)).TotalDays) # Calculate the number of days since the backup was created

                                # Set the alert and message based on the status of the backup
                                if ($backupTask.node_backup_statuses.success -eq $true) {
                                    $alert = 'GREEN' # Ok; success
                                    $message = 'The backup completed without errors. ' # Set the backup status message
                                } else {
                                    $alert = 'RED' # Critical; failure
                                    $message = 'The backup failed with errors. Please investigate before proceeding. ' # Critical; failure
                                }

                                # Set the alert and message update for the backup task based on the age of the backup
                                if ($backupAge -ge 3) {
                                    $alert = 'RED' # Critical; >= 3 days
                                    $messageBackupAge = 'Backup is more than 3 days old.' # Set the alert message
                                } elseif ($backupAge -gt 1) {
                                    $alert = 'YELLOW' # Warning; > 1 days
                                    $messageBackupAge = 'Backup is more than 1 days old.' # Set the alert message
                                } else {
                                    $alert = 'GREEN' # Ok; <= 1 days
                                    $messageBackupAge = 'Backup is less than 1 day old.' # Set the alert message
                                }

                                $message += $messageBackupAge # Combine the alert message

                                # Set the alert and message if the backup is located on the SDDC Manager
                                $backupServer = (Get-NsxtBackupConfiguration -fqdn $vcfNsxDetails.fqdn).remote_file_server.server # Get the backup server

                                if ($backupServer -eq (Get-VCFManager).fqdn -or $backupServer -eq (Get-VCFManager).ipAddress) {
                                    $alert = 'RED' # Critical; backup server is located on the SDDC Manager
                                    $messageBackupServer = "Backup is located on the SDDC Manager ($server). Reconfigure backups to use another location." # Set the alert message
                                    $message = $messageBackupServer # Override the message
                                }

                                # Add Backup Status Properties to the element object
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                                $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $vcfNsxDetails.fqdn # Set the element name
                                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain
                                $elementObject | Add-Member -NotePropertyName 'Date' -NotePropertyValue $timestamp # Set the end timestamp
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }

                            $outputObject += $customObject # Add the custom object to the output object

                            # NSX Cluster Backup
                            $component = 'NSX Manager' # Define the component name
                            $resource = 'Inventory Backup Operation' # Define the resource name
                            foreach ($element in $backupTask.cluster_backup_statuses) {
                                $timestamp = [DateTimeOffset]::FromUnixTimeMilliseconds($backupTask.cluster_backup_statuses.end_time).DateTime
                                $backupAge = [math]::Ceiling(((Get-Date) - ([DateTime]$timestamp)).TotalDays) # Calculate the number of days since the backup was created

                                # Set the alert and message based on the status of the backup
                                if ($backupTask.node_backup_statuses.success -eq $true) {
                                    $alert = 'GREEN' # Ok; success
                                    $message = 'The backup completed without errors. ' # Set the backup status message
                                } else {
                                    $alert = 'RED' # Critical; failure
                                    $message = 'The backup failed with errors. Please investigate before proceeding. ' # Critical; failure
                                }

                                # Set the alert and message update for the backup task based on the age of the backup
                                if ($backupAge -ge 3) {
                                    $alert = 'RED' # Critical; >= 3 days
                                    $messageBackupAge = 'Backup is more than 3 days old.' # Set the alert message
                                } elseif ($backupAge -gt 1) {
                                    $alert = 'YELLOW' # Warning; > 1 days
                                    $messageBackupAge = 'Backup is more than 1 days old.' # Set the alert message
                                } else {
                                    $alert = 'GREEN' # Ok; <= 1 days
                                    $messageBackupAge = 'Backup is less than 1 day old.' # Set the alert message
                                }

                                $message += $messageBackupAge # Combine the alert message

                                # Set the alert and message if the backup is located on the SDDC Manager
                                $backupServer = (Get-NsxtBackupConfiguration -fqdn $vcfNsxDetails.fqdn).remote_file_server.server # Get the backup server

                                if ($backupServer -eq (Get-VCFManager).fqdn -or $backupServer -eq (Get-VCFManager).ipAddress) {
                                    $alert = 'RED' # Critical; backup server is located on the SDDC Manager
                                    $messageBackupServer = "Backup is located on the SDDC Manager ($server). Reconfigure backups to use another location." # Set the alert message
                                    $message = $messageBackupServer # Override the message
                                }

                                # Add Backup Status Properties to the element object
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                                $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $vcfNsxDetails.fqdn # Set the element name
                                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain
                                $elementObject | Add-Member -NotePropertyName 'Date' -NotePropertyValue $timestamp # Set the end timestamp
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }
                            $customObject | Sort-Object Domain, Element, Resource
                        }
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtManagerBackupStatus

Function Request-VcenterBackupStatus {
    <#
        .SYNOPSIS
        Returns the status of the file-level latest backup of a vCenter Server instance.

        .DESCRIPTION
        The Request-VcenterBackupStatus cmdlet returns the status of the latest backup of a vCenter Server instance.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the details for the NvCenter Server instance from the SDDC Manager
        - Validates network connectivity and authentication to the vCenter Server instance
        - Collects the file-level backup status details

        .EXAMPLE
        Request-VcenterBackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the status of the latest file-level backup of a vCenter Server instance managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-VcenterBackupStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the status of the latest file-level backup of a vCenter Server instance managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly

    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-VcenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    Request-VcenterApiToken -fqdn $vcfVcenterDetails.fqdn -username $vcfVcenterDetails.ssoAdmin -password $vcfVcenterDetails.ssoAdminPass | Out-Null
                    $customObject = New-Object System.Collections.ArrayList
                    $component = 'vCenter Server' # Define the component name
                    $resource = 'vCenter Server Backup Operation' # Define the resource name
                    if ($global:backupTask = (Get-VcenterBackupStatus | Select-Object -Last 1).Value) {
                        if ($PSEdition -eq 'Core') {
                            $timestamp = $backupTask.end_time
                        } else {
                            $timestamp = [DateTime]::ParseExact($backupTask.end_time, 'yyyy-MM-ddTHH:mm:ss.fffZ', [System.Globalization.CultureInfo]::InvariantCulture) # Define the date
                        }
                        if ($timestamp) {
                            $backupAge = [math]::Ceiling(((Get-Date) - ([DateTime]$timestamp)).TotalDays) # Calculate the number of days since the backup was created
                        } else {
                            $backupAge = 0 # Set the backup age to 0 if not available
                        }

                        # Set the status for the backup task
                        if ($backupTask.status -eq 'SUCCEEDED') {
                            $alert = "GREEN" # Ok; success
                        } elseif ($backupTask.status -eq 'IN PROGRESS') {
                            $alert = "YELLOW" # Warning; in progress
                        } else {
                            $alert = "RED" # Critical; failure
                        }

                        if ($timestamp) {
                            # Set the message for the backup task
                            if ([String]::IsNullOrEmpty($backupTask.messages)) {
                                $message = "The backup completed without errors. " # Ok; success
                            } else {
                                $message = "The backup failed with errors. Please investigate before proceeding. " # Critical; failure
                            }
                        }

                        # Set the alert and message update for the backup task based on the age of the backup
                        if ($backupAge -eq 0) {
                            $alert = "RED" # Critical; 0 days
                            $messageBackupAge = "Backup has not completed." # Set the alert message
                        } elseif ($backupAge -ge 3) {
                            $alert = "RED" # Critical; >= 3 days
                            $messageBackupAge = "Backup is more than 3 days old." # Set the alert message
                        } elseif ($backupAge -gt 1) {
                            $alert = "YELLOW" # Warning; > 1 days
                            $messageBackupAge = "Backup is more than 1 days old." # Set the alert message
                        } else {
                            $alert = "GREEN" # Ok; <= 1 days
                            $messageBackupAge = "Backup is less than 1 day old." # Set the alert message
                        }

                        $message += $messageBackupAge # Combine the alert message

                        # Set the alert and message if the backup is located on the SDDC Manager
                        $backupLocation = $backupTask.location # Get the backup server
                        $backupServer = (($backupLocation -Split ('sftp://'))[-1] -Split ('/'))[0]

                        if ($backupServer -eq (Get-VCFManager).fqdn -or $backupServer -eq (Get-VCFManager).ipAddress) { # Compare against the `host` attribute
                            $alert = 'RED' # Critical; backup server is located on the SDDC Manager
                            $messageBackupServer = "Backup is located on the SDDC Manager. Reconfigure backups to use another location." # Set the alert message
                            $message = $messageBackupServer # Override the message
                        }
                    } else {
                        $alert = "RED" # Critical; backup job no
                        $message = "Backup is not configured." # Set the alert message
                    }

                    # Add Backup Status Properties to the element object
                    $elementObject = New-Object -TypeName psobject
                    $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component # Set the component name
                    $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                    $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $vcfVcenterDetails.fqdn # Set the element name
                    $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain(s)
                    $elementObject | Add-Member -NotePropertyName 'Date' -NotePropertyValue $timestamp # Set the timestamp
                    $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                    $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                            $customObject += $elementObject
                        }
                    } else {
                        $customObject += $elementObject
                    }
                    $customObject | Sort-Object Component, Resource, Element
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VcenterBackupStatus

Function Request-DatastoreStorageCapacity {
    <#
        .SYNOPSIS
        Checks the datastore usage in all vCenter Server instances.

        .DESCRIPTION
        The Request-DatastoreStorageCapacity cmdlet checks the datastore usage in all vCenters. The cmdlet
        connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the details for each vCenter Server
        - Collects information about datastore usage

        .EXAMPLE
        Request-DatastoreStorageCapacity -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will check datastores on all vCenter Servers managed by SDDC Manager in a VMware Cloud Foundation instance but only failed items.

        .EXAMPLE
        Request-DatastoreStorageCapacity -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will check datastore on a vCenter Servers managed by SDDC Manager for a workload domain.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    # Define thresholds Green < Yellow < Red
    $greenThreshold = 80
    $redThreshold = 90

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $customObject = New-Object System.Collections.ArrayList
                $vcenter = (Get-VCFWorkloadDomain | Where-Object { $_.name -eq $domain }).vcenters
                $vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain
                if (Test-VsphereConnection -server $($vcenter.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcenter.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        $datastores = Get-Datastore
                        foreach ($datastore in $datastores) {
                            # Calculate datastore usage and capacity
                            $usage = [math]::Round((($datastore.CapacityGB - $datastore.FreeSpaceGB) / $datastore.CapacityGB * 100))
                            $usage = [int]$usage
                            $capacity = [int]$datastore.CapacityGB

                            # Applying thresholds and creating collection from input
                            Switch ($usage) {
                                { $_ -le $greenThreshold } {
                                    $alert = 'GREEN' # Green if $usage is up to $greenThreshold
                                    $message = "Used space is less than $greenThreshold%."
                                }
                                { $_ -ge $redThreshold } {
                                    $alert = 'RED' # Red if $usage is equal or above $redThreshold
                                    $message = "Used space is above $redThreshold%. Please reclaim space on the datastore."
                                }
                                Default {
                                    $alert = 'YELLOW' # Yellow if above two are not matched
                                    $message = "Used space is between $greenThreshold% and $redThreshold%. Please consider reclaiming some space on the datastore."
                                }
                            }

                            # Populate data into the object
                            if (($PsBoundParameters.ContainsKey("failureOnly")) -and ($alert -eq 'GREEN')) { continue } # Skip population of object if "failureOnly" is selected and alert is "GREEN"
                            $userObject = New-Object -TypeName psobject
                            $userObject | Add-Member -notepropertyname 'vCenter Server' -notepropertyvalue $vcenter.fqdn
                            $userObject | Add-Member -notepropertyname 'Datastore Name' -notepropertyvalue $datastore.Name
                            $userObject | Add-Member -notepropertyname 'Datastore Type' -notepropertyvalue $datastore.Type.ToUpper()
                            $userObject | Add-Member -notepropertyname 'Size (GB)' -notepropertyvalue $capacity
                            $userObject | Add-Member -notepropertyname 'Used %' -notepropertyvalue $usage
                            $userObject | Add-Member -notepropertyname 'Alert' -notepropertyvalue $alert
                            $userObject | Add-Member -notepropertyname 'Message' -notepropertyvalue $message
                            $customObject += $userObject # Creating collection to work with afterwords
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                    $customObject | Sort-Object 'vCenter Server', 'Datastore Type', 'Datastore Name'
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }

}
Export-ModuleMember -Function Request-DatastoreStorageCapacity

Function Request-VcenterStorageHealth {
    <#
        .SYNOPSIS
        Checks the disk usage on a vCenter Server instance.

        .DESCRIPTION
        The Request-VcenterStorageHealth cmdlets checks the disk space usage on a vCenter Server. The cmdlet
        connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Validates network connectivity and authentication to the vCenter Server instance
        - Collects information for the disk usage
        - Checks disk usage against thresholds and outputs the results

        .EXAMPLE
        Request-VcenterStorageHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will check disk usage for a single workload domain

        .EXAMPLE
        Request-VcenterStorageHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will check the disk usage for all vCenter Server instances but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $command = 'df -h | grep -e "^/" | grep -v "/dev/loop"' # Define Command for Retriveing Disk Information
                            $vcenter = (Get-VCFWorkloadDomain | Where-Object { $_.name -eq $domain }).vcenters
                            $rootPass = (Get-VCFCredential | Where-Object { $_.credentialType -eq "SSH" -and $_.resource.resourceName -eq $vcenter.fqdn }).password
                            $dfOutput = Invoke-VMScript -VM ($vcenter.fqdn.Split(".")[0]) -ScriptText $command -GuestUser root -GuestPassword $rootPass -Server $vcfVcenterDetails.fqdn

                            if ($PsBoundParameters.ContainsKey("failureOnly")) {
                                Format-DfStorageHealth -dfOutput $dfOutput -systemFqdn $vcenter.fqdn -failureOnly
                            } else {
                                Format-DfStorageHealth -dfOutput $dfOutput -systemFqdn $vcenter.fqdn
                            }
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VcenterStorageHealth

Function Request-SddcManagerStorageHealth {
    <#
        .SYNOPSIS
        Checks the storage health (capacity) in an SDDC Manager appliance.

        .DESCRIPTION
        The Request-SddcManagerStorageHealth cmdlet checks the disk free space on the SDDC Manager appliance.
        The cmdlet connects to SDDC Manager using the -server, -user, -pass, -localUser, and -localPass values:
        - Performs checks on the local storage used space and outputs the results

        .EXAMPLE
        Request-SddcManagerStorageHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -localUser vcf -localPass VMw@re1!
        This example checks the hard disk space in the SDDC Manager appliance.

        .EXAMPLE
        Request-SddcManagerStorageHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -localUser vcf -localPass VMw@re1! -failureOnly
        This example checks the hard disk space in the SDDC Manager appliance and outputs only the failures.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER localUser
        The username to authenticate to the SDDC Manager appliance as a local user.

        .PARAMETER localPass
        The password to authenticate to the SDDC Manager appliance as a local user.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$localUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$localPass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        $command = 'df -h | grep -e "^/" | grep -v "/dev/loop"' # Define Command for Retriveing Disk Information
        $dfOutput = Invoke-SddcCommand -server $server -user $user -pass $pass -vmUser $localUser -vmPass $localPass -command $command # Get Disk Information from SDDC Manager

        if ($PsBoundParameters.ContainsKey("failureOnly")) {
            Format-DfStorageHealth -dfOutput $dfOutput -systemFqdn $server -failureOnly
        } else {
            Format-DfStorageHealth -dfOutput $dfOutput -systemFqdn $server
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-SddcManagerStorageHealth

Function Request-EsxiStorageCapacity {
    <#
        .SYNOPSIS
        Checks the disk usage for ESXi hosts.

        .DESCRIPTION
        The Request-EsxiStorageCapacity cmdlets checks the disk space usage on ESXi hosts. The cmdlet connects to SDDC
        Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Collects disk usage information for each ESXi host in the workload domain
        - Checks disk usage against thresholds and outputs the results

        .EXAMPLE
        Request-EsxiStorageCapacity -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will check disk usage for ESXi hosts managed by SDDC Manager for a single workload domain.

        .EXAMPLE
        Request-EsxiStorageCapacity -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will check disk usage for ESXi hosts managed by SDDC Manager for a single workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $esxiPartitionsObject = New-Object System.Collections.ArrayList
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $esxiHosts = Get-VMHost -Server $vcfVcenterDetails.fqdn
                            foreach ($esxiHost in $esxiHosts) {
                                $esxcli = Get-EsxCli -VMhost $esxiHost.Name -V2
                                $allPrtitions = $esxcli.storage.filesystem.list.invoke()
                                foreach ($partition in $allPrtitions) {
                                    if ($partition.Type -eq "VMFS-L" -or $partition.Type -eq "vfat") {
                                        $threshold = Format-StorageThreshold -size $partition.Size -free $partition.Free
                                        $esxiPartition = New-Object -TypeName psobject
                                        $esxiPartition | Add-Member -notepropertyname 'Domain' -notepropertyvalue $domain
                                        $esxiPartition | Add-Member -notepropertyname 'ESXi FQDN' -notepropertyvalue $esxiHost.Name
                                        $esxiPartition | Add-Member -notepropertyname 'Volume Name' -notepropertyvalue $partition.VolumeName.ToLower()
                                        $esxiPartition | Add-Member -notepropertyname 'Filesystem' -notepropertyvalue $partition.Type.ToLower()
                                        $esxiPartition | Add-Member -notepropertyname 'Used %' -notepropertyvalue $threshold.usage
                                        $esxiPartition | Add-Member -notepropertyname 'Alert' -notepropertyvalue $threshold.alert
                                        $esxiPartition | Add-Member -notepropertyname 'Message' -notepropertyvalue $threshold.message
                                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                            if (($esxiPartition.alert -eq 'RED') -or ($esxiPartition.alert -eq 'YELLOW')) {
                                                $esxiPartitionsObject += $esxiPartition
                                            }
                                        } else {
                                            $esxiPartitionsObject += $esxiPartition
                                        }
                                    }
                                }
                            }
                        }
                    }
                    Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                }
                $esxiPartitionsObject | Sort-Object Domain, 'ESXi FQDN', 'Volume Name', Alert
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-EsxiStorageCapacity

Function Publish-ComponentConnectivityHealthNonSOS {
    <#
        .SYNOPSIS
        Request and publish Component Connectivity Health only for health checks which are not a part of SOS Utility NSX health.
        Data obtained is a subset of Publish-ComponentConnectivityHealth cmdlet.

        .DESCRIPTION
        The Publish-ComponentConnectivityHealthNonSOS cmdlet checks component connectivity across the VMware Cloud Foundation
        instance and prepares the data to be published to an HTML report. The cmdlet connects to SDDC Manager using the
        -server, -user, and password values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs connectivityy health checks and outputs the results

        .EXAMPLE
        Publish-ComponentConnectivityHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example checks the component connectivity outside of SOS utility for all workload domains across the VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-ComponentConnectivityHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example checks the component connectivity outside of SOS utility for a single workload domain in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-ComponentConnectivityHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example checks the component connectivity outside of SOS utility for all workload domains across the VMware Cloud Foundation instance but only reports issues.

        .EXAMPLE
        Publish-ComponentConnectivityHealthNonSOS -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -jsonOutput F:\Reporting
        This example checks the component connectivity outside of SOS utility for all workload domains across the VMware Cloud Foundation instance
        and saves it under F:\Reporting with filename <timestamp>-componentconnectivityhealthnonsos-status.json

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER outputJson
        The path to save the output as a JSON file.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputJson
    )

    Try {
        $allConnectivityObject = New-Object System.Collections.ArrayList
        if ($PsBoundParameters.ContainsKey('failureOnly')) {
            if ($PsBoundParameters.ContainsKey("allDomains")) {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -alldomains -failureOnly; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -alldomains -failureOnly; $allConnectivityObject += $NsxtConnectivity
            } else {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain -failureOnly; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain -failureOnly; $allConnectivityObject += $NsxtConnectivity
            }
        } else {
            if ($PsBoundParameters.ContainsKey("allDomains")) {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -alldomains; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -alldomains; $allConnectivityObject += $NsxtConnectivity
            } else {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain; $allConnectivityObject += $NsxtConnectivity
            }
        }

        if ($PsBoundParameters.ContainsKey("outputJson")) {
            $json = Start-CreateOutputJsonDirectory -jsonFolder $outputJson -jsonFileSuffix $ComponentConnectivityHealthNonSOSJsonSuffix
            $allConnectivityObject | ConvertTo-JSON -Depth 10 | Out-File $json -Encoding ASCII
            Write-Output "JSON Created at $json"
        } else {
            if ($allConnectivityObject.Count -eq 0) { $addNoIssues = $true }
            if ($addNoIssues) {
                $allConnectivityObject = $allConnectivityObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -PostContent '<p>No issues found.</p>'
            } else {
                $allConnectivityObject = $allConnectivityObject | Sort-Object Component, Resource | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -As Table
            }
            $allConnectivityObject = Convert-CssClass -htmldata $allConnectivityObject
            $allConnectivityObject
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ComponentConnectivityHealthNonSOS

Function Publish-ComponentConnectivityHealth {
    <#
        .SYNOPSIS
        Request and publish Component Connectivity Health.

        .DESCRIPTION
        The Publish-ComponentConnectivityHealth cmdlet checks component connectivity across the VMware Cloud Foundation
        instance and prepares the data to be published to an HTML report. The cmdlet connects to SDDC Manager using the
        -server, -user, and password values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Performs connectivityy health checks and outputs the results

        .EXAMPLE
        Publish-ComponentConnectivityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -json <json-file> -allDomains
        This example checks the component connectivity for all workload domains across the VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-ComponentConnectivityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -json <json-file> -workloadDomain sfo-w01
        This example checks the component connectivity for a single workload domain in a VMware Cloud Foundation instance.

        .EXAMPLE
        Publish-ComponentConnectivityHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -json <json-file> -allDomains -failureOnly
        This example checks the component connectivity for all workload domains across the VMware Cloud Foundation instance but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER json
        The full path to the JSON file to output the results to.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$json,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        $allConnectivityObject = New-Object System.Collections.ArrayList
        if ($PsBoundParameters.ContainsKey('failureOnly')) {
            if ($PsBoundParameters.ContainsKey("allDomains")) {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -alldomains -failureOnly; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -alldomains -failureOnly; $allConnectivityObject += $NsxtConnectivity
            } else {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain -failureOnly; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain -failureOnly; $allConnectivityObject += $NsxtConnectivity
            }
            $apiSshConnectivity = Publish-ConnectivityHealth -json $json -failureOnly
            $pingConnectivity = Publish-PingConnectivityHealth -json $json -failureOnly
        } else {
            if ($PsBoundParameters.ContainsKey("allDomains")) {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -alldomains; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -alldomains; $allConnectivityObject += $NsxtConnectivity
            } else {
                $vcenterConnectivity = Request-VcenterAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain; $allConnectivityObject += $vcenterConnectivity
                $NsxtConnectivity = Request-NsxtAuthentication -server $server -user $user -pass $pass -workloadDomain $workloadDomain; $allConnectivityObject += $NsxtConnectivity
            }
            $apiSshConnectivity = Publish-ConnectivityHealth -json $json
            $pingConnectivity = Publish-PingConnectivityHealth -json $json
        }
        $allConnectivityObject += $apiSshConnectivity
        $allConnectivityObject += $pingConnectivity
        if ($allConnectivityObject.Count -eq 0) { $addNoIssues = $true }
        if ($addNoIssues) {
            $allConnectivityObject = $allConnectivityObject | Sort-Object Component, Resource, Message | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -PostContent '<p>No issues found.</p>'
        } else {
            $allConnectivityObject = $allConnectivityObject | Sort-Object Component, Resource, Message | ConvertTo-Html -Fragment -PreContent '<a id="general-connectivity"></a><h3>Connectivity Health Status</h3>' -As Table
        }
        $allConnectivityObject = Convert-CssClass -htmldata $allConnectivityObject
        $allConnectivityObject
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ComponentConnectivityHealth

Function Request-VcenterAuthentication {
    <#
        .SYNOPSIS
        Checks API authentication to vCenter Server instance.

        .DESCRIPTION
        The Request-VcenterAuthentication cmdlets checks the authentication to vCenter Server instance. The cmdlet
        connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance

        .EXAMPLE
        Request-VcenterAuthentication -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will check authentication to vCenter Server API for all vCenter Server instances managed by SDDC Manager.

        .EXAMPLE
        Request-VcenterAuthentication -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will check authentication to vCenter Server API for a single workload domain

        .EXAMPLE
        Request-VcenterAuthentication -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will check authentication to vCenter Server API for all vCenter Server instances but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.

        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $account = (Get-VCFCredential | Where-Object {$_.accountType -eq "SYSTEM" -and $_.resource.resourceType -eq "PSC"})
                $customObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains) {
                        if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain.name)) {
                            if (Test-vSphereApiAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass -ErrorAction SilentlyContinue) {
                                $alert = "GREEN"
                                $message = "API Connection check successful!"
                            } else {
                                $alert = "RED"
                                $message = "API Connection check failed!"
                            }
                            $elementObject = New-Object System.Collections.ArrayList
                            $elementObject = New-Object -TypeName psobject
                            $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue "vCenter"
                            $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $vcfVcenterDetails.fqdn
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                            $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                if (($elementObject.alert -eq 'RED')) {
                                    $customObject += $elementObject
                                }
                            } else {
                                $customObject += $elementObject
                            }
                        }
                    }
                } else {
                    if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $workloadDomain)) {
                        if (Test-vSphereApiAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass -ErrorAction SilentlyContinue) {
                            $alert = "GREEN"
                            $message = "API Connection check successful!"
                        } else {
                            $alert = "RED"
                            $message = "API Connection check failed!"
                        }
                        $elementObject = New-Object System.Collections.ArrayList
                        $elementObject = New-Object -TypeName psobject
                        $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue "vCenter"
                        $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $vcfVcenterDetails.fqdn
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                            if (($elementObject.alert -eq 'RED')) {
                                $customObject += $elementObject
                            }
                        } else {
                            $customObject += $elementObject
                        }
                    }
                }
                $customObject | Sort-Object Component, Resource
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VcenterAuthentication

Function Request-NsxtAuthentication {
    <#
        .SYNOPSIS
        Checks API authentication to NSX Manager cluster.

        .DESCRIPTION
        The Request-NsxtAuthentication cmdlets checks the authentication to NSX Manager cluster. The cmdlet
        connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the NSX Manager cluster

        .EXAMPLE
        Request-NsxtAuthentication -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will check authentication to NSX Manager API for all NSX Manager clusters managed by SDDC Manager.

        .EXAMPLE
        Request-NsxtAuthentication -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will check authentication to NSX Manager API for a single workload domain

        .EXAMPLE
        Request-NsxtAuthentication -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will check authentication to NSX Manager API for all NSX Manager clusters but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $customObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains) {
                        $vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain.name -listNodes
                        foreach ($node in $vcfNsxDetails.nodes) {
                            if (Test-NsxtConnection -server $node.fqdn -ErrorAction SilentlyContinue -ErrorVariable ErrorMessage ) {
                                if (Test-NsxtAuthentication -server $node.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -first 1) -pass ($vcfNsxDetails.adminPass | Select-Object -first 1)) {
                                    $alert = "GREEN"
                                    $message = "API Connection check successful!"
                                } else {
                                    $alert = "RED"
                                    $message = "API Connection check failed!"
                                }
                            } else {
                                $alert = "RED"
                                $message = "API Connection check failed! " + $ErrorMessage
                            }
                            $elementObject = New-Object System.Collections.ArrayList
                            $elementObject = New-Object -TypeName psobject
                            $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue "NSX"
                            $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $node.fqdn
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                            $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                if (($elementObject.alert -eq 'RED')) {
                                    $customObject += $elementObject
                                }
                            } else {
                                $customObject += $elementObject
                            }
                        }
                    }
                } else {
                    $vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $workloadDomain -listNodes
                    foreach ($node in $vcfNsxDetails.nodes) {
                        if (Test-NsxtConnection -server $node.fqdn -ErrorAction SilentlyContinue -ErrorVariable ErrorMessage ) {
                            if (Test-NsxtAuthentication -server $node.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -first 1) -pass ($vcfNsxDetails.adminPass | Select-Object -first 1)) {
                                $alert = "GREEN"
                                $message = "API Connection check successful!"
                            } else {
                                $alert = "RED"
                                $message = "API Connection check failed!"
                            }
                        } else {
                            $alert = "RED"
                            $message = $ErrorMessage
                        }
                        $elementObject = New-Object System.Collections.ArrayList
                        $elementObject = New-Object -TypeName psobject
                        $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue "NSX"
                        $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $node.fqdn
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                            if (($elementObject.alert -eq 'RED')) {
                                $customObject += $elementObject
                            }
                        } else {
                            $customObject += $elementObject
                        }
                    }
                }
                $customObject | Sort-Object Component, Resource
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtAuthentication

Function Request-NsxtTransportNodeStatus {
    <#
        .SYNOPSIS
        Returns the status of NSX transport nodes managed by an NSX Manager cluster.

        .DESCRIPTION
        The Request-NsxtTransportNodeStatus cmdlet returns the status NSX transport nodes managed by an NSX Manager cluster.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the details for the NSX Manager cluster from the SDDC Manager
        - Validates network connectivity and authentication to the NSX Local Manager cluster
        - Collects the status of the transport nodes

        .EXAMPLE
        Request-NsxtTransportNodeStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the status of the NSX transport nodes managed by an NSX Manager cluster which is managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-NsxtTransportNodeStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the status of the NSX transport nodes managed by an NSX Manager cluster which is managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -First 1) -pass ($vcfNsxDetails.adminPass | Select-Object -First 1)) {
                            $customObject = New-Object System.Collections.ArrayList
                            # NSX Transport Nodes
                            $types = @("edge","host")
                            foreach ($type in $types) {
                                $resource = $vcfNsxDetails.fqdn # Define the resource name
                                $transportNodeStatus = (Get-NsxtTransportNodeStatus -type $type) # Get the status of the transport nodes
                                $nodeType = (Get-Culture).textinfo.ToTitleCase($type.ToLower()) # Convert the type to title case
                                # Set the alert and message based on the status of the transport node
                                if ($downCount -ge 0 -or $unknownCount -ge 0) {
                                    $alert = 'RED' # Critical, transport node(s) down or unknown
                                    $message = $nodeType + ' transport node(s) in down or unknown state.' # Set the alert message
                                } elseif ($degradedCount -ge 0) {
                                    $alert = 'YELLOW' # Warning, transport node(s) degraded
                                    $message = $nodeType + ' transport node(s) in degraded state.' #
                                } else {
                                    $alert = 'GREEN' # OK, transport node(s) up
                                    $message = $nodeType + ' transport node(s) in up state.' # Set the alert message
                                }
                                # Add the properties to the element object
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                                $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $nodeType # Set the node type
                                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the message
                                $elementObject | Add-Member -NotePropertyName 'Up' -NotePropertyValue $transportNodeStatus.up_count # Set the up count
                                $elementObject | Add-Member -NotePropertyName 'Down' -NotePropertyValue $transportNodeStatus.down_count # Set the down count
                                $elementObject | Add-Member -NotePropertyName 'Degraded' -NotePropertyValue $transportNodeStatus.degraded_count # Set the degraded count
                                $elementObject | Add-Member -NotePropertyName 'Unknown' -NotePropertyValue $transportNodeStatus.unknown_count # Set the unknown count
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                                $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if ($elementObject.alert -eq 'RED') {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }
                        }
                        $customObject | Sort-Object Domain, Resource, Element
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtTransportNodeStatus

Function Request-NsxtTransportNodeTunnelStatus {
    <#
        .SYNOPSIS
        Returns the status of NSX transport node tunnels.

        .DESCRIPTION
        The Request-NsxtTransportNodeTunnelStatus cmdlet returns the status NSX transport nodes tunnels.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates network connectivity and authentication to the SDDC Manager instance
        - Gathers the details for the NSX Manager cluster from the SDDC Manager
        - Validates network connectivity and authentication to the NSX Local Manager cluster
        - Collects the status of the transport node tunnels

        .EXAMPLE
        Request-NsxtTransportNodeTunnelStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the status of the NSX transport node tunnels for a workload domain.

        .EXAMPLE
        Request-NsxtTransportNodeTunnelStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the status of the NSX transport node tunnels for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -First 1) -pass ($vcfNsxDetails.adminPass | Select-Object -First 1)) {
                            $customObject = New-Object System.Collections.ArrayList
                            # NSX Transport Nodes
                            $types = @("edge","host")
                            foreach ($type in $types) {
                                $transportNodes = Get-NsxtTransportNode -type $type
                                foreach ($transportNode in $transportNodes) {
                                    $resource = $vcfNsxDetails.fqdn # Define the resource name
                                    $nodeType = (Get-Culture).textinfo.ToTitleCase($type.ToLower()) # Convert the type to title case
                                    $tunnels = (Get-NsxtTransportNodeTunnel -id $transportNode.id).tunnels # Get the tunnels for the transport node
                                    foreach ($tunnel in $tunnels) {
                                        # Set the alert and message based on the status of the tunnel
                                        if ($tunnel.status -eq 'UP') {
                                            $alert = 'GREEN' # OK, transport node up
                                            $message = $nodeType + ' transport node tunnel is up.' # Set the alert message
                                        } else {
                                            $alert = 'RED' # Critical, transport node down or unknown
                                            $message = $nodeType + ' transport node tunnel is down or in unknown state.' # Set the alert message
                                        }
                                        # Update the alert and message based on the status of BFD
                                        if ($tunnel.bfd.state -ne 'UP') {
                                            $alert = 'YELLOW' # WARNING, BFD down or unknown
                                            $message = "Bidirectional forwarding is down or in unknown state. Run '(Get-NsxtTransportNodeTunnelStatus -id $($transportNode.id)).tunnels' for more information." # Set the alert message
                                        }
                                        # Add the properties to the element object
                                        $elementObject = New-Object -TypeName psobject
                                        $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $resource # Set the resource name
                                        $elementObject | Add-Member -NotePropertyName 'Element' -NotePropertyValue $nodeType # Set the element name
                                        $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain
                                        $elementObject | Add-Member -NotePropertyName 'Source IP' -NotePropertyValue $tunnel.local_ip # Set the source IP
                                        $elementObject | Add-Member -NotePropertyName 'Remote IP' -NotePropertyValue $tunnel.remote_ip # Set the remote IP
                                        $elementObject | Add-Member -NotePropertyName 'Source Node' -NotePropertyValue $transportNode.display_name # Set the source name
                                        $elementObject | Add-Member -NotePropertyName 'Remote Node' -NotePropertyValue $tunnel.remote_node_display_name # Set the source name
                                        $elementObject | Add-Member -NotePropertyName 'Interface' -NotePropertyValue $tunnel.egress_interface # Set the egress interface
                                        $elementObject | Add-Member -NotePropertyName 'Status' -NotePropertyValue $tunnel.status # Set the status
                                        $elementObject | Add-Member -NotePropertyName 'BFD' -NotePropertyValue $tunnel.bfd.state # Set the BFD status
                                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue "$message" # Set the message
                                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                            if ($elementObject.alert -eq 'RED' -or $elementObject.alert -eq 'YELLOW') {
                                                $customObject += $elementObject
                                            }
                                        } else {
                                            $customObject += $elementObject
                                        }
                                    }
                                }
                                $outputObject += $customObject
                            }
                        }
                        $outputObject | Sort-Object Domain, Resource, Element, 'Local Transport Node', Interface
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtTransportNodeTunnelStatus

Function Request-NsxtTier0BgpStatus {
    <#
        .SYNOPSIS
        Returns the BGP status for all Tier-0 gateways managed by the NSX Local Manager cluster.

        .DESCRIPTION
        The Request-NsxtTier0BgpStatus cmdlet returns the BGP status for all Tier-0 gateways managed by the NSX Manager
        cluster. The cmdlet connects to the NSX Local Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the NSX Local Manager cluster
        - Gathers the details for the NSX Local Manager cluster
        - Collects the BGP status for all Tier-0s managed by the NSX Local Manager cluster

        .EXAMPLE
        Request-NsxtTier0BgpStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return the BGP status for all Tier-0 gateways managed by the NSX Local Manager cluster that is managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-NsxtTier0BgpStatus -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return the BGP status for all Tier-0 gateways managed by the NSX Local Manager cluster that is managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -first 1) -pass ($vcfNsxDetails.adminPass | Select-Object -first 1)) {
                            $customObject = New-Object System.Collections.ArrayList
                            if ($tier0s = Get-NsxtTier0Gateway) {
                                foreach ($tier0 in $tier0s) {
                                    $bgpStatus = Get-NsxtTier0BgpStatus -id $tier0.id | Where-Object {$_.type -eq 'USER'}
                                    $localAsn = (Get-NsxtTier0LocaleServiceBgp -id $tier0.id).local_as_num
                                    foreach ($element in $bgpStatus) {
                                        if ($element.connection_state -eq 'ESTABLISHED') {
                                            $alert = "GREEN"
                                            $message = "BGP is established."
                                        } else {
                                            $alert = "RED"
                                            $message = "BGP is not established. Please check the configuration."
                                        }
                                        $elementObject = New-Object -TypeName psobject
                                        # NSX Tier-0 BGP Status Properties
                                        $elementObject | Add-Member -NotePropertyName 'NSX Manager' -NotePropertyValue $vcfNsxDetails.fqdn
                                        $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                                        $elementObject | Add-Member -NotePropertyName 'Tier-0 ID' -NotePropertyValue $tier0.id
                                        $elementObject | Add-Member -NotePropertyName 'Connection' -NotePropertyValue $element.connection_state
                                        $elementObject | Add-Member -NotePropertyName 'Source Address' -NotePropertyValue $element.source_address
                                        $elementObject | Add-Member -NotePropertyName 'Neighbor Address' -NotePropertyValue $element.neighbor_address
                                        $elementObject | Add-Member -NotePropertyName 'Local ASN' -NotePropertyValue $localAsn
                                        $elementObject | Add-Member -NotePropertyName 'Remote ASN' -NotePropertyValue $element.remote_as_number
                                        $elementObject | Add-Member -NotePropertyName 'Hold' -NotePropertyValue $element.hold_time
                                        $elementObject | Add-Member -NotePropertyName 'Keep Alive ' -NotePropertyValue $element.keep_alive_interval
                                        $elementObject | Add-Member -NotePropertyName 'Established Time (sec)' -NotePropertyValue $element.time_since_established
                                        $elementObject | Add-Member -NotePropertyName 'Total In Prefix' -NotePropertyValue $element.total_in_prefix_count
                                        $elementObject | Add-Member -NotePropertyName 'Total Out Prefix' -NotePropertyValue $element.total_out_prefix_count
                                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message

                                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                            if ($element.connection_state -ne 'ESTABLISHED') {
                                                $customObject += $elementObject
                                            }
                                        } else {
                                            $customObject += $elementObject
                                        }
                                    }
                                }
                            }
                            $customObject | Sort-Object 'NSX Manager', 'Domain', 'Tier-0 ID', 'Source Address'
                        }
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-NsxtTier0BgpStatus

Function Publish-VmConnectedCdrom {
    <#
        .SYNOPSIS
        Publish the status of virtual machines with connected CD-ROMs in a workload domain in HTML format.

        .DESCRIPTION
        The Publish-VmConnectedCdrom cmdlet returns the status of virtual machines with connected CD-ROMS in a workload
        domain in HTML format. The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-VmConnectedCdrom -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will returns the status of virtual machines with connected CD-ROMs in all workload domains.

        .EXAMPLE
        Publish-VmConnectedCdrom -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will returns the status of virtual machines with connected CD-ROMs in a workload domain.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allHealthObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $vmConnectedCdrom = Request-VmConnectedCdrom -server  $server -user $user -pass $pass -domain $domain.name; $allHealthObject += $vmConnectedCdrom
                    }
                } else {
                    $vmConnectedCdrom = Request-VmConnectedCdrom -server  $server -user $user -pass $pass -domain $workloadDomain; $allHealthObject += $vmConnectedCdrom
                }

                if ($allHealthObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allHealthObject = $allHealthObject | ConvertTo-Html -Fragment -PreContent '<a id="storage-vm-cdrom"></a><h3>Virtual Machines with Connected CD-ROMs</h3>' -PostContent '<p>No virtual machines with connected CD-ROMs found.</p>'
                } else {
                    $allHealthObject = $allHealthObject | Sort-Object Cluster, 'VM Name' | ConvertTo-Html -Fragment -PreContent '<a id="storage-vm-cdrom"></a><h3>Virtual Machines with Connected CD-ROMs</h3>' -As Table
                }
                $allHealthObject = Convert-CssClass -htmlData $allHealthObject
                $allHealthObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VmConnectedCdrom

Function Request-VmConnectedCdrom {
    <#
        .SYNOPSIS
        Returns the status of virtual machines with connected CD-ROMs in a workload domain.

        .DESCRIPTION
        The Request-VmConnectedCdrom cmdlet returns the status of virtual machines with connected CD-ROMs in a workload
        domain. The cmdlet connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the status of virtual machines with connected CD-ROMs in a workload domain.

        .EXAMPLE
        Request-VmConnectedCdrom -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example returns the status of virtual machines with connected CD-ROMs in a workload domain.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $allVms = Get-VM -Server $vcfVcenterDetails.fqdn | Where-Object { $_ | Get-CDDrive | Where-Object { $_.ConnectionState.Connected -eq 'true' } } | Select-Object Name, @{Name = 'ISO Path'; Expression = { (Get-CDDrive $_).isopath } }
                                foreach ($vm in $allVms) {
                                    # Set the alert and message based on the CD-ROM connection
                                    $alert = 'YELLOW' # Warning, connected CD-ROM
                                    $message = 'A virtual CD-ROM is connected.' # Set the status message
                                    # Set the object properties
                                    $customObject = New-Object -TypeName psobject
                                    $customObject | Add-Member -NotePropertyName 'vCenter Server' -NotePropertyValue $vcfVcenterDetails.fqdn
                                    $customObject | Add-Member -NotePropertyName 'Cluster' -NotePropertyValue $cluster.Name
                                    $customObject | Add-Member -NotePropertyName 'VM Name' -NotePropertyValue $vm.Name
                                    $customObject | Add-Member -NotePropertyName 'ISO Path' -NotePropertyValue $vm.'ISO Path'
                                    $customObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                                    $customObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                                    $allClustersObject += $customObject
                                }
                            }
                            $allClustersObject | Sort-Object Cluster, 'VM Name', 'ISO Path'
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VmConnectedCdrom

Function Publish-EsxiConnectionHealth {
    <#
        .SYNOPSIS
        Publish the connection status of ESXi hosts in a workload domain in HTML format.

        .DESCRIPTION
        The Publish-EsxiConnectionHealth cmdlet returns the status of virtual machines with connected CD-ROMS in a workload
        domain in HTML format. The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-EsxiConnectionHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will publish the connection status of ESXi hosts in all workload domains.

        .EXAMPLE
        Publish-EsxiConnectionHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will publish the connection status of ESXi hosts in a workload domain.

        .EXAMPLE
        Publish-EsxiConnectionHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will publish the connection status of ESXi hosts in all workload domains but only for failures.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allHealthObject = New-Object System.Collections.ArrayList
                $allWorkloadDomains = Get-VCFWorkloadDomain
                if ($PsBoundParameters.ContainsKey("allDomains") -and $PsBoundParameters.ContainsKey("failureOnly")) {
                    foreach ($domain in $allWorkloadDomains ) {
                        $esxiConnectionStatus = Request-EsxiConnectionHealth -server  $server -user $user -pass $pass -domain $domain.name -failureOnly; $allHealthObject += $esxiConnectionStatus
                    }
                } elseif ($PsBoundParameters.ContainsKey("allDomains")) {
                    foreach ($domain in $allWorkloadDomains ) {
                        $esxiConnectionStatus = Request-EsxiConnectionHealth -server  $server -user $user -pass $pass -domain $domain.name; $allHealthObject += $esxiConnectionStatus
                    }
                }

                if ($PsBoundParameters.ContainsKey("workloadDomain") -and $PsBoundParameters.ContainsKey("failureOnly")) {
                    $esxiConnectionStatus = Request-EsxiConnectionHealth -server  $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allHealthObject += $esxiConnectionStatus
                } elseif ($PsBoundParameters.ContainsKey("workloadDomain")) {
                    $esxiConnectionStatus = Request-EsxiConnectionHealth -server  $server -user $user -pass $pass -domain $workloadDomain; $allHealthObject += $esxiConnectionStatus
                }

                if ($allHealthObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allHealthObject = $allHealthObject | ConvertTo-Html -Fragment -PreContent '<a id="esxi-connection"></a><h3>ESXi Connection Health</h3>' -PostContent '<p>No issues found.</p>'
                } else {
                    $allHealthObject = $allHealthObject | Sort-Object Resource, Cluster | ConvertTo-Html -Fragment -PreContent '<a id="esxi-connection"></a><h3>ESXi Connection Health</h3>' -As Table
                }
                $allHealthObject = Convert-CssClass -htmlData $allHealthObject
                $allHealthObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-EsxiConnectionHealth

Function Request-EsxiConnectionHealth {
    <#
        .SYNOPSIS
        Returns the connection status of ESXi hosts in a workload domain.

        .DESCRIPTION
        The Request-EsxiConnectionHealth cmdlet returns the connection status of ESXi hosts in a workload domain.
        The cmdlet connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the connection status of ESXi hosts in a workload domain.

        .EXAMPLE
        Request-EsxiConnectionHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example returns the connection status of ESXi hosts in a workload domain.

        .EXAMPLE
        Request-EsxiConnectionHealth -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example returns the connection status of ESXi hosts in a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $esxihosts = Get-VMHost -Server $vcfVcenterDetails.fqdn
                                foreach ($esxiHost in $esxiHosts) {
                                    $component = "ESXi"
                                    # Set the alert and message based on the CD-ROM connection
                                    if ($esxiHost.ConnectionState -eq 'Connected') {
                                        $alert = 'GREEN' # Ok, connected
                                        $message = 'Host is connected.' # Set the status message
                                    } elseif ($esxiHost.ConnectionState -eq 'Maintenance') {
                                        $alert = 'YELLOW' # Warning, maintenance
                                        $message = 'Host is in maintenance mode.' # Set the status message
                                    } elseif ($esxiHost.ConnectionState -eq 'Disconnected') {
                                        $alert = 'RED' # Critical, disconnected
                                        $message = 'Host is disconnected.' # Set the status message
                                    } else {
                                        $alert = 'RED' # Critical, unknown state
                                        $message = 'Host is in an unknown state.' # Set the status message
                                    }
                                    # Set the object properties
                                    $customObject = New-Object -TypeName psobject
                                    $customObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue $component
                                    $customObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $esxiHost.Name
                                    $customObject | Add-Member -NotePropertyName 'Cluster' -NotePropertyValue $cluster.Name
                                    $customObject | Add-Member -NotePropertyName 'Connection' -NotePropertyValue $esxiHost.ConnectionState
                                    $customObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                                    $customObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message

                                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                        if ($esxiHost.ConnectionState -ne 'Connected' ) {
                                            $allClustersObject += $customObject
                                        }
                                    } else {
                                        $allClustersObject += $customObject
                                    }
                                }
                            }
                            $allClustersObject | Sort-Object Resource, Cluster
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-EsxiConnectionHealth

Function Publish-SddcManagerFreePool {
    <#
        .SYNOPSIS
        Publish SDDC Manager free pool health information in HTML format.

        .DESCRIPTION
        The Publish-SddcManagerFreePool cmdlet returns SDDC Manager free pool information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates the network connectivity and authentication to the SDDC Manager instance
        - Publishes information

        .EXAMPLE
        Publish-SddcManagerFreePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return the free pool health from SDDC Manager.

        .EXAMPLE
        Publish-SddcManagerFreePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -failureOnly
        This example will return the free pool health from SDDC Manager and return the failures only.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                $unassignedEsxiHosts = (Get-VCFHost | Where-Object {$_.status -eq "UNASSIGNED_USEABLE"})
                if ($unassignedEsxiHosts.Count -gt 0) {
                    if ($PsBoundParameters.ContainsKey('failureOnly')) {
                        $allConfigurationObject = Request-SddcManagerFreePool -server $server -user $user -pass $pass -failureOnly
                    } else {
                        $allConfigurationObject = Request-SddcManagerFreePool -server $server -user $user -pass $pass
                    }

                    if ($allConfigurationObject.Count -eq 0) { $addNoIssues = $true }
                    if ($addNoIssues) {
                        $allConfigurationObject = $allConfigurationObject | ConvertTo-Html -Fragment -PreContent '<a id="esxi-free-pool"></a><h3>Free Pool Health</h3>' -As Table -PostContent '<p>No issues found.</p>'
                    } else {
                        $allConfigurationObject = $allConfigurationObject | ConvertTo-Html -Fragment -PreContent '<a id="esxi-free-pool"></a><h3>Free Pool Health</h3>' -As Table
                    }
                } else {
                    $allConfigurationObject = $allConfigurationObject | ConvertTo-Html -Fragment -PreContent '<a id="esxi-free-pool"></a><h3>Free Pool Health</h3>' -As Table -PostContent '<p>No ESXi hosts present in the free pool.</p>'
                }

                $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-SddcManagerFreePool

Function Request-SddcManagerFreePool {
    <#
        .SYNOPSIS
        Returns the status of the ESXi hosts in the free pool.

        .DESCRIPTION
        The Request-SddcManagerFreePool cmdlet returns status of the ESXi hosts in the free pool. The cmdlet connects
        to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity and authentication is possible to the SDDC Manager instance
        - Gathers the details for the ESXi hosts in the free pool

        .EXAMPLE
        Request-SddcManagerFreePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return the ESXi hosts in the free pool managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-SddcManagerFreePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -failureOnly
        This example will return the ESXi hosts in the free pool managed by SDDC Manager for a workload domain but only reports issues.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $versionList = New-Object System.Collections.ArrayList
                $customObject = New-Object System.Collections.ArrayList
                $assignedEsxiHost = (Get-VCFHost | Where-Object {$_.status -eq "ASSIGNED"})
                foreach ($esxiHost in $assignedEsxiHost) {
                    if ($versionList -notcontains $esxiHost.esxiVersion ) {
                        $versionList += $esxiHost.esxiVersion
                    }
                }

                $unassignedEsxiHosts = (Get-VCFHost | Where-Object {$_.status -eq "UNASSIGNED_USEABLE"})
                foreach ($esxiHost in $unassignedEsxiHosts) {
                    if ($esxiHost.status -eq "UNASSIGNED_USEABLE") {
                        foreach ($version in $versionList) {
                            if ($esxiHost.esxiVersion -eq $version) {
                                $alert = "GREEN"
                                $message = "Current ESXi Host version $($esxiHost.esxiVersion) matches supported version(s)."
                            } else {
                                $alert = "RED"
                                $message = "Current ESXi Host version $($esxiHost.esxiVersion), does not match supported version(s)."
                            }
                        }
                        $elementObject = New-Object -TypeName psobject
                        $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue 'ESXi Version'
                        $elementObject | Add-Member -NotePropertyName 'ESXi FQDN' -NotePropertyValue $esxiHost.fqdn
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                            if ($alert -eq "RED") {
                                $customObject += $elementObject
                            }
                        } else {
                            $customObject += $elementObject
                        }

                        $esxiCreds = Get-VCFCredential | Where-Object {$_.resource.resourceName -eq $esxiHost.fqdn -and $_.username -eq "root"}
                        Connect-VIServer -Server $esxiHost.fqdn -User $esxiCreds.username -Password $esxiCreds.password | Out-Null
                        $licenseManager = Get-View -Id "LicenseManager-ha-license-manager"
                        foreach ($properties in $licenseManager.Evaluation.Properties) {
                            if ($properties.key -eq "expirationDate") {
                                $expirationDate = $properties.value
                                $expiryDate = [math]::Ceiling((([DateTime]$expirationDate) - (Get-Date)).TotalDays)
                            } elseif ($properties.key -eq "diagnostic") {
                                $expiryDate = 0
                            }
                        }
                        if ($expiryDate -gt "0") {
                            $alert = "GREEN"
                            $message = "No expired license running on the host."
                        } else {
                            $alert = "RED"
                            $message = "License installed on the ESXi host has expired."
                        }
                        $elementObject = New-Object -TypeName psobject
                        $elementObject | Add-Member -NotePropertyName 'Component' -NotePropertyValue 'ESXi License'
                        $elementObject | Add-Member -NotePropertyName 'ESXi FQDN' -NotePropertyValue $esxiHost.fqdn
                        $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                        $elementObject | Add-Member -NotePropertyName 'Message' -NotePropertyValue $message
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        if ($PsBoundParameters.ContainsKey('failureOnly')) {
                            if ($alert -eq "RED") {
                                $customObject += $elementObject
                            }
                        } else {
                            $customObject += $elementObject
                        }
                    }
                }
                $customObject | Sort-Object Component, 'ESXi FQDN'
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-SddcManagerFreePool

########################################## E N D O F F U N C T I O N S ##########################################
#######################################################################################################################


#######################################################################################################################
################################### S Y S T E M A L E R T F U N C T I O N S ####################################

Function Publish-EsxiAlert {
    <#
        .SYNOPSIS
        Publish system alerts/alarms from ESXi hosts in a vCenter Server instance managed by SDDC Manager.

        .DESCRIPTION
        The Publish-EsxiAlert cmdlet returns all alarms from ESXi hosts managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Collects the alerts from all ESXi hosts in vCenter Server instance

        .EXAMPLE
        Publish-EsxiAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return alarms from all ESXi hosts in vCenter Server managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-EsxiAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will return alarms from all ESXi hosts in vCenter Server managed by SDDC Manager for a all workload domains but only for the failed items.

        .EXAMPLE
        Publish-EsxiAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return alarms from all ESXi hosts in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allAlertObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $esxiSystemAlert = Request-EsxiAlert -server $server -user $user -pass $pass $domain.name -failureOnly; $allAlertObject += $esxiSystemAlert
                        }
                    } else {
                        $esxiSystemAlert = Request-EsxiAlert -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allAlertObject += $esxiSystemAlert
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $esxiSystemAlert = Request-EsxiAlert -server $server -user $user -pass $pass $domain.name; $allAlertObject += $esxiSystemAlert
                        }
                    } else {
                        $esxiSystemAlert = Request-EsxiAlert -server $server -user $user -pass $pass -domain $workloadDomain; $allAlertObject += $esxiSystemAlert
                    }
                }

                if ($allAlertObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-esxi"></a><h3>ESXi Host Alert</h3>' -PostContent '<p>No alerts found.</p>'
                } else {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-esxi"></a><h3>ESXi Host Alerts</h3>' -As Table
                }
                $allAlertObject = Convert-CssClass -htmldata $allAlertObject
                $allAlertObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-EsxiAlert

Function Publish-NsxtAlert {
    <#
        .SYNOPSIS
        Publish system alerts/alarms from a NSX Manager cluster managed by SDDC Manager.

        .DESCRIPTION
        The Publish-NsxtAlert cmdlet returns all alarms from an NSX Manager cluster.
        The cmdlet connects to the NSX Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the NSX Manager cluster
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the details for the NSX Manager cluster
        - Collects the alerts

        .EXAMPLE
        Publish-NsxtAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return alarms from all NSX Manager clusters managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-NsxtAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will return alarms from all NSX Manager clusters managed by SDDC Manager for a all workload domains but only for the failed items.

        .EXAMPLE
        Publish-NsxtAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return alarms from the NSX Manager cluster managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allAlertObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey("allDomains")) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtSystemAlert = Request-NsxtAlert -server $server -user $user -pass $pass -domain $domain.name -failureOnly; $allAlertObject += $nsxtSystemAlert
                        }
                    } else {
                        $nsxtSystemAlert = Request-NsxtAlert -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allAlertObject += $nsxtSystemAlert
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey("allDomains")) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $nsxtSystemAlert = Request-NsxtAlert -server $server -user $user -pass $pass -domain $domain.name; $allAlertObject += $nsxtSystemAlert
                        }
                    } else {
                        $nsxtSystemAlert = Request-NsxtAlert -server $server -user $user -pass $pass -domain $workloadDomain; $allAlertObject += $nsxtSystemAlert
                    }
                }

                if ($allAlertObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-nsx"></a><h3>NSX Alert</h3>' -PostContent '<p>No alerts found.</p>'
                } else {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-nsx"></a><h3>NSX Alerts</h3>' -As Table
                }
                $allAlertObject = Convert-CssClass -htmldata $allAlertObject
                $allAlertObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-NsxtAlert

Function Publish-VcenterAlert {
    <#
        .SYNOPSIS
        Returns alarms from vCenter Server managed by SDDC Manager.

        .DESCRIPTION
        The Publish-VcenterAlert cmdlet returns all alarms from vCenter Server managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Collects the alerts from vCenter Server

        .EXAMPLE
        Publish-VcenterAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return alarms from a vCenter Server managed by SDDC Manager for all workload domains.

        .EXAMPLE
        Publish-VcenterAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return alarms from a vCenter Server managed by SDDC Manager for all workload domains but only for the failed items.

        .EXAMPLE
        Publish-VcenterAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return alarms from a vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allAlertObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $vcenterSystemAlert = Request-VcenterAlert -server $server -user $user -pass $pass $domain.name -failureOnly; $allAlertObject += $vcenterSystemAlert
                        }
                    } else {
                        $vcenterSystemAlert = Request-VcenterAlert -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allAlertObject += $vcenterSystemAlert
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $vcenterSystemAlert = Request-VcenterAlert -server $server -user $user -pass $pass $domain.name; $allAlertObject += $vcenterSystemAlert
                        }
                    } else {
                        $vcenterSystemAlert = Request-VcenterAlert -server $server -user $user -pass $pass -domain $workloadDomain; $allAlertObject += $vcenterSystemAlert
                    }
                }

                if ($allAlertObject.Count -eq 0) { $addNoIssues = $true  }
                if ($addNoIssues) {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-vcenter"></a><h3>vCenter Server Alert</h3>' -PostContent '<p>No alerts found.</p>'
                } else {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-vcenter"></a><h3>vCenter Server Alerts</h3>' -As Table
                }
                $allAlertObject = Convert-CssClass -htmldata $allAlertObject
                $allAlertObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VcenterAlert

Function Publish-VsanAlert {
    <#
        .SYNOPSIS
        RPublish the vSAN Healthcheck alarms from a vCenter Server instance.

        .DESCRIPTION
        The Publish-VsanAlert cmdlet returns vSAN Healthcheck alarms from vCenter Server managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Collects the vSAN Healthcheck alarms from vCenter Server

        .EXAMPLE
        Publish-VsanAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return vSAN Healthcheck alarms for all vCenter Server instances managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Publish-VsanAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains -failureOnly
        This example will return vSAN Healthcheck alarms for all vCenter Server instances managed by SDDC Manager for a workload domain but only failed items.

        .EXAMPLE
        Publish-VsanAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return vSAN Healthcheck alarms of a vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allAlertObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $vsanSystemAlert = Request-VsanAlert -server $server -user $user -pass $pass $domain.name -failureOnly; $allAlertObject += $vsanSystemAlert
                        }
                    } else {
                        $vsanSystemAlert = Request-VsanAlert -server $server -user $user -pass $pass -domain $workloadDomain -failureOnly; $allAlertObject += $vsanSystemAlert
                    }
                } else {
                    if ($PsBoundParameters.ContainsKey('allDomains')) {
                        foreach ($domain in $allWorkloadDomains ) {
                            $vsanSystemAlert = Request-VsanAlert -server $server -user $user -pass $pass $domain.name; $allAlertObject += $vsanSystemAlert
                        }
                    } else {
                        $vsanSystemAlert = Request-VsanAlert -server $server -user $user -pass $pass -domain $workloadDomain; $allAlertObject += $vsanSystemAlert
                    }
                }

                if ($allAlertObject.Count -eq 0) { $addNoIssues = $true }
                if ($addNoIssues) {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-vsan"></a><h3>vSAN Alert</h3>' -PostContent '<p>No alerts found.</p>'
                } else {
                    $allAlertObject = $allAlertObject | Sort-Object Component, Resource, Domain | ConvertTo-Html -Fragment -PreContent '<a id="alert-vsan"></a><h3>vSAN Alerts</h3>' -As Table
                }
                $allAlertObject = Convert-CssClass -htmldata $allAlertObject
                $allAlertObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VsanAlert

Function Request-NsxtAlert {
    <#
        .SYNOPSIS
        Returns alarms from an NSX Manager cluster.

        .DESCRIPTION
        The Request-NsxtAlert cmdlet returns all alarms from an NSX Manager cluster.
        The cmdlet connects to the NSX Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the NSX Manager cluster
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the details for the NSX Manager cluster
        - Collects the alerts

        .EXAMPLE
        Request-NsxtAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return alarms of an NSX Manager cluster managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-NsxtAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return alarms of an NSX Manager cluster managed by SDDC Manager for a workload domain but only for the failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    if (Test-VCFConnection -server $server) {
        if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
            if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain)) {
                if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                    if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user ($vcfNsxDetails.adminUser | Select-Object -first 1) -pass ($vcfNsxDetails.adminPass | Select-Object -first 1)) {
                        $nsxtAlarms = Get-NsxtAlarm -fqdn $vcfNsxDetails.fqdn # Get the NSX alarms
                        $customObject = New-Object System.Collections.ArrayList
                        # TODO: Define the YELLOW alert based on Status and Severity
                        foreach ($alarm in $nsxtAlarms.results) {
                            if ($alarm.status -eq "RESOLVED") {
                                $alert = "GREEN"
                            } else {
                                $alert = "RED"
                            }
                            $elementObject = New-Object -TypeName psobject
                            $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $vcfNsxDetails.fqdn # Set the resource name
                            $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain # Set the domain
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert # Set the alert
                            # Alarm properties
                            $elementObject | Add-Member -NotePropertyName 'Feature Name' -NotePropertyValue $alarm.feature_name # Set the feature_name
                            $elementObject | Add-Member -NotePropertyName 'Event Type' -NotePropertyValue $alarm.event_type # Set the event_type
                            $elementObject | Add-Member -NotePropertyName 'Description' -NotePropertyValue $alarm.description # Set the description
                            $elementObject | Add-Member -NotePropertyName 'Status' -NotePropertyValue $alarm.status # Set the status
                            $elementObject | Add-Member -NotePropertyName 'Severity' -NotePropertyValue $alarm.severity # Set the severity
                            $elementObject | Add-Member -NotePropertyName 'Node Name' -NotePropertyValue $alarm.node_display_name # Set the node_display_name
                            $elementObject | Add-Member -NotePropertyName 'Node IP Address' -NotePropertyValue "$($alarm.node_ip_addresses)" # Set the node_ip_addresses array converted to sting

                            if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                    $customObject += $elementObject
                                }
                            } else {
                                $customObject += $elementObject
                            }
                            $customObject | Sort-Object Component, Resource, Domain, Status
                        }
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function Request-NsxtAlert

Function Request-VsanAlert {
    <#
        .SYNOPSIS
        Returns vSAN Healthcheck alarms from a vCenter Server instance.

        .DESCRIPTION
        The Request-VsanAlert cmdlet returns vSAN Healthcheck alarms from vCenter Server managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Collects the vSAN Healthcheck alarms from vCenter Server

        .EXAMPLE
        Request-VsanAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return vSAN Healthcheck alarms of a vCenter Server managed by SDDC Manager for a workload domain.

        .EXAMPLE
        Request-VsanAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return vSAN Healthcheck alarms of a vCenter Server managed by SDDC Manager for a workload domain but only for the failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    if (Test-VCFConnection -server $server) {
        if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
            if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        foreach ($cluster in Get-Cluster -Server $vcfVcenterDetails.fqdn ) {
                            $vsanAlarms = Get-VsanHealthTest -cluster $cluster
                            $customObject = New-Object System.Collections.ArrayList
                            foreach ($vsanAlarm in $vsanAlarms) {
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $vcfVcenterDetails.fqdn
                                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                                $elementObject | Add-Member -NotePropertyName 'Cluster' -NotePropertyValue $cluster
                                # Alarm properties
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $vsanAlarm.TestHealth
                                $elementObject | Add-Member -NotePropertyName 'GroupName' -NotePropertyValue $vsanAlarm.GroupName
                                $elementObject | Add-Member -NotePropertyName 'TestName' -NotePropertyValue $vsanAlarm.TestName

                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        $customObject | Sort-Object Component, Resource, Domain, Cluster, Alert
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function Request-VsanAlert

Function Request-VcenterAlert {
    <#
        .SYNOPSIS
        Returns alarms from vCenter Server managed by SDDC Manager.

        .DESCRIPTION
        The Request-VcenterAlert cmdlet returns all alarms from vCenter Server managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Collects the alerts from vCenter Server

        .EXAMPLE
        Request-VcenterAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return alarms of a vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .EXAMPLE
        Request-VcenterAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -filterOut hostOnly
        This example will return alarms from ESXi hosts of a vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .EXAMPLE
        Request-VcenterAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return alarms from vSAN clusters of a vCenter Server managed by SDDC Manager for a workload domain but only for the failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER filterOut
        Filter out alarms. One of: hostOnly, vsanOnly.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateSet("hostOnly","vsanOnly")][ValidateNotNullOrEmpty()] [String]$filterOut,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    if (Test-VCFConnection -server $server) {
        if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
            if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        $vcenterAlarms = Get-VcenterTriggeredAlarm -server $vcfVcenterDetails.fqdn # Get the vCenter alarms
                        $customObject = New-Object System.Collections.ArrayList
                        foreach ($alarm in $vcenterAlarms) {
                            [String]$alert = $alarm.Status
                            $alert = $alert.ToUpper()
                            $elementObject = New-Object -TypeName psobject
                            $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $vcfVcenterDetails.fqdn
                            $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                            $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                            # Alarm properties
                            $elementObject | Add-Member -NotePropertyName 'Entity Type' -NotePropertyValue $alarm.EntityType
                            $elementObject | Add-Member -NotePropertyName 'Alarm' -NotePropertyValue $alarm.Alarm
                            $elementObject | Add-Member -NotePropertyName 'Time' -NotePropertyValue $alarm.Time
                            $elementObject | Add-Member -NotePropertyName 'Acknowledged' -NotePropertyValue $alarm.Acknowledged
                            $elementObject | Add-Member -NotePropertyName 'Acknowledged By' -NotePropertyValue $alarm.AckBy
                            $elementObject | Add-Member -NotePropertyName 'Acknowledged Time' -NotePropertyValue $alarm.AcknowledgedTime

                            $addToCustomObject = $false

                            switch ($filterOut) {
                                "hostOnly" {
                                    if ($elementObject.EntityType -eq "HostSystem") {
                                        $addToCustomObject = $true
                                    }
                                    Break
                                }
                                "vsanOnly" {
                                    if (($elementObject.EntityType -eq "ClusterComputeResource") -and ($elementObject.Alarm -like "vsan*")) {
                                        $addToCustomObject = $true
                                    }
                                    Break
                                }
                                default {
                                    $addToCustomObject = $true
                                }
                            }
                            if ($addToCustomObject){
                                if ($PsBoundParameters.ContainsKey('failureOnly')) {
                                    if (($elementObject.alert -eq 'RED') -or ($elementObject.alert -eq 'YELLOW')) {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }

                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        $customObject | Sort-Object Component, Resource, Domain, 'Entity Type', Alert
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function Request-VcenterAlert

Function Request-EsxiAlert {
    <#
        .SYNOPSIS
        Returns Alarms from all ESXi hosts in vCenter Server instance.

        .DESCRIPTION
        The Request-EsxiAlert cmdlet returns all alarms from all ESXi hosts in vCenter Server managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Collects the alerts from all ESXi hosts in vCenter Server instance

        .EXAMPLE
        Request-EsxiAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01
        This example will return alarms from all ESXi hosts in vCenter Server managed by SDDC Manager for a workload domain sfo-w01.

        .EXAMPLE
        Request-EsxiAlert -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-w01 -failureOnly
        This example will return alarms from all ESXi hosts in vCenter Server managed by SDDC Manager for a workload domain sfo-w01 but only for the failed items.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.

        .PARAMETER failureOnly
        Switch to only output issues to the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$domain,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    if (Test-VCFConnection -server $server) {
        if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
            if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        $customObject = New-Object System.Collections.ArrayList
                        foreach ($allHosts in Get-VMHost -Server $vcfVcenterDetails.fqdn) {
                            $esxiAlarms = Get-EsxiAlert -host $allHosts # Get the ESXi alarms
                            foreach ($alarm in $esxiAlarms) {
                                [String]$alert = $alarm.Status
                                $alert = $alert.ToUpper()
                                $elementObject = New-Object -TypeName psobject
                                $elementObject | Add-Member -NotePropertyName 'Resource' -NotePropertyValue $vcfVcenterDetails.fqdn
                                $elementObject | Add-Member -NotePropertyName 'Domain' -NotePropertyValue $domain
                                $elementObject | Add-Member -NotePropertyName 'Alert' -NotePropertyValue $alert
                                # Alarm properties
                                $elementObject | Add-Member -NotePropertyName 'Entity' -NotePropertyValue $alarm.Entity
                                $elementObject | Add-Member -NotePropertyName 'Alarm' -NotePropertyValue $alarm.Alarm
                                $elementObject | Add-Member -NotePropertyName 'Time' -NotePropertyValue $alarm.Time
                                $elementObject | Add-Member -NotePropertyName 'Acknowledged' -NotePropertyValue $alarm.Acknowledged
                                $elementObject | Add-Member -NotePropertyName 'Acknowledged By' -NotePropertyValue $alarm.AckBy
                                $elementObject | Add-Member -NotePropertyName 'Acknowledged Time' -NotePropertyValue $alarm.AcknowledgedTime
                                if ($PsBoundParameters.ContainsKey("failureOnly")) {
                                    if ((($elementObject.alert -eq 'RED') -or ($elementObject.Alert -eq 'YELLOW')) -and !($elementObject.Acknowledged)) {
                                        $customObject += $elementObject
                                    }
                                } else {
                                    $customObject += $elementObject
                                }
                            }
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        $customObject | Sort-Object Component, Resource, Domain, Entity, Alert
                    }
                }
            }
        }
    }
}
Export-ModuleMember -Function Request-EsxiAlert

########################################## E N D O F F U N C T I O N S ##########################################
#######################################################################################################################


#######################################################################################################################
################################# C O N F I G U R A T I O N F U N C T I O N S ####################################

Function Publish-ClusterConfiguration {
    <#
        .SYNOPSIS
        Publish cluster configuration information in HTML format.

        .DESCRIPTION
        The Publish-ClusterConfiguration cmdlet returns cluster configuration information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-ClusterConfiguration -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return cluster configuration from all clusters in vCenter Server managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-ClusterConfiguration -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return cluster configuration from all clusters in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $clusterConfiguration = Request-ClusterConfiguration -server  $server -user $user -pass $pass -domain $domain.name;
                        $allConfigurationObject += $clusterConfiguration
                    }
                } else {
                    $clusterConfiguration = Request-ClusterConfiguration -server  $server -user $user -pass $pass -domain $workloadDomain; $allConfigurationObject += $clusterConfiguration
                }
                $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster | ConvertTo-Html -Fragment -PreContent '<a id="cluster-config"></a><h3>Cluster Configuration</h3>' -As Table
                $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ClusterConfiguration

Function Publish-EsxiCoreDumpConfig {
    <#
        .SYNOPSIS
        Generates an ESXi core dump configuration report.

        .DESCRIPTION
        The Publish-EsxiCoreDumpConfig cmdlet generates an ESXi core dump report for a workload domain. The cmdlet
        connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Generates an ESXi core dump report for all ESXi hosts in a workload domain

        .EXAMPLE
        Publish-EsxiCoreDumpConfig -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -alldomains
        This example generates an ESXi core dump report for all ESXi hosts across the VMware Cloud Foundation instance.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER html
        Switch to output the report in HTML format.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$html,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific--WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allHostObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey("allDomains")) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    $domainHostObject = New-Object System.Collections.ArrayList
                    foreach ($domain in $allWorkloadDomains) {
                        if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain.name)) {
                            if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                                if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                                    $coreDumpObject = New-Object -TypeName psobject
                                    $esxiHosts = Get-VMHost
                                    foreach ($esxiHost in $esxiHosts) {
                                        $coreDumpObject = New-Object -TypeName psobject
                                        $esxcli = Get-EsxCli -VMhost $esxiHost.Name -V2
                                        $coreDumpConfig = $esxcli.system.coredump.partition.get.invoke()
                                        $coreDumpObject | Add-Member -notepropertyname 'Domain' -notepropertyvalue $domain.name
                                        $coreDumpObject | Add-Member -notepropertyname 'Host' -notepropertyvalue $esxiHost.Name
                                        $coreDumpObject | Add-Member -notepropertyname 'Active Core Dump' -notepropertyvalue $coreDumpConfig.Active
                                        $coreDumpObject | Add-Member -notepropertyname 'Configured Core Dump' -notepropertyvalue $coreDumpConfig.Configured
                                        $domainHostObject += $coreDumpObject
                                    }
                                }
                            }
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                    $allHostObject += $domainHostObject
                } else {
                    if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $workloadDomain)) {
                        if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                            if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                                $coreDumpObject = New-Object -TypeName psobject
                                $esxiHosts = Get-VMHost
                                foreach ($esxiHost in $esxiHosts) {
                                    $coreDumpObject = New-Object -TypeName psobject
                                    $esxcli = Get-EsxCli -VMhost $esxiHost.Name -V2
                                    $coreDumpConfig = $esxcli.system.coredump.partition.get.invoke()
                                    $coreDumpObject | Add-Member -notepropertyname 'Domain' -notepropertyvalue $workloadDomain
                                    $coreDumpObject | Add-Member -notepropertyname 'Host' -notepropertyvalue $esxiHost.Name
                                    $coreDumpObject | Add-Member -notepropertyname 'Active Core Dump' -notepropertyvalue $coreDumpConfig.Active
                                    $coreDumpObject | Add-Member -notepropertyname 'Configured Core Dump' -notepropertyvalue $coreDumpConfig.Configured
                                    $allHostObject += $coreDumpObject
                                }
                            }
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
                if ($PsBoundParameters.ContainsKey('html')) {
                    $allHostObject = $allHostObject | Sort-Object Domain, Host | ConvertTo-Html -Fragment -PreContent '<a id="esxi-coredmp"></a><h3>ESXi Core Dump Configuration</h3>' -As Table
                }
                $allHostObject = Convert-CssClass -htmldata $allHostObject
                $allHostObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-EsxiCoreDumpConfig

Function Request-ClusterConfiguration {
    <#
        .SYNOPSIS
        Gets cluster configuration from a vCenter Server instance.

        .DESCRIPTION
        The Request-ClusterConfiguration cmdlets gets the cluster configuration for a vCenter Server instance. The
        cmdlet connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the cluster details from vCenter Server

        .EXAMPLE
        Request-ClusterConfiguration -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-m01
        This example gets the cluster configuration for a vCenter Server instance based on the workload domain provided.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $haStatus = if ($cluster.HAEnabled -eq "True") { "Enabled" } else { "Disabled" }
                                $drsStatus = if ($cluster.DrsEnabled -eq "True") { "Enabled" } else { "Disabled" }
                                $evcStatus = if ($null -eq $cluster.EVCMode) { "Disabled" } else { $cluster.EVCMode }
                                $clusterAdvancedSettings = Get-AdvancedSetting -Entity (Get-Cluster -Name $cluster) | Select-Object Name, Value
                                $settingsObject = New-Object System.Collections.ArrayList
                                foreach ($AdvancedSetting in $clusterAdvancedSettings) {
                                    $settingsObject += "$($AdvancedSetting.Name) : $($AdvancedSetting.Value)"
                                }

                                $customObject = New-Object -TypeName psobject
                                $customObject | Add-Member -notepropertyname "Cluster Name" -notepropertyvalue $cluster.Name
                                $customObject | Add-Member -notepropertyname "vSphere HA" -notepropertyvalue $haStatus
                                $customObject | Add-Member -notepropertyname "vSphere DRS" -notepropertyvalue $drsStatus
                                $customObject | Add-Member -notepropertyname "vSphere DRS Mode" -notepropertyvalue $cluster.DrsAutomationLevel
                                $customObject | Add-Member -notepropertyname "vSphere EVC" -notepropertyvalue $evcStatus
                                $customObject | Add-Member -Type NoteProperty -Name "Advanced Settings" -Value ($settingsObject -join ':-: ')
                                $allClustersObject += $customObject
                            }
                            $allClustersObject
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-ClusterConfiguration

Function Publish-ClusterDrsRule {
    <#
        .SYNOPSIS
        Publish cluster DRS rule information in HTML format.

        .DESCRIPTION
        The Publish-ClusterDrsRule cmdlet returns cluster DRS rule information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-ClusterDrsRule -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return cluster DRS rules from all clusters in vCenter Server managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-ClusterDrsRule -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return cluster DRS rules from all clusters in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $drsRulesConfig = Request-ClusterDrsRule -server  $server -user $user -pass $pass -domain $domain.name; $allConfigurationObject += $drsRulesConfig
                    }
                } else {
                    $drsRulesConfig = Request-ClusterDrsRule -server  $server -user $user -pass $pass -domain $workloadDomain; $allConfigurationObject += $drsRulesConfig
                }

                if ($allConfigurationObject.Count -ne 0) {
                    $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster, 'VM/Host Rule' | ConvertTo-Html -Fragment -PreContent '<a id="cluster-drs-rules"></a><h3>vSphere DRS Rules</h3>' -As Table
                    $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                } else {
                    $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster, 'VM/Host Rule' | ConvertTo-Html -Fragment -PreContent '<a id="cluster-drs-rules"></a><h3>vSphere DRS Rules</h3>' -PostContent '<p>No DRS Rules found.</p>'
                }
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ClusterDrsRule

Function Request-ClusterDrsRule {
    <#
        .SYNOPSIS
        Gets cluster DRS rules from a vCenter Server instance.

        .DESCRIPTION
        The Request-ClusterDrsRule cmdlets gets the cluster DRS rules for a vCenter Server instance. The
        cmdlet connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the cluster DRS rules from vCenter Server

        .EXAMPLE
        Request-ClusterDrsRule -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-m01
        This example gets the cluster DRS rules for a vCenter Server instance based on the workload domain provided.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $drsRules = Get-Cluster -Name $cluster -Server $vcfVcenterDetails.fqdn | Get-DrsRule
                                foreach ($drsRule in $drsRules) {
                                    $vmList = New-Object System.Collections.ArrayList
                                    foreach ($vm in $drsRule.VMIDS) {
                                        $vmName = (Get-VM -Id $vm -Server $vcfVcenterDetails.fqdn).Name
                                        $vmList += $vmName
                                    }

                                    $customObject = New-Object -TypeName psobject
                                    $customObject | Add-Member -notepropertyname "Cluster" -notepropertyvalue $cluster.Name
                                    $customObject | Add-Member -notepropertyname "VM/Host Rule" -notepropertyvalue $drsRule.Name
                                    $customObject | Add-Member -notepropertyname "Enabled" -notepropertyvalue $drsRule.Enabled
                                    $customObject | Add-Member -notepropertyname "Type" -notepropertyvalue $drsRule.Type
                                    $customObject | Add-Member -notepropertyname "VMs" -notepropertyvalue ($vmList -join ':-: ')
                                    $allClustersObject += $customObject
                                }
                            }
                            $allClustersObject
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-ClusterDrsRule

Function Publish-ResourcePool {
    <#
        .SYNOPSIS
        Publish resource pool information in HTML format.

        .DESCRIPTION
        The Publish-ResourcePool cmdlet returns resource pool information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes resource pool information

        .EXAMPLE
        Publish-ResourcePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return resource pool details from all clusters in vCenter Server managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-ResourcePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return resource pool details from all clusters in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $resourcePoolConfig = Request-ResourcePool -server  $server -user $user -pass $pass -domain $domain.name; $allConfigurationObject += $resourcePoolConfig
                    }
                } else {
                    $resourcePoolConfig = Request-ResourcePool -server  $server -user $user -pass $pass -domain $workloadDomain; $allConfigurationObject += $resourcePoolConfig
                }
                $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster, 'Resource Pool' | ConvertTo-Html -Fragment -PreContent '<a id="cluster-resource-pools"></a><h3>Resource Pools</h3>' -As Table
                $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-ResourcePool

Function Request-ResourcePool {
    <#
        .SYNOPSIS
        Gets resource pool details from a vCenter Server instance.

        .DESCRIPTION
        The Request-ResourcePool cmdlets gets the resource pool details for a vCenter Server instance. The cmdlet
        connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the resource pool details from vCenter Server

        .EXAMPLE
        Request-ResourcePool -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-m01
        This example gets the resource pool details for a vCenter Server instance based on the workload domain provided.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $resourcePools = Get-ResourcePool -Server $vcfVcenterDetails.fqdn
                                foreach ($resourcePool in $resourcePools) {
                                    if ($resourcePool.Parent -ne $cluster) {
                                        $customObject = New-Object -TypeName psobject
                                        $customObject | Add-Member -notepropertyname "Cluster" -notepropertyvalue $cluster.Name
                                        $customObject | Add-Member -notepropertyname "Resource Pool" -notepropertyvalue $resourcePool.Name
                                        $customObject | Add-Member -notepropertyname "CPU Share Level" -notepropertyvalue $resourcePool.CpuSharesLevel
                                        $customObject | Add-Member -notepropertyname "CPU Expandable" -notepropertyvalue $resourcePool.CpuExpandableReservation
                                        $customObject | Add-Member -notepropertyname "Memory Share Level" -notepropertyvalue $resourcePool.MemSharesLevel
                                        $customObject | Add-Member -notepropertyname "Memory Expandable" -notepropertyvalue $resourcePool.MemExpandableReservation
                                        $allClustersObject += $customObject
                                    }
                                }
                            }
                            $allClustersObject
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-ResourcePool

Function Publish-VmOverride {
    <#
        .SYNOPSIS
        Publish VM Override information in HTML format.

        .DESCRIPTION
        The Publish-VmOverride cmdlet returns VM Override information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-VmOverride -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return VM Override details from all clusters in vCenter Server managed by SDDC Manager for all workload domains.

        .EXAMPLE
        Publish-VmOverride -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return VM Override details from all clusters in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $vmOverRideConfig = Request-VmOverride -server  $server -user $user -pass $pass -domain $domain.name; $allConfigurationObject += $vmOverRideConfig
                    }
                } else {
                    $vmOverRideConfig = Request-VmOverride -server  $server -user $user -pass $pass -domain $workloadDomain; $allConfigurationObject += $vmOverRideConfig
                }
                $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster, 'DRS Automation Level', 'VM Name' | ConvertTo-Html -Fragment -PreContent '<a id="cluster-overrides"></a><h3>VM Overrides</h3>' -As Table
                $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VmOverride

Function Request-VmOverride {
    <#
        .SYNOPSIS
        Gets VM Override setting from a vCenter Server instance.

        .DESCRIPTION
        The Request-VmOverride cmdlets gets VM Override setting for a vCenter Server instance. The cmdlet connects to
        SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the VM Override settings from vCenter Server

        .EXAMPLE
        Request-VmOverride -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-m01
        This example gets the VM Override setting for a vCenter Server instance based on the workload domain provided.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $allVms = Get-VM -Server $vcfVcenterDetails.fqdn | Select-Object Name,DrsAutomationLevel
                                foreach ($vm in $allVms) {
                                    $customObject = New-Object -TypeName psobject
                                    $customObject | Add-Member -notepropertyname "Cluster" -notepropertyvalue $cluster.Name
                                    $customObject | Add-Member -notepropertyname "VM Name" -notepropertyvalue $vm.Name
                                    $customObject | Add-Member -notepropertyname "DRS Automation Level" -notepropertyvalue ($vm.DrsAutomationLevel -creplace '.(?![a-z])','$& ' )
                                    $allClustersObject += $customObject
                                }
                            }
                            $allClustersObject
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VmOverride

Function Publish-VirtualNetwork {
    <#
        .SYNOPSIS
        Publish vSphere virtual networking information in HTML format.

        .DESCRIPTION
        The Publish-VirtualNetwork cmdlet returns vSphere virtual networking information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-VirtualNetwork -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return vSphere virtual networking details from all clusters in vCenter Server managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-VirtualNetwork -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return vSphere virtual networking details from all clusters in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $virtualNetworkConfig = Request-VirtualNetwork -server  $server -user $user -pass $pass -domain $domain.name; $allConfigurationObject += $virtualNetworkConfig
                    }
                } else {
                    $virtualNetworkConfig = Request-VirtualNetwork -server  $server -user $user -pass $pass -domain $workloadDomain; $allConfigurationObject += $virtualNetworkConfig
                }
                $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster, 'vSphere Distributed Switch' | ConvertTo-Html -Fragment -PreContent '<a id="cluster-networks"></a><h3>Virtual Networks</h3>' -As Table
                $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VirtualNetwork

Function Request-VirtualNetwork {
    <#
        .SYNOPSIS
        Gets vSphere virtual networking configuration from a vCenter Server instance.

        .DESCRIPTION
        The Request-VirtualNetwork cmdlets gets vSphere virtual networking configuration for a vCenter Server instance.
        The cmdlet connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the vSphere virtual networking configuration from vCenter Server

        .EXAMPLE
        Request-VirtualNetwork -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-m01
        This example gets the vSphere virtual networking configurationfor a vCenter Server instance based on the workload domain provided.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $allVds = Get-VDSwitch -Server $vcfVcenterDetails.fqdn
                                foreach ($vds in $allVds) {
                                    $allPortgroups = Get-VDPortgroup -Server $vcfVcenterDetails.fqdn | Where-Object {$_.VDSwitch -eq $vds}
                                    $settingsObject = New-Object System.Collections.ArrayList
                                    foreach ($portgroup in $allPortgroups) {
                                        $settingsObject += "$($portgroup.Name) : $($portgroup.PortBinding)"
                                    }
                                    $customObject = New-Object -TypeName psobject
                                    $customObject | Add-Member -notepropertyname "Cluster" -notepropertyvalue $cluster.Name
                                    $customObject | Add-Member -notepropertyname "vSphere Distributed Switch" -notepropertyvalue $vds.Name
                                    $customObject | Add-Member -notepropertyname "Switch Version" -notepropertyvalue $vds.Version
                                    $customObject | Add-Member -notepropertyname "MTU" -notepropertyvalue $vds.Mtu
                                    $customObject | Add-Member -notepropertyname "Portgroups" -notepropertyvalue ($settingsObject -join ':-: ')
                                    $allClustersObject += $customObject
                                }
                            }
                            $allClustersObject
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-VirtualNetwork

Function Publish-EsxiSecurityConfiguration {
    <#
        .SYNOPSIS
        Publish ESXi security information in HTML format.

        .DESCRIPTION
        The Publish-EsxiSecurityConfiguration cmdlet returns ESXi security information in HTML format.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the vCenter Server instance
        - Validates the authentication to vCenter Server with credentials from SDDC Manager
        - Publishes information

        .EXAMPLE
        Publish-EsxiSecurityConfiguration -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -allDomains
        This example will return ESXi security details from all clusters in vCenter Server managed by SDDC Manager for a all workload domains.

        .EXAMPLE
        Publish-EsxiSecurityConfiguration -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -workloadDomain sfo-w01
        This example will return ESXi security details from all clusters in vCenter Server managed by SDDC Manager for a workload domain named sfo-w01.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER allDomains
        Switch to run health checks across all workload domains.

        .PARAMETER workloadDomain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (ParameterSetName = 'All-WorkloadDomains', Mandatory = $true)] [ValidateNotNullOrEmpty()] [Switch]$allDomains,
        [Parameter (ParameterSetName = 'Specific-WorkloadDomain', Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$workloadDomain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allConfigurationObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('allDomains')) {
                    $allWorkloadDomains = Get-VCFWorkloadDomain
                    foreach ($domain in $allWorkloadDomains ) {
                        $esxiSecurityConfig = Request-EsxiSecurityConfiguration -server  $server -user $user -pass $pass -domain $domain.name; $allConfigurationObject += $esxiSecurityConfig
                    }
                } else {
                    $esxiSecurityConfig = Request-EsxiSecurityConfiguration -server  $server -user $user -pass $pass -domain $workloadDomain; $allConfigurationObject += $esxiSecurityConfig
                }
                $allConfigurationObject = $allConfigurationObject | Sort-Object Cluster, 'ESXi FQDN' | ConvertTo-Html -Fragment -PreContent '<a id="esxi-security"></a><h3>Security Configuration</h3>' -As Table
                $allConfigurationObject = Convert-CssClass -htmldata $allConfigurationObject
                $allConfigurationObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-EsxiSecurityConfiguration

Function Request-EsxiSecurityConfiguration {
    <#
        .SYNOPSIS
        Gets ESXi security configuration from a vCenter Server instance.

        .DESCRIPTION
        The Request-EsxiSecurityConfiguration cmdlets gets ESXi security configuration for a vCenter Server instance.
        The cmdlet connects to SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the vCenter Server instance
        - Gathers the ESXi security configuration from vCenter Server

        .EXAMPLE
        Request-EsxiSecurityConfiguration -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -domain sfo-m01
        This example gets the ESXi security configurationfor a vCenter Server instance based on the workload domain provided.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER domain
        The name of the workload domain to run against.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$domain
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $allClustersObject = New-Object System.Collections.ArrayList
                            $allClusters = Get-Cluster -Server $vcfVcenterDetails.fqdn
                            foreach ($cluster in $allClusters) {
                                $allHosts = Get-VMHost -Server $vcfVcenterDetails.fqdn
                                foreach ($esxHost in $allHosts) {
                                    $lockdownStatus = if ($esxHost.ExtensionData.Config.LockdownMode -eq "lockdownDisabled" ) { "False" } else { "True" }
                                    $customObject = New-Object -TypeName psobject
                                    $customObject | Add-Member -notepropertyname "Cluster" -notepropertyvalue $cluster.Name
                                    $customObject | Add-Member -notepropertyname "ESXi FQDN" -notepropertyvalue $esxHost.Name
                                    $customObject | Add-Member -notepropertyname "SSH Enabled" -notepropertyvalue (Get-VMHostService -VMHost $esxHost | Where-Object { $_.key -eq 'TSM-SSH' }).Running
                                    $customObject | Add-Member -notepropertyname "Lockdown Enabled" -notepropertyvalue $lockdownStatus
                                    $allClustersObject += $customObject
                                }
                            }
                            $allClustersObject
                        }
                        Disconnect-VIServer * -Force -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Request-EsxiSecurityConfiguration

########################################## E N D O F F U N C T I O N S ##########################################
#######################################################################################################################


#######################################################################################################################
############################### S Y S T E M O V E R V I E W F U N C T I O N S ##################################

Function Publish-VcfSystemOverview {
    <#
        .SYNOPSIS
        Publishs a system overview report.

        .DESCRIPTION
        The Publish-VcfSystemOverview cmdlet returns an overview of the Vmware Cloud Foundation instance.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Collects the system overview details from the environment

        .EXAMPLE
        Publish-VcfSystemOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return system overview report for a all workload domains.

        .EXAMPLE
        Publish-VcfSystemOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -anonymized
        This example will return system overview report for a all workload domains, but with anonymized data.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allOverviewObject = New-Object System.Collections.ArrayList
                if ($PsBoundParameters.ContainsKey('anonymized')) {
                    $vcfOverview = Request-VcfOverview -server $server -user $user -pass $pass -anonymized
                    $hardwareOverview = Request-HardwareOverview -server $server -user $user -pass $pass
                    $vcenterOverview = Request-VcenterOverview -server $server -user $user -pass $pass -anonymized
                    $esxiOverview = Request-EsxiOverview -server $server -user $user -pass $pass -anonymized
                    $clusterOverview = Request-ClusterOverview -server $server -user $user -pass $pass -anonymized
                    $networkingOverview = Request-NetworkOverview -server $server -user $user -pass $pass -anonymized
                    $ariaSuiteOverview = Request-VMwareAriaSuiteOverview -server $server -user $user -pass $pass -anonymized
                    $vvsOverview = Request-ValidatedSolutionOverview -server $server -user $user -pass $pass
                } else {
                    $vcfOverview = Request-VcfOverview -server $server -user $user -pass $pass
                    $hardwareOverview = Request-HardwareOverview -server $server -user $user -pass $pass
                    $vcenterOverview = Request-VcenterOverview -server $server -user $user -pass $pass
                    $clusterOverview = Request-ClusterOverview -server $server -user $user -pass $pass
                    $esxiOverview = Request-EsxiOverview -server $server -user $user -pass $pass
                    $networkingOverview = Request-NetworkOverview -server $server -user $user -pass $pass
                    $ariaSuiteOverview = Request-VMwareAriaSuiteOverview -server $server -user $user -pass $pass
                    $vvsOverview = Request-ValidatedSolutionOverview -server $server -user $user -pass $pass
                }

                $vcfOverview = $vcfOverview | ConvertTo-Html -Fragment -PreContent '<h4>VMware Cloud Foundation Overview</h4>'
                $vcfOverview = Convert-CssClass -htmldata $vcfOverview
                $hardwareOverview = $hardwareOverview | ConvertTo-Html -Fragment -PreContent '<h4>Hardware Overview</h4>'
                $hardwareOverview = Convert-CssClass -htmldata $hardwareOverview
                $vcenterOverview = $vcenterOverview | ConvertTo-Html -Fragment -PreContent '<h4>vCenter Server Overview</h4>' -As Table
                $vcenterOverview = Convert-CssClass -htmldata $vcenterOverview
                $clusterOverview = $clusterOverview | ConvertTo-Html -Fragment -PreContent '<h4>vSphere Cluster Overview</h4>'
                $clusterOverview = Convert-CssClass -htmldata $clusterOverview
                $esxiOverview = $esxiOverview | ConvertTo-Html -Fragment -PreContent '<h4>ESXi Host Overview</h4>'
                $esxiOverview = Convert-CssClass -htmldata $esxiOverview
                $networkingOverview = $networkingOverview | ConvertTo-Html -Fragment -PreContent '<h4>Networking Overview</h4>'
                $networkingOverview = Convert-CssClass -htmldata $networkingOverview
                if ($ariaSuiteOverview) {
                    $ariaSuiteOverview = $ariaSuiteOverview | ConvertTo-Html -Fragment -PreContent '<h4>VMware Aria Suite Overview</h4>'
                    $ariaSuiteOverview = Convert-CssClass -htmldata $ariaSuiteOverview
                } else {
                    $ariaSuiteOverview = $ariaSuiteOverview | ConvertTo-Html -Fragment -PreContent '<h4>VMware Aria Suite Overview</h4>' -PostContent '<p>No VMware Aria Suite products deployed in VMware Cloud Foundation mode.</p>'
                }
                $vvsOverview = $vvsOverview | ConvertTo-Html -Fragment -PreContent '<h4>VMware Validated Solutions Overview</h4>'
                $vvsOverview = Convert-CssClass -htmldata $vvsOverview

                $allOverviewObject += $vcfOverview
                $allOverviewObject += $hardwareOverview
                $allOverviewObject += $vcenterOverview
                $allOverviewObject += $clusterOverview
                $allOverviewObject += $networkingOverview
                $allOverviewObject += $ariaSuiteOverview
                $allOverviewObject += $vvsOverview
                $allOverviewObject += $esxiOverview
                $allOverviewObject
            }
        }
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Publish-VcfSystemOverview

Function Request-VcfOverview {
    <#
        .SYNOPSIS
        Returns System Overview.

        .DESCRIPTION
        The Request-VcfOverview cmdlet returns an overview of the SDDC Manager instance.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Collects the overview detail

        .EXAMPLE
        Request-VcfOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of the SDDC Manager instance.

        .EXAMPLE
        Request-VcfOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -anonymized
        This example will return an overview of the SDDC Manager instance, but will anonymize the output.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) { # Gather VCF Architecture
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            $vcfArchitecture = (Get-AdvancedSetting -Name "config.SDDC.Deployed.Flavor" -Entity $vcfVcenterDetails.fqdn -Server $vcfVcenterDetails.fqdn).value
                            Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                }
                if (Get-VCFDepotCredential) {
                    $depotUser = (Get-VCFDepotCredential).username.ToLower()
                    $depotStatus = ((Get-VCFDepotCredential).message -Split ": ")[-1]
                } else {
                    $depotUser = "Not Configured"
                    $depotStatus = "Not Connected"
                }
                $systemObject = New-Object -TypeName psobject
                if ($PsBoundParameters.ContainsKey('anonymized')) {
                    $systemObject | Add-Member -notepropertyname "SDDC Manager UUID" -notepropertyvalue (Get-VCFManager).id
                } else {
                    $systemObject | Add-Member -notepropertyname "SDDC Manager FQDN" -notepropertyvalue (Get-VCFManager).fqdn
                }
                $systemObject | Add-Member -notepropertyname "Version" -notepropertyvalue (Get-VCFManager).version
                $systemObject | Add-Member -notepropertyname "Architecture" -notepropertyvalue $vcfArchitecture
                $systemObject | Add-Member -notepropertyname "CEIP Status" -notepropertyvalue (Get-Culture).TextInfo.ToTitleCase((Get-VCFCeip).status.ToLower())
                $systemObject | Add-Member -notepropertyname "Customer Connect User" -notepropertyvalue $depotUser
                $systemObject | Add-Member -notepropertyname "Customer Connect Status" -notepropertyvalue $depotStatus
                $systemObject
            }
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-VcfOverview

Function Request-HardwareOverview {
    <#
        .SYNOPSIS
        Returns Hardware Overview.

        .DESCRIPTION
        The Request-VcfOverview cmdlet returns an overview of the hardware in an SDDC Manager instance.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Collects the hardware details

        .EXAMPLE
        Request-HardwareOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of the SDDC Manager instance.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                # Calculate Total VMs Across VCF Instance
                $totalVms = $null
                $totalPoweredOnVms = $null
                $totalPoweredOffVms = $null
                $allWorkloadDomains = Get-VCFWorkloadDomain
                foreach ($domain in $allWorkloadDomains) {
                    if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain.name)) {
                        if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                            if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                                $totalVms += (Get-VM -Server $vcfVcenterDetails.fqdn).Count
                                $totalPoweredOnVms += (Get-VM -Server $vcfVcenterDetails.fqdn | Where-Object {$_.PowerState -eq "PoweredOn"}).Count
                                $totalPoweredOffVms += (Get-VM -Server $vcfVcenterDetails.fqdn | Where-Object {$_.PowerState -eq "PoweredOff"}).Count
                            }
                            Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                }
                # Gather Hardware OEM
                $harwdareOemObject = New-Object System.Collections.ArrayList
                foreach ($esxiHost in (Get-VCFHost)) {
                    if (-not ($harwdareOemObject -match $esxiHost.hardwareVendor)) {
                        $harwdareOemObject.Add($esxiHost.hardwareVendor)
                    }
                }

                # Gather Hardware Platform
                $harwdareModelObject = New-Object System.Collections.ArrayList
                foreach ($esxiHost in (Get-VCFHost)) {
                    if (-not ($harwdareModelObject -match $esxiHost.hardwareModel)) {
                        $harwdareModelObject.Add($esxiHost.hardwareModel)
                    }
                }

                # Gather CPU Sockets / Cores Platform
                $totalSockets = $null
                foreach ($esxiHost in (Get-VCFHost)) {
                    $totalSockets = $totalSockets + $esxiHost.cpu.cpuCores.Count
                }

                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -Type NoteProperty -Name "Hardware OEM" -Value ($harwdareOemObject -join ':-: ')
                $customObject | Add-Member -Type NoteProperty -Name "Hardware Platform" -Value ($harwdareModelObject -join ':-: ')
                $customObject | Add-Member -notepropertyname "CPUs Sockets Deployed" -notepropertyvalue $totalSockets
                $customObject | Add-Member -notepropertyname "Hosts Deployed" -notepropertyvalue (Get-VCFHost).Count
                $customObject | Add-Member -notepropertyname "Workload Domains" -notepropertyvalue (Get-VCFWorkloadDomain | Measure-Object).Count
                $customObject | Add-Member -notepropertyname "Total VMs" -notepropertyvalue $totalVms
                $customObject | Add-Member -notepropertyname "Powered On" -notepropertyvalue $totalPoweredOnVms
                $customObject | Add-Member -notepropertyname "Powered Off" -notepropertyvalue $totalPoweredOffVms
                $customObject
            }
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-HardwareOverview

Function Request-VcenterOverview {
    <#
        .SYNOPSIS
        Returns overview of vSphere.

        .DESCRIPTION
        The Request-VcenterOverview cmdlet returns an overview of the vSphere environment managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity and authentication to the SDDC Manager instance
        - Validates that network connectivity and authentication to the vCenter Server instances
        - Collects the vSphere overview detail

        .EXAMPLE
        Request-VcenterOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of the vSphere environment managed by the SDDC Manager instance.

        .EXAMPLE
        Request-VcenterOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -aanonymized
        This example will return an overview of the vSphere environment managed by the SDDC Manager instance, but will anonymize the output.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        if (Test-VCFConnection -server $server -ErrorAction SilentlyContinue -ErrorVariable ErrorMessage) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass -ErrorAction SilentlyContinue -ErrorVariable ErrorMessage) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allVsphereObject = New-Object System.Collections.ArrayList

                if (($vcfMgmtVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                    if (Test-VsphereConnection -server $vcfMgmtVcenterDetails.fqdn) {
                        if (Test-VsphereAuthentication -server $vcfMgmtVcenterDetails.fqdn -user $vcfMgmtVcenterDetails.ssoAdmin -pass $vcfMgmtVcenterDetails.ssoAdminPass) {
                            foreach ($domain in $allWorkloadDomains) {
                                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain.name)) {
                                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                                            $customObject = New-Object -TypeName psobject
                                            $vm = Get-VM -Server $vcfMgmtVcenterDetails.fqdn -ErrorAction SilentlyContinue | Where-Object {$_.Name -eq $vcfVcenterDetails.vmName} | Select-Object Name, NumCpu, MemoryGB
                                            $vcsaSizing = $vcsaSize | ConvertFrom-Json
                                            if ($PsBoundParameters.ContainsKey('anonymized')) {
                                                $customObject | Add-Member -notepropertyname "vCenter Server UUID" -notepropertyvalue $domain.vcenters.id
                                                $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue (Get-VCFvCenter -id $domain.vcenters.id).version
                                                $customObject | Add-Member -notepropertyname "Domain UUID" -notepropertyvalue $domain.id
                                            } else {
                                                $customObject | Add-Member -notepropertyname "vCenter Server FQDN" -notepropertyvalue $domain.vcenters.fqdn
                                                $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue (Get-VCFvCenter -id $domain.vcenters.id).version
                                                $customObject | Add-Member -notepropertyname "Domain Name" -notepropertyvalue $domain.name
                                            }
                                            $customObject | Add-Member -notepropertyname "Domain Type" -notepropertyvalue $domain.type.ToLower()
                                            if ($size = $vcsaSizing | Where-Object {$_.resources.cpu -eq $vm.NumCpu -and $_.resources.memory -eq $vm.MemoryGB}) {
                                                $customObject | Add-Member -notepropertyname "Size" -notepropertyvalue $size.name
                                            } else {
                                                $customObject | Add-Member -notepropertyname "Size" -notepropertyvalue "custom"
                                            }
                                            $customObject | Add-Member -notepropertyname "CPU" -notepropertyvalue $vm.NumCpu
                                            $customObject | Add-Member -notepropertyname "Memory (GB)" -notepropertyvalue $vm.MemoryGB
                                            $customObject | Add-Member -notepropertyname "Total Clusters" -notepropertyvalue (Get-VCFWorkloadDomain -id $domain.id).clusters.Count
                                            $customObject | Add-Member -notepropertyname "Total Hosts" -notepropertyvalue (Get-VCFHost | Where-Object {$_.domain.id -eq $domain.id}).Count
                                            $customObject | Add-Member -notepropertyname "Total VMs" -notepropertyvalue (Get-VM -Server $vcfVcenterDetails.fqdn).Count
                                            $customObject | Add-Member -notepropertyname "Powered On VMs" -notepropertyvalue (Get-VM -Server $vcfVcenterDetails.fqdn | Where-Object {$_.PowerState -eq "PoweredOn"}).Count
                                            $customObject | Add-Member -notepropertyname "Powered Off VMs" -notepropertyvalue (Get-VM -Server $vcfVcenterDetails.fqdn | Where-Object {$_.PowerState -eq "PoweredOff"}).Count
                                        }
                                        Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                                    }
                                }
                                $allVsphereObject += $customObject
                            }
                        }
                        Disconnect-VIServer -Server $vcfMgmtVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }

                $allVsphereObject | Sort-Object 'Domain Type', 'Domain Name'
            } else {
                Write-LogMessage -Type ERROR -Message "$ErrorMessage"
            }
        } else {
            Write-LogMessage -Type ERROR -Message "$ErrorMessage"
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-VcenterOverview

Function Request-EsxiOverview {
    <#
        .SYNOPSIS
        Returns overview of ESXi hosts.

        .DESCRIPTION
        The Request-EsxiOverview cmdlet returns an overview of the ESXi host managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity and authentication to the SDDC Manager instance
        - Validates that network connectivity and authentication to the vCenter Server instances
        - Collects the ESXi host overview detail

        .EXAMPLE
        Request-EsxiOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of the ESXi hosts managed by the SDDC Manager instance.

        .EXAMPLE
        Request-EsxiOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -subscription
        This example will return an overview of the ESXi hosts managed by the SDDC Manager instance with the number of cores for VCF+ subscription.

        .EXAMPLE
        Request-EsxiOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -subscription -outputCsv F:\Reporting
        This example will return an overview of the ESXi hosts managed by the SDDC Manager instance with the number of cores for VCF+ subscription and save as a CSV file to F:\Reporting.

        .EXAMPLE
        Request-EsxiOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -anonymized
        This example will return an overview of the ESXi hosts managed by the SDDC Manager instance, but will anonymize the output.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.

        .PARAMETER subscription
        Switch to enable subscription output for the report.

        .PARAMETER outputCsv
        The path to save the output as a CSV file.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$subscription,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$outputCsv
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allEsxiHostObject = New-Object System.Collections.ArrayList
                foreach ($domain in $allWorkloadDomains) {
                    foreach ($cluster in $domain.clusters) {
                        $allAssignedEsxiHosts = (Get-VCFHost | Where-Object {$_.domain.id -eq $domain.id})
                        foreach ($assignedEsxiHost in $allAssignedEsxiHosts) {
                            $customObject = New-Object -TypeName psobject

                            $sockets = $assignedEsxiHost.cpu.cpuCores.Count
                            $coresPerSocket = $assignedEsxiHost.cpu.Cores / $sockets

                            if ($coresPerSocket -le 16) {
                                $vcfPlusLicenseCount = $sockets * 16
                            } else {
                                $vcfPlusLicenseCount = $sockets * $coresPerSocket
                            }

                            if ($PsBoundParameters.ContainsKey('anonymized')) {
                                $customObject | Add-Member -notepropertyname "Domain UUID" -notepropertyvalue $domain.id
                                $customObject | Add-Member -notepropertyname "Cluster UUID" -notepropertyvalue $cluster.id
                                $customObject | Add-Member -notepropertyname "ESXi Host UUID" -notepropertyvalue $assignedEsxiHost.id
                                $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue $assignedEsxiHost.esxiVersion
                                $customObject | Add-Member -notepropertyname "Hardware OEM" -notepropertyvalue $assignedEsxiHost.hardwareVendor
                                $customObject | Add-Member -notepropertyname "Hardware Platform" -notepropertyvalue $assignedEsxiHost.hardwareModel
                                $customObject | Add-Member -notepropertyname "CPU Sockets" -notepropertyvalue $assignedEsxiHost.cpu.cpuCores.Count
                                $customObject | Add-Member -notepropertyname "CPU Cores" -notepropertyvalue $assignedEsxiHost.cpu.Cores
                                $customObject | Add-Member -notepropertyname "CPU Cores per Socket" -notepropertyvalue ($assignedEsxiHost.cpu.Cores / $assignedEsxiHost.cpu.cpuCores.Count)
                                $customObject | Add-Member -notepropertyname "Memory (GB)" -notepropertyvalue ([Math]::round(($assignedEsxiHost.memory.totalCapacityMB) / 1024))
                                if ($PsBoundParameters.ContainsKey('subscription')) {
                                    $customObject | Add-Member -notepropertyname "VCF+ Subscription Core Count" -notepropertyvalue $vcfPlusLicenseCount
                                }
                                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $assignedEsxiHost.status
                            } else {
                                $customObject | Add-Member -notepropertyname "Domain Name" -notepropertyvalue $domain.name
                                $customObject | Add-Member -notepropertyname "Cluster Name" -notepropertyvalue (Get-VCFCluster -id $cluster.id).name
                                $customObject | Add-Member -notepropertyname "ESXi Host FQDN" -notepropertyvalue (Get-VCFHost -id $assignedEsxiHost.id).fqdn
                                $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue $assignedEsxiHost.esxiVersion
                                $customObject | Add-Member -notepropertyname "Hardware OEM" -notepropertyvalue $assignedEsxiHost.hardwareVendor
                                $customObject | Add-Member -notepropertyname "Hardware Platform" -notepropertyvalue $assignedEsxiHost.hardwareModel
                                $customObject | Add-Member -notepropertyname "CPU Sockets" -notepropertyvalue $assignedEsxiHost.cpu.cpuCores.Count
                                $customObject | Add-Member -notepropertyname "CPU Cores" -notepropertyvalue $assignedEsxiHost.cpu.Cores
                                $customObject | Add-Member -notepropertyname "CPU Cores per Socket" -notepropertyvalue ($assignedEsxiHost.cpu.Cores / $assignedEsxiHost.cpu.cpuCores.Count)
                                $customObject | Add-Member -notepropertyname "Memory (GB)" -notepropertyvalue ([Math]::round(($assignedEsxiHost.memory.totalCapacityMB) / 1024))
                                if ($PsBoundParameters.ContainsKey('subscription')) {
                                    $customObject | Add-Member -notepropertyname "VCF+ Subscription Core Count" -notepropertyvalue $vcfPlusLicenseCount
                                }
                                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $assignedEsxiHost.status
                            }
                            $allEsxiHostObject += $customObject
                        }
                    }
                }
                $allUnassignedEsxiHosts = (Get-VCFHost | Where-Object {$_.status -eq "UNASSIGNED_USEABLE"})
                foreach ($unassignedEsxiHost in $allUnassignedEsxiHosts) {
                    $customObject = New-Object -TypeName psobject

                    $sockets = $unassignedEsxiHost.cpu.cpuCores.Count
                    $coresPerSocket = $unassignedEsxiHost.cpu.Cores / $sockets

                    if ($coresPerSocket -le 16) {
                        $vcfPlusLicenseCount = $sockets * 16
                    } else {
                        $vcfPlusLicenseCount = $sockets * $coresPerSocket
                    }

                    if ($PsBoundParameters.ContainsKey('anonymized')) {
                        $customObject | Add-Member -NotePropertyName 'Domain UUID' -NotePropertyValue ""
                        $customObject | Add-Member -notepropertyname "Cluster UUID" -notepropertyvalue ""
                        $customObject | Add-Member -notepropertyname "ESXi Host UUID" -notepropertyvalue $unassignedEsxiHost.id
                        $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue $unassignedEsxiHost.esxiVersion
                        $customObject | Add-Member -notepropertyname "Hardware OEM" -notepropertyvalue $assignedEsxiHost.hardwareVendor
                        $customObject | Add-Member -notepropertyname "Hardware Platform" -notepropertyvalue $assignedEsxiHost.hardwareModel
                        $customObject | Add-Member -notepropertyname "CPU Sockets" -notepropertyvalue $assignedEsxiHost.cpu.cpuCores.Count
                        $customObject | Add-Member -notepropertyname "CPU Cores" -notepropertyvalue $assignedEsxiHost.cpu.Cores
                        $customObject | Add-Member -notepropertyname "CPU Cores per Socket" -notepropertyvalue ($assignedEsxiHost.cpu.Cores / $assignedEsxiHost.cpu.cpuCores.Count)
                        $customObject | Add-Member -notepropertyname "Memory (GB)" -notepropertyvalue ([Math]::round(($unassignedEsxiHost.memory.totalCapacityMB) / 1024))
                        if ($PsBoundParameters.ContainsKey('subscription')) {
                            $customObject | Add-Member -notepropertyname "VCF+ Subscription Core Count" -notepropertyvalue $vcfPlusLicenseCount
                        }
                        $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $unassignedEsxiHost.status
                    } else {
                        $customObject | Add-Member -notepropertyname "Domain Name" -notepropertyvalue ""
                        $customObject | Add-Member -notepropertyname "Cluster Name" -notepropertyvalue ""
                        $customObject | Add-Member -notepropertyname "ESXi Host FQDN" -notepropertyvalue (Get-VCFHost -id $unassignedEsxiHost.id).fqdn
                        $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue $unassignedEsxiHost.esxiVersion
                        $customObject | Add-Member -notepropertyname "Hardware OEM" -notepropertyvalue $assignedEsxiHost.hardwareVendor
                        $customObject | Add-Member -notepropertyname "Hardware Platform" -notepropertyvalue $assignedEsxiHost.hardwareModel
                        $customObject | Add-Member -notepropertyname "CPU Sockets" -notepropertyvalue $assignedEsxiHost.cpu.cpuCores.Count
                        $customObject | Add-Member -notepropertyname "CPU Cores" -notepropertyvalue $assignedEsxiHost.cpu.Cores
                        $customObject | Add-Member -notepropertyname "CPU Cores per Socket" -notepropertyvalue ($assignedEsxiHost.cpu.Cores / $assignedEsxiHost.cpu.cpuCores.Count)
                        $customObject | Add-Member -notepropertyname "Memory (GB)" -notepropertyvalue ([Math]::round(($unassignedEsxiHost.memory.totalCapacityMB) / 1024))
                        if ($PsBoundParameters.ContainsKey('subscription')) {
                            $customObject | Add-Member -notepropertyname "VCF+ Subscription Core Count" -notepropertyvalue $vcfPlusLicenseCount
                        }
                        $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $unassignedEsxiHost.status
                    }
                    $allEsxiHostObject += $customObject
                }

                if ($PsBoundParameters.ContainsKey('outputCsv')) {
                    $csv = Start-CreateOutputCsvDirectory -csvFolder $outputCsv -csvFileSuffix $server
                    $allEsxiHostObject | ConvertTo-Csv -NoTypeInformation | Out-File $csv
                    Write-Output "CSV file saved to $csv"
                } else {
                    $allEsxiHostObject | Sort-Object -Property Status,  'Domain Name', 'Domain UUID', 'Cluster Name', 'Cluster UUID', 'ESXi Host FQDN', 'ESXi Host UUID'
                }

            }
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-EsxiOverview

Function Request-ClusterOverview {
    <#
        .SYNOPSIS
        Returns overview of vSphere.

        .DESCRIPTION
        The Request-ClusterOverview cmdlet returns an overview of the vSphere environment managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity and authentication to the SDDC Manager instance
        - Validates that network connectivity and authentication to the vCenter Server instances
        - Collects the vSphere overview detail

        .EXAMPLE
        Request-ClusterOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of the vSphere environment managed by the SDDC Manager instance.

        .EXAMPLE
        Request-ClusterOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -anonymized
        This example will return an overview of the vSphere environment managed by the SDDC Manager instance, but will anonymize the output.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allClusterObject = New-Object System.Collections.ArrayList
                foreach ($domain in $allWorkloadDomains) {
                    foreach ($cluster in $domain.clusters) {
                        $customObject = New-Object -TypeName psobject
                        if ($PsBoundParameters.ContainsKey('anonymized')) {
                            $customObject | Add-Member -notepropertyname "Domain UUID" -notepropertyvalue $domain.id
                            $customObject | Add-Member -notepropertyname "Cluster UUID" -notepropertyvalue $cluster.id
                        } else {
                            $customObject | Add-Member -notepropertyname "Domain Name" -notepropertyvalue $domain.name
                            $customObject | Add-Member -notepropertyname "Cluster Name" -notepropertyvalue (Get-VCFCluster -id $cluster.id).name
                        }
                        $customObject | Add-Member -notepropertyname "Principal Storage" -notepropertyvalue (Get-VCFCluster -id $cluster.id).primaryDatastoreType
                        $customObject | Add-Member -notepropertyname "Stretched Cluster" -notepropertyvalue (Get-VCFCluster -id $cluster.id).isStretched
                        $allClusterObject += $customObject
                    }
                }
                $allClusterObject | Sort-Object 'Domain Name','Cluster Name','Domain UUID','Cluster UUID'
            }
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-ClusterOverview

Function Request-NetworkOverview {
    <#
        .SYNOPSIS
        Returns overview of networking.

        .DESCRIPTION
        The Request-NetworkOverview cmdlet returns an overview of the networking managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity and authentication to the SDDC Manager instance
        - Collects the networking overview detail

        .EXAMPLE
        Request-NetworkOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of the networking managed by the SDDC Manager instance.

        .EXAMPLE
        Request-NetworkOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -anonymized
        This example will return an overview of the networking managed by the SDDC Manager instance, but will anonymize the output.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allWorkloadDomains = Get-VCFWorkloadDomain
                $allNetworkingObject = New-Object System.Collections.ArrayList
                if (($vcfMgmtVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                    if (Test-VsphereConnection -server $vcfMgmtVcenterDetails.fqdn) {
                        if (Test-VsphereAuthentication -server $vcfMgmtVcenterDetails.fqdn -user $vcfMgmtVcenterDetails.ssoAdmin -pass $vcfMgmtVcenterDetails.ssoAdminPass) {
                            foreach ($domain in $allWorkloadDomains) {
                                foreach ($cluster in $domain.clusters) {
                                    if (Get-VCFEdgeCluster | Where-Object {$_.nsxtCluster.id -eq $domain.nsxtCluster.id}) { $edgeCluster = "True" } else { $edgeCluster = "False" }
                                    if ($domain.type -eq "MANAGEMENT" -and (Get-VCFApplicationVirtualNetwork)) { $avnStatus = "True"} else { $avnStatus = "False" }
                                    $customObject = New-Object -TypeName psobject
                                    $nsxManagerNode = ($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domain $domain.name -listNodes).nodes | Select-Object -First 1
                                    $vm = Get-VM -Server $vcfMgmtVcenterDetails.fqdn -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $nsxManagerNode.name } | Select-Object Name, NumCpu, MemoryGB
                                    $nsxLocalManagerSizing = $nsxLocalManagerSize | ConvertFrom-Json
                                    if ($PsBoundParameters.ContainsKey('anonymized')) {
                                        $customObject | Add-Member -notepropertyname "NSX Manager Cluster UUID" -notepropertyvalue $domain.nsxtCluster.id
                                        $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue (Get-VCFNsxtCluster -id $domain.nsxtCluster.id).version
                                        $customObject | Add-Member -notepropertyname "Domain UUID" -notepropertyvalue $domain.id
                                    } else {
                                        $customObject | Add-Member -notepropertyname "NSX Manager Cluster FQDN" -notepropertyvalue $domain.nsxtCluster.vipFqdn
                                        $customObject | Add-Member -notepropertyname "Version" -notepropertyvalue (Get-VCFNsxtCluster -id $domain.nsxtCluster.id).version
                                        $customObject | Add-Member -notepropertyname "Domain Name" -notepropertyvalue $domain.name
                                    }
                                    $customObject | Add-Member -notepropertyname "Domain Type" -notepropertyvalue $domain.type.ToLower()
                                    $customObject | Add-Member -notepropertyname "Shared" -notepropertyvalue (Get-VCFNsxtCluster -id $domain.nsxtCluster.id).isShared
                                    if ($size = $nsxLocalManagerSizing | Where-Object {$_.resources.cpu -eq $vm.NumCpu -and $_.resources.memory -eq $vm.MemoryGB}) {
                                        $customObject | Add-Member -notepropertyname "Size" -notepropertyvalue $size.name
                                    } else {
                                        $customObject | Add-Member -notepropertyname "Size" -notepropertyvalue "custom"
                                    }
                                    $customObject | Add-Member -notepropertyname "CPU" -notepropertyvalue $vm.NumCpu
                                    $customObject | Add-Member -notepropertyname "Memory (GB)" -notepropertyvalue $vm.MemoryGB
                                    $customObject | Add-Member -notepropertyname "Edge Cluster" -notepropertyvalue $edgeCluster
                                    $customObject | Add-Member -notepropertyname "AVN" -notepropertyvalue $avnStatus
                                }
                                $allNetworkingObject += $customObject
                            }
                        }
                        Disconnect-VIServer -Server $vcfMgmtVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }
                $allNetworkingObject | Sort-Object 'Domain Type'
            }

        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-NetworkOverview

Function Request-VMwareAriaSuiteOverview {
    <#
        .SYNOPSIS
        Returns an overview of VMware Aria Suite products managed by SDDC Manager.

        .DESCRIPTION
        The Request-VMwareAriaSuiteOverview cmdlet returns an overview of VMware Aria Suite products managed by SDDC Manager.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity and authentication to the SDDC Manager instance
        - Collects the VMware Aria Suite product overview detail

        .EXAMPLE
        Request-VMwareAriaSuiteOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of VMware Aria Suite products managed by the SDDC Manager instance.

        .EXAMPLE
        Request-VMwareAriaSuiteOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -anonymized
        This example will return an overview of VMware Aria Suite products managed by the SDDC Manager instance, but
        will anonymize the output.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER anonymized
        Switch to enable anonymized output for the report.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$anonymized
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allAriaSuiteObject = New-Object System.Collections.ArrayList
                $vcfApiCmdlet = @("Get-VCFvRSLCM","Get-VCFWSA","Get-VCFvRLI","Get-VCFvROPS","Get-VCFvRA")
                foreach ($apiCmdlet in $vcfApiCmdlet) {
                    if ((Invoke-Expression $apiCmdlet).status -eq "ACTIVE") {
                        if ($apiCmdlet -eq "Get-VCFvRSLCM") {
                            $product = "VMware Aria Suite Lifecycle"
                            $nodeCount = "1"
                        } elseif ($apiCmdlet -eq "Get-VCFWSA") {
                            $product = "VMware Workspace ONE Access"
                            $nodeCount = (Get-VCFWSA).nodes.Count
                        } elseif ($apiCmdlet -eq "Get-VCFvRLI") {
                            $product = "VMware Aria Operations for Logs"
                            $nodeCount = (Get-VCFvRLI).nodes.Count
                        } elseif ($apiCmdlet -eq "Get-VCFvROPS") {
                            $product = "VMware Aria Operations"
                            $nodeCount = (Get-VCFvROPS).nodes.Count
                        } elseif ($apiCmdlet -eq "Get-VCFvRA") {
                            $product = "VMware Aria Automation"
                            $nodeCount = (Get-VCFvRA).nodes.Count
                        }

                        $customObject = New-Object -TypeName psobject
                        $customObject | Add-Member -NotePropertyName "VMware Aria Suite Product" -NotePropertyValue $product
                        if ($PsBoundParameters.ContainsKey("anonymized")) {
                            $customObject | Add-Member -NotePropertyName "UUID" -NotePropertyValue (Invoke-Expression $apiCmdlet).id
                        } else {
                            if ($apiCmdlet -eq "Get-VCFvRSLCM") {
                                $customObject | Add-Member -NotePropertyName "FQDN" -NotePropertyValue (Invoke-Expression $apiCmdlet).fqdn
                            } else {
                                $customObject | Add-Member -NotePropertyName "FQDN" -NotePropertyValue (Invoke-Expression $apiCmdlet).loadBalancerFqdn
                            }
                        }
                        $customObject | Add-Member -NotePropertyName "Version" -NotePropertyValue (Invoke-Expression $apiCmdlet).version
                        $customObject | Add-Member -NotePropertyName "Status" -NotePropertyValue (Invoke-Expression $apiCmdlet).status
                        $customObject | Add-Member -NotePropertyName "Nodes" -NotePropertyValue $nodeCount
                        $allAriaSuiteObject += $customObject
                    }
                }
                $allAriaSuiteObject
            }
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-VMwareAriaSuiteOverview

Function Request-ValidatedSolutionOverview {
    <#
        .SYNOPSIS
        Returns VMware Validated Solution Overview.

        .DESCRIPTION
        The Request-ValidatedSolutionOverview cmdlet returns an overview of VMware Validated Solutions deployed.
        The cmdlet connects to the SDDC Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Collects the VMware Validated Solution details

        .EXAMPLE
        Request-ValidatedSolutionOverview -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1!
        This example will return an overview of VMware Validated Solutions.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass
    )

    Try {
        if (Test-VCFConnection -server $server) {
            if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
                $allVvsObject = New-Object System.Collections.ArrayList

                # Validate IAM Deployment
                if (($vcfNsxDetails = Get-NsxtServerDetail -fqdn $server -username $user -password $pass -domainType MANAGEMENT)) {
                    if (Test-NSXTConnection -server $vcfNsxDetails.fqdn) {
                        if (Test-NSXTAuthentication -server $vcfNsxDetails.fqdn -user $vcfNsxDetails.adminUser -pass $vcfNsxDetails.adminPass) {
                            if ((Get-NsxtVidm).vidm_enable -eq "True") {
                                $iamEnabled = "Enabled"
                            } else {
                                $iamEnabled = "Not Enabled"
                            }
                        }
                    }
                }

                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -notepropertyname "Name" -notepropertyvalue "Identity and Access Management"
                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $iamEnabled
                $allVvsObject += $customObject

                # Validate DRI Deployment
                $allWorkloadDomains = Get-VCFWorkloadDomain
                foreach ($domain in $allWorkloadDomains) {
                    if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domain $domain.name)) {
                        if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                            if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                                foreach ($cluster in Get-Cluster -Server $vcfVcenterDetails.fqdn ) {
                                    if (Get-WMCluster -Server $vcfVcenterDetails.fqdn) {
                                        $driEnabled = "Enabled"
                                    } else {
                                        $driEnabled = "Not Enabled"
                                    }
                                }
                            }
                            Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                        }
                    }
                }

                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -notepropertyname "Name" -notepropertyvalue "Developer Ready Infrastructure"
                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $driEnabled
                $allVvsObject += $customObject

                # Validate ILA Deployment
                if ((Get-VCFvRLI).status -eq "ACTIVE") {
                    if (($vcfVrliDetails = Get-vRLIServerDetail -fqdn $server -username $user -password $pass)) {
                        if (Test-vRLIConnection -server $vcfVrliDetails.fqdn) {
                            if (Test-vRLIAuthentication -server $vcfVrliDetails.fqdn -user $vcfVrliDetails.adminUser -pass $vcfVrliDetails.adminPass) {
                                if (Get-vRLISmtpConfiguration) {
                                    $ilaEnabled = "Enabled"
                                } else {
                                    $ilaEnabled = "Not Enabled"
                                }
                            }
                        }
                    }
                } else {
                    $ilaEnabled = "Not Enabled"
                }
                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -notepropertyname "Name" -notepropertyvalue "Intelligent Logging and Analytics"
                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $ilaEnabled
                $allVvsObject += $customObject

                # Validate IOM Deployment
                if ((Get-VCFvROPS).status -eq "ACTIVE") {
                    if (($vcfVropsDetails = Get-vROPsServerDetail -fqdn $server -username $user -password $pass)) {
                        if (Test-vROPSConnection -server $vcfVropsDetails.loadBalancerFqdn) {
                            if (Test-vROPSAuthentication -server $vcfVropsDetails.loadBalancerFqdn -user $vcfVropsDetails.adminUser -pass $vcfVropsDetails.adminPass) {
                                if ((Get-vROPSCollectorGroup).Count -gt 1 ) {
                                    $iomEnabled = "Enabled"
                                } else {
                                    $iomEnabled = "Not Enabled"
                                }
                            }
                        }
                    }
                } else {
                    $iomEnabled = "Not Enabled"
                }
                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -notepropertyname "Name" -notepropertyvalue "Intelligent Operations Management"
                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $iomEnabled
                $allVvsObject += $customObject

                # Validate PCA Deployment
                # TODO: Extract configadmin user and password from vRSLCM to check config in vRA
                if ((Get-VCFvRA).status -eq "ACTIVE") {
                    $vraEnabled = "Enabled"
                } else {
                    $vraEnabled = "Not Enabled"
                }
                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -notepropertyname "Name" -notepropertyvalue "Private Cloud Automation"
                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $vraEnabled
                $allVvsObject += $customObject

                # Validate PDR Deployment
                if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                    if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                        if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                            if (((Get-View -Id 'ExtensionManager-ExtensionManager').ExtensionList | Where-Object {$_.Key -eq "com.vmware.vcDr"}) -and ((Get-View -Id 'ExtensionManager-ExtensionManager').ExtensionList | Where-Object {$_.Key -eq "com.vmware.vcHms"})) {
                                $pdrEnabled = "Enabled"
                            } else {
                                $pdrEnabled = "Not Enabled"
                            }
                        }
                        Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                    }
                }

                $customObject = New-Object -TypeName psobject
                $customObject | Add-Member -notepropertyname "Name" -notepropertyvalue "Site Protection and Disaster Recovery"
                $customObject | Add-Member -notepropertyname "Status" -notepropertyvalue $pdrEnabled
                $allVvsObject += $customObject
                $allVvsObject | Sort-Object Name
            }
        }
    }
    Catch {
        Debug-ExceptionWriter -object $_
    }
}
Export-ModuleMember -Function Request-ValidatedSolutionOverview

########################################## E N D O F F U N C T I O N S ##########################################
#######################################################################################################################


#########################################################################################
############################# Start Supporting Functions ##############################

Function Test-VcfReportingPrereq {
    <#
        .SYNOPSIS
        Verifies that the minimum dependencies are met to run the PowerShell module.

        .DESCRIPTION
        The Test-VcfReportingPrereq cmdlet verifies that the minimum dependencies are met to
        run the the PowerShell module.

        .EXAMPLE
        Test-VcfReportingPrereq -sddcManagerFqdn sfo-vcf01.sfo.rainpole.io -sddcManagerUser admin@local -sddcManagerPass VMw@re1!VMw@re1!
        This example runs the prerequisite validation.

        .PARAMETER sddcManagerFqdn
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER sddcManagerUser
        The username to authenticate to the SDDC Manager.

        .PARAMETER sddcManagerPass
        The password to authenticate to the SDDC Manager.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerPass
    )

    Try {
        Clear-Host; Write-Host ""

        $vcfMinVersion = "4.4.0"

        if (Test-VCFConnection -server $sddcManagerFqdn) {
            if (Test-VCFAuthentication -server $sddcManagerFqdn -user $sddcManagerUser -pass $sddcManagerPass) {

                $vcfVersion = ((Get-VCFManager).version).Split('-')[0]
                if ($vcfVersion -lt $vcfMinVersion) {
                    $message = "VMware Cloud Foundation: SDDC Manager $vcfVersion ($($sddcManagerFqdn)) is not supported by this module. Minimum required version is $($vcfMinVersion)."
                    Show-ReportingOutput -type ERROR -message $message
                    Break
                } else {
                    $message = "VMware Cloud Foundation: SDDC Manager $vcfVersion ($($sddcManagerFqdn)) and supports the minimum required version."
                    Show-ReportingOutput -Type INFO -Message $message
                }

                $moduleName = $myInvocation.myCommand.ModuleName
                $moduleData = (Get-Module -Name $moduleName)

                if ($PSEdition -eq 'Core' -and ($PSVersionTable.OS).Split(' ')[0] -eq 'Linux') {
                    $moduleManifestPath = $moduleData.ModuleBase + '/' + $moduleData.Name + '.psd1'
                } else {
                    $moduleManifestPath = $moduleData.ModuleBase + '\' + $moduleData.Name + '.psd1'
                }

                $moduleManifest = Import-PowerShellDataFile -Path $moduleManifestPath
                $requiredModules = $moduleManifest.RequiredModules

                foreach ($module in $requiredModules) {
                    $moduleName = $module.ModuleName
                    $requiredVersion = $module.ModuleVersion
                    $installedModule = Get-Module -ListAvailable -Name $moduleName

                    if ($installedModule) {
                        $installedVersion = $installedModule.Version
                        if ($installedVersion -lt $requiredVersion) {
                            $message = "$($moduleName) $($installedVersion) is installed. Install $($moduleName) $($requiredVersion) or higher."
                            Show-ReportingOutput -type ERROR -message $message
                        } elseif ($installedVersion -ge $requiredVersion) {
                            $message = "$($moduleName) $($installedVersion) is installed version and meets the minimum required version of $($moduleName) $($requiredVersion)."
                            Show-ReportingOutput -type INFO -message $message
                        }
                    } else {
                        $message = "$($moduleName) is not installed. Install $($moduleName) $($requiredVersion) or higher."
                        Show-ReportingOutput -type ERROR -message $message
                    }
                }
            }
        }
    } Catch {
        Write-Error $_.Exception.Message
    }
}
Export-ModuleMember -Function Test-VcfReportingPrereq

Function Show-ReportingOutput {

    Param (
        [Parameter (Mandatory = $true)] [AllowEmptyString()] [String]$message,
        [Parameter (Mandatory = $false)] [ValidateSet("INFO", "ERROR", "WARNING", "EXCEPTION","ADVISORY","NOTE","QUESTION","WAIT")] [String]$type = "INFO",
        [Parameter (Mandatory = $false)] [Switch]$skipnewline
    )

    If ($type -eq "INFO") {
        $messageColour = "92m" #Green
    } elseIf ($type -in "ERROR","EXCEPTION") {
        $messageColour = "91m" # Red
    } elseIf ($type -in "WARNING","ADVISORY","QUESTION") {
        $messageColour = "93m" #Yellow
    } elseIf ($type -in "NOTE","WAIT") {
        $messageColour = "97m" # White
    }

    $ESC = [char]0x1b
    $timestampColour = "97m"

    $timeStamp = Get-Date -Format "MM-dd-yyyy_HH:mm:ss"

    If ($skipnewline) {
        Write-Host -NoNewline "$ESC[${timestampcolour} [$timestamp]$ESC[${threadColour} $ESC[${messageColour} [$type] $message$ESC[0m"
    } else {
        Write-Host "$ESC[${timestampcolour} [$timestamp]$ESC[${threadColour} $ESC[${messageColour} [$type] $message$ESC[0m"
    }
}
Export-ModuleMember -Function Show-ReportingOutput

Function Start-CreateReportDirectory {
    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$path,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sddcManagerFqdn,
        [Parameter (Mandatory = $true)] [ValidateSet("health","alert","config","upgrade","overview")] [String]$reportType
    )

    $filetimeStamp = Get-Date -Format "MM-dd-yyyy_hh_mm_ss"
    if ($reportType -eq "health") { $Global:reportFolder = $path + '\HealthReports\' }
    if ($reportType -eq "alert") { $Global:reportFolder = $path + '\AlertReports\' }
    if ($reportType -eq "config") { $Global:reportFolder = $path + '\ConfigReports\' }
    if ($reportType -eq "upgrade") { $Global:reportFolder = $path + '\UpgradeReports\' }
    if ($reportType -eq "overview") { $Global:reportFolder = $path + '\OverviewReports\' }
    if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -eq "Linux") {
        $reportFolder = ($reportFolder).split('\') -join '/' | Split-Path -NoQualifier
    }
    if (!(Test-Path -Path $reportFolder)) {
        New-Item -Path $reportFolder -ItemType "directory" | Out-Null
    }
    $reportName = $reportFolder + $filetimeStamp + "-" + $reportType + ".htm"
    $reportName
}
Export-ModuleMember -Function Start-CreateReportDirectory

Function Start-CreateOutputJsonDirectory {
    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$jsonFolder,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$jsonFileSuffix
    )

    $filetimeStamp = Get-Date -Format "MM-dd-yyyy_hh_mm_ss"
    $Global:jsonFolder = $jsonFolder
    $jsonName = $filetimeStamp + "-" + $jsonFileSuffix

    if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -eq "Linux") {
        $jsonDestination = ($jsonDestination = ($jsonFolder + "\" + $jsonName)).split('\') -join '/' | Split-Path -NoQualifier
        $jsonFolder = ($jsonFolder).split('\') -join '/' | Split-Path -NoQualifier
    } else {
        $jsonDestination = ($jsonFolder + "\" + $jsonName)
    }

    if (!(Test-Path -Path $jsonFolder)) {
        New-Item -Path $jsonFolder -ItemType "directory" | Out-Null
    }

    $jsonDestination
}

Function Start-CreateOutputCsvDirectory {
    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$csvFolder,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$csvFileSuffix
    )

    $filetimeStamp = Get-Date -Format "MM-dd-yyyy_hh_mm_ss"
    $Global:csvFolder = $csvFolder
    $csvName = $filetimeStamp + "-" + $csvFileSuffix + ".csv"

    if ($PSEdition -eq "Core" -and ($PSVersionTable.OS).Split(' ')[0] -eq "Linux") {
        $csvDestination = ($csvDestination = ($csvFolder + "\" + $csvName)).split('\') -join '/' | Split-Path -NoQualifier
        $csvFolder = ($csvFolder).split('\') -join '/' | Split-Path -NoQualifier
    } else {
        $csvDestination = ($csvFolder + "\" + $csvName)
    }

    if (!(Test-Path -Path $csvFolder)) {
        New-Item -Path $csvFolder -ItemType "directory" | Out-Null
    }

    $csvDestination
}

Function Invoke-SddcCommand {
    <#
        .SYNOPSIS
        Run a command on SDDC Manager.

        .DESCRIPTION
        The Invoke-SddcCommand cmdlet runs a command within the SDDC Manager appliance. The cmdlet connects to SDDC
        Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the Management Domain vCenter Server instance
        - Runs the command provided within the SDDC Manager appliance

        .EXAMPLE
        Invoke-SddcCommand -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -vmUser root -vmPass VMw@re1! -command "chage -l backup"
        This example runs the command provided on the SDDC Manager appliance as the root user.

        .EXAMPLE
        Invoke-SddcCommand -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -vmUser vcf -vmPass VMw@re1! -command "echo Hello World."
        This example runs the command provided on the SDDC Manager appliance as the vcf user.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER vmUser
        The username to authenticate to the virtual machine.

        .PARAMETER vmPass
        The password to authenticate to the virtual machine.

        .PARAMETER command
        The command to run on the virtual machine.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$vmUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$vmPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$command
    )

    if (Test-VCFConnection -server $server) {
        if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
            if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        $output = Invoke-VMScript -VM ($server.Split(".")[0]) -ScriptText $command -GuestUser $vmUser -GuestPassword $vmPass -Server $vcfVcenterDetails.fqdn
                        $output
                    }
                    Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                }
            }
        }
    }
}
Export-ModuleMember -Function Invoke-SddcCommand

Function Copy-FiletoSddc {
    <#
        .SYNOPSIS
        Copy a file to SDDC Manager.

        .DESCRIPTION
        The Copy-FiletoSddc cmdlet copies files to the SDDC Manager appliance. The cmdlet connects to SDDC
        Manager using the -server, -user, and -pass values:
        - Validates that network connectivity is available to the SDDC Manager instance
        - Validates that network connectivity is available to the Management Domain vCenter Server instance
        - Copies the files to the SDDC Manager appliance

        .EXAMPLE
        Copy-FiletoSddc -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -vmUser vcf -vmPass VMw@re1! -source "C:\Temp\foo.txt" -destination "/home/vcf/foo.txt"
        This example copies a file to the SDDC Manager appliance.

        .EXAMPLE
        Copy-FiletoSddc -server sfo-vcf01.sfo.rainpole.io -user admin@local -pass VMw@re1!VMw@re1! -vmuser vcf -vmPass VMw@re1! -source "C:\Temp\bar" -destination "/home/vcf/"
        This example copies a file to the SDDC Manager appliance.

        .PARAMETER server
        The fully qualified domain name of the SDDC Manager.
        
        .PARAMETER user
        The username to authenticate to the SDDC Manager.

        .PARAMETER pass
        The password to authenticate to the SDDC Manager.

        .PARAMETER vmUser
        The username to authenticate to the virtual machine.

        .PARAMETER vmPass
        The password to authenticate to the virtual machine.

        .PARAMETER source
        The source file or folder to copy to the SDDC Manager appliance.

        .PARAMETER destination
        The destination file or folder to copy to on the SDDC Manager appliance.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$server,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$user,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$pass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$vmUser,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$vmPass,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$source,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$destination
    )

    if (Test-VCFConnection -server $server) {
        if (Test-VCFAuthentication -server $server -user $user -pass $pass) {
            if (($vcfVcenterDetails = Get-vCenterServerDetail -server $server -user $user -pass $pass -domainType MANAGEMENT)) {
                if (Test-VsphereConnection -server $($vcfVcenterDetails.fqdn)) {
                    if (Test-VsphereAuthentication -server $vcfVcenterDetails.fqdn -user $vcfVcenterDetails.ssoAdmin -pass $vcfVcenterDetails.ssoAdminPass) {
                        $output = Copy-VMGuestFile -VM ($server.Split('.')[0]) -LocalToGuest -GuestUser $vmUser -GuestPassword $vmPass -Source $source -Destination $destination -Server $vcfVcenterDetails.fqdn
                        $output
                    }
                    Disconnect-VIServer -Server $vcfVcenterDetails.fqdn -Confirm:$false -WarningAction SilentlyContinue | Out-Null
                }
            }
        }
    }
}
Export-ModuleMember -Function Copy-FiletoSddc

Function Read-JsonElement {
    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCustomObject]$inputData,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly
    )

    $outputData = New-Object System.Collections.ArrayList
    foreach ($element in $inputData.PsObject.Properties.Value) {
        $elementObject = New-Object -TypeName psobject
        $elementObject | Add-Member -notepropertyname 'Component' -notepropertyvalue ($element.area -Split (":"))[0].Trim()
        $elementObject | Add-Member -notepropertyname 'Resource' -notepropertyvalue ($element.area -Split (":"))[-1].Trim()
        $elementObject | Add-Member -notepropertyname 'Alert' -notepropertyvalue $element.alert
        $elementObject | Add-Member -notepropertyname 'Message' -notepropertyvalue $element.message
        if ($PsBoundParameters.ContainsKey("failureOnly")) {
            if (($element.status -eq "FAILED")) {
                $outputData += $elementObject
            }
        } else {
            $outputData += $elementObject
        }
    }
    $outputData
}
Export-ModuleMember -Function Read-JsonElement

Function Convert-TextToHtml {
    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$sourceFile,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$label
    )

    Get-Content $sourceFile | ConvertTo-HTML -Property @{Label=$label;Expression={$_}} -Fragment
}
Export-ModuleMember -Function Convert-TextToHtml

Function Get-ClarityReportHeader {
    Param (
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$dark
    )

    # Define the default Clarity Cascading Style Sheets (CSS) for the HTML report header
    if ($PsBoundParameters.ContainsKey("dark")) {
        $clarityCssHeader = '
        <head>
        <style>
            <!--- Used Clarify CSS components for this project --->
            article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { display: block; }
            .main-container { display: flex; flex-direction: column; height: 100vh; background: var(--clr-global-app-background, #21333b); }
            header.header-6, .header.header-6 { background-color: #0e161b; }
            header, .header { display: flex; color: #fafafa; background-color: #0e161b; height: 3rem; white-space: nowrap; }
            .nav { display: flex; height: 1.8rem; list-style-type: none; align-items: center; margin: 0; width: 100%; white-space: nowrap; box-shadow: 0 -0.05rem 0 #495865 inset; }
            .nav .nav-item { display: inline-block; margin-right: 1.2rem; }
            .nav .nav-item.active > .nav-link { color: white; box-shadow: 0 -0.05rem 0 #495865 inset; }
            .nav .nav-link { color: #acbac3; font-size: 0.7rem; font-weight: 400; letter-spacing: normal; line-height: 1.8rem; display: inline-block; padding: 0 0.15rem; box-shadow: none; }
            .nav .nav-link.btn { text-transform: none; margin: 0; margin-bottom: -0.05rem; border-radius: 0; }
            .nav .nav-link:hover, .nav .nav-link:focus, .nav .nav-link:active { color: inherit; }
            .nav .nav-link:hover, .nav .nav-link.active { box-shadow: 0 -0.15rem 0 #4aaed9 inset; transition: box-shadow 0.2s ease-in; }
            .nav .nav-link:hover, .nav .nav-link:focus, .nav .nav-link:active, .nav .nav-link.active { text-decoration: none; }
            .nav .nav-link.active { color: white; font-weight: 400; }
            .nav .nav-link.nav-item { margin-right: 1.2rem; }
            .sub-nav, .subnav { display: flex; box-shadow: 0 -0.05rem 0 #cccccc inset; justify-content: space-between; align-items: center; background-color: #17242b; height: 1.8rem; }
            .sub-nav .nav, .subnav .nav { flex: 1 1 auto; padding-left: 1.2rem; }
            .sub-nav aside, .subnav aside { flex: 0 0 auto; display: flex; align-items: center; height: 1.8rem; padding: 0 1.2rem; }
            .sub-nav aside > :last-child, .subnav aside > :last-child { margin-right: 0; padding-right: 0; }
            .sidenav { line-height: 1.2rem; max-width: 15.6rem; min-width: 10.8rem; width: 18%; border-right: 0.05rem solid #152228; display: flex; flex-direction: column; }
            .sidenav .sidenav-content { flex: 1 1 auto; overflow-x: hidden; padding-bottom: 1.2rem; }
            .sidenav .sidenav-content .nav-link { border-radius: 0; border-top-left-radius: 0.15rem; border-bottom-left-radius: 0.15rem; display: inline-block; color: inherit; cursor: pointer; text-decoration: none; width: 100%; }
            .sidenav .sidenav-content > .nav-link { margin: 1.2rem 0 0 1.5rem; padding-left: 0.6rem; color: #acbac3; font-weight: 500; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 0.7rem; line-height: 1.2rem; letter-spacing: normal; }
            .sidenav .sidenav-content > .nav-link:hover { background: #324f62; }
            .sidenav .sidenav-content > .nav-link.active { background: #324f62; color: black; }
            .sidenav .nav-group { color: #acbac3; font-weight: 400; font-size: 0.7rem; letter-spacing: normal; margin-top: 1.2rem; width: 100%; }
            .sidenav .nav-group .nav-list, .sidenav .nav-group label { padding: 0 0 0 1.8rem; cursor: pointer; display: inline-block; width: 100%; margin: 0 0.3rem; }
            .sidenav .nav-group .nav-list { list-style: none; margin-top: 0; }
            .sidenav .nav-group .nav-list .nav-link { line-height: 0.8rem; padding: 0.2rem 0 0.2rem 0.6rem; }
            .sidenav .nav-group .nav-list .nav-link:hover { background: #324f62; }
            .sidenav .nav-group .nav-list .nav-link.active { background: #324f62; color: black; }
            .sidenav .nav-group label { color: #acbac3; font-weight: 500; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 0.7rem; line-height: 1.2rem; letter-spacing: normal; }
            .sidenav .nav-group input[type=checkbox] { position: absolute; clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); padding: 0; border: 0; height: 1px; width: 1px; overflow: hidden; white-space: nowrap; top: 0; left: 0; }
            .sidenav .nav-group input[type=checkbox]:focus + label { outline: #3b99fc auto 0.25rem; }
            .sidenav .collapsible label { padding: 0 0 0 1.3rem; }
            .sidenav .collapsible label:after { content: ""; float: left; height: 0.5rem; width: 0.5rem; transform: translateX(-0.4rem) translateY(0.35rem); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2012%2012%22%3E%0A%20%20%20%20%3Cdefs%3E%0A%20%20%20%20%20%20%20%20%3Cstyle%3E.cls-1%7Bfill%3A%239a9a9a%3B%7D%3C%2Fstyle%3E%0A%20%20%20%20%3C%2Fdefs%3E%0A%20%20%20%20%3Ctitle%3ECaret%3C%2Ftitle%3E%0A%20%20%20%20%3Cpath%20class%3D%22cls-1%22%20d%3D%22M6%2C9L1.2%2C4.2a0.68%2C0.68%2C0%2C0%2C1%2C1-1L6%2C7.08%2C9.84%2C3.24a0.68%2C0.68%2C0%2C1%2C1%2C1%2C1Z%22%2F%3E%0A%3C%2Fsvg%3E%0A"); background-repeat: no-repeat; background-size: contain; vertical-align: middle; margin: 0; }
            .sidenav .collapsible input[type=checkbox]:checked ~ .nav-list, .sidenav .collapsible input[type=checkbox]:checked ~ ul { height: 0; display: none; }
            .sidenav .collapsible input[type=checkbox] ~ .nav-list, .sidenav .collapsible input[type=checkbox] ~ ul { height: auto; }
            .sidenav .collapsible input[type=checkbox]:checked ~ label:after { transform: rotate(-90deg) translateX(-0.35rem) translateY(-0.4rem); }
            body:not([cds-text]) { color: #acbac3; font-weight: 400; font-size: 0.7rem; letter-spacing: normal; line-height: 1.2rem; margin-bottom: 0px; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; margin-top: 0px !important; }
            html:not([cds-text]) { color: #eaedf0; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 125%; }
            a:link { color: #4aaed9; text-decoration: none; }
            h1:not([cds-text]) { color: #eaedf0; font-weight: 200; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 1.6rem; letter-spacing: normal; line-height: 2.4rem; margin-top: 1.2rem; margin-bottom: 0; }
            h2:not([cds-text]) { color: #eaedf0; font-weight: 200; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 1.4rem; letter-spacing: normal; line-height: 2.4rem; margin-top: 1.2rem; margin-bottom: 0; }
            h3:not([cds-text]) { color: #eaedf0; font-weight: 200; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 1.1rem; letter-spacing: normal; line-height: 1.2rem; margin-top: 1.2rem; margin-bottom: 0; }
            h4:not([cds-text]) { color: #eaedf0; font-weight: 200; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 0.9rem; letter-spacing: normal; line-height: 1.2rem; margin-top: 1.2rem; margin-bottom: 0; }
            .table th { color: #eaedf0; font-size: 0.55rem; font-weight: 600; letter-spacing: 0.03em; background-color: #1b2a32; vertical-align: bottom; border-bottom-style: solid; border-bottom-width: 0.05rem; border-bottom-color: #495865; border-top: 0 none; }
            .table { border-collapse: separate; border-style: solid; border-width: 0.05rem; border-color: #495865; border-radius: 0.15rem; background-color: #21333b; color: #acbac3; margin: 0; margin-top: 1.2rem; max-width: 100%; width: 100%; }

            h3 { display: block; font-size: 1.17em; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold; }
            h4 { display: block; margin-block-start: 1.33em; margin-block-end: 1.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold; }
            .table th, .table td {font-size: 0.65rem; line-height: 0.7rem; border-top-style: solid; border-top-width: 0.05rem; border-top-color: #495865; padding: 0.55rem 0.6rem 0.55rem; text-align: left; vertical-align: top; }
            th { display: table-cell; vertical-align: inherit; font-weight: bold; text-align: -internal-center; }
            table { display: table; border-collapse: separate; box-sizing: border-box; text-indent: initial; border-spacing: 2px; border-color: gray; }
        '

    } else {
        $clarityCssHeader = '
        <head>
        <style>
            <!--- Used Clarify CSS components for this project --->
            article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { display: block; }
            .main-container { display: flex; flex-direction: column; height: 100vh; background: var(--clr-global-app-background, #fafafa); }
            header.header-6, .header.header-6 { background-color: var(--clr-header-6-bg-color, #00364d); }
            header, .header { display: flex; color: var(--clr-header-font-color, #fafafa); background-color: var(--clr-header-bg-color, #333333); height: 3rem; white-space: nowrap; }
            .nav {display: flex; height: 1.8rem; list-style-type: none; align-items: center; margin: 0; width: 100%; white-space: nowrap; box-shadow: 0 -0.05rem 0 #cccccc inset; box-shadow: 0 -0.05rem 0 var(--clr-nav-box-shadow-color, #cccccc) inset; }
            .nav .nav-item { display: inline-block; margin-right: 1.2rem; }
            .nav .nav-item.active > .nav-link { color: black; color: var(--clr-nav-link-active-color, black); box-shadow: 0 -0.05rem 0 #cccccc inset; box-shadow: 0 -0.05rem 0 var(--clr-nav-box-shadow-color, #cccccc) inset; }
            .nav .nav-link { color: #666666; color: var(--clr-nav-link-color, #666666); font-size: 0.7rem; font-weight: 400; font-weight: var(--clr-nav-link-font-weight, 400); letter-spacing: normal; line-height: 1.8rem; display: inline-block; padding: 0 0.15rem; box-shadow: none; }
            .nav .nav-link.btn { text-transform: none; margin: 0; margin-bottom: -0.05rem; border-radius: 0; }
            .nav .nav-link:hover, .nav .nav-link:focus, .nav .nav-link:active { color: inherit; }
            .nav .nav-link:hover, .nav .nav-link.active { box-shadow: 0 -0.15rem 0 #0072a3 inset; box-shadow: 0 -0.15rem 0 var(--clr-nav-active-box-shadow-color, #0072a3) inset; transition: box-shadow 0.2s ease-in; }
            .nav .nav-link:hover, .nav .nav-link:focus, .nav .nav-link:active, .nav .nav-link.active { text-decoration: none; }
            .nav .nav-link.active { color: black; color: var(--clr-nav-link-active-color, black); font-weight: 400; font-weight: var(--clr-nav-link-active-font-weight, 400); }
            .nav .nav-link.nav-item { margin-right: 1.2rem; }
            .sub-nav, .subnav { display: flex; box-shadow: 0 -0.05rem 0 #cccccc inset; box-shadow: 0 -0.05rem 0 var(--clr-nav-box-shadow-color, #cccccc) inset; justify-content: space-between; align-items: center; background-color: white; background-color: var(--clr-subnav-bg-color, white); height: 1.8rem; }
            .sub-nav .nav, .subnav .nav { flex: 1 1 auto; padding-left: 1.2rem; }
            .sub-nav aside, .subnav aside { flex: 0 0 auto; display: flex; align-items: center; height: 1.8rem; padding: 0 1.2rem; }
            .sub-nav aside > :last-child, .subnav aside > :last-child { margin-right: 0; padding-right: 0; }
            .sidenav { line-height: 1.2rem; max-width: 15.6rem; min-width: 10.8rem; width: 18%; border-right: 0.05rem solid #cccccc; display: flex; flex-direction: column; }
            .sidenav .collapsible label padding: 0 0 0 1.3rem; }
            .sidenav .nav-group label {color: #333333; color: var(--clr-sidenav-header-color, #333333); font-weight: 500; font-weight: var(--clr-sidenav-header-font-weight, 500); font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-family: var(--clr-sidenav-header-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 0.7rem; line-height: 1.2rem; letter-spacing: normal; }
            .sidenav { line-height: 1.2rem; max-width: 15.6rem; min-width: 10.8rem; width: 18%; border-right: 0.05rem solid #cccccc; display: flex; flex-direction: column; }
            .sidenav .sidenav-content { flex: 1 1 auto; overflow-x: hidden; padding-bottom: 1.2rem; }
            .sidenav .sidenav-content .nav-link { border-radius: 0; border-top-left-radius: 0.15rem; border-top-left-radius: var(--clr-sidenav-link-active-border-radius, 0.15rem); border-bottom-left-radius: 0.15rem; border-bottom-left-radius: var(--clr-sidenav-link-active-border-radius, 0.15rem); display: inline-block; color: inherit; cursor: pointer; text-decoration: none; width: 100%; }
            .sidenav .sidenav-content > .nav-link { margin: 1.2rem 0 0 1.5rem; padding-left: 0.6rem; color: #333333; color: var(--clr-sidenav-header-color, #333333); font-weight: 500; font-weight: var(--clr-sidenav-header-font-weight, 500); font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-family: var(--clr-sidenav-header-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif);font-size: 0.7rem; line-height: 1.2rem; letter-spacing: normal; }
            .sidenav .sidenav-content > .nav-link:hover { background: #e8e8e8; background: var(--clr-sidenav-link-hover-color, #e8e8e8); }
            .sidenav .sidenav-content > .nav-link.active { background: #d8e3e9; background: var(--clr-sidenav-link-active-bg-color, #d8e3e9); color: black; color: var(--clr-sidenav-link-active-color, black); }
            .sidenav .nav-group { color: #666666; color: var(--clr-sidenav-color, #666666); font-weight: 400; font-weight: var(--clr-sidenav-font-weight, 400); font-size: 0.7rem; letter-spacing: normal; margin-top: 1.2rem; width: 100%; }
            .sidenav .nav-group .nav-list, .sidenav .nav-group label { padding: 0 0 0 1.8rem; cursor: pointer; display: inline-block; width: 100%; margin: 0 0.3rem; }
            .sidenav .nav-group .nav-list { list-style: none; margin-top: 0; }
            .sidenav .nav-group .nav-list .nav-link { line-height: 0.8rem; padding: 0.2rem 0 0.2rem 0.6rem; }
            .sidenav .nav-group .nav-list .nav-link:hover { background: #e8e8e8; background: var(--clr-sidenav-link-hover-color, #e8e8e8); }
            .sidenav .nav-group .nav-list .nav-link.active { background: #d8e3e9; background: var(--clr-sidenav-link-active-bg-color, #d8e3e9); color: black; color: var(--clr-sidenav-link-active-color, black); }
            .sidenav .nav-group label { color: #333333; color: var(--clr-sidenav-header-color, #333333); font-weight: 500; font-weight: var(--clr-sidenav-header-font-weight, 500); font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-family: var(--clr-sidenav-header-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 0.7rem; line-height: 1.2rem; letter-spacing: normal; }
            .sidenav .nav-group input[type=checkbox] { position: absolute; clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); padding: 0; border: 0; height: 1px; width: 1px; overflow: hidden; white-space: nowrap; top: 0; left: 0; }
            .sidenav .nav-group input[type=checkbox]:focus + label { outline: #3b99fc auto 0.25rem; }
            .sidenav .collapsible label { padding: 0 0 0 1.3rem; }
            .sidenav .collapsible label:after { content: ""; float: left; height: 0.5rem; width: 0.5rem; transform: translateX(-0.4rem) translateY(0.35rem); background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2012%2012%22%3E%0A%20%20%20%20%3Cdefs%3E%0A%20%20%20%20%20%20%20%20%3Cstyle%3E.cls-1%7Bfill%3A%239a9a9a%3B%7D%3C%2Fstyle%3E%0A%20%20%20%20%3C%2Fdefs%3E%0A%20%20%20%20%3Ctitle%3ECaret%3C%2Ftitle%3E%0A%20%20%20%20%3Cpath%20class%3D%22cls-1%22%20d%3D%22M6%2C9L1.2%2C4.2a0.68%2C0.68%2C0%2C0%2C1%2C1-1L6%2C7.08%2C9.84%2C3.24a0.68%2C0.68%2C0%2C1%2C1%2C1%2C1Z%22%2F%3E%0A%3C%2Fsvg%3E%0A"); background-repeat: no-repeat; background-size: contain; vertical-align: middle; margin: 0; }
            .sidenav .collapsible input[type=checkbox]:checked ~ .nav-list, .sidenav .collapsible input[type=checkbox]:checked ~ ul { height: 0; display: none; }
            .sidenav .collapsible input[type=checkbox] ~ .nav-list, .sidenav .collapsible input[type=checkbox] ~ ul { height: auto; }
            .sidenav .collapsible input[type=checkbox]:checked ~ label:after { transform: rotate(-90deg) translateX(-0.35rem) translateY(-0.4rem); }
            body:not([cds-text]) { color: var(--clr-p1-color, #666666); font-weight: var(--clr-p1-font-weight, 400); font-size: 0.7rem; letter-spacing: normal; line-height: 1.2rem; margin-bottom: 0px; font-family: var(--clr-font, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); margin-top: 0px !important; }
            html:not([cds-text]) { color: var(--clr-global-font-color, #666666); font-family: var(--clr-font, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 125%; }
            a:link { color: var(--clr-link-color, #0072a3); text-decoration: none; }
            h1:not([cds-text]) { color: var(--clr-h1-color, black); font-weight: var(--clr-h1-font-weight, 200); font-family: var(--clr-h1-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 1.6rem; letter-spacing: normal; line-height: 2.4rem; margin-top: 1.2rem; margin-bottom: 0px; }
            h2:not([cds-text]) { color: var(--clr-h2-color, black); font-weight: var(--clr-h2-font-weight, 200); font-family: var(--clr-h2-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 1.4rem; letter-spacing: normal; line-height: 2.4rem; margin-top: 1.2rem; margin-bottom: 0px; }
            h3:not([cds-text]) { color: var(--clr-h3-color, black); font-weight: var(--clr-h3-font-weight, 200); font-family: var(--clr-h3-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 1.1rem; letter-spacing: normal; line-height: 1.2rem; margin-top: 1.2rem; margin-bottom: 0px; }
            h4:not([cds-text]) { color: var(--clr-h4-color, black); font-weight: var(--clr-h4-font-weight, 200); font-family: var(--clr-h4-font-family, ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif); font-size: 0.9rem; letter-spacing: normal; line-height: 1.2rem; margin-top: 1.2rem; margin-bottom: 0px; }
            .table th { color: var(--clr-thead-color, #666666); font-size: 0.55rem; font-weight: 600; letter-spacing: 0.03em; background-color: var(--clr-thead-bgcolor, #fafafa); vertical-align: bottom; border-bottom-style: solid; border-bottom-width: var(--clr-table-borderwidth, 0.05rem); border-bottom-color: var(--clr-table-border-color, #cccccc); border-top: 0px none; }
            .table { border-collapse: separate; border-style: solid; border-width: var(--clr-table-borderwidth, 0.05rem); border-color: var(--clr-table-border-color, #cccccc); border-radius: var(--clr-table-border-radius, 0.15rem); background-color: var(--clr-table-bgcolor, white); color: var(--clr-table-font-color, #666666); margin: 1.2rem 0px 0px; max-width: 100%; width: 100%; }

            a { background-color: transparent; }
            abbr[title] { border-bottom: none; text-decoration: underline dotted; }
            b, strong { font-weight: inherit; }
            b, strong { font-weight: bolder; }
            [type="checkbox"], [type="radio"] { box-sizing: border-box; padding: 0px; }
            pre { border-color: var(--clr-color-neutral-400, #cccccc); border-width: var(--clr-global-borderwidth, 0.05rem); border-style: solid; border-radius: var(--clr-global-borderradius, 0.15rem); }
            ul:not([cds-list]), ol:not([cds-list]) { list-style-position: inside; margin-left: 0px; margin-top: 0px; margin-bottom: 0px; padding-left: 0px; }
            li > ul:not([cds-list]) { margin-top: 0px; margin-left: 1.1em; }
            body p:not([cds-text]) { color: var(--clr-p1-color, #666666); font-weight: var(--clr-p1-font-weight, 400); font-size: 0.7rem; letter-spacing: normal; line-height: 1.2rem; margin-top: 1.2rem; margin-bottom: 0px; }
            a:visited { color: var(--clr-link-visited-color, #5659b8); text-decoration: none; }
            .main-container .content-container .content-area > :first-child { margin-top: 0px; }
            .nav .nav-link:hover, .nav .nav-link.active { box-shadow: 0 -0.15rem 0 var(--clr-nav-active-box-shadow-color, #0072a3) inset; transition: box-shadow 0.2s ease-in 0s; }
            .nav .nav-link.active { color: var(--clr-nav-link-active-color, black); font-weight: var(--clr-nav-link-active-font-weight, 400); }
            :root { --clr-subnav-bg-color:var(--clr-color-neutral-0); --clr-nav-box-shadow-color:var(--clr-color-neutral-400); }
            :root { --clr-sidenav-border-color:var(--clr-color-neutral-400); --clr-sidenav-border-width:var(--clr-global-borderwidth); --clr-sidenav-link-hover-color:var(--clr-color-neutral-200); --clr-sidenav-link-active-color:var(--clr-color-neutral-1000); --clr-sidenav-link-active-bg-color:var(--clr-global-selection-color); --clr-sidenav-link-active-border-radius:var(--clr-global-borderradius); --clr-sidenav-header-color:var(--clr-h6-color); --clr-sidenav-header-font-weight:var(--clr-h6-font-weight); --clr-sidenav-header-font-family:var(--clr-h6-font-family); --clr-sidenav-color:var(--clr-p1-color); --clr-sidenav-font-weight:var(--clr-p1-font-weight); }
            .table th, .table td { font-size: 0.65rem; line-height: 0.7rem; border-top-style: solid; border-top-width: var(--clr-table-borderwidth, 0.05rem); border-top-color: var(--clr-tablerow-bordercolor, #e8e8e8); padding: 0.55rem 0.6rem; text-align: left; vertical-align: top; }
        '

    }
    $clarityCssShared = '
            .alertOK { color: #61B715; font-weight: bold }
            .alertWarning { color: #FDD008; font-weight: bold }
            .alertCritical { color: #F55047; font-weight: bold }
            .alertSkipped { font-weight: bold }
            .table th, .table td { text-align: left; }

            :root { --cds-global-base: 20; }
            body { margin: 0px; }
            .main-container .content-container .sidenav { flex: 0 0 auto; order: -1; overflow: hidden; }
            .main-container .content-container .content-area > :first-child { margin-top: 0; }
            .main-container .content-container .content-area { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 1.2rem 1.2rem 1.2rem 1.2rem; }
            .main-container header, .main-container .header { flex: 0 0 3rem; }
            .main-container .header .branding { max-width: auto; min-width: 0px; overflow: hidden; }
            .main-container .sub-nav, .main-container .subnav { flex: 0 0 1.8rem; }
            .main-container .content-container { display: flex; flex: 1 1 auto; min-height: 0.05rem; }
            header .branding, .header .branding { display: flex; flex: 0 0 auto; min-width: 10.2rem; padding: 0px 1.2rem; height: 3rem; }
            header .branding .title, .header .branding .title { color: #fafafa; font-weight: 400; font-family: ClarityCityRegular, "Avenir Next", "Helvetica Neue", Arial, sans-serif; font-size: 0.8rem; letter-spacing: 0.01em; line-height: 3rem; text-decoration: none; }
            header .branding > a, header .branding > .nav-link, .header .branding > a, .header .branding > .nav-link { display: inline-flex; align-items: center; height: 3rem; }
            header .branding .clr-icon, header .branding cds-icon, header .branding clr-icon, .header .branding .clr-icon, .header .branding cds-icon, .header .branding clr-icon { flex-grow: 0; flex-shrink: 0; height: 1.8rem; width: 1.8rem; margin-right: 0.45rem; }

            ul:not([cds-list]), ol:not([cds-list]) { list-style-position: inside; margin-left: 0; margin-top: 0; margin-bottom: 0; padding-left: 0; }
            a { background-color: transparent; -webkit-text-decoration-skip: objects; }
            h1 { font-size: 2em; margin: 0.67em 0px; }
            img { border-style: none; }
            img { vertical-align: middle; }
            *, ::before, ::after { box-sizing: border-box; }
            *, ::before, ::after { box-sizing: inherit; }
            table { border-spacing: 0px; }
            pre { margin: 0.6rem 0px; }
            html { box-sizing: border-box; }
            html { -webkit-tap-highlight-color: transparent; }
            html { -ms-overflow-style: scrollbar; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
            html { font-family: sans-serif; line-height: 1.15; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; }
            .table tbody tr:first-child td { border-top: 0px none; }
            .table thead th:first-child { border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border-top-left-radius: var(--clr-table-cornercellradius, 0.1rem); }
            .table thead th:last-child { border-top-left-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border-top-right-radius: var(--clr-table-cornercellradius, 0.1rem); }
            .table tbody:last-child tr:last-child td:first-child { border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: var(--clr-table-cornercellradius, 0.1rem); }
            .table tbody:last-child tr:last-child td:last-child { border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-left-radius: 0px; border-bottom-right-radius: var(--clr-table-cornercellradius, 0.1rem); }

            @font-face {font-family: ClarityCityRegular;src: url(data:font/ttf;base64,AAEAAAASAQAABAAgRFNJRwAAAAEAAKDAAAAACEdERUYOFg7OAAABLAAAAKRHUE9Ty6vPZgAAAdAAAAUGR1NVQgABAAAAAAbYAAAACk9TLzJn6qhoAAAG5AAAAGBjbWFw6o/7lgAAB0QAAAPOY3Z0IAtzAz0AAJHMAAAANGZwZ22eNhHKAACSAAAADhVnYXNwAAAAEAAAkcQAAAAIZ2x5Zq5Az/QAAAsUAAB1OGhlYWQUt0WjAACATAAAADZoaGVhBusE8gAAgIQAAAAkaG10eNCiRG8AAICoAAAE8GxvY2EmLwnmAACFmAAAAnptYXhwAzwPUQAAiBQAAAAgbmFtZR5T2ZUAAIg0AAADqHBvc3RL5mIwAACL3AAABeZwcmVwaEbInAAAoBgAAACnAAEAAAAMAAAAAACaAAIAFwACAAsAAQAOACAAAQAiACQAAQAmAC0AAQAxADQAAQA2AEMAAQBHAFsAAQBdAGEAAQBjAHYAAQB4AHsAAQB+AH4AAQCAAIoAAQCMAI4AAQCQAJkAAQCcAK8AAQCzALoAAQC/AMcAAQDJAM0AAQDPAOEAAQEWARgAAQEaARoAAQEiASIAAQEtAS4AAwACAAEBLQEuAAEAAQAAAAoAIAA8AAFERkxUAAgABAAAAAD//wACAAAAAQACbWFyawAObWttawAWAAAAAgAAAAEAAAABAAIAAwAIABAAGAAEAAAAAQAYAAQAAAABAFwABgEAAAEDmgABBAgEEAABAAwAFgACAAAAFgAAABwABQAYAB4AJAAqADAAAf+EAgUAAf+LAgUAAQFJAgUAAQFLAq8AAQIAAq8AAQF4Aq8AAQEOAa0AAQO8A9IAAQAMABYAAgAAAZAAAAGWAMIBkgGYAZgBmAGYAZgBmAGSAZgBmAGeAaQBpAGeAaoBsAG2AbABvAHCAcIBwgHCAcIByAHCAcIBvAHCAZ4BpAGeAc4B1AHUAdQB1AHUAdQBzgHaAeAB2gHmAewB8gHyAewB8gGeAaQBpAGkAaQBpAGkAfgBpAGeAf4CBAIEAf4CCgIQAhACCgIWAhwCFgIiAigCKAIoAigCKAIoAiICKAIuAjQCNAI0AjQCOgJAAkACQAJAAkYCTAJMAkwCUgJYAlgCWAJYAlgCWAJSAlgCWAJeAmQCagJqAmQCcAJ2AnwCfAJ8AnwCfAKCAnwCfAJ2AnwCiAKOAogClAKUApoCmgKaApoCmgKaAqACoAKmAqwCpgKyApQCuAK+Ar4CuAK+AsQCygLKAsoCygLKAsoC0ALKAtYC3ALiAuIC3ALoAu4C7gLoAvQC+gL6AvoC+gL6AvoC9AL6AwADBgMGAwYDBgMMAxIDEgMSAxIDGAMeAx4DHgMkAyoDKgMqAyoDKgMqAyQDKgMqAAH/hAIFAAH/iwIFAAEBcwKvAAEBcwNqAAEBmAKvAAEBmANqAAEBVQKvAAEBaAKvAAEBVQNqAAEBZAKvAAEBZANqAAEBXP+/AAEAlAKvAAEAlANqAAEAlwKvAAEAlwNqAAEAtwKvAAEBkAKvAAEBkANqAAEBoQKvAAEBTgKvAAEBTgNqAAEBMwKvAAEBMwNqAAEBJwKvAAEBJwNqAAEBegKvAAEBegNqAAECFgKvAAECFgNqAAEBUQKvAAEBUQNqAAEBNAKvAAEBNANqAAEBEAIFAAEBEALAAAEClgIFAAEBIQIFAAEBIQLAAAEAjwLAAAEBKwIFAAEBKwLAAAEBO/++AAEBKgIFAAEBKgLAAAEAfQIFAAEAfQLAAAEAeAIFAAEAfQK7AAEAfQN2AAEAlwK7AAEBMgIFAAEBMgLAAAEBNQIFAAEBNQLAAAEBPgIFAAEC8wIFAAEA3QIFAAEA3QLAAAEA7wIFAAEA7wLAAAEBHwIFAAEBHwLAAAEBjwIFAAEBjwLAAAEBEQIFAAEBEQLAAAEA8wIFAAEA8wLAAAEBQQIFAAEBQQLAAAEAdgECAAEADAAWAAIAAAAiAAAAKAALACQAKgAwADAANgA8AEIASABOAFQAWgAB/4QCBQAB/4sCBQABADsCwAABAMcCwAABAK0CwAABANkCwAABAHkCwAABAMgCwAABAKACwAABAPECwAABAKYCwAABAOMCwAABAAIBLQEuAAEABQEWARcBGAEaASIAAgATAAIACwAAAA4AIAAKACIAJAAdACYALQAgADEANAAoADYAQwAsAEcAWwA6AF0AYQBPAGMAdgBUAHgAewBoAH4AfgBsAIAAigBtAIwAjgB4AJAAmQB7AJwArwCFALMAugCZAL8AxwChAMkAzQCqAM8A4QCvAAIAAwEvATEAAAEzATgAAwE6ATsACQAAAAEAAAAAAAAAAAAAAAMCSwGQAAUACAKKAlgAAABLAooCWAAAAV4AFAE2AAAAAAUAAAAAAAAAAAAABwAAAAAAAAAAAAAAAFVLV04AQAAgIhIDG/8zAAADGwDNIAAAkwAAAAACBQKvAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAO6AAAAYABAAAUAIAAvADkAfgCjAKUAqQCrAK8AtAC4ALsBBwETARsBHwEjASsBMQE3AToBPgFIAU0BWwFlAWsBfgI3AscC3QMHAyYehR65Hr0e8yAGIBQgGSAeICIgJiAwIDogrCEiIhL//wAAACAAMAA6AKEApQCoAKsArgC0ALYAuwC/AQwBFgEeASIBKgEuATYBOQE9AUEBTAFQAV4BagFuAjcCxgLYAwcDJh6AHrgevB7yIAIgEyAYIBwgIiAmIC8gOSCsISIiEv//AAAAsgAAAAAAdQAAAF8AAAB7AAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/mIAAAAA/ib+CAAAAAAAAAAAAADg7+DwAADg1ODKAADg0+Bs4AnfCgABAGAAAAB8AQQAAAEGAAABBgAAAQYAAAEIAZgBpgGwAbIBtAG2AbwBvgHAAcIB0AHSAegB9gH4AAACFgIYAAAAAAIeAigCKgIsAi4AAAAAAjIAAAAAAjIAAAAAAAAAAAAAAAEA8QEOAPgBFwEkAScBDwD7APwA9wEbAO0BAQDsAPkA7gDvASEBHwEgAPMBJgACAA0ADgASABYAIQAiACUAJgAuAC8AMQA1ADYAOwBFAEcASABMAFAAUwBcAF0AYgBjAGgA/wD6AQABIwEEATYAbAB3AHgAfACAAIsAjACPAJAAmACaAJwAoAChAKYAsACyALMAtwC8AL8AyADJAM4AzwDUAP0BLAD+ASIA8gEWARkBNAEpASoBOAEoAPUBMgD0AAcAAwAFAAsABgAKAAwAEQAdABcAGQAaACsAJwAoACkAEwA6AD8APAA9AEMAPgEdAEIAVwBUAFUAVgBkAEYAuwBxAG0AbwB1AHAAdAB2AHsAhwCBAIMAhACVAJIAkwCUAH0ApQCqAKcAqACuAKkBHgCtAMMAwADBAMIA0ACxANIACAByAAQAbgAJAHMADwB5ABAAegAUAH4AFQB/AB4AiAAbAIUAHwCJABgAggAjAI0AJACOACwAlgAtAJcAKgCRADAAmwAyAJ0AMwCeADQAnwA3AKIAOQCkADgAowBBAKwAQACrAEQArwBJALQASwC2AEoAtQBNALgATwC6AE4AuQBSAL4AUQC9AFkAxQBbAMcAWADEAFoAxgBfAMsAZQDRAGYAaQDVAGsA1wBqANYBMwExATABNQE6ATkBOwE3AGEAzQBeAMoAYADMABwAhgAgAIoAZwDTAREBEAEVARIBFAEGAQcBBQETASUAAAAKAF3/MwGaAxsAAwAPABUAGQAjACkANQA5AD0ASAD6QPdBASEBSwAWGBUVFnIAASQBBwIBB2cGAQIFAQMEAgNnAAQlAQoMBApnAAwLAQkIDAlnAAgmARENCBFnJwEUDg0UVxABDQAODw0OZwAPABITDxJnABMoGgIYFhMYZwAVABcZFRdoABkpARweGRxnAB4AHRseHWcAGyoBIx8bI2ciAR8AISAfIWcAIAAAIFcAICAAXwAAIABPPj42NioqJCQaGhAQBAQ+SD5IR0ZFRENCQD89PDs6Njk2OTg3KjUqNTQzMjEwLy4tLCskKSQpKCcmJRojGiMiISAfHh0cGxkYFxYQFRAVFBMSEQQPBA8REREREhEQKwYdKwUhESEHFTMVIxUzNSM1MzUHFTM1IzUHIzUzBxUzFSMVMzUzNQcVIxUzNQcVMzUzFSM1IxUzNQcVMzUHIzUzBxUzBxUzNSM3MzUBmv7DAT3yQUKmQkKmpkIiISFCQkJkQiGFpmQiIWQhpqamIWRkhUZGpmZGIM0D6EMhJSEhJSGBaCJGRiRhISUhRiE8QiJkejgXL1Bxca1xcVAvZyEvISEvIQACABkAAALMAq8ABwAKACtAKAkBBAIBTAUBBAAAAQQAaAACAg5NAwEBAQ8BTggICAoIChERERAGBxorJSEHIwEzASMnAwMCMv6BRVUBL1UBL1Vln6CcnAKv/VHmAWn+l///ABkAAALMA3oAIgACAAABBwEvATgAqgAIsQIBsKqwNSsAAP//ABkAAALMA2IAIgACAAABBwEwAKwAqgAIsQIBsKqwNSsAAP//ABkAAALMA3MAIgACAAABBwEzAMYAqgAIsQIBsKqwNSsAAP//ABkAAALMA1sAIgACAAABBwE0AJoAqgAIsQICsKqwNSsAAP//ABkAAALMA3oAIgACAAABBwE2AKsAqgAIsQIBsKqwNSsAAP//ABkAAALMAzMAIgACAAABBwE4AIIAqgAIsQIBsKqwNSsAAAACABn/UwMbAq8AFgAZAD9APBgBBgMHAQIBFgEFAgNMBwEGAAECBgFoAAMDDk0EAQICD00ABQUAYQAAABMAThcXFxkXGSQREREVIQgHHCsFBiMiJjU0NychByMBMwEiBhUUFjMyNwsCAxsjMTE8HET+gUVVAS9VAS8ZIR8bHhHpn6CQHTkyKByanAKv/VEgGRseEwFFAWn+lwAAAP//ABkAAALMA8UAIgACAAABBwE6AM0AqgAIsQICsKqwNSsAAP//ABkAAALMA2cAIgACAAABBwE7AJAAqgAIsQIBsKqwNSsAAAACABUAAAO+Aq8ADwASAEBAPREBAQFLAAIAAwgCA2cJAQgABgQIBmcAAQEAXwAAAA5NAAQEBV8HAQUFDwVOEBAQEhASERERERERERAKBx4rASEVIRUhFSEVIRUhNSEHIyURAwGhAh3+YgF1/osBnv4T/vhaWgG83QKvSuJK70qcnOYBf/6BAAAAAwBtAAACgAKvABAAGQAiAD1AOggBBQIBTAYBAgAFBAIFZwADAwBfAAAADk0HAQQEAV8AAQEPAU4bGhIRIR8aIhsiGBYRGRIZLCAIBxgrEyEyFhYVFAYHFhYVFAYGIyEBMjY1NCYjIxUTMjY1NCYjIxVtATs3VjAxMTxBNF07/rkBJzlJSTnZ5UBRUUDlAq8sTjE0Rh0eWzg2VjABjD4wMD7c/rtHODhH/gAAAAEAOP/0Ao8CuwAdAC5AKxoZCwoEAgEBTAABAQBhAAAAFE0AAgIDYQQBAwMVA04AAAAdABwmJSYFBxkrBCYmNTQ2NjMyFhcHJiYjIgYGFRQWFjMyNjcXBgYjATujYGCjXUaAMTUmZTdJfUtLfUk3ZSY1MYBGDGGkX1+jYTcyNikuTYNLTIJOLik2MTj//wA4//QCjwN6ACIADgAAAQcBLwFdAKoACLEBAbCqsDUrAAD//wA4//QCjwNzACIADgAAAQcBMQDrAKoACLEBAbCqsDUrAAAAAQA4/0ACjwK7ADYA+kAbNjUnJgQHBhsBAQAaAwIEARkPAgMEDgECAwVMS7AMUFhALAABAAQDAXIABAMABHAABgYFYQAFBRRNAAcHAGEAAAAVTQADAwJiAAICGQJOG0uwFFBYQC0AAQAEAAEEgAAEAwAEcAAGBgVhAAUFFE0ABwcAYQAAABVNAAMDAmIAAgIZAk4bS7AjUFhALgABAAQAAQSAAAQDAAQDfgAGBgVhAAUFFE0ABwcAYQAAABVNAAMDAmIAAgIZAk4bQCsAAQAEAAEEgAAEAwAEA34AAwACAwJmAAYGBWEABQUUTQAHBwBhAAAAFQBOWVlZQAsmJSokJCQTEQgHHiskBgcHNjMyFhUUBiMiJic3FjMyNjU0JiMiByc3LgI1NDY2MzIWFwcmJiMiBgYVFBYWMzI2NxcCYXVADwQHHic4KhkvEBMdJBYcFBMUDhAYV5NWYKNdRoAxNSZlN0l9S0t9STdlJjUvNgQjASUdJC0QDCoXFREPEwsROQhknVlfo2E3MjYpLk2DS0yCTi4pNgAAAgBtAAACyQKvAAoAFQAmQCMAAwMAXwAAAA5NBAECAgFfAAEBDwFODAsUEgsVDBUmIAUHGCsTMzIWFhUUBgYjIzcyNjY1NCYmIyMRbehsqV9fqWzo6FWFS0uFVZoCr1icY2OdWEdGfE9Pe0b93wAAAAIALAAAAtwCrwAOAB0APEA5BQECBgEBBwIBZwAEBANfCAEDAw5NCQEHBwBfAAAADwBODw8AAA8dDxwbGhkYFxUADgANEREmCgcZKwAWFhUUBgYjIxEjNTMRMxI2NjU0JiYjIxUzByMVMwHUqV9fqWzoVFToVYVLS4VVmrsBupoCr1icY2OdWAE5SgEs/ZhGfE9Pe0blSvL//wBtAAACyQNzACIAEgAAAQcBMQCoAKoACLECAbCqsDUrAAD//wAsAAAC3AKvAAIAEwAAAAEAbQAAAloCrwALAC9ALAAAAAECAAFnBgEFBQRfAAQEDk0AAgIDXwADAw8DTgAAAAsACxERERERBwcbKxMVIRUhFSEVIREhFbwBdf6LAZ7+EwHtAmXiSu9KAq9KAAAA//8AbQAAAloDegAiABYAAAEHAS8BKQCqAAixAQGwqrA1KwAA//8AbQAAAloDcwAiABYAAAEHATEAtwCqAAixAQGwqrA1KwAA//8AbQAAAloDcwAiABYAAAEHATMAtwCqAAixAQGwqrA1KwAA//8AbQAAAloDWwAiABYAAAEHATQAiwCqAAixAQKwqrA1KwAA//8AbQAAAloDWwAiABYAAAEHATUA6wCqAAixAQGwqrA1KwAA//8Abf9UAloCrwAiABYAAAEHATUA4/z/AAmxAQG4/P+wNSsA//8AbQAAAloDegAiABYAAAEHATYAnACqAAixAQGwqrA1KwAA//8AbQAAAloDMwAiABYAAAEHATgAcwCqAAixAQGwqrA1KwAAAAEAbf9TAloCrwAcAEdARBABBAMRAQUEAkwAAAABAgABZwkBCAgHXwAHBw5NAAICA18GAQMDD00ABAQFYQAFBRMFTgAAABwAHBEUIyQhERERCgceKxMVIRUhFSEVIyIGFRQWMzI3FwYjIiY1NDchESEVvAF1/osBnmkZIR8bHhEgIzExPBr+1AHtAmXiSu9KIBkbHhMxHTkyJR0Cr0oAAP//AG0AAAJaA2cAIgAWAAABBwE7AIEAqgAIsQEBsKqwNSsAAAABAG0AAAJaAq8ACQApQCYAAAABAgABZwUBBAQDXwADAw5NAAICDwJOAAAACQAJEREREQYHGisTFSEVIREjESEVvAF1/otPAe0CZeJK/scCr0oAAAABADj/9AKjArsAIQA1QDIREAIAAx8CAgQFAkwAAAAFBAAFZwADAwJhAAICFE0ABAQBYQABARUBThMmJSYjEAYHHCsBIREGBiMiJiY1NDY2MzIWFwcmJiMiBgYVFBYWMzI2NzUjAZABEzCTSF2jYGCjXUiULzUleDlJfUtLfUkwZybFAWr+8zA5YaRfX6NhOTA2JzBNg0tMgk4jHaQAAAD//wA4//QCowNiACIAIgAAAQcBMADRAKoACLEBAbCqsDUrAAD//wA4/u0CowK7ACIAIgAAAAMBLgINAAAAAQBtAAACngKvAAsAJ0AkAAQAAQAEAWcGBQIDAw5NAgEAAA8ATgAAAAsACxERERERBwcbKwERIxEhESMRMxEhEQKeTv5rTk4BlQKv/VEBOf7HAq/+1AEsAAEAbQAAALsCrwADABNAEAAAAA5NAAEBDwFOERACBxgrEzMRI21OTgKv/VH//wBtAAABPwN6ACIAJgAAAQcBLwBZAKoACLEBAbCqsDUrAAD//wANAAABHQNzACIAJgAAAQcBM//nAKoACLEBAbCqsDUrAAD//wAGAAABIgNbACIAJgAAAQcBNP+7AKoACLEBArCqsDUrAAD//wBmAAAAwgNbACIAJgAAAQcBNQAbAKoACLEBAbCqsDUrAAD////pAAAAuwN6ACIAJgAAAQcBNv/MAKoACLEBAbCqsDUrAAD////5AAABLwMzACIAJgAAAQcBOP+jAKoACLEBAbCqsDUrAAAAAQBJ/1MBCgKvABMAKUAmCAECARMBAwICTAABAQ5NAAICD00AAwMAYQAAABMATiQRFiEEBxorBQYjIiY1NDY3ETMRIgYVFBYzMjcBCiMxMTwUEE4ZIR8bHhGQHTkyFygMAqb9USAZGx4TAAAAAQAW//QBwgKvABAAJkAjAwICAAEBTAABAQ5NAAAAAmEDAQICFQJOAAAAEAAPEyUEBxgrFiYnNxYWMzI2NREzERQGBiOsdx85FFcvPU5ON2M/DEAyOCs4XEkBz/4xRGw8AAAAAAEAbQAAApECrwALACBAHQkIBQIEAgABTAEBAAAOTQMBAgIPAk4TEhIQBAcaKxMzEQEzAQEjAQcVI21OAVxn/t8BNGX++2xOAq/+hAF8/sb+iwFAcs4AAP//AG3+7QKRAq8AIgAvAAAAAwEuAfQAAAABAG0AAAIyAq8ABQAfQBwAAQEOTQMBAgIAYAAAAA8ATgAAAAUABRERBAcYKyUVIREzEQIy/jtOSkoCr/2bAAD//wBtAAACMgN6ACIAMQAAAQcBLwBcAKoACLEBAbCqsDUrAAD//wBtAAACMgK7ACIAMQAAAQcBLgGmAw0ACbEBAbgDDbA1KwAAAQAgAAACUgKvAA0ALEApDAsKCQYFBAMIAgEBTAABAQ5NAwECAgBgAAAADwBOAAAADQANFREEBxgrJRUhEQc1NxEzETcVBxECUv47bW1OcHBKSgEiOEs4AUL+5jpLOv8AAAAAAAEAbQAAAwcCrwALACBAHQkIBwIEAgABTAEBAAAOTQMBAgIPAk4UERIQBAcaKxMzAQEzESMRAQERI21OAP8A/05O/wH/AU4Cr/4hAd/9UQIH/iEB3/35AAABAG0AAAKzAq8ACQAeQBsHAgICAAFMAQEAAA5NAwECAg8CThIREhAEBxorEzMBETMRIwERI21OAapOTv5WTgKv/dECL/1RAi/90QD//wBtAAACswN6ACIANgAAAQcBLwFVAKoACLEBAbCqsDUrAAD//wBtAAACswNzACIANgAAAQcBMQDjAKoACLEBAbCqsDUrAAD//wBt/u0CswKvACIANgAAAAMBLgIFAAD//wBtAAACswNnACIANgAAAQcBOwCtAKoACLEBAbCqsDUrAAAAAgA4//QC9wK7AA8AHwAsQCkAAgIAYQAAABRNBQEDAwFhBAEBARUBThAQAAAQHxAeGBYADwAOJgYHFysEJiY1NDY2MzIWFhUUBgYjPgI1NCYmIyIGBhUUFhYzATujYGCjXV6hYGChXkl9Skp9SUl9S0t9SQxhpF9fo2Fho19fpGFIToJMS4NNTYNLTIJOAAD//wA4//QC9wN6ACIAOwAAAQcBLwFdAKoACLECAbCqsDUrAAD//wA4//QC9wNzACIAOwAAAQcBMwDrAKoACLECAbCqsDUrAAD//wA4//QC9wNbACIAOwAAAQcBNAC/AKoACLECArCqsDUrAAD//wA4//QC9wN6ACIAOwAAAQcBNgDQAKoACLECAbCqsDUrAAD//wA4//QC9wN6ACIAOwAAAQcBNwD4AKoACLECArCqsDUrAAD//wA4//QC9wMzACIAOwAAAQcBOACnAKoACLECAbCqsDUrAAAAAwBB//QDAAK7ABkAIwAtAQtLsApQWEAUFgEEAisqHRwZDAYFBAJMCQEFAUsbS7AMUFhAFBYBBAMrKh0cGQwGBQQCTAkBBQFLG0uwFFBYQBQWAQQCKyodHBkMBgUEAkwJAQUBSxtAFBYBBAMrKh0cGQwGBQQCTAkBBQFLWVlZS7AKUFhAGAAEBAJhAwECAhRNBgEFBQBhAQEAABUAThtLsAxQWEAgAAMDDk0ABAQCYQACAhRNAAEBD00GAQUFAGEAAAAVAE4bS7AUUFhAGAAEBAJhAwECAhRNBgEFBQBhAQEAABUAThtAIAADAw5NAAQEAmEAAgIUTQABAQ9NBgEFBQBhAAAAFQBOWVlZQA4kJCQtJCwmEycTJQcHGysAFhUUBgYjIiYnByM3JiY1NDY2MzIWFzczBwAWFwEmIyIGBhUANjY1NCYnARYzAsw0YKFeOWstNVVaLTJgo104aC0xVVb98iMfAWtJU0l9SwFafUolIf6VSlcCG35FX6RhJiI8ZzF9Q1+jYSQgOGP+2V8lAZ40TYNL/uROgkw1YCb+YTgAAAD//wA4//QC9wNnACIAOwAAAQcBOwC1AKoACLECAbCqsDUrAAAAAgA3AAAD1AKvABIAHQAtQCoAAgADBAIDZwcBAQEAXwAAAA5NBgEEBAVfAAUFDwVOISYhERERESIIBx4rEjY2MyEVIRUhFSEVIRUhIiYmNR4CMzMRIyIGBhU3YKJeAj3+YgF1/osBnv3DXqJgT0p9SlBQSX1LAa2iYEriSu9KV5leS3hDAh9NgUsAAAACAG0AAAKBAq8ADAAVACpAJwUBAwABAgMBZwAEBABfAAAADk0AAgIPAk4ODRQSDRUOFREmIAYHGSsTITIWFhUUBgYjIxUjATI2NTQmIyMRbQEXRnRDQ3RGyU4BCVVnZ1W7Aq84ZT8/ZTj3AUFORERO/twAAAIAZgAAAnoCrwAOABcALkArAAEABQQBBWcGAQQAAgMEAmcAAAAOTQADAw8DThAPFhQPFxAXESYhEAcHGisTMxUzMhYWFRQGBiMjFSMlMjY1NCYjIxFmUMdGdENDdEbJTgEJVWdnVbsCr3k4ZT8/ZTh+yE5ERE7+3AAAAAACADj/9AL3ArsAFAAnADVAMhkYFxYFAgYDAgQDAgADAkwAAgIBYQABARRNBAEDAwBhAAAAFQBOFRUVJxUmLSYnBQcZKwAGBxcHJwYGIyImJjU0NjYzMhYWFQA3JzcXNjU0JiYjIgYGFRQWFjMC9yklSjJOLnA9XaNgYKNdXqFg/vpJbTJwOUp9SUl9S0t9SQEbcS9BOkQmKmGkX1+jYWGjX/7kOV86Yk5eS4NNTYNLTIJOAAACAG0AAAKBAq8ADwAYACtAKAMBAQQBTAAEAAEABAFnAAUFA18AAwMOTQIBAAAPAE4kJCERERQGBxwrAAYGBxcjJyMVIxEhMhYWFQUzMjY1NCYjIwKBN2I9r1mumE4BF0Z0Q/46u1VnZ1W7AZpePAf59/cCrzhlP5JOREROAAAA//8AbQAAAoEDegAiAEgAAAEHAS8BEwCqAAixAgGwqrA1KwAA//8AbQAAAoEDcwAiAEgAAAEHATEAoQCqAAixAgGwqrA1KwAA//8Abf7tAoECrwAiAEgAAAADAS4BwwAAAAEALv/1Ai8CuwApAC5AKxgXAgEEAAIBTAACAgFhAAEBFE0AAAADYQQBAwMVA04AAAApACglLSQFBxkrFic3FhYzMjY2NTQmJy4CNTQ2NjMyFhcHJiYjIgYGFRQWFxYWFRQGBiOdbzEvbkQzSCVXYkthNTxpQUl1MzAsZDUpRCZWY25zN25NC209LzQhNyA0NxcSLEs5NlozMy89Ki4gNx8xMxgaWVQ4WjQAAP//AC7/9QIvA3oAIgBMAAABBwEvAPgAqgAIsQEBsKqwNSsAAP//AC7/9QIvA3MAIgBMAAABBwExAIYAqgAIsQEBsKqwNSsAAAABAC7/QAIvArsAQgD6QBs1NB8eBAUHHAEABRsEAgQBGhACAwQPAQIDBUxLsAxQWEAsAAEABAMBcgAEAwAEcAAHBwZhAAYGFE0ABQUAYQAAABVNAAMDAmIAAgIZAk4bS7AUUFhALQABAAQAAQSAAAQDAARwAAcHBmEABgYUTQAFBQBhAAAAFU0AAwMCYgACAhkCThtLsCNQWEAuAAEABAABBIAABAMABAN+AAcHBmEABgYUTQAFBQBhAAAAFU0AAwMCYgACAhkCThtAKwABAAQAAQSAAAQDAAQDfgADAAIDAmYABwcGYQAGBhRNAAUFAGEAAAAVAE5ZWVlACyUtKCQkJBMSCAceKyQGBgcHNjMyFhUUBiMiJic3FjMyNjU0JiMiByc3Jic3FhYzMjY2NTQmJy4CNTQ2NjMyFhcHJiYjIgYGFRQWFxYWFQIvNWhKDwQHHic4KhkvEBMdJBYcFBMUDhAYhWExL25EM0glV2JLYTU8aUFJdTMwLGQ1KUQmVmNuc4RYNQIjASUdJC0QDCoXFREPEwsROgtgPS80ITcgNDcXEixLOTZaMzMvPSouIDcfMTMYGllUAAEAGQAAAjUCrwAHABtAGAIBAAABXwABAQ5NAAMDDwNOEREREAQHGisBIzUhFSMRIwEA5wIc504CZUpK/ZsAAP//ABkAAAI1A3MAIgBQAAABBwExAHoAqgAIsQEBsKqwNSsAAAABABn/QAI1Aq8AIQC6QBAYAQIDABcNAgIDDAEBAgNMS7AMUFhAKwAABAMCAHIAAwIEAwJ+BwEFBQZfAAYGDk0JCAIEBA9NAAICAWIAAQEZAU4bS7AjUFhALAAABAMEAAOAAAMCBAMCfgcBBQUGXwAGBg5NCQgCBAQPTQACAgFiAAEBGQFOG0ApAAAEAwQAA4AAAwIEAwJ+AAIAAQIBZgcBBQUGXwAGBg5NCQgCBAQPBE5ZWUARAAAAIQAhEREREyQkJBMKBx4rIQc2MzIWFRQGIyImJzcWMzI2NTQmIyIHJzcjESM1IRUjEQE/EwQHHic4KhkvEBMdJBYcFBMUDhAcC+cCHOcuASUdJC0QDCoXFREPEwsRQwJlSkr9mwAAAAABAFv/9AKZAq8AFQAhQB4CAQAADk0AAQEDYQQBAwMVA04AAAAVABQUJBQFBxkrBCYmNREzERQWFjMyNjY1ETMRFAYGIwEng0lONl88PF82TkmDUwxMh1YBkv5uQWc6OmdBAZL+blaHTAAAAP//AFv/9AKZA3oAIgBTAAABBwEvAT8AqgAIsQEBsKqwNSsAAP//AFv/9AKZA3MAIgBTAAABBwEzAM0AqgAIsQEBsKqwNSsAAP//AFv/9AKZA1sAIgBTAAABBwE0AKEAqgAIsQECsKqwNSsAAP//AFv/9AKZA3oAIgBTAAABBwE2ALIAqgAIsQEBsKqwNSsAAP//AFv/9AKZA3oAIgBTAAABBwE3ANoAqgAIsQECsKqwNSsAAP//AFv/9AKZAzMAIgBTAAABBwE4AIkAqgAIsQEBsKqwNSsAAAABAFv/RwKZAq8AJQA7QDgWAQAEDQEBAA4BAgEDTAYFAgMDDk0ABAQAYQAAABVNAAEBAmEAAgIZAk4AAAAlACUkGSMkJAcHGysBERQGBiMiBhUUFjMyNxcGIyImNTQ2NyYmNREzERQWFjMyNjY1EQKZSYNTGSEfGx4RICMxMTwUEF9yTjZfPDxfNgKv/m5Wh0wgGRseEzEdOTIXKAwYnGwBkv5uQWc6OmdBAZL//wBb//QCmQPFACIAUwAAAQcBOgDUAKoACLEBArCqsDUrAAAAAQAZAAACzAKvAAYAIUAeBQEAAQFMAwICAQEOTQAAAA8ATgAAAAYABhERBAcYKwEBIwEzAQECzP7RVf7RVQEFAQQCr/1RAq/9sQJPAAEAHgAABA0CrwAMACFAHgoFAgMDAAFMAgECAAAOTQQBAwMPA04SERISEAUHGysTMxMTMxMTMwMjAwMjHli+tle2vljrTb/ATQKv/dQCLP3UAiz9UQJJ/bcAAAD//wAeAAAEDQN6ACIAXQAAAQcBLwHbAKoACLEBAbCqsDUrAAD//wAeAAAEDQNzACIAXQAAAQcBMwFpAKoACLEBAbCqsDUrAAD//wAeAAAEDQNbACIAXQAAAQcBNAE9AKoACLEBArCqsDUrAAD//wAeAAAEDQN6ACIAXQAAAQcBNgFOAKoACLEBAbCqsDUrAAAAAQAcAAACiQKvAAsAH0AcCQYDAwACAUwDAQICDk0BAQAADwBOEhISEQQHGisBASMDAyMBATMTEzMBggEHYNfXXwEH/vlg19dfAVj+qAEZ/ucBVwFY/ucBGQAAAAABABMAAAKOAq8ACAAdQBoGAwADAgABTAEBAAAOTQACAg8CThISEQMHGSsBATMTEzMBESMBKP7rYd3fXv7sUgEYAZf+swFN/mn+6AD//wATAAACjgN6ACIAYwAAAQcBLwEWAKoACLEBAbCqsDUrAAD//wATAAACjgNzACIAYwAAAQcBMwCkAKoACLEBAbCqsDUrAAD//wATAAACjgNbACIAYwAAAQcBNAB4AKoACLEBArCqsDUrAAD//wATAAACjgN6ACIAYwAAAQcBNgCJAKoACLEBAbCqsDUrAAAAAQAsAAACOQKvAAkAKUAmBQEAAQABAwICTAAAAAFfAAEBDk0AAgIDXwADAw8DThESEREEBxorNwEhNSEVASEVISwBnv5pAgL+YQGj/fM+AidKPv3ZSgAA//8ALAAAAjkDegAiAGgAAAEHAS8A+QCqAAixAQGwqrA1KwAA//8ALAAAAjkDcwAiAGgAAAEHATEAhwCqAAixAQGwqrA1KwAA//8ALAAAAjkDWwAiAGgAAAEHATUAuwCqAAixAQGwqrA1KwAAAAIAJP/0AeICEQAbACcA00AUGQEDBBgBAgMRAQUCHx4FAwYFBExLsApQWEAgAAIABQYCBWkAAwMEYQcBBAQXTQgBBgYAYQEBAAAPAE4bS7AMUFhAJAACAAUGAgVpAAMDBGEHAQQEF00AAAAPTQgBBgYBYQABARUBThtLsBRQWEAgAAIABQYCBWkAAwMEYQcBBAQXTQgBBgYAYQEBAAAPAE4bQCQAAgAFBgIFaQADAwRhBwEEBBdNAAAAD00IAQYGAWEAAQEVAU5ZWVlAFRwcAAAcJxwmIiAAGwAaJCUjEwkHGisAFhURIzUGBiMiJjU0NjYzMhc1NCYjIgYHJzYzEjY3NSYjIgYVFBYzAXdrSxtlNlNqN103TlpATCNJKx5kVhZjDkxQO1NJOAIRdWH+xVEsMVpLMk8rHRM/VxkWPTL+JTkzTxU8Li83AAD//wAk//QB4gLQACIAbAAAAAMBLwDVAAD//wAk//QB4gK4ACIAbAAAAAIBMEkAAAD//wAk//QB4gLJACIAbAAAAAIBM2MAAAD//wAk//QB4gKxACIAbAAAAAIBNDcAAAD//wAk//QB4gLQACIAbAAAAAIBNkgAAAD//wAk//QB4gKJACIAbAAAAAIBOB8AAAAAAgAk/1MCMQIRACsANwFqS7AKUFhAHB0BAwQcAQIDFQEHAi8uCQMIBwgBAQgrAQYBBkwbS7AMUFhAHB0BAwQcAQIDFQEHAi8uCQMIBwgBBQgrAQYBBkwbS7AUUFhAHB0BAwQcAQIDFQEHAi8uCQMIBwgBAQgrAQYBBkwbQBwdAQMEHAECAxUBBwIvLgkDCAcIAQUIKwEGAQZMWVlZS7AKUFhAKQACAAcIAgdpAAMDBGEABAQXTQkBCAgBYQUBAQEVTQAGBgBhAAAAEwBOG0uwDFBYQC0AAgAHCAIHaQADAwRhAAQEF00ABQUPTQkBCAgBYQABARVNAAYGAGEAAAATAE4bS7AUUFhAKQACAAcIAgdpAAMDBGEABAQXTQkBCAgBYQUBAQEVTQAGBgBhAAAAEwBOG0AtAAIABwgCB2kAAwMEYQAEBBdNAAUFD00JAQgIAWEAAQEVTQAGBgBhAAAAEwBOWVlZQBEsLCw3LDYmJBMkJCUoIQoHHisFBiMiJjU0Njc1BgYjIiY1NDY2MzIXNTQmIyIGByc2MzIWFREiBhUUFjMyNyY2NzUmIyIGFRQWMwIxIzExPBYRG2U2U2o3XTdOWkBMI0krHmRWZ2sZIR8bHhHrYw5MUDtTSTiQHTkyFyoMRiwxWksyTysdEz9XGRY9MnVh/sUgGRseE5U5M08VPC4vNwAA//8AJP/0AeIDGwAiAGwAAAACATpqAAAA//8AJP/0AeICvQAiAGwAAAACATstAAAAAAMAJP/0A4sCEQAsADMAQABoQGUdAQMEIhwCAgMVAQoIOAEGCgkDAgMHBgVMAAIACgYCCmkACAAGBwgGZw0JAgMDBGEFAQQEF00OCwwDBwcAYQEBAAAVAE40NC0tAAA0QDQ/OzktMy0yMC8ALAArEiQkJCUkJQ8HHSskNjcXBgYjIiYnBgYjIiY1NDY2MzIXNTQmIyIGByc2MzIWFzY2MzIWFSEWFjMCBgchJiYjADY2NTUmIyIGFRQWMwLKXRguIXc4QXQkIXREXmk3XTdOWkBMI0krHmRWRV8XJGo9dIH+WQhlS0lkCgFcCFZK/olJLExQO1NKQTckGjEkLD84NkFXTjJPKx0TP1cZFj0yNzExN6KJTWIBl1tKSlv+aClEKCYVPC4wNgAAAgBX//QCTAK7ABIAIgC4tg8KAgUEAUxLsApQWEAdAAICEE0ABAQDYQYBAwMXTQcBBQUAYQEBAAAVAE4bS7AMUFhAIQACAhBNAAQEA2EGAQMDF00AAQEPTQcBBQUAYQAAABUAThtLsBRQWEAdAAICEE0ABAQDYQYBAwMXTQcBBQUAYQEBAAAVAE4bQCEAAgIQTQAEBANhBgEDAxdNAAEBD00HAQUFAGEAAAAVAE5ZWVlAFBMTAAATIhMhGxkAEgARERMmCAcZKwAWFhUUBgYjIiYnFSMRMxE2NjMSNjY1NCYmIyIGBhUUFhYzAaFtPj5tQz1hHktLHmE9JE8sLE8yMlAsLFAyAhFFe05OfEU5NGECu/7pNDn+JjRdOztcNDRcOztdNAAAAAABACn/9AHwAhEAHQAuQCsaGQsKBAIBAUwAAQEAYQAAABdNAAICA2EEAQMDFQNOAAAAHQAcJiUmBQcZKxYmJjU0NjYzMhYXByYmIyIGBhUUFhYzMjY3FwYGI+58SUl8RzRfJTQaRCYzVzMzVzMmRhs0JWE1DEp9SEh8SigkMxwgN142N104IR4zJSoA//8AKf/0AfAC0AAiAHgAAAADAS8A5gAA//8AKf/0AfACyQAiAHgAAAACATF0AAAAAAEAKf9AAfACEQA2AQNAGzAvISAEBQQVAQYFNBQCAgcTCQIBAggBAAEFTEuwDFBYQC0IAQcGAgEHcgACAQYCcAAEBANhAAMDF00ABQUGYQAGBhVNAAEBAGIAAAAZAE4bS7AUUFhALggBBwYCBgcCgAACAQYCcAAEBANhAAMDF00ABQUGYQAGBhVNAAEBAGIAAAAZAE4bS7AjUFhALwgBBwYCBgcCgAACAQYCAX4ABAQDYQADAxdNAAUFBmEABgYVTQABAQBiAAAAGQBOG0AsCAEHBgIGBwKAAAIBBgIBfgABAAABAGYABAQDYQADAxdNAAUFBmEABgYVBk5ZWVlAEAAAADYANhUmJSokJCQJBx0rBBYVFAYjIiYnNxYzMjY1NCYjIgcnNy4CNTQ2NjMyFhcHJiYjIgYGFRQWFjMyNjcXBgYHBzYzAVknOCoZLxATHSQWHBQTFA4QGT9oPEl8RzRfJTQaRCYzVzMzVzMmRhs0JFszDgQHLSUdJC0QDCoXFREPEwsROgpNdEFIfEooJDMcIDdeNjddOCEeMyQpAiIBAAAAAAIAL//0AiQCuwASACIAuLYRAwIFBAFMS7AKUFhAHQYBAwMQTQAEBAJhAAICF00HAQUFAGEBAQAADwBOG0uwDFBYQCEGAQMDEE0ABAQCYQACAhdNAAAAD00HAQUFAWEAAQEVAU4bS7AUUFhAHQYBAwMQTQAEBAJhAAICF00HAQUFAGEBAQAADwBOG0AhBgEDAxBNAAQEAmEAAgIXTQAAAA9NBwEFBQFhAAEBFQFOWVlZQBQTEwAAEyITIRsZABIAEiYjEQgHGSsBESM1BgYjIiYmNTQ2NjMyFhcRAjY2NTQmJiMiBgYVFBYWMwIkSx5hPUNtPj5tQz1hHnxQLCxQMjJPLCxPMgK7/UVhNDlFfE5Oe0U5NAEX/Xw0XTs7XDQ0XDs7XTQAAAAAAgAr//QCJgLMAB0ALQBaQBMQAQMCAUwdHBsaGBcVFBMSCgFKS7AaUFhAFgACAgFhAAEBEU0EAQMDAGEAAAAVAE4bQBQAAQACAwECaQQBAwMAYQAAABUATllADR4eHi0eLCYkJiUFBxgrABYVFAYGIyImJjU0NjYzMhcmJwcnNyYnNxYXNxcHAjY2NTQmJiMiBgYVFBYWMwHPV0BzSUp0QTxqQ2NEKV56G1s2G0wpIGMbRxlPLCtQNTJQLC1RMwIMoF9Sf0hDdktIckBRUFI2PigoEh8fGyw+IP3YMFY3NFUyL1U1N1cx//8AL//0At0CuwAiAHwAAAEHAS4DHgMNAAmxAgG4Aw2wNSsAAAIAL//0AmwCuwAaACoA2rYSBAIJCAFMS7AKUFhAJgcBBQQBAAMFAGcABgYQTQAICANhAAMDF00KAQkJAWECAQEBDwFOG0uwDFBYQCoHAQUEAQADBQBnAAYGEE0ACAgDYQADAxdNAAEBD00KAQkJAmEAAgIVAk4bS7AUUFhAJgcBBQQBAAMFAGcABgYQTQAICANhAAMDF00KAQkJAWECAQEBDwFOG0AqBwEFBAEAAwUAZwAGBhBNAAgIA2EAAwMXTQABAQ9NCgEJCQJhAAICFQJOWVlZQBIbGxsqGyknEREREyYjERALBx8rASMRIzUGBiMiJiY1NDY2MzIWFzUjNTM1MxUzADY2NTQmJiMiBgYVFBYWMwJsSEseYT1DbT4+bUM9YR6jo0tI/vFQLCxQMjJPLCxPMgJJ/bdhNDlFfE5Oe0U5NKUyQED9vDRdOztcNDRcOztdNAAAAgAs//QCIAIRABUAHAA9QDoDAgIDAgFMAAQAAgMEAmcHAQUFAWEAAQEXTQYBAwMAYQAAABUAThYWAAAWHBYbGRgAFQAUEiYlCAcZKyQ2NxcGBiMiJiY1NDY2MzIWFSEWFjMCBgchJiYjAV9dGC4hdzhFeElFdUV0gf5ZCGVLSWQKAVwIVko3JBoxJCxGfU1LfEaiiU1iAZdbSkpbAAAA//8ALP/0AiAC0AAiAIAAAAADAS8A8AAA//8ALP/0AiACyQAiAIAAAAACATF+AAAA//8ALP/0AiACyQAiAIAAAAACATN+AAAA//8ALP/0AiACsQAiAIAAAAACATRSAAAA//8ALP/0AiACsQAiAIAAAAADATUAsgAA//8ALP9TAiACEQAiAIAAAAEHATUAwvz+AAmxAgG4/P6wNSsA//8ALP/0AiAC0AAiAIAAAAACATZjAAAA//8ALP/0AiACiQAiAIAAAAACATg6AAAAAAIALP9jAiACEQAnAC4A1EAXHh0CBAMgAQUECQEBBQEBBgECAQAGBUxLsB9QWEAuAAcAAwQHA2cKAQgIAmEAAgIXTQAFBQ9NAAQEAWEAAQEVTQkBBgYAYQAAABMAThtLsCFQWEAxAAUEAQQFAYAABwADBAcDZwoBCAgCYQACAhdNAAQEAWEAAQEVTQkBBgYAYQAAABMAThtALgAFBAEEBQGAAAcAAwQHA2cJAQYAAAYAZQoBCAgCYQACAhdNAAQEAWEAAQEVAU5ZWUAXKCgAACguKC0rKgAnACYWIhImJSMLBxwrBDcXBiMiJjU0NwYjIiYmNTQ2NjMyFhUhFhYzMjY3FwYHFyIGFRQWMwIGByEmJiMB4REgIzExPAkVE0V4SUV1RXSB/lkIZUsuXRguGS8JGSEfG9tkCgFcCFZKYhMxHTkyFxIDRn1NS3xGoolNYiQaMRwVAyAZGx4CMFtKSlsAAAD//wAs//QCIAK9ACIAgAAAAAIBO0gAAAAAAQAYAAABRwLQABYAWkAKDwEGBRABAAYCTEuwMlBYQBwABgYFYQAFBRZNAwEBAQBfBAEAABFNAAICDwJOG0AaAAUABgAFBmkDAQEBAF8EAQAAEU0AAgIPAk5ZQAokIxEREREQBwcdKxMzFSMRIxEjNTM1NDYzMhcHJiYjIgYVtH9/S1FRRzc2KiUJHBEXIQIFQ/4+AcJDRzpKITcJDCUcAAIAKv9UAh8CEQAfAC8Ay0AMHhACBgUJCAIBAgJMS7AKUFhAIAgBBgACAQYCaQAFBQNhBwQCAwMXTQABAQBhAAAAEwBOG0uwDFBYQCQIAQYAAgEGAmkHAQQEEU0ABQUDYQADAxdNAAEBAGEAAAATAE4bS7AUUFhAIAgBBgACAQYCaQAFBQNhBwQCAwMXTQABAQBhAAAAEwBOG0AkCAEGAAIBBgJpBwEEBBFNAAUFA2EAAwMXTQABAQBhAAAAEwBOWVlZQBUgIAAAIC8gLigmAB8AHyYlJSQJBxorAREUBgYjIiYnNxYWMzI2NTUGBiMiJiY1NDY2MzIWFzUCNjY1NCYmIyIGBhUUFhYzAh9Bc0o9cCQhHlgwWWQeYT1FbD09bEU9YR58UCwsUDIyTywsTzICBf4yQmc6Jh87HSBUTFQwNT9wR0dwPjUwWf5kLlE0M1EuLlEzNFEuAAAA//8AKv9UAh8CuAAiAIwAAAACATBjAAAAAAMAKv9UAh8DGAAOAC4APgEHQBEtHwIIBxgXAgMEAkwGBQIASkuwClBYQCsLAQgABAMIBGkJAQEBAGEAAAAOTQAHBwVhCgYCBQUXTQADAwJhAAICEwJOG0uwDFBYQC8LAQgABAMIBGkJAQEBAGEAAAAOTQoBBgYRTQAHBwVhAAUFF00AAwMCYQACAhMCThtLsBRQWEArCwEIAAQDCARpCQEBAQBhAAAADk0ABwcFYQoGAgUFF00AAwMCYQACAhMCThtALwsBCAAEAwgEaQkBAQEAYQAAAA5NCgEGBhFNAAcHBWEABQUXTQADAwJhAAICEwJOWVlZQCAvLw8PAAAvPi89NzUPLg8uKykjIRwaFRMADgANGAwHFysAJjU0NjcXBgcyFhUUBiMXERQGBiMiJic3FhYzMjY1NQYGIyImJjU0NjYzMhYXNQI2NjU0JiYjIgYGFRQWFjMBEhwbJiAjCRMaGxP1QXNKPXAkIR5YMFlkHmE9RWw9PWxFPWEefFAsLFAyMk8sLE8yAlcmHhk0MBcoJxsTEhtS/jJCZzomHzsdIFRMVDA1P3BHR3A+NTBZ/mQuUTQzUS4uUTM0US4AAAABAFcAAAIOArsAFQAtQCoSAQABAUwAAwMQTQABAQRhBQEEBBdNAgEAAA8ATgAAABUAFBEUIxQGBxorABYWFREjETQmIyIGBhURIxEzETY2MwGFWDFLSDkrSitLSxdcNwIRMls7/rcBP0BPJD0k/rcCu/74KjQAAP//AEsAAACuAsYAIgCRAAAAAwEtAPkAAAABAFcAAACiAgUAAwATQBAAAAARTQABAQ8BThEQAgcYKxMzESNXS0sCBf37//8AVwAAASgC0AAiAJEAAAACAS9CAAAA////9gAAAQYCyQAiAJEAAAACATPQAAAA////7wAAAQsCsQAiAJEAAAACATSkAAAA////0gAAAKIC0AAiAJEAAAACATa1AAAA////4gAAARgCiQAiAJEAAAACATiMAAAAAAIAMP9TAPECsQALAB8AP0A8FAEEAx8BBQQCTAYBAQEAYQAAAA5NAAMDEU0ABAQPTQAFBQJhAAICEwJOAAAeHBgXFhUPDQALAAokBwcXKxImNTQ2MzIWFRQGIxMGIyImNTQ2NxEzESIGFRQWMzI3ahsbExMbGxN0IzExPBYRSxkhHxseEQJVGxMSHBwSExv9Gx05MhcqDAH6/fsgGRseE////87/TgCpAsYAIgCZAAAAAwEtAPQAAAAB/87/TgCdAgUADwApQCYDAQABAgECAAJMAAEBEU0AAAACYgMBAgIZAk4AAAAPAA4TJQQHGCsWJic3FhYzMjY1ETMRFAYjCi0PDAwlDxkfS0c3sgkHPgUGJB0CM/3NOkoAAAAAAQBXAAACHQK7AAsAJEAhCQgFAgQCAQFMAAAAEE0AAQERTQMBAgIPAk4TEhIQBAcaKxMzEQEzBxMjJwcVI1dLARtg6dxes11LArv+LAEe7f7o5VyJAAAA//8AV/7tAh0CuwAiAJoAAAADAS4BhwAAAAEAVwAAAKICuwADABNAEAAAABBNAAEBDwFOERACBxgrEzMRI1dLSwK7/UX//wBXAAABKAOGACIAnAAAAQcBLwBCALYACLEBAbC2sDUrAAD//wBXAAABTgK7ACIAnAAAAQcBLgGPAw0ACbEBAbgDDbA1KwAAAQAcAAABBQK7AAsAIEAdCwoHBgUEAQAIAAEBTAABARBNAAAADwBOFRICBxgrAQcRIxEHNTcRMxE3AQVJS1VVS0kBYib+xAEWLEssAVr+zCYAAAAAAQBXAAADPgIRACMAmLYgGgIAAQFMS7AKUFhAFgMBAQEFYQgHBgMFBRFNBAICAAAPAE4bS7AMUFhAGgAFBRFNAwEBAQZhCAcCBgYXTQQCAgAADwBOG0uwFFBYQBYDAQEBBWEIBwYDBQURTQQCAgAADwBOG0AaAAUFEU0DAQEBBmEIBwIGBhdNBAICAAAPAE5ZWVlAEAAAACMAIiMREyMTIxQJBx0rABYWFREjETQmIyIGFREjETQmIyIGFREjETMVNjYzMhYXNjYzArtUL0tDND5OS0M0Pk5LSxRPMz1bFAxZPQIRM1s6/rcBPz9QSzr+twE/P1BLOv63AgVMKS9COTdEAAABAFcAAAIOAhEAFQCItRIBAAEBTEuwClBYQBMAAQEDYQUEAgMDEU0CAQAADwBOG0uwDFBYQBcAAwMRTQABAQRhBQEEBBdNAgEAAA8AThtLsBRQWEATAAEBA2EFBAIDAxFNAgEAAA8AThtAFwADAxFNAAEBBGEFAQQEF00CAQAADwBOWVlZQA0AAAAVABQRFCMUBgcaKwAWFhURIxE0JiMiBgYVESMRMxU2NjMBhVgxS0g5K0orS0sXXDcCETJbO/63AT9ATyQ9JP63AgVSKjT//wBXAAACDgLQACIAoQAAAAMBLwD3AAD//wBXAAACDgLJACIAoQAAAAMBMQCFAAD//wBX/u0CDgIRACIAoQAAAAMBLgGnAAD//wBXAAACDgK9ACIAoQAAAAIBO08AAAAAAgAq//QCPwIRAA8AHwAsQCkAAgIAYQAAABdNBQEDAwFhBAEBARUBThAQAAAQHxAeGBYADwAOJgYHFysWJiY1NDY2MzIWFhUUBgYjPgI1NCYmIyIGBhUUFhYz7XtISHtISHpISHpIM1YzM1YzM1czM1czDEl9SUl8SUl8SUl9SUM3Xjc3XTc3XTc3XjcAAAD//wAq//QCPwLQACIApgAAAAMBLwD6AAD//wAq//QCPwLJACIApgAAAAMBMwCIAAD//wAq//QCPwKxACIApgAAAAIBNFwAAAD//wAq//QCPwLQACIApgAAAAIBNm0AAAD//wAq//QCQgLQACIApgAAAAMBNwCVAAD//wAq//QCPwKJACIApgAAAAIBOEQAAAAAAwAz//QCTQIRABcAIQAqAPpLsApQWEASFQEEAiQjGxoMBQUECQEABQNMG0uwDFBYQBIVAQQDJCMbGgwFBQQJAQEFA0wbS7AUUFhAEhUBBAIkIxsaDAUFBAkBAAUDTBtAEhUBBAMkIxsaDAUFBAkBAQUDTFlZWUuwClBYQBcABAQCYQMBAgIXTQAFBQBhAQEAABUAThtLsAxQWEAfAAMDEU0ABAQCYQACAhdNAAEBD00ABQUAYQAAABUAThtLsBRQWEAXAAQEAmEDAQICF00ABQUAYQEBAAAVAE4bQB8AAwMRTQAEBAJhAAICF00AAQEPTQAFBQBhAAAAFQBOWVlZQAknJRInEiYGBxwrARYWFRQGBiMiJwcjNyYmNTQ2NjMyFzczABYXEyYjIgYGFSQnAxYzMjY2NQIHHyJIekhNQxpVPiMnSHtIVUUgVf40FxX9MjozVzMBeST6LjQzVjMBsyRaMkl9SSsfSCVhNUl8STIm/tlDGgEoJTddN0I1/tseN143//8AKv/0Aj8CvQAiAKYAAAACATtSAAAAAAMAKv/0A+gCEQAhADEAOABRQE4XAQgGCQMCAwUEAkwACAAEBQgEZwwJAgYGAmEDAQICF00LBwoDBQUAYQEBAAAVAE4yMiIiAAAyODI3NTQiMSIwKigAIQAgEiQmJCUNBxsrJDY3FwYGIyImJwYGIyImJjU0NjYzMhYXNjYzMhYVIRYWMyA2NjU0JiYjIgYGFRQWFjMABgchJiYjAyddGC4hdzhFeSMkeUdIe0hIe0hHeCQidUR0gf5ZCGVL/m9WMzNWMzNXMzNXMwF7ZAoBXAhWSjckGjEkLEc9PEhJfUlJfElGPDxGoolNYjdeNzddNzddNzdeNwGXW0pKWwAAAAACAFf/VAJMAhEAEgAiALi2DwoCBQQBTEuwClBYQB0ABAQCYQYDAgICEU0HAQUFAGEAAAAVTQABARMBThtLsAxQWEAhAAICEU0ABAQDYQYBAwMXTQcBBQUAYQAAABVNAAEBEwFOG0uwFFBYQB0ABAQCYQYDAgICEU0HAQUFAGEAAAAVTQABARMBThtAIQACAhFNAAQEA2EGAQMDF00HAQUFAGEAAAAVTQABARMBTllZWUAUExMAABMiEyEbGQASABEREyYIBxkrABYWFRQGBiMiJicRIxEzFTY2MxI2NjU0JiYjIgYGFRQWFjMBoW0+Pm1DPWEeS0seYT0kTywsTzIyUCwsUDICEUV7Tk58RTk0/vMCsWE0Of4mNF07O1w0NFw7O100AAAAAAIAWP9UAk0CuwASACIAP0A8DwoCBQQBTAACAhBNAAQEA2EGAQMDF00HAQUFAGEAAAAVTQABARMBThMTAAATIhMhGxkAEgARERMmCAcZKwAWFhUUBgYjIiYnESMRMxE2NjMSNjY1NCYmIyIGBhUUFhYzAaJtPj5tQz1hHktLHmE9JE8sLE8yMlAsLFAyAhFFe05OfEU5NP7zA2f+6TQ5/iY0XTs7XDQ0XDs7XTQAAAAAAgAv/1QCJAIRABIAIgC4thEDAgUEAUxLsApQWEAdAAQEAmEGAwICAhdNBwEFBQFhAAEBFU0AAAATAE4bS7AMUFhAIQYBAwMRTQAEBAJhAAICF00HAQUFAWEAAQEVTQAAABMAThtLsBRQWEAdAAQEAmEGAwICAhdNBwEFBQFhAAEBFU0AAAATAE4bQCEGAQMDEU0ABAQCYQACAhdNBwEFBQFhAAEBFU0AAAATAE5ZWVlAFBMTAAATIhMhGxkAEgASJiMRCAcZKwERIxEGBiMiJiY1NDY2MzIWFzUCNjY1NCYmIyIGBhUUFhYzAiRLHmE9Q20+Pm1DPWEefFAsLFAyMk8sLE8yAgX9TwENNDlFfE5Oe0U5NGH+MjRdOztcNDRcOztdNAAAAAABAFcAAAFqAhEADAB5tQwBAgEBTEuwClBYQBEAAQEAYQMBAAAXTQACAg8CThtLsAxQWEAVAAMDEU0AAQEAYQAAABdNAAICDwJOG0uwFFBYQBEAAQEAYQMBAAAXTQACAg8CThtAFQADAxFNAAEBAGEAAAAXTQACAg8CTllZWbYRFBERBAcaKxI2MxUiBgYVESMRMxW5akc6WzNLSwHUPUMsTzL+3wIFZf//AFcAAAGIAtAAIgCzAAAAAwEvAKIAAP//AFYAAAFqAskAIgCzAAAAAgExMAAAAP//AFf+7QFqAhEAIgCzAAAAAwEuAVIAAAABACH/9AG0AhEAJgAxQC4VAQIBFgMCAwACAkwAAgIBYQABARdNAAAAA2EEAQMDFQNOAAAAJgAlJCskBQcZKxYmJzcWMzI2NTQmJicmJjU0NjMyFhcHJiMiBhUUFhYXHgIVFAYjwHEuJ1lWMz8iMy1fTmZPL2AqJE1ILjwaNDg3RC5rUgwoJTdBLCUaIxQNG0E3RVMfGzoxKCQWHBYSESA5LkZW//8AIf/0AbQC0AAiALcAAAADAS8AtAAA//8AIf/0AbQCyQAiALcAAAACATFCAAAAAAEAIf9AAbQCEQA/AP1AHjEBBwYyHx4DBQcbAQAFGgMCBAEZDwIDBA4BAgMGTEuwDFBYQCwAAQAEAwFyAAQDAARwAAcHBmEABgYXTQAFBQBhAAAAFU0AAwMCYgACAhkCThtLsBRQWEAtAAEABAABBIAABAMABHAABwcGYQAGBhdNAAUFAGEAAAAVTQADAwJiAAICGQJOG0uwI1BYQC4AAQAEAAEEgAAEAwAEA34ABwcGYQAGBhdNAAUFAGEAAAAVTQADAwJiAAICGQJOG0ArAAEABAABBIAABAMABAN+AAMAAgMCZgAHBwZhAAYGF00ABQUAYQAAABUATllZWUALJCsoJCQkExEIBx4rJAYHBzYzMhYVFAYjIiYnNxYzMjY1NCYjIgcnNyYmJzcWMzI2NTQmJicmJjU0NjMyFhcHJiMiBhUUFhYXHgIVAbRmTw4EBx4nOCoZLxATHSQWHBQTFA4QGS1aJSdZVjM/IjMtX05mTy9gKiRNSC48GjQ4N0QuTFYCIgElHSQtEAwqFxURDxMLEToGJh43QSwlGiMUDRtBN0VTHxs6MSgkFhwWEhEgOS4AAAABAFcAAAITAsMAKgAxQC4LAQMEAUwABAADAgQDaQAFBQBhAAAAFk0AAgIBXwYBAQEPAU4TJSEkISwjBwcdKxM0NjYzMhYWFRQGBxYWFRQGBiMjNTMyNjU0JiMjNTMyNjY1NCYjIgYVESNXNl88PF81MDJAPTRdO1dDQFFRQEM3JTsiSTk5SU4CDjRTLi5TNDtPFxlXQTZWMEdHODhHRyE3IDVDRTb9/wABABX/9AFEApMAFgAvQCwWAQYBAUwAAwIDhQUBAQECXwQBAgIRTQAGBgBiAAAAFQBOIxERERETIQcHHSslBiMiJjURIzUzNTMVMxUjERQWMzI2NwFEKjY3R1FRS39/IRcRHAkVIUo6AUpDjo5D/rYcJQwJ//8AFf/0AfECuwAiALwAAAEHAS4CMgMNAAmxAQG4Aw2wNSsAAAEAFf9AAUQCkwAvAMdAGSkBCAMqFQIJCC0UAgIJEwkCAQIIAQABBUxLsAxQWEAsAAUEBYUKAQkIAgEJcgAIAAIBCAJpBwEDAwRfBgEEBBFNAAEBAGIAAAAZAE4bS7AjUFhALQAFBAWFCgEJCAIICQKAAAgAAgEIAmkHAQMDBF8GAQQEEU0AAQEAYgAAABkAThtAKgAFBAWFCgEJCAIICQKAAAgAAgEIAmkAAQAAAQBmBwEDAwRfBgEEBBEDTllZQBIAAAAvAC8jERERERckJCQLBx8rBBYVFAYjIiYnNxYzMjY1NCYjIgcnNyYmNREjNTM1MxUzFSMRFBYzMjY3FwYHBzYzARQnOCoZLxATHSQWHBQTFA4QGSw1UVFLf38hFxEcCSUhKQ8EBy0lHSQtEAwqFxURDxMLEToKRjEBSkOOjkP+thwlDAk3GgUkAQAAAAEAS//0AgICBQAVAIi1AwEDAgFMS7AKUFhAEwUEAgICEU0AAwMAYQEBAAAPAE4bS7AMUFhAFwUEAgICEU0AAAAPTQADAwFhAAEBFQFOG0uwFFBYQBMFBAICAhFNAAMDAGEBAQAADwBOG0AXBQQCAgIRTQAAAA9NAAMDAWEAAQEVAU5ZWVlADQAAABUAFSMUIxEGBxorAREjNQYGIyImJjURMxEUFjMyNjY1EQICSxdcNzlYMUtIOStKKwIF/ftSKjQyWzsBSf7BQE8kPSQBSf//AEv/9AICAtAAIgC/AAAAAwEvAOQAAP//AEv/9AICAskAIgC/AAAAAgEzcgAAAP//AEv/9AICArEAIgC/AAAAAgE0RgAAAP//AEv/9AICAtAAIgC/AAAAAgE2VwAAAP//AEv/9AIsAtAAIgC/AAAAAgE3fwAAAP//AEv/9AICAokAIgC/AAAAAgE4LgAAAAABAEv/UwI3AgUAJwD7S7AKUFhADwoBAwIJCAIBAycBBgEDTBtLsAxQWEAPCgEDAgkIAgUDJwEGAQNMG0uwFFBYQA8KAQMCCQgCAQMnAQYBA0wbQA8KAQMCCQgCBQMnAQYBA0xZWVlLsApQWEAcBAECAhFNAAMDAWEFAQEBFU0ABgYAYQAAABMAThtLsAxQWEAgBAECAhFNAAUFD00AAwMBYQABARVNAAYGAGEAAAATAE4bS7AUUFhAHAQBAgIRTQADAwFhBQEBARVNAAYGAGEAAAATAE4bQCAEAQICEU0ABQUPTQADAwFhAAEBFU0ABgYAYQAAABMATllZWUAKJCEUIxQpIQcHHSsFBiMiJjU0NjcXNQYGIyImJjURMxEUFjMyNjY1ETMRIyIGFRQWMzI3AjcjMTE8HxYMF1w3OVgxS0g5K0orSxoZIR8bHhGQHTkyHDAJBEMqNDJbOwFJ/sFATyQ9JAFJ/fsgGRseEwAA//8AS//0AgIDGwAiAL8AAAACATp5AAAAAAEACgAAAgcCBQAGABtAGAIBAgABTAEBAAARTQACAg8CThESEAMHGSsTMxMTMwMjClSsqVTZSAIF/lMBrf37AAABABAAAAMNAgUADAAhQB4KBQIDAwABTAIBAgAAEU0EAQMDDwNOEhESEhAFBxsrEzMTEzMTEzMDIwMDIxBQgY1BjYFQrkeJi0cCBf5fAaH+XwGh/fsBnf5jAAAA//8AEAAAAw0C0AAiAMkAAAADAS8BVAAA//8AEAAAAw0CyQAiAMkAAAADATMA4gAA//8AEAAAAw0CsQAiAMkAAAADATQAtgAA//8AEAAAAw0C0AAiAMkAAAADATYAxwAAAAEADAAAAfMCBQALACZAIwoHBAEEAAEBTAIBAQERTQQDAgAADwBOAAAACwALEhISBQcZKyEnByMTJzMXNzMHEwGbm5xYyMBYlJNYv8fOzgEI/cPD/f74AAABAAn/TAINAgUAEQAtQCoLCAIDAAEBAQMAAkwCAQEBEU0AAAADYgQBAwMZA04AAAARABASFCMFBxkrFic3FjMyNjc3AzMTEzMDBgYHVyUSGiMaIg8e4VO1q1HsGkw3tBFADRQaQAIH/lIBrv3BQDkBAAAA//8ACf9MAg0C0AAiAM8AAAADAS8A1gAA//8ACf9MAg0CyQAiAM8AAAACATNkAAAA//8ACf9MAg0CsQAiAM8AAAACATQ4AAAA//8ACf9MAg0C0AAiAM8AAAACATZJAAAAAAEAIQAAAbkCBQAJAClAJgUBAAEAAQMCAkwAAAABXwABARFNAAICA18AAwMPA04REhERBAcaKzcBITUhFQEhFSEhATH+1QGQ/s4BNP5oOwGDRzv+fUcAAP//ACEAAAG5AtAAIgDUAAAAAwEvALgAAP//ACEAAAG5AskAIgDUAAAAAgExRgAAAP//ACEAAAG5ArEAIgDUAAAAAgE1egAAAAACAC//9AIkAhEAEgAiAElARhEDAgUEAUwGAQMCBAIDBIAAAAUBBQABgAACAAQFAgRpBwEFAAEFWQcBBQUBYQABBQFRExMAABMiEyEbGQASABImIxEIBhkrAREjNQYGIyImJjU0NjYzMhYXNQI2NjU0JiYjIgYGFRQWFjMCJEseYT1DbT4+bUM9YR58UCwsUDIyTywsTzICBf37YTQ5RXxOTntFOTRh/jI0XTs7XDQ0XDs7XTQAAAD//wAv//QCJALQACIA2AAAAAMBLwEGAAD//wAv//QCJAK4ACIA2AAAAAIBMHoAAAD//wAv//QCJALJACIA2AAAAAMBMwCUAAD//wAv//QCJAKxACIA2AAAAAIBNGgAAAD//wAv//QCJALQACIA2AAAAAIBNnkAAAD//wAv//QCJAKJACIA2AAAAAIBOFAAAAAAAgAv/1MCcwIRACIAMgBTQFAXCQIHBggBBAciAQUBA0wAAwIGAgMGgAAEBwEHBAGAAAIABgcCBmkIAQcAAQUHAWkABQAABVkABQUAYQAABQBRIyMjMiMxKCQREyYoIQkGHSsFBiMiJjU0Njc1BgYjIiYmNTQ2NjMyFhc1MxEiBhUUFjMyNyY2NjU0JiYjIgYGFRQWFjMCcyMxMTwWER5hPUNtPj5tQz1hHksZIR8bHhH2UCwsUDIyTywsTzKQHTkyFyoMVjQ5RXxOTntFOTRh/fsgGRseE5Y0XTs7XDQ0XDs7XTT//wAv//QCJAMbACIA2AAAAAMBOgCbAAD//wAv//QCJAK9ACIA2AAAAAIBO14AAAAAAgA8//QCdwK7AA8AHwAsQCkAAgIAYQAAABRNBQEDAwFhBAEBARUBThAQAAAQHxAeGBYADwAOJgYHFysEJiY1NDY2MzIWFhUUBgYjPgI1NCYmIyIGBhUUFhYzAQeCSUmCU1KCSUmBUzxeNDRePDxeNTVePAxbomdnoVtboWdnoltISYFSUoFISIFSUoFJAAAAAQAUAAAA/AKvAAYAG0AYAgEAAwEAAUwAAAAOTQABAQ8BThETAgcYKxMHJzczESOueCKpP04CUE46c/1RAAAAAAEAIgAAAfwCuwAZACpAJwwLAgIAAAEDAgJMAAAAAWEAAQEUTQACAgNfAAMDDwNOERckJwQHGis3NzY2NTQmJiMiBgcnNjMyFhYVFAYHByEVISL7SEEoQiU5WSY3XZU8ZDtLV7QBXf4mQdY+ZzQoPSA4My2EMlw8RXtLnEoAAAAAAQAe//QCBwK7ACkAP0A8GRgCAgMiAQECAwICAAEDTAACAAEAAgFnAAMDBGEABAQUTQAAAAVhBgEFBRUFTgAAACkAKCQkISQlBwcbKxYmJzcWFjMyNjU0JiMjNRcWNjU0JiMiBgcnNjMyFhYVFAYHFhYVFAYGI8eBKDYjZD1IWF5TSUpHWldBNlYnNFuQQWc7Szo9VzxtRgxBNTMuNEg7PD5IAQFBOTZGMi8veS9VNj5SEQ5URDlaMwAAAAACABsAAAIxAq8ACgANAC1AKgwBAgEBTAYFAgIDAQAEAgBoAAEBDk0ABAQPBE4LCwsNCw0RERESEAcHGyslIScBMxEzFSMVIzURAQF8/qkKAVVaZ2dO/vinQQHH/j9Hp+4BY/6dAAAAAAEAN//0Ah0CrwAeADxAORQBAQQPDgMCBAABAkwABAABAAQBaQADAwJfAAICDk0AAAAFYQYBBQUVBU4AAAAeAB0iERMlJAcHGysWJic3FjMyNjY1NCYjIgcnEyEVIQc2MzIWFhUUBgYj3XktM1hqL0opXEhSRzkKAZb+tgdETz9nPT9vRgw6MzlgKEYrQlI1HAFhSucuM2FCQ2c4AAIAP//0AjYCuwAdACoAQkA/EhECAwInGgIFBAJMBgEDAAQFAwRpAAICAWEAAQEUTQcBBQUAYQAAABUATh4eAAAeKh4pJSMAHQAcJSUmCAcZKwAWFhUUBgYjIiY1NDY2MzIWFwcmJiMiBgYVFTY2MxI2NjU0JiMiBgcWFjMBj2k+Pm5GhYBKf008XikrJEguNlo1IGU9JEsoXkdBXQ0QV0UBrTNhQj9oPL2cZahhKig9IyVPh1EJMjf+ii1IJ0VSRzlSYQABABYAAAHnAq8ABgAfQBwEAQABAUwAAAABXwABAQ5NAAICDwJOEhEQAwcZKwEhNSEVASMBiv6MAdH+2FoCZUo7/YwAAAMAO//0AjECuwAbACoAOgBEQEEUBgIEAwFMBwEDAAQFAwRpAAICAGEAAAAUTQgBBQUBYQYBAQEVAU4rKxwcAAArOis5MzEcKhwpJCIAGwAaLAkHFysWJiY1NDY3JiY1NDY2MzIWFhUUBgcWFhUUBgYjEjY2NTQmJiMiBhUUFhYXEjY2NTQmJicOAhUUFhYz73JCVUM+SEFrPT5rQUs7QlVCc0YkSC8pRytBWTBHIy9PLjZQJiZQNi5PLwwwVzk+XBUYUTk3UywtUzc7UBYWXD05VzABkB42JSI2H0QzJjYdAv65IDklJzwhAQEhPCclOSAAAAACADL/9AIpArsAHQAqAEJAPyASAgUECgkCAQICTAcBBQACAQUCaQAEBANhBgEDAxRNAAEBAGEAAAAVAE4eHgAAHioeKSQiAB0AHCYlJQgHGSsAFhUUBgYjIiYnNxYWMzI2NjU1BgYjIiYmNTQ2NjMSNjcmJiMiBgYVFBYzAamASn9NPF4pKyRILjZaNSBlPT9pPj5uRkNdDRBXRTFLKF5HAru9nGWoYSooPSMlT4dRCTI3M2FCP2g8/opHOVJhLUgnRVIAAQBs//QA2ABgAAsAGUAWAAAAAWECAQEBFQFOAAAACwAKJAMHFysWJjU0NjMyFhUUBiOMICAXFh8fFgwgFxUgIBUXIAABAGX/ewDZAGAADgAXQBQOAQBJAAEBAGEAAAAVAE4kEgIHGCsXNjciJjU0NjMyFhUUBgdnKAwWICAXHCEgLWkuLyAXFh8tIx4/OAAAAgBj//QAzwH/AAsAFwAsQCkEAQEBAGEAAAARTQACAgNhBQEDAxUDTgwMAAAMFwwWEhAACwAKJAYHFysSJjU0NjMyFhUUBiMCJjU0NjMyFhUUBiODICAXFh8fFhcgIBcWHx8WAZMgFxUgIBUXIP5hIBcVICAVFyD//wBj/3sA1wH/ACIA7f4AAQcA7P/4AZ8ACbEBAbgBn7A1KwD//wBs//QCaABgACIA7AAAACMA7ADIAAAAAwDsAZAAAAACAGj/9ADUAq8AAwAPACVAIgABAQBfAAAADk0AAgIDYQQBAwMVA04EBAQPBA4lERAFBxkrEzMDIxYmNTQ2MzIWFRQGI3FcFDUEICAXFh8fFgKv/hLNIBcVICAVFyAAAAACAGn/VgDVAhEACwAPACdAJAAAAAFhBAEBARdNAAMDAl8AAgITAk4AAA8ODQwACwAKJAUHFysSFhUUBiMiJjU0NjMTIxMztSAgFxYfHxYuXBQ1AhEgFxUgIBUXIP1FAe4AAAAAAgAi//QB1gK7ABgAJAA3QDQWDAsABAIAAUwAAgADAAIDgAAAAAFhAAEBFE0AAwMEYQUBBAQVBE4ZGRkkGSMlGCQnBgcaKxM+AjU0JiYjIgYHJzYzMhYWFRQGBgcVIxYmNTQ2MzIWFRQGI786WzMhPig0UiUzXIY+YDQzWzpPECAgFxYfHxYBfAUkOCAfNyEyLTNzMFQzLFA7DIDNIBcVICAVFyAAAAAAAgAz/0oB5wIRAAsAJAA6QDciGBcMBAIEAUwABAACAAQCgAAAAAFhBQEBARdNAAICA2IAAwMZA04AACQjGxkVEwALAAokBgcXKwAWFRQGIyImNTQ2MxMOAhUUFhYzMjY3FwYjIiYmNTQ2Njc1MwE6ICAXFh8fFic6WzMhPig0UiUzXIY+YDQzWzpPAhEgFxUgIBUXIP54BSQ4IB83ITItM3MwVDMsUDsMgAAAAAEAbQDdANQBRAALAB5AGwAAAQEAWQAAAAFhAgEBAAFRAAAACwAKJAMHFys2JjU0NjMyFhUUBiOLHh4WFR4eFd0eFhYdHRYWHgAAAAABAF8AsgFOAaEACwAeQBsAAAEBAFkAAAABYQIBAQABUQAAAAsACiQDBxcrNiY1NDYzMhYVFAYjpkdHMTFGRjGyRzExRkYxMUcAAAAAAQBWAXgBeAK+AF8AQkA/V0xHNyccFwcIAgABTD0BAA0BAgJLAAABAgEAAoAAAgMBAgN+BAEDAwFhAAEBFANOAAAAXwBeUlAwLiIgBQcWKxImNTQ2NzY3BgcGBwYjIiYnJjU0NzY3NycmJyY1NDc2NjMyFxYXFhcmJyYmNTQ2MzIWFRQGBwYHNjc2NzYzMhYXFhUUBwYHBxcWFxYVFAcGBiMiJyYnJicWFxYWFRQGI90PCAEDBA0YKBcFBQYMBAQLHC0rKy0cCwQEDAYGBBcoGA0EAwEIDwoKDwgBAwQNGCgXBAYGDAQECxwtKystHAsEBAwGBgQXKBgNBAMBCA8KAXgNCRczBg8eCRQiDQMHBwgFDQYQEBEREBAGDQUIBwcDDSIUCR4PBjMXCQ0NCRczBg8eCRQiDQMHBwgFDQYQEBEREBAGDQUIBwcDDSIUCR4PBjMXCQ0AAAACAC8AAAKCAq8AGwAfAHpLsDJQWEAoDwYCAAUDAgECAAFnCwEJCQ5NDhANAwcHCF8MCgIICBFNBAECAg8CThtAJgwKAggOEA0DBwAIB2gPBgIABQMCAQIAAWcLAQkJDk0EAQICDwJOWUAeAAAfHh0cABsAGxoZGBcWFRQTEREREREREREREQcfKwEHMwcjByM3IwcjNyM3MzcjNzM3MwczNzMHMwcjIwczAf00dg92LUItli1CLXEPcTRyD3MtQi2WLUItdQ+4ljSWAb7OPbOzs7M9zj20tLS0Pc4AAQAA/7YBtALnAAMAEUAOAAABAIUAAQF2ERACBxgrATMBIwFfVf6hVQLn/M8AAAAAAQAA/7YBtALnAAMAEUAOAAABAIUAAQF2ERACBxgrETMBI1UBX1UC5/zPAAABADX/VgEuArwADQAGsw0FATIrFiY1NDY3FwYGFRQWFwehbGxlKFVUVFUoVd6Agd1VKli6d3a7VysAAAABABr/VgETArwADQAGsw0HATIrFzY2NTQmJzcWFhUUBgcaVVRUVShlbGxlf1e7dne6WCpV3YGA3lUAAAABABb/WgFHArkAIgAmQCMZAQABAUwQAQFKIgEASQABAAABWQABAQBhAAABAFERFgIHGCsWJiY1NTQmIzUyNjU1NDY2NxcOAhUXFAYHFhYVBxQWFhcH9FclLTU1LSVXTQY9OxYBJSYmJQEXOzwGnihENY0yLDctMow2QygINQkaLCmQLjcNDjcujyotGQk1AAABABv/WgFMArkAIgAoQCUIAQEAAUwRAQBKIgEBSQAAAQEAWQAAAAFhAAEAAVEbGhkYAgcWKxc+AjUnNDY3JiY1NzQmJic3HgIVFRQWMxUiBhUVFAYGBxs8OxcBJSYmJQEWOz0GTVclLTU1LSVXTXEJGS0qjy43Dg03LpApLBoJNQgoQzaMMi03LDKNNUQoCAABAGn/jQFVAt4ABwAiQB8AAAABAgABZwACAwMCVwACAgNfAAMCA08REREQBAcaKxMzFSMRMxUjaeyqquwC3jn9ITkAAAEAHf+NAQkC3gAHACJAHwACAAEAAgFnAAADAwBXAAAAA18AAwADTxERERAEBxorFzMRIzUzESMdqqrs7DoC3zn8rwAAAQBWAOsBWQE0AAMAGEAVAAABAQBXAAAAAV8AAQABTxEQAgcYKxMhFSFWAQP+/QE0SQAAAAEAVgDsAicBMwADABhAFQAAAQEAVwAAAAFfAAEAAU8REAIHGCsTIRUhVgHR/i8BM0cAAAABAFYA7ANYATMAAwAYQBUAAAEBAFcAAAABXwABAAFPERACBxgrEyEVIVYDAvz+ATNHAAAAAQBW/3ICqv+1AAMAILEGZERAFQAAAQEAVwAAAAFfAAEAAU8REAIHGCuxBgBEFyEVIVYCVP2sS0MAAAD//wBU/3sBkABgACIA7e8AAAMA7QC3AAAAAgBQAbwBjAKhAA4AHQA6tB0OAgBKS7AWUFhADQMBAQEAYQIBAAAXAU4bQBMCAQABAQBZAgEAAAFhAwEBAAFRWbYkGCQSBAcaKxMGBzIWFRQGIyImNTQ2NxcGBzIWFRQGIyImNTQ2N8IoDBYgHxgcISAt7SgMFiAfGBwhIC0ChS4vIBcVIC0jHj84HC4vIBcVIC0jHj84AP//AFQBvgGQAqMAJwDt/+8CQwEHAO0AtwJDABKxAAG4AkOwNSuxAQG4AkOwNSsAAAABAFABvADEAqEADgAysw4BAEpLsBZQWEALAAEBAGEAAAAXAU4bQBAAAAEBAFkAAAABYQABAAFRWbQkEgIHGCsTBgcyFhUUBiMiJjU0NjfCKAwWIB8YHCEgLQKFLi8gFxUgLSMePzgA//8AVAG+AMgCowEHAO3/7wJDAAmxAAG4AkOwNSsAAAD//wAiADQB1AHAACIBDAAAAAMBDADGAAD//wAsADQB3gHAACIBDQAAAAMBDQDGAAAAAQAiADQBDgHAAAUABrMFAQEyKzc3FwcXByKhS5OTS/vFELS3EQAAAAEALAA0ARgBwAAFAAazBQMBMis3Nyc3Fwcsk5NLoaFFt7QQxccAAP//AF0BrQFtAqMAIgEPAAAAAwEPALQAAAABAF0BrQC5AqMADgAtS7ApUFhACwABAQBhAAAADgFOG0AQAAABAQBZAAAAAV8AAQABT1m0FiUCBxgrEjUmNTQ2MzIWFRQHFAcjbhEbExMbEQcsAdgCcSoTGxsTKnECKwACAFH/tgIYAk0AGgAhAClAJh4dGhkXFhQTEA0FAgwAAQFMAAEAAAFXAAEBAF8AAAEATxoTAgcYKyQGBxUjNS4CNTQ2Njc1MxUWFhcHJicRNjcXJBYXEQYGFQH5TyxOPmY7O2Y+TitNHzQqOT0pNP6IUj4+UiQoBkBCC01yQUFyTAtAPgYmHjMtDP5vDDAzem8RAYsRb0UAAAMARv+2AkcC9wAeACUALAAmQCMsKyIhGxoYFxUSCwoIBwUCEAABAUwAAQABhQAAAHYfEwIHGCskBgcVIzUmJzcWFzUmJjU0NjY3NTMVFhcHJicVFhYVABYXNQYGFQA2NTQmJxUCR2tjToVgMVFjZ2Y1XTtOZFgwQ0llaf5mPUE3RwEIQz1CbGwJQUENXj1RD/wZVFAzVTUFPUAOUD1AEfAaV1EBGjET4gZDLP5IQysrNBTpAAEAUP/0AvcCuwAvAE9ATBwbAgQGAwICCwECTAcBBAgBAwIEA2cJAQIKAQELAgFnAAYGBWEABQUUTQwBCwsAYQAAABUATgAAAC8ALiwrKikREiUjERQREyUNBx8rJDY3FwYGIyImJicjNTMmNTQ3IzUzPgIzMhYXByYmIyIGByEVIQYVFBchFSEWFjMCN2UmNTGARkuIZRdhUgIFVWgaY4NIRoAxNSZlN02EIgEz/rUGAwFO/sQgilI8Lik2MThAcUdDGg8bIENCaDs3MjYpLldGQxwfFRRDTmIAAAABAFUAAAJbArsAHABDQEAREAICBAMBAAcCTAQBBwFLBQECBgEBBwIBZwAEBANhAAMDFE0IAQcHAF8AAAAPAE4AAAAcABwREyUkERMRCQcdKyUVITU3NSM1MzU0NjYzMhYXByYmIyIGFRUzFSMVAlv9+kE+PjdjP0V2GDkNVjc9TsrKSkomJLtDh0RsPEAyOCs4XEmHQ7sAAAEAOgAAArUCrwAWADlANhQBAAkBTAgBAAcBAQIAAWgGAQIFAQMEAgNnCgEJCQ5NAAQEDwROFhUTEhEREREREREREAsHHysBMxUjFTMVIxUjNSM1MzUjNTMDMxMTMwHCr9DQ0FLOzs6t9GHd314BSUNQQ3NzQ1BDAWb+swFNAAAAAQBCAGkCDQI0AAsAJkAjAAQDAQRXBQEDAgEAAQMAZwAEBAFfAAEEAU8RERERERAGBxwrASMVIzUjNTM1MxUzAg3AS8DAS8ABKcDASsHBAAABAIIBKQJNAXMAAwAYQBUAAAEBAFcAAAABXwABAAFPERACBhgrEyEVIYIBy/41AXNKAAAAAQBOAJIBxwILAAsABrMIAgEyKwEXBycHJzcnNxc3FwFAhzWHiDWIiDWIiDQBT4g1iIg0iIg1iIg1AAAAAwBEAHYCDwImAAsADwAbAGJLsBhQWEAcAAIAAwQCA2cABAcBBQQFZQYBAQEAYQAAABcBThtAIgAABgEBAgABaQACAAMEAgNnAAQFBQRZAAQEBWEHAQUEBVFZQBYQEAAAEBsQGhYUDw4NDAALAAokCAcXKwAmNTQ2MzIWFRQGIwchFSEWJjU0NjMyFhUUBiMBFCAgFxYfHxbnAcv+NdAgIBcWHx8WAbogFxUgIBUXIEdKsyAXFSAgFRcgAAACAIMAuwJOAeEAAwAHACJAHwAAAAECAAFnAAIDAwJXAAICA18AAwIDTxERERAEBxorEyEVIRUhFSGDAcv+NQHL/jUB4UqSSgABAE8AWAIWAlcABgAGswYDATIrNyUlNQUVBU8Bff6DAcf+OZ+5uUbiO+IAAAEAOwBYAgICVwAGAAazBgIBMisTNSUVBQUVOwHH/oMBfQE6O+JGublHAAD//wBtASoBrgGqAQcBOwAr/u0ACbEAAbj+7bA1KwAAAAABAD8BnQG9Aq8ABgAhsQZkREAWBAEBAAFMAAABAIUCAQEBdhIREAMHGSuxBgBEEzMTIycHI+E7oUF+f0ACr/7u29sAAAUAP//2AwYCtQAPABMAHwAvADsAykuwGFBYQCsLAQUKAQEGBQFpAAYACAkGCGoABAQAYQIBAAAOTQ0BCQkDYQwHAgMDDwNOG0uwJ1BYQC8LAQUKAQEGBQFpAAYACAkGCGoABAQAYQIBAAAOTQADAw9NDQEJCQdhDAEHBxUHThtAMwsBBQoBAQYFAWkABgAICQYIagACAg5NAAQEAGEAAAAOTQADAw9NDQEJCQdhDAEHBxUHTllZQCYwMCAgFBQAADA7MDo2NCAvIC4oJhQfFB4aGBMSERAADwAOJg4HFysSJiY1NDY2MzIWFhUUBgYjATMBIxI2NTQmIyIGFRQWMwAmJjU0NjYzMhYWFRQGBiM2NjU0JiMiBhUUFjOwSSgpSS4uSSgpSS4Bi0z+Kkx2NjcqKjY4KQFaSSkpSS4uSSgpSS4rNjcqKjY3KgFdLk4uMU8uLk8vME8tAVL9UQGTQzIzREM0MkP+Yy1PMC9PLi5PLjFPLTZDMzJEQzIzRAAAAAcAP//2BHsCtQAPABMAHwAvAD8ASwBXAOxLsBhQWEAxDwEFDgEBBgUBaQgBBgwBCgsGCmoABAQAYQIBAAAOTRMNEgMLCwNhEQkQBwQDAw8DThtLsCdQWEA1DwEFDgEBBgUBaQgBBgwBCgsGCmoABAQAYQIBAAAOTQADAw9NEw0SAwsLB2ERCRADBwcVB04bQDkPAQUOAQEGBQFpCAEGDAEKCwYKagACAg5NAAQEAGEAAAAOTQADAw9NEw0SAwsLB2ERCRADBwcVB05ZWUA2TExAQDAwICAUFAAATFdMVlJQQEtASkZEMD8wPjg2IC8gLigmFB8UHhoYExIREAAPAA4mFAcXKxImJjU0NjYzMhYWFRQGBiMBMwEjEjY1NCYjIgYVFBYzACYmNTQ2NjMyFhYVFAYGIyAmJjU0NjYzMhYWFRQGBiMkNjU0JiMiBhUUFjMgNjU0JiMiBhUUFjOwSSgpSS4uSSgpSS4Bi0z+Kkx2NjcqKjY4KQFaSSkpSS4uSSgpSS4BSEkpKUkuLkkoKUku/rY2NyoqNjcqAZ82NyoqNjcqAV0uTi4xTy4uTy8wTy0BUv1RAZNDMjNEQzQyQ/5jLU8wL08uLk8uMU8tLU8wL08uLk8uMU8tNkMzMkRDMjNEQzMyREMyM0QAAAIASf+MA2ICowA/AE4BtUuwClBYQBIhIAIIA0IfEgMECDw7AgYBA0wbS7AMUFhAEiEgAggDQh8SAwkIPDsCBgEDTBtLsBRQWEASISACCANCHxIDBAg8OwIGAQNMG0ASISACCANCHxIDCQg8OwIGAQNMWVlZS7AKUFhAKAsJAgQCAQEGBAFpAAYKAQcGB2UABQUAYQAAAA5NAAgIA2EAAwMRCE4bS7AMUFhALQsBCQQBCVkABAIBAQYEAWkABgoBBwYHZQAFBQBhAAAADk0ACAgDYQADAxEIThtLsBRQWEAoCwkCBAIBAQYEAWkABgoBBwYHZQAFBQBhAAAADk0ACAgDYQADAxEIThtLsB9QWEAtCwEJBAEJWQAEAgEBBgQBaQAGCgEHBgdlAAUFAGEAAAAOTQAICANhAAMDEQhOG0uwKVBYQCsAAwAICQMIaQsBCQQBCVkABAIBAQYEAWkABgoBBwYHZQAFBQBhAAAADgVOG0AxAAAABQMABWkAAwAICQMIaQsBCQQBCVkABAIBAQYEAWkABgcHBlkABgYHYQoBBwYHUVlZWVlZQBhAQAAAQE5ATUhGAD8APiYmKiUkJiYMBx0rBCYmNTQ2NjMyFhYVFAYGIyImJwYGIyImNTQ2NjMyFhc3FwYxBhUUFjMyNjY1NCYmIyIGBhUUFhYzMjY3FwYGIzY2NzY1NCYjIgYGFRQWMwFhsGhywHBkrWY0Ui4uPQgeUzFJXUNsOjJFEg1DESQkHh45JV2dW2WuZ16gXD5jNxI6bkQoWAgBNjUtTi89M3RmrWRvwHFhpF9ObTYuKCcvYE1Ec0MtJEIFVbYXHyMpV0JVlFdnr2VbnV0cIRslIP1iTAcPMzo0VjE1QQAAAwA///QCigK3ACAAKwA1AD5AOy8tJR8dHBoYCgEKAwIgAQADAkwEAQICAWEAAQEUTQUBAwMAYQAAABUATiwsISEsNSw0ISshKiwiBgcYKwUnBiMiJiY1NDY3JiY1NDY2MzIWFhUUBgcWFzY3FwYHFwAGFRQXNjY1NCYjEjcmJwYGFRQWMwJDWl11PWM4TVAeHC1QMi1KK1RUPWAwHkExLnL+mzg2SD0xJh9JdEI6P1M9B1tgL1g7Q2MjKUgmLUkrK0krP00hR2VHUx1tQXQCWjMrNkUcNikoNv3DTnhQG0svO0YAAQA9/84CFQKvAA8AI0AgAAADAgMAAoAEAQIChAADAwFfAAEBDgNOERERJhAFBxsrASImJjU0NjYzMxEjESMRIwEkRWg6OGM//j51PgEpMVg5OVky/R8Cp/1ZAAADAEr/jANjAqMADwAfAD0AXrEGZERAUzo5KyoEBgUBTAAAAAIEAAJpAAQABQYEBWkABgoBBwMGB2kJAQMBAQNZCQEDAwFhCAEBAwFRICAQEAAAID0gPDc1Ly0oJhAfEB4YFgAPAA4mCwcXK7EGAEQEJiY1NDY2MzIWFhUUBgYjPgI1NCYmIyIGBhUUFhYzLgI1NDY2MzIWFwcmJiMiBgYVFBYWMzI2NxcGBiMBbLdra7dra7Zra7ZrYaZhYaZhYqZhYaZiR3xJSXxHNF8lNBpEJjNXMzNXMyZGGzQlYTV0a7Zra7Vra7Vra7ZrJGGmYWGlYWGlYWGmYVlKfUhIfEooJDMcIDdeNjddOCEeMyUqAAQAVAC4AkECowAPAB8ALQA2AGOxBmREQFgiAQUIAUwGAQQFAwUEA4AKAQEAAgcBAmkABwAJCAcJaQAIAAUECAVnCwEDAAADWQsBAwMAYQAAAwBREBAAADY0MC4rKSgnJiUkIxAfEB4YFgAPAA4mDAcXK7EGAEQAFhYVFAYGIyImJjU0NjYzEjY2NTQmJiMiBgYVFBYWMzYGBxcjJyMVIxEzMhYVBzMyNjU0JiMjAY1xQ0NxQkNxQ0NxQzlhODlgOTlhOTlhOYUjHkZLQC5LjTM/tDoTGBgTOgKjQnFCQnFDQ3FCQnFC/jk4YTk5YDg4YDk5YDndMwtkW1sBLDkvHxEODREAAgBTARcCvgJDAAcAEwAzQDAREA8KBAMAAUwHBgIDAAOGBQQCAQAAAVcFBAIBAQBfAgEAAQBPFBESERERERAIBh4rEyM1MxUjFSMTMxc3MxEjNQcnFSOhTuxQTs5OWVlPT1lZTgH4S0vhASyVlf7Up5aWpwABAIP/tgC/AucAAwARQA4AAAEAhQABAXYREAIHGCsTMxEjgzw8Auf8zwAAAAH/UgJj/7UCxgALACaxBmREQBsAAAEBAFkAAAABYQIBAQABUQAAAAsACiQDBxcrsQYARAImNTQ2MzIWFRQGI5EdHRUUHR0UAmMeFBQdHRQUHgAAAAH/Xf7t/7//rgAOACSxBmREQBkOAQBJAAEAAAFZAAEBAGEAAAEAUSQSAgcYK7EGAEQHNjciJjU0NjMyFhUUBgeiIwkTGhsTGBwbJvwoJxsTEhsmHhk0MAABAB4CQADmAtAAAwAXsQZkREAMAQEASgAAAHYSAQcXK7EGAEQTFwcjl0+OOgLQEn4AAAAAAQA1Ak0BWQK4AA0AMrEGZERAJwoCAgEAAUwJAwIASgAAAQEAWQAAAAFhAgEBAAFRAAAADQAMJQMHFyuxBgBEEiYnNxYWMzI2NxcGBiOfTB4vFDUaGjUULx1MKQJNHh4vFBYWFC8dHwABACYCQAE2AskABgAhsQZkREAWAgECAAFMAQEAAgCFAAICdhESEAMHGSuxBgBEEzMXNzMHIyY5Tk86ZkYCyVZWiQAAAAEAMP9AAOoACwAZAHaxBmREQBAXFAICBBMJAgECCAEAAQNMS7AMUFhAIAUBBAMCAQRyAAMAAgEDAmkAAQAAAVkAAQEAYgAAAQBSG0AhBQEEAwIDBAKAAAMAAgEDAmkAAQAAAVkAAQEAYgAAAQBSWUANAAAAGQAZEyQkJAYHGiuxBgBEFhYVFAYjIiYnNxYzMjY1NCYjIgcnNzMHNjPDJzgqGS8QEx0kFhwUExQOECE0GAQHLSUdJC0QDCoXFREPEwsRTjkBAAAAAQAmAkABNgLJAAYAIbEGZERAFgQBAQABTAAAAQCFAgEBAXYSERADBxkrsQYARBMzFyMnByOKRmY6T045AsmJVlYAAAACAEsCVQFnArEACwAXADKxBmREQCcCAQABAQBZAgEAAAFhBQMEAwEAAVEMDAAADBcMFhIQAAsACiQGBxcrsQYARBImNTQ2MzIWFRQGIzImNTQ2MzIWFRQGI2YbGxMTGxsTrRsbExMbGxMCVRsTEhwcEhMbGxMSHBwSExsAAQBLAlUApwKxAAsAJrEGZERAGwAAAQEAWQAAAAFhAgEBAAFRAAAACwAKJAMHFyuxBgBEEiY1NDYzMhYVFAYjZhsbExMbGxMCVRsTEhwcEhMbAAAAAQAdAkAA5QLQAAMAF7EGZERADAEBAEoAAAB2EgEHFyuxBgBEEzcXIx1PeToCvhKQAAAAAAIAHQI/Aa0C0AADAAcAGrEGZERADwUBAgBKAQEAAHYTEgIHGCuxBgBEExcHIyUXByOWT446AUFPjjoC0BJ/jxJ9AAAAAQBWAlIBjAKJAAMAILEGZERAFQAAAQEAVwAAAAFfAAEAAU8REAIHGCuxBgBEEyEVIVYBNv7KAok3AAAAAQBD/1MBBAATABEAOrEGZERALw4BAQAPAQIBAkwFAQBKAAABAIUAAQICAVkAAQECYQMBAgECUQAAABEAECQWBAcYK7EGAEQWJjU0NjcXIgYVFBYzMjcXBiN/PB8WPRkhHxseESAjMa05MhwwCRMgGRseEzEdAAAAAgA/Ak0BDQMbAAsAFwA4sQZkREAtAAAAAgMAAmkFAQMBAQNZBQEDAwFhBAEBAwFRDAwAAAwXDBYSEAALAAokBgcXK7EGAEQSJjU0NjMyFhUUBiM2NjU0JiMiBhUUFjN7PDwrKzw8KxgjIxgYIyMYAk08Kys8PCsrPCwjGBgjIxgYIwAAAQBCAj0BgwK9ABcAQrEGZERANxUBAAEJAQMCAkwUAQFKCAEDSQABAAACAQBpAAIDAwJZAAICA2EEAQMCA1EAAAAXABYkJCQFBxkrsQYARAAmJyYmIyIGByc2MzIWFxYWMzI2NxcGIwEOHxkRFg0UGAYuEFAVHxkRFg0UGAYuEFACPxITDw4gJAd3EhMPDiAkB3cAAAEAAAABAAAQ4kEwXw889QAHA+gAAAAA2OeADgAAAADY54H//1L+7QR7A8UAAAAHAAIAAAAAAAAAAQAAAxv/MwAABLr/Uv/IBHsAAQAAAAAAAAAAAAAAAAAAATwB9ABdARMAAALlABkC5QAZAuUAGQLlABkC5QAZAuUAGQLlABkC5QAZAuUAGQLlABkD9gAVAr8AbQK0ADgCtAA4ArQAOAK0ADgDAQBtAxQALAMBAG0DFAAsApIAbQKSAG0CkgBtApIAbQKSAG0CkgBtApYAbQKSAG0CkgBtApUAbQKSAG0CfwBtAu4AOALuADgC7gA4AwsAbQEoAG0BKABtASgADQEoAAYBKABmASj/6QEo//kBKABJAiMAFgKoAG0CqABtAksAbQJLAG0CSwBtAmsAIAN0AG0DIABtAyAAbQMgAG0DIABtAyAAbQMwADgDMAA4AzAAOAMwADgDMAA4AzAAOAMwADgDQABBAzAAOAQMADcCrQBtArsAZgM8ADgCsgBtArIAbQKyAG0CsgBtAmQALgJkAC4CZAAuAmQALgJOABkCTgAZAk4AGQL0AFsC9ABbAvQAWwL0AFsC9ABbAvQAWwL0AFsC9ABbAvQAWwLlABkEKwAeBCsAHgQrAB4EKwAeBCsAHgKlABwCoQATAqEAEwKhABMCoQATAqEAEwJlACwCZQAsAmUALAJlACwCLQAkAi0AJAItACQCLQAkAi0AJAItACQCLQAkAi0AJAItACQCLQAkA7IAJAJ7AFcCBwApAgcAKQIHACkCBwApAnsALwJZACsCpQAvAnsALwJHACwCRwAsAkcALAJHACwCRwAsAkcALAJYACwCRwAsAkcALAJYACwCRwAsAT0AGAJ2ACoCdgAqAnYAKgJZAFcA+QBLAPkAVwD5AFcA+f/2APn/7wD5/9IA+f/iAPkAMADv/84A7//OAiUAVwIlAFcA+QBXAPkAVwEWAFcBJwAcA4kAVwJZAFcCWQBXAlkAVwJZAFcCWQBXAmkAKgJpACoCaQAqAmkAKgJpACoCaQAqAmkAKgKBADMCaQAqBCAAKgJ7AFcCfABYAnsALwGMAFcBjABXAYwAVgGMAFcB3gAhAd4AIQHeACEB3gAhAkMAVwFZABUBuQAVAVkAFQJZAEsCWQBLAlkASwJZAEsCWQBLAlkASwJZAEsCWQBLAlkASwIRAAoDHQAQAx0AEAMdABADHQAQAx0AEAH/AAwCGAAJAhgACQIYAAkCGAAJAhgACQHbACEB2wAhAdsAIQHbACECewAvAnsALwJ7AC8CewAvAnsALwJ7AC8CewAvAnsALwJ7AC8CewAvArMAPAFlABQCNQAiAkYAHgJMABsCUQA3AmgAPwIDABYCbQA7AmgAMgFEAGwBOgBlATMAYwE1AGMC1ABsAT0AaAE9AGkCCQAiAgkAMwFBAG0BrgBfAc8AVgKxAC8BtAAAAbQAAAFIADUBSAAaAWIAFgFiABsBcgBpAXIAHQGvAFYCfQBWA64AVgMAAFYB4ABUAeAAUAHgAFQBGABQARgAVAIBACICAAAsATsAIgE6ACwBygBdARYAXQPoAAAB9AAAAPoAAAETAAAApgAAAU0AAAJcAFECkgBGAzkAUAKcAFUC8QA6Ak8AQgLPAIICFgBOAlMARALRAIMCUQBPAlEAOwIbAG0B/AA/A0YAPwS6AD8DqABJAsIAPwKYAD0DrQBKApUAVANBAFMBQgCDAAD/UgAA/10BBwAeAZEANQFbACYBJQAwAVsAJgGxAEsA8QBLAQMAHQHOAB0B4gBWASYAQwFNAD8BxABC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)
        </style>
        </head>
            <body>
                <div class="main-container">
                    <header class="header header-6">
                        <div class="branding">
                            <a href="">
                                <cds-icon shape="vm-bug">
                                    <img height="36px" width="36px" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI2LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNiAzNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzYgMzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojMDA5MURBO30KCS5zdDF7ZmlsbDojMUQ0MjhBO30KCS5zdDJ7ZmlsbDojMDBDMUQ1O30KPC9zdHlsZT4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI4LjIsMzAuNGMtMC4zLDAtMC41LTAuMi0wLjYtMC40Yy0wLjItMC40LDAtMC44LDAuMy0wLjljMy45LTEuOCw2LjQtNS43LDYuNC05LjljMC0yLjMtMC43LTQuNC0yLTYuMwoJYy0xLjMtMS44LTMtMy4yLTUuMS00Yy0wLjQtMC4xLTAuNi0wLjYtMC40LTAuOWMwLjEtMC40LDAuNi0wLjYsMC45LTAuNGMyLjMsMC45LDQuMywyLjQsNS44LDQuNWMxLjUsMi4xLDIuMyw0LjUsMi4zLDcuMQoJYzAsNC44LTIuOCw5LjItNy4yLDExLjJDMjguNCwzMC40LDI4LjMsMzAuNCwyOC4yLDMwLjR6Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0yMy4yLDExLjNjLTAuMSwwLTAuMiwwLTAuMywwYy0wLjUtMS44LTEuNi0zLjMtMy4xLTQuNWMtMS43LTEuMy0zLjctMi01LjgtMmMtNS4xLDAtOS4zLDQuMS05LjMsOS4yCgljMCwwLDAsMC4xLDAsMC4xYy0zLjUsMS4xLTUuNCw0LjktNC4zLDguNGMwLjYsMS44LDEuOSwzLjMsMy43LDQuMXYtMS42Yy0yLjUtMS41LTMuNC00LjctMS45LTcuMmMwLjctMS4zLDItMi4yLDMuNC0yLjVsMC42LTAuMQoJbDAtMC42YzAtMC4yLDAtMC40LDAtMC42YzAtNC4zLDMuNS03LjcsNy44LTcuN2MzLjYsMCw2LjgsMi41LDcuNiw2bDAuMSwwLjZsMC42LTAuMWMwLjIsMCwwLjUsMCwwLjcsMGMzLjYsMCw2LjUsMi45LDYuNSw2LjUKCWMwLDEuOS0wLjgsMy43LTIuMiw0Ljl2MS44YzIuMy0xLjQsMy43LTMuOSwzLjctNi42QzMxLjEsMTQuOCwyNy41LDExLjMsMjMuMiwxMS4zeiIvPgo8cGF0aCBjbGFzcz0ic3QyIiBkPSJNMS4yLDEyLjJjMCwwLTAuMSwwLTAuMSwwYy0wLjQtMC4xLTAuNi0wLjQtMC42LTAuOEMxLjcsNC45LDcuNCwwLjIsMTQsMC4yYzMuMSwwLDYuMiwxLjEsOC42LDMKCWMwLjksMC43LDEuNiwxLjUsMi4zLDIuM2MwLjIsMC4zLDAuMiwwLjgtMC4xLDFjLTAuMywwLjItMC44LDAuMi0xLTAuMWMtMC42LTAuOC0xLjMtMS41LTItMi4xYy0yLjItMS44LTUtMi43LTcuOC0yLjcKCWMtNiwwLTExLjEsNC4yLTEyLjIsMTBDMS44LDEyLDEuNSwxMi4yLDEuMiwxMi4yeiBNMTguMywxOGMtMC40LDAtMC43LDAuMy0wLjgsMC43YzAsMC40LDAuMywwLjcsMC43LDAuOGMwLDAsMCwwLDAuMSwwaDMuOQoJbC04LjUsOC4xYy0wLjMsMC4zLTAuMywwLjcsMCwxYzAuMywwLjMsMC43LDAuMywxLDBsOC41LTh2My45YzAsMC40LDAuNCwwLjcsMC44LDAuN2MwLjQsMCwwLjctMC4zLDAuNy0wLjdWMThMMTguMywxOEwxOC4zLDE4egoJIE0xNC43LDIzLjFWMThIOS42Yy0wLjQsMC0wLjcsMC40LTAuNywwLjhjMCwwLjQsMC4zLDAuNywwLjcsMC43aDIuNkw3LDI0LjJjLTAuMywwLjMtMC4zLDAuNywwLDFjMC4zLDAuMywwLjcsMC4zLDEsMGw1LjItNC44djIuNwoJYzAsMC40LDAuNCwwLjcsMC44LDAuN0MxNC40LDIzLjgsMTQuNywyMy41LDE0LjcsMjMuMUwxNC43LDIzLjF6IE0xOC44LDI4LjZjMCwwLjQsMC4zLDAuNywwLjcsMC43aDIuN2wtNC43LDUuMQoJYy0wLjMsMC4zLTAuMywwLjcsMCwxYzAuMywwLjMsMC43LDAuMywxLDBjMCwwLDAsMCwwLjEtMC4xbDQuNi01VjMzYzAsMC40LDAuMywwLjcsMC43LDAuOGMwLjQsMCwwLjctMC4zLDAuOC0wLjdjMCwwLDAsMCwwLTAuMQoJdi01LjFoLTUuMUMxOS4xLDI3LjksMTguOCwyOC4yLDE4LjgsMjguNkwxOC44LDI4LjZ6Ii8+Cjwvc3ZnPgo=" alt="VMware Cloud Foundation"/>
                                </cds-icon>
                                <span class="title">VMware Cloud Foundation</span>
                            </a>
                        </div>
                    </header>
    '

    $clarityCssHeader += $clarityCssShared
    $clarityCssHeader
}
Export-ModuleMember -Function Get-ClarityReportHeader

Function Get-ClarityReportNavigation {
    Param (
        [Parameter (Mandatory = $true)] [ValidateSet("health","alert","config","upgrade","overview")] [String]$reportType
    )

    if ($reportType -eq "health") { # Define the Clarity Cascading Style Sheets (CSS) for a Health Report
        $clarityCssNavigation = '
                <nav class="subnav">
                <ul class="nav">
                <li class="nav-item">
                    <a class="nav-link active" href="">Health Report</a>
                </li>
                </ul>
            </nav>
            <div class="content-container">
            <nav class="sidenav">
            <section class="sidenav-content">
                <section class="nav-group collapsible">
                    <input id="general" type="checkbox"/>
                    <label for="general">General</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#general-service">Service Health</a></li>
                        <li><a class="nav-link" href="#general-version">Version Health</a></li>
                        <li><a class="nav-link" href="#general-connectivity">Connectivity</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="security" type="checkbox"/>
                    <label for="security">Security</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#security-password">Passwords</a></li>
                        <li><a class="nav-link" href="#security-certificate">Certificates</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                <input id="infrastructure" type="checkbox"/>
                <label for="infrastructure">Infrastructure</label>
                <ul class="nav-list">
                    <li><a class="nav-link" href="#infra-backup">Backups</a></li>
                    <li><a class="nav-link" href="#infra-snapshot">Snapshots</a></li>
                    <li><a class="nav-link" href="#infra-dns-forward">DNS Forward Lookup</a></li>
                    <li><a class="nav-link" href="#infra-dns-reverse">DNS Reverse Lookup</a></li>
                    <li><a class="nav-link" href="#infra-ntp">Network Time</a></li>
                </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="vcenter" type="checkbox"/>
                    <label for="vcenter">vCenter Server</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#vcenter-overall">Overall Health</a></li>
                        <li><a class="nav-link" href="#vcenter-ring-topology">Single Sign-On Health</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="esxi" type="checkbox"/>
                    <label for="esxi">ESXi</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#esxi-overall">Overall Health</a></li>
                        <li><a class="nav-link" href="#esxi-coredump">Core Dump Health</a></li>
                        <li><a class="nav-link" href="#esxi-license">Licensing Health</a></li>
                        <li><a class="nav-link" href="#esxi-disk">Disk Health</a></li>
                        <li><a class="nav-link" href="#esxi-connection">Connection Health</a></li>
                        <li><a class="nav-link" href="#esxi-free-pool">Free Pool Health</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="vsan" type="checkbox"/>
                    <label for="vsan">vSAN</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#vsan-overall">Overall Health</a></li>
                        <li><a class="nav-link" href="#vsan-spbm">Storage Policy Health</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="nsx" type="checkbox"/>
                    <label for="nsx">NSX</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#nsx-local-manager">NSX Local Managers</a></li>
                        <li><a class="nav-link" href="#nsx-edge-cluster">NSX Edge Cluster</a></li>
                        <li><a class="nav-link" href="#nsx-edge">NSX Edge Nodes</a></li>
                        <li><a class="nav-link" href="#nsx-tn">NSX Transport Nodes</a></li>
                        <li><a class="nav-link" href="#nsx-tn-tunnel">NSX Transport Node Tunnels</a></li>
                        <li><a class="nav-link" href="#nsx-t0-bgp">NSX Tier-0 Gateway BGP</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="storage" type="checkbox"/>
                    <label for="storage">Storage</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#storage-sddcmanager">SDDC Manager</a></li>
                        <li><a class="nav-link" href="#storage-vcenter">vCenter Server</a></li>
                        <li><a class="nav-link" href="#storage-esxi">ESXi</a></li>
                        <li><a class="nav-link" href="#storage-datastore">Datastores</a></li>
                        <li><a class="nav-link" href="#storage-vm-cdrom">Connected CD-ROMs</a></li>
                    </ul>
                </section>
            </section>
            </nav>
                <div class="content-area">
                    <div class="content-area">'

        $clarityCssNavigation
    }

    if ($reportType -eq "alert") { # Define the Clarity Cascading Style Sheets (CSS) for a System Alert Report
        $clarityCssNavigation = '
                <nav class="subnav">
                <ul class="nav">
                <li class="nav-item">
                    <a class="nav-link active" href="">Alert Report</a>
                </li>
                </ul>
            </nav>
            <div class="content-container">
            <nav class="sidenav">
            <section class="sidenav-content">
                <a class="nav-link nav-icon" href="#alert-vcenter">vCenter Server</a>
                <a class="nav-link nav-icon" href="#alert-esxi">ESXi</a>
                <a class="nav-link nav-icon" href="#alert-vsan">vSAN</a>
                <a class="nav-link nav-icon" href="#alert-nsx">NSX</a>
            </section>
            </nav>
                <div class="content-area">
                    <div class="content-area">'

        $clarityCssNavigation
    }

    if ($reportType -eq "config") { # Define the Clarity Cascading Style Sheets (CSS) for a Configuration Report
        $clarityCssNavigation = '
                <nav class="subnav">
                <ul class="nav">
                <li class="nav-item">
                    <a class="nav-link active" href="">Configuration Report</a>
                </li>
                </ul>
            </nav>
            <div class="content-container">
            <nav class="sidenav">
            <section class="sidenav-content">
                <section class="nav-group collapsible">
                    <input id="vcenter" type="checkbox"/>
                    <label for="vcenter">vCenter Server</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#cluster-config">Cluster Configuration</a></li>
                        <li><a class="nav-link" href="#cluster-drs-rules">vSphere DRS Rules</a></li>
                        <li><a class="nav-link" href="#cluster-resource-pools">Resource Pools</a></li>
                        <li><a class="nav-link" href="#cluster-overrides">VM Overrides</a></li>
                        <li><a class="nav-link" href="#cluster-networks">Virtual Networks</a></li>
                    </ul>
                </section>
                <section class="nav-group collapsible">
                    <input id="esxi" type="checkbox"/>
                    <label for="esxi">ESXi Hosts</label>
                    <ul class="nav-list">
                        <li><a class="nav-link" href="#esxi-security">Security Configuration</a></li>
                    </ul>
                </section>
            </section>
            </nav>
                <div class="content-area">
                    <div class="content-area">'

        $clarityCssNavigation
    }

    if ($reportType -eq "upgrade") { # Define the Clarity Cascading Style Sheets (CSS) for a Upgrade Report
        $clarityCssNavigation = '
            <nav class="subnav">
            <ul class="nav">
                <li class="nav-item">
                <a class="nav-link active" href="">Upgrade Precheck Report</a>
                </li>
            </ul>
            </nav>
            <div class="content-container">
            <div class="content-area">'

        $clarityCssNavigation
    }

    if ($reportType -eq "overview") { # Define the Clarity Cascading Style Sheets (CSS) for a System Overview Report
        $clarityCssNavigation = '
            <nav class="subnav">
            <ul class="nav">
                <li class="nav-item">
                <a class="nav-link active" href="">System Overview Report</a>
                </li>
            </ul>
            </nav>
            <div class="content-container">
            <div class="content-area">'

        $clarityCssNavigation
    }
}
Export-ModuleMember -Function Get-ClarityReportNavigation

Function Get-ClarityReportFooter {
    # Define the default Clarity Cascading Style Sheets (CSS) for the HTML report footer
    $clarityCssFooter = '
                </div>
            </div>
        </div>
    </body>
    </html>'

    $clarityCssFooter
}
Export-ModuleMember -Function Get-ClarityReportFooter

Function Format-DfStorageHealth {
    <#
        .SYNOPSIS
        Formats output from 'fd -h' command and sets alerts based on thresholds.

        .DESCRIPTION
        The Format-DfStorageHealth cmdlet formats and returns output from 'df -h' in html or plain text

        .EXAMPLE
        Format-DfStorageHealth -reportTitle '<h3>SDDC Manager Disk Health Status</h3>' -dfOutput $dfOutput -html -failureOnly -greenThreshold 20 -redThreshold 40
        This example returns only failures (Alert is not GREEN), produces html report with title '<h3>SDDC Manager Disk Health Status</h3>' and overwrites the default thresholds.

        .PARAMETER dfOutput
        The output from 'df -h' command.

        .PARAMETER systemFqdn
        The fully qualified domain name of the system.

        .PARAMETER failureOnly
        Switch to only output issues to the report.

        .PARAMETER greenThreshold
        The threshold for a "Green" alert. Default is 70%.

        .PARAMETER redThreshold
        The threshold for a "Red" alert. Default is 85%.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] $dfOutput,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] $systemFqdn,
        [Parameter (Mandatory = $false)] [ValidateNotNullOrEmpty()] [Switch]$failureOnly,
        [Parameter (Mandatory = $false)] [ValidateRange(1, 100)] [int]$greenThreshold = 70, # Define default value for "Green" threshold
        [Parameter (Mandatory = $false)] [ValidateRange(1, 100)] [int]$redThreshold = 85    # Define default value for "Red" threshold
    )

    Try {
        # Define object that will be returned and format input
        $customObject = New-Object System.Collections.ArrayList
        $formatOutput = ($dfOutput.ScriptOutput -split '\r?\n').Trim() -replace '(^\s+|\s+$)', '' -replace '\s+', ' '

        # Set Alarms for each partition
        foreach ($partition in $formatOutput) {
            $usage = $partition.Split(" ")[4]
            # Make sure that only rows with calculated usage will be included
            if ( !$usage ) { continue }

            # Get the usage percentage as numeric value
            $usage = $usage.Substring(0, $usage.Length - 1)
            $usage = [int]$usage

            # Applying thresholds and creating collection from input
            Switch ($usage) {
                { $_ -le $greenThreshold } {
                    $alert = 'GREEN' # Green if $usage is up to $greenThreshold
                    $message = "Used space is less than $greenThreshold%."
                }
                { $_ -ge $redThreshold } {
                    $alert = 'RED' # Red if $usage is equal or above $redThreshold
                    $message = "Used space is above $redThreshold%. Please reclaim space on the partition."
                    # TODO: Find how to display the message in html on multiple rows (Add <br> with the right escape chars)
                    # In order to display usage, you could run as root in SDDC Manager 'du -Sh <mount-point> | sort -rh | head -10' "
                    # As an alternative you could run PowerCLI commandlet:
                    # 'Invoke-SddcCommand -server <SDDC_Manager_FQDN> -user <admin@local> -pass <admin@local_password> -GuestUser root -vmPass <SDDC_Manager_RootPassword> -command "du -Sh <mount-point> | sort -rh | head -10" '
                }
                Default {
                    # TODO: Same as above - add hints on new lines }
                    $alert = 'YELLOW' # Yellow if above two are not matched
                    $message = "Used space is between $greenThreshold% and $redThreshold%. Please consider reclaiming some space on the partition."
                }
            }

            # Skip population of object if "failureOnly" is selected and alert is "GREEN"
            if (($PsBoundParameters.ContainsKey("failureOnly")) -and ($alert -eq 'GREEN')) { continue }

            $userObject = New-Object -TypeName psobject
            $userObject | Add-Member -notepropertyname 'FQDN' -notepropertyvalue $systemFqdn
            $userObject | Add-Member -notepropertyname 'Filesystem' -notepropertyvalue $partition.Split(" ")[0]
            $userObject | Add-Member -notepropertyname 'Size' -notepropertyvalue $partition.Split(" ")[1]
            $userObject | Add-Member -notepropertyname 'Available' -notepropertyvalue $partition.Split(" ")[2]
            $userObject | Add-Member -notepropertyname 'Used %' -notepropertyvalue $partition.Split(" ")[4]
            $userObject | Add-Member -notepropertyname 'Mounted on' -notepropertyvalue $partition.Split(" ")[5]
            $userObject | Add-Member -notepropertyname 'Alert' -notepropertyvalue $alert
            $userObject | Add-Member -notepropertyname 'Message' -notepropertyvalue $message
            $customObject += $userObject # Creating collection to work with afterwords
        }
        $customObject | Sort-Object FQDN # Return $customObject in HTML or pain format
    }
    Catch {
        Debug-CatchWriter -object $_
    }
}
Export-ModuleMember -Function Format-DfStorageHealth

Function Convert-CssClass {
    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCustomObject]$htmlData
    )

    # Function to replace Alerts with colour coded CSS Style
    $oldAlertOK = '<td>GREEN</td>'
    $newAlertOK = '<td class="alertOK">GREEN</td>'
    $oldAlertCritical = '<td>RED</td>'
    $newAlertCritical = '<td class="alertCritical">RED</td>'
    $oldAlertWarning = '<td>YELLOW</td>'
    $newAlertWarning = '<td class="alertWarning">YELLOW</td>'
    $oldAlertSkipped = '<td>SKIPPED/td>'
    $newAlertSkipped = '<td class="alertSkipped">SKIPPED</td>'
    $oldTable = '<table>'
    $newTable = '<table class="table">'
    $oldAddLine = ':-: '
    $newNewLine = '<br/>'

    $htmlData = $htmlData -replace $oldAlertOK,$newAlertOK
    $htmlData = $htmlData -replace $oldAlertCritical,$newAlertCritical
    $htmlData = $htmlData -replace $oldAlertWarning,$newAlertWarning
    $htmlData = $htmlData -replace $oldTable,$newTable
    $htmlData = $htmlData -replace $oldAddLine,$newNewLine
    $htmlData
}
Export-ModuleMember -Function Convert-CssClass

Function Format-StorageThreshold {
    <#
        .SYNOPSIS
        Calculate storage percentage.

        .DESCRIPTION
        The Format-StorageThreshold cmdlet converts the storage to a percentage and checks capacity .

        .EXAMPLE
        Format-StorageThreshold -size <size> -free <free>
        This example converts the storage to a percentage and checks capacity.

        .PARAMETER size
        The size of the datastore.

        .PARAMETER free
        The free space of the datastore.
    #>


    Param (
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$size,
        [Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$free

    )

    Try {
        # Define thresholds Green < Yellow < Red
        $greenThreshold = 80
        $redThreshold = 90
        # Calculate datastore usage and capacity
        [Int]$usage = [Math]::Round((($size - $free) / $size * 100))
        # Applying thresholds and creating collection from input
        Switch ($usage) {
            { $_ -le $greenThreshold } {
                # Green if $usage is up to $greenThreshold
                $alert = 'GREEN'
                $message = "Used space is less than $greenThreshold%. "
            }
            { $_ -ge $redThreshold } {
                # Red if $usage is equal or above $redThreshold
                $alert = 'RED'
                $message = "Used space is above $redThreshold%. Please reclaim space on the volume."
            }
            Default {
                # Yellow if above two are not matched
                $alert = 'YELLOW'
                $message = "Used space is between $greenThreshold% and $redThreshold%. Please consider reclaiming some space on the volume."
            }
        }
        $thresholdObject = New-Object -TypeName psobject
        $thresholdObject | Add-Member -notepropertyname 'usage' -notepropertyvalue $usage
        $thresholdObject | Add-Member -notepropertyname 'alert' -notepropertyvalue $alert
        $thresholdObject | Add-Member -notepropertyname 'message' -notepropertyvalue $message
        $thresholdObject
    }
    Catch {
        Write-Error $_.Exception.Message
    }
}

############################## End Supporting Functions ###############################
########################################################################################