AzStackHciSBEHealth/AzStackHci.SBEHealth.psm1

<#############################################################
# #
# Copyright (C) Microsoft Corporation. All rights reserved. #
# #
#############################################################>


Import-Module $PSScriptRoot\AzStackHci.SBEHealth.Helpers.psm1 -Force -DisableNameChecking -Global
Import-Module $PSScriptRoot\..\AzStackHci.EnvironmentChecker.Utilities.psm1 -Force -DisableNameChecking -Global
Import-Module $PSScriptRoot\..\AzStackHci.EnvironmentChecker.Reporting.psm1 -Force -DisableNameChecking -Global
Import-LocalizedData -BindingVariable lhwTxt -FileName AzStackHci.SBEHealth.Strings.psd1

function Invoke-AzStackHciSBEHealthValidation
{
    <#
        .SYNOPSIS
            Perform AzStackHci SBE Health validation
        .DESCRIPTION
            Perform AzStackHci SBE Health validation
        .EXAMPLE
            PS C:\> Invoke-AzStackHciSBEHealthValidation -SBESourcePath "C:\SBE"
            Perform SBE Health validations on localhost
        .EXAMPLE
            PS C:\> $Credential = Get-Credential -Message "Credential for RemoteSystem"
            PS C:\> $RemoteSystemSession = New-PSSession -Computer 10.0.0.4,10.0.0.5 -Credential $Credential
            PS C:\> Invoke-AzStackHciSBEHealthValidation -SBESourcePath "C:\SBE" -PsSession $RemoteSystemSession
            Perform SBE Health validations on the localhost and all specified remote PS sessions
        .PARAMETER PsSession
            Specify the PsSession(s) used to validation from
        .PARAMETER Tag
            Specify the Tag value to be passed to the SolutionExtension module when called
        .PARAMETER SBESourcePath
            Specify the full local path to the folder containing the extracted SBE Package
        .PARAMETER SBEMetadataPath
            Specify the full local path to the folder containing the SBE Metadata files
        .PARAMETER PassThru
            Return PSObject result
        .PARAMETER OutputPath
            Directory path for log and report output
        .PARAMETER CleanReport
            Remove all previous progress and create a clean report
        .INPUTS
            Inputs (if any)
        .OUTPUTS
            Output (if any)
    #>


    [CmdletBinding()]
    param (

        [Parameter(Mandatory = $false, HelpMessage = "Specify the PsSession(s) used to validation from. If null the local machine will be used.")]
        [System.Management.Automation.Runspaces.PSSession[]]
        $PsSession,

        [Parameter(Mandatory = $false, HelpMessage = "Tag to pass to SolutionExtension module functions.")]
        [string]
        $Tag = "Deployment",

        [Parameter(Mandatory = $true, HelpMessage = "Local path to the folder containing the extracted SBE Package.")]
        [string]
        $SBESourcePath,

        [Parameter(Mandatory = $true, HelpMessage = "Local path to the folder containing the SBE metadata file.")]
        [string]
        $SBEMetadataPath,

        [Parameter(Mandatory = $true, HelpMessage = "Version of the SBE package to use for validation interfaces.")]
        [string]
        $SBEVersion,

        [Parameter(Mandatory = $false, HelpMessage = "Return PSObject result.")]
        [switch]
        $PassThru,

        [Parameter(Mandatory = $false, HelpMessage = "Directory path for log and report output")]
        [string]$OutputPath,

        [Parameter(Mandatory = $false, HelpMessage = "Remove all previous progress and create a clean report")]
        [switch]$CleanReport = $false,

        [Parameter(Mandatory = $false, HelpMessage = "Show only failed results on screen.")]
        [switch]$ShowFailedOnly,

        [Parameter(Mandatory = $false, HelpMessage = "Skip the SBE file integrity test.")]
        [switch]$SkipIntegrityTest,

        [Parameter(Mandatory = $true, HelpMessage = "ECE Params")]
        [CloudEngine.Configurations.EceInterfaceParameters]$ECEParameters
    )

    try
    {
        $script:ErrorActionPreference = 'Stop'
        Set-AzStackHciOutputPath -Path $OutputPath
        $allResult = @()

        Write-AzStackHciHeader -Invocation $MyInvocation -Params $PSBoundParameters -PassThru:$PassThru

        # Use the SBE role defined local cache path
        $templatedLocalSBEPath = $ECEParameters.Roles["SBE"].PublicConfiguration.PublicInfo.SBEContentPaths.SBELocalPath
        $defaultLocalShare = $ECEParameters.Roles["Cloud"].PublicConfiguration.PublicInfo.DefaultInfraStorageLocations.DefaultLocalShare

        if ($null -eq $defaultLocalShare)
        {
            $defaultLocalShare = "D:"
            Trace-Execution "Older build - using hardcoded path '$defaultLocalShare' to defaultLocalShare"
        }
        $sbeLocalPath = $templatedLocalSBEPath.Replace('{DefaultLocalShare}', $defaultLocalShare.TrimEnd('\'))
        $cacheBase = Join-Path -Path $sbeLocalPath -ChildPath $ECEParameters.Roles["SBE"].PublicConfiguration.PublicInfo.SBEContentPaths.RelativePaths.LocalSBECachePath
        $sbeWorkingDir = Join-Path -Path $cacheBase -ChildPath $SBEVersion

        Log-Info -Message "Using '$sbeWorkingDir' to cache SBE content locally."

        $excludeFromContent = @()
        if ($SBESourcePath -eq $SBEMetadataPath)
        {
            # The update service combines the content and metadata together
            $sbeMetadataFiles = (Get-ChildItem -Path $SBESourcePath | Where-Object {$PSItem.Name -like "SBE*.xml" -or $PSItem.Name -eq "oemMetadata.xml"})
            $sbeZip = Get-ChildItem -Path $SBESourcePath -Filter "SBE*.zip"
            [array]$excludeFromContent = [array]$sbeMetadataFiles.Name + $sbeZip.Name
            Log-Info -Message ("Will exclude the following files from local SBE content cache: " + ($excludeFromContent -Join ", " | Out-String))
        }

        Test-ModuleUpdate -PassThru:$PassThru

        # Call/Initialise reporting
        $envcheckerReport = Get-AzStackHciEnvProgress -Clean:$CleanReport
        $envcheckerReport = Add-AzStackHciEnvJob -Report $envcheckerReport

        Log-Info -Message ("Check partner properties in unattended.json match SBE manifest") -Type Info
        try
        {
            $result = Test-SBEPropertiesValid -ECEParameters $ECEParameters
        }
        catch
        {
            Log-Info -Message "Error validating partner properties in unattended.json with SBE manifest" -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $exceptionResult = New-SBEHealthResultObject -TestName 'Test-SBEPropertiesValid' -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Validate partner properties in unattended.json"
            $detailedMessage = "Found invalid partnerProperties in unattended.json. $($PSItem.Exception.Message)"
            $exceptionResult.Remediation = "Fix PartnerProperties to be compliant with the JSON schema in the <PartnerProperties> element of the SBE manifest located at c:\SBE\SBE_Discovery*.xml."
            $exceptionResult.AdditionalData.Detail = $detailedMessage
            $allResult += $exceptionResult
            throw $detailedMessage
        }

        Log-Info -Message ("Check SBE credentials in secret store match SBE manifest") -Type Info
        try
        {
            $result = Test-SBECredentialsValid -ECEParameters $ECEParameters
        }
        catch
        {
            Log-Info -Message "Error validating SBE credentials in secret store with SBE manifest" -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $exceptionResult = New-SBEHealthResultObject -TestName 'Test-SBECredentialsValid' -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Validate SBE credentials in secret store"
            $detailedMessage = "Found invalid SBE Credentials in secret store. $($PSItem.Exception.Message)"
            $exceptionResult.Remediation = "Correct issues with the indicated secrets in the Key Vault associated with this cluster and restart your deployment."
            $exceptionResult.AdditionalData.Detail = $detailedMessage
            $allResult += $exceptionResult
            throw $detailedMessage
        }

        # Pre-validation checks and preparation
        Log-Info -Message ("Validate the SolutionExtension module is present and meets the requirements for health testing.") -Type Info
        try
        {
            $result = Test-SolutionExtensionModule -PackagePath $SBESourcePath
            if ($false -eq $result)
            {
                $detailedMessage = "Skipping as the provided SolutionExtension module has not implemented any tests or does not support the HealthServiceIntegration tag."
                Log-Info -Message $detailedMessage -Type Info
                $instanceResult = New-SBEHealthResultObject -TestName 'Test-SolutionExtensionModule' -TargetName $env:ComputerName -Status 'SUCCESS' -Description "Validate SolutionExtension module exists and supports health tests"
                $instanceResult.AdditionalData.Detail = $detailedMessage
                $allResult += $instanceResult
                return $allResult
            }
        }
        catch
        {
            Log-Info -Message "The SolutionExtension module could not be validated" -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $exceptionResult = New-SBEHealthResultObject -TestName 'Test-SolutionExtensionModule' -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Validate SolutionExtension module exists and supports health tests"
            $detailedMessage = "The SolutionExtension module could not be validated. $($PSItem.Exception.Message)"
            $exceptionResult.Remediation = "https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-troubleshoot#rerun-deployment"
            $exceptionResult.AdditionalData.Detail = $detailedMessage
            $allResult += $exceptionResult
            throw $detailedMessage
        }

        # Copy SBE package to local working dir
        try
        {
            $result = Copy-SBEContentLocalToNode -PackagePath $SBESourcePath -SkipNugetCopy:($Tag -ne 'Deployment') -TargetNodeName $env:ComputerName -ExcludeDirs @("IntegratedContent") -ExcludeFiles $excludeFromContent -DestPath $sbeWorkingDir
            if ($false -eq $result)
            {
                throw "An error occurred during the SBE package copy operation. See logs for details."
            }
        }
        catch
        {
            Log-Info -Message "An error occurred during the SBE package copy operation" -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $detailedMessage = $PSItem.Exception.Message
            $exceptionResult = New-SBEHealthResultObject -TestName 'Copy-SBEContentLocalToNode' -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Copy the SBE Package to working folder on '$($env:ComputerName)'"
            $exceptionResult.Remediation = "https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-troubleshoot#rerun-deployment"
            $exceptionResult.AdditionalData.Detail = $detailedMessage
            $allResult += $exceptionResult
            throw $detailedMessage
        }

        # Validate SBE content integrity of working dir
        try
        {
            if (-not $SkipIntegrityTest)
            {
                $result = Invoke-TestSBEContentIntegrity -SBEMetadataPath $SBEMetadataPath -SBEContentPath $sbeWorkingDir
                if ($false -eq $result)
                {
                    throw "SBE content integrity check found irregularities in the files at '$($sbeWorkingDir)'. Check the ECE logs for more information."
                }
            }
        }
        catch
        {
            $detailedMessage = "SBE content failed integrity check at '$($sbeWorkingDir)'"
            Log-Info -Message $detailedMessage -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $exceptionResult = New-SBEHealthResultObject -TestName 'Test-SBEContentIntegrity' -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Validate SBE content integrity"
            $exceptionResult.Remediation = "https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-troubleshoot#rerun-deployment"
            $exceptionResult.AdditionalData.Detail = $detailedMessage
            $allResult += $exceptionResult
            throw $detailedMessage
        }

        # Import the SolutionExtension module
        try
        {
            $result = Import-SolutionExtensionModule -PackagePath $sbeWorkingDir
        }
        catch
        {
            $detailedMessage = "Import SolutionExtension module from '$($sbeWorkingDir)' failed. The exception was: $($PSItem.Exception.Message)"
            Log-Info -Message "Import SolutionExtension module from '$($sbeWorkingDir)' failed." -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $exceptionResult = New-SBEHealthResultObject -TestName 'Import-SolutionExtensionModule' -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Import SolutionExtension module"
            $exceptionResult.Remediation = "https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-troubleshoot#rerun-deployment"
            $exceptionResult.AdditionalData.Detail = $detailedMessage
            $allResult += $exceptionResult
            throw $detailedMessage
        }

        # Run validation
        try
        {
            $functionName = 'Get-SBEHealthCheckResult'
            $exceptionResult = $null
            $instanceResult = @()
            $functionFound = Get-Command -Module SolutionExtension -Name $functionName -ErrorAction SilentlyContinue
            if ($null -eq $functionFound)
            {
                $detailedMessage = "A function named '$($functionName)' was not found in the SolutionExtension module."
                Log-Info -Message $detailedMessage -Type Error -ConsoleOut
                $thisResult = New-SBEHealthResultObject -TestName $functionName -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Invoke $functionName"
                $thisResult.AdditionalData.Detail = $detailedMessage
                $instanceResult += $thisResult
            }
            else
            {
                $solExtVersion = $functionFound.Version.ToString()
                Log-Info -Message "SolutionExtension version being used: $solExtVersion" -Type Info

                $params = Get-SBEHealthCheckParams -ECEParameters $ECEParameters -Tag $Tag
                Log-Info -Message "Invoke $functionName on $($env:ComputerName)" -Type Info
                [array]$thisResult = & $functionName @params
                if ($thisResult.Count -eq 0)
                {
                    Log-Info -Message "'$($functionName)' did not return any test results or no tests have been implemented." -Type Warning
                    $detailedMessage = "'$($functionName)' did not return any test results or no tests have been implemented."
                    $thisResult = New-SBEHealthResultObject -TestName $functionName -TargetName $env:ComputerName -Status 'FAILURE' -Description "Received health check results from $functionName"
                    $thisResult.AdditionalData.Detail = $detailedMessage
                    $instanceResult += $thisResult
                }
                else
                {
                    [array]$assertResult = Assert-ResponseSchemaValid -ResultObject $thisResult
                    if ($assertResult.Count -gt 0) { $instanceResult += $assertResult }
                }
            }
        }
        catch
        {
            # Unexpected exception occurred during partner tests
            Log-Info -Message "An error occurred during '$($functionName)'" -Type Error -ConsoleOut
            Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
            $exceptionResult = New-SBEHealthResultObject -TestName $functionName -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "$functionName on $($env:ComputerName)"
            $exceptionResult.Remediation = "https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-troubleshoot#rerun-deployment"
            $exceptionResult.AdditionalData.Detail = "An unhandled error occurred: " + ($PSItem | Format-List * | Out-String).Trim()
            $instanceResult += $exceptionResult
            throw $PSItem
        }
        finally
        {
            Log-Info -Message "Before adding $($functionName) instances... all count = $($allResult.Count) / instance count = $($instanceResult.Count))"
            if ($null -ne $instanceResult) { $allResult += $instanceResult }
        }

        try
        {
            $jobRun = @()
            $exceptionResult = $null
            $instanceResult = @()
            $functionName = 'Get-SBEHealthCheckResultOnNode'
            $functionFound = Get-Command -Module SolutionExtension -Name $functionName -ErrorAction SilentlyContinue
            if ($null -eq $functionFound)
            {
                $detailedMessage = "A function named '$($functionName)' was not found in the SolutionExtension module."
                Log-Info -Message $detailedMessage -Type Error -ConsoleOut
                $exceptionResult = New-SBEHealthResultObject -TestName $functionName -TargetName $env:ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Invoke $functionName"
                $exceptionResult.AdditionalData.Detail = $detailedMessage
                throw $detailedMessage
            }
            else
            {
                if ($null -eq $params)
                {
                    $params = Get-SBEHealthCheckParams -ECEParameters $ECEParameters -Tag $Tag
                }
                foreach ($session in $PsSession)
                {
                    try
                    {
                        $result = Copy-SBEContentToSession -PackagePath $SBESourcePath -SkipNugetCopy:($Tag -ne 'Deployment') -Session $session -ExcludeDirs @("IntegratedContent") -ExcludeFiles $excludeFromContent -DestPath $sbeWorkingDir
                        if ($false -eq $result)
                        {
                            Log-Info -Message "File copy via PsSession to '$($session.ComputerName)' failed. Will try copy to this node via PsDrive."
                            $thisCred = $session.Runspace.ConnectionInfo.Credential
                            $result = Copy-SBEContentLocalToNode -PackagePath $SBESourcePath -SkipNugetCopy:($Tag -ne 'Deployment') -TargetNodeName ($session.ComputerName) -ExcludeDirs @("IntegratedContent") -ExcludeFiles $excludeFromContent -Credential $thisCred -DestPath $sbeWorkingDir
                            if ($false -eq $result)
                            {
                                throw "An error occurred during the SBE package copy operation to '$($session.ComputerName)'. See logs for details."
                            }
                        }
                    }
                    catch
                    {
                        Log-Info -Message "An unhandled error occurred during 'Copy-SBEContentLocalToNode' to '$($session.ComputerName)'" -Type Error -ConsoleOut
                        Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
                        $detailedMessage = $PSItem.Exception.Message
                        $exceptionResult = New-SBEHealthResultObject -TestName $functionName -TargetName $session.ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Copy-SBEContentLocalToNode to '$($session.ComputerName)'"
                        $exceptionResult.AdditionalData.Detail = $detailedMessage
                        throw $detailedMessage
                    }
                }
                try
                {
                    foreach ($session in $PsSession)
                    {
                        # Verify SBE content integrity
                        if (-not $SkipIntegrityTest)
                        {
                            $result = Invoke-TestSBEContentIntegrity -SBEMetadataPath $SBEMetadataPath -SBEContentPath $sbeWorkingDir -PsSession $session
                            if ($false -eq $result)
                            {
                                throw "SBE content integrity check found irregularities in the files at '$($sbeWorkingDir)' on '$($session.ComputerName)'"
                            }
                        }

                        # Import the SolutionExtension module
                        $result = Import-SolutionExtensionModule -PackagePath $sbeWorkingDir -PsSession $session

                        Log-Info -Message "Invoke $functionName on '$($session.ComputerName)'" -Type Info
                        $sbJob = {
                            if ($null -eq $functionName)
                            {
                                $functionName = $using:functionName
                            }
                            if ($null -eq $params)
                            {
                                $params = $using:params
                            }
                            & $functionName @params
                        }
                        $jobRun += Invoke-Command -Session $session -ScriptBlock $sbJob -AsJob
                    }
                }
                catch
                {
                    Log-Info -Message "An unhandled error occurred on '$($session.ComputerName)' during '$($functionName)'" -Type Error -ConsoleOut
                    Log-Info -Message ("The exception message was: $($PSItem.Exception.Message)") -Type Error -ConsoleOut
                    $detailedMessage = $PSItem.Exception.Message
                    $exceptionResult = New-SBEHealthResultObject -TestName $functionName -TargetName $session.ComputerName -Status 'FAILURE' -Severity 'CRITICAL' -Description "Invoke '$($functionName)' on '$($session.ComputerName)'"
                    $exceptionResult.AdditionalData.Detail = $detailedMessage
                    throw $detailedMessage
                }

                # Wait for all jobs to complete with a timeout after 30 minutes
                Log-Info -Message "Waiting for all '$($functionName)' jobs to complete" -Type Info
                $waitJob = $true
                $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                $timeoutMinutes = 30
                while ($true -eq $waitJob)
                {
                    if ($stopwatch.Elapsed.TotalMinutes -ge $timeoutMinutes)
                    {
                        Log-Info -Message "All jobs have not completed in the specified timeout period." -Type Error
                        $stopwatch.Stop()
                        $waitJob = $false
                    }
                    else
                    {
                        $keepWaiting = $false
                        foreach ($job in $jobRun.ChildJobs)
                        {
                            if ($job.State -eq 'Running')
                            {
                                $keepWaiting = $true
                            }
                        }
                        if ($false -eq $keepWaiting)
                        {
                            $stopwatch.Stop()
                            $waitJob = $false
                        }
                        else
                        {
                            Start-Sleep -Seconds 30
                        }
                    }
                }

                foreach ($job in $jobRun.ChildJobs)
                {
                    $thisComputerName = $job.Location
                    if ($job.State -eq 'Failed')
                    {
                        [string]$detailedMessage = "Error while running '$($functionName)' on '$($thisComputerName)'. Exception message: " + $job.JobStateInfo.Reason.Message
                        Log-Info -Message $detailedMessage -Type Error -ConsoleOut
                        $exceptionResult = New-SBEHealthResultObject -TestName $functionName -TargetName $job.Location -Status 'FAILURE' -Severity 'CRITICAL' -Description "An exception occurred during $functionName"
                        $exceptionResult.AdditionalData.Detail = $detailedMessage
                        $allResult += $exceptionResult
                    }
                    elseif ($job.State -eq 'Running')
                    {
                        Log-Info -Message "'$($functionName)' was still running on '$($thisComputerName)' when the timeout period was hit." -Type Warning
                        $job | Stop-Job
                    }
                    else
                    {
                        Log-Info -Message "Log results for '$($thisComputerName)'" -Type Info
                        [array]$thisOutput = $job.Output
                        if ($thisOutput.Count -gt 0)
                        {
                            $instanceResult += $thisOutput
                        }
                    }
                }

                if ($instanceResult.Count -gt 0)
                {
                    [array]$assertResult = Assert-ResponseSchemaValid -ResultObject $instanceResult
                    if ($assertResult.Count -gt 0) { $instanceResult += $assertResult }
                }
                else
                {
                    Log-Info -Message "'$($functionName)' did not return any test results or no tests have been implemented." -Type Warning
                    $detailedMessage = "'$($functionName)' did not return any test results or no tests have been implemented."
                    $thisResult = New-SBEHealthResultObject -TestName $functionName -TargetName $env:ComputerName -Status 'FAILURE' -Description "Received health check results from $functionName"
                    $thisResult.AdditionalData.Detail = $detailedMessage
                    $instanceResult += $thisResult
                }
            }
        }
        catch
        {
            if ($null -ne $exceptionResult)
            {
                Log-Info -Message "'$($functionName)' hit exception - adding result details: $($exceptionResult.AdditionalData.Detail)" -Type Warning
                $instanceResult += $exceptionResult
            }
            foreach ($job in $jobRun.ChildJobs)
            {
                if ($job.State -eq 'Running')
                {
                    Log-Info -Message "'$($functionName)' was still running on '$($thisComputerName)' when an exception occurred." -Type Warning
                    $job | Stop-Job
                }
            }
            throw $PSItem
        }
        finally
        {
            Log-Info -Message "Before adding $($functionName) instances... all count = $($allResult.Count) / instance count = $($instanceResult.Count) )"
            if ($null -ne $instanceResult) { $allResult += $instanceResult }
        }

        Log-Info -Message "Returning with all count = $($allResult.Count)"
        return $allResult
    }
    catch
    {
        Log-Info -Message "" -ConsoleOut
        Log-Info -Message "$($PSItem.Exception.Message)" -ConsoleOut -Type Error
        Log-Info -Message "$($PSItem.ScriptStackTrace)" -ConsoleOut -Type Error
        $cmdletException = $_
        throw $PSItem
    }
    finally
    {
        Log-Info -Message "Performing clean up"
        $cleanupScriptBlock = {
            Get-Module -Name SolutionExtension -ErrorAction SilentlyContinue | Remove-Module -Force -Verbose:$false
            <#
            We now depend on deploy and action plans to clean up the cache dirs we leave behind for them
            if ($null -eq $sbeWorkingDir)
            {
                $sbeWorkingDir = $using:sbeWorkingDir
            }
            if (Test-Path -Path $sbeWorkingDir)
            {
                Write-Output "Remove SBE temporary working folder '$($sbeWorkingDir)' on '$($env:ComputerName)'"
                Remove-Item -Path $sbeWorkingDir -Recurse -Force
            }
            #>

        }
        if ($PsSession.Count -gt 0)
        {
            $jobClean = Invoke-Command -Session $PsSession -ScriptBlock $cleanupScriptBlock -AsJob
            $jobClean | Wait-Job | Out-Null
            foreach ($job in $jobClean.ChildJobs)
            {
                if ($job.State -eq 'Failed')
                {
                    [string]$detailedMessage = "An exception occurred during clean-up on '$($job.Location)'. Exception message: " + $job.JobStateInfo.Reason.Message
                    Log-Info -Message $detailedMessage -Type Warning
                }
                else
                {
                    [string]$output = $job.Output
                    Log-Info -Message $output -Type Info
                }
            }
        }
        else
        {
            Invoke-Command -ScriptBlock $cleanupScriptBlock
        }

        $script:ErrorActionPreference = 'SilentlyContinue'
        # Write result to telemetry channel
        foreach ($res in $allResult)
        {
            Write-ETWResult -Result $res
        }
        # Write validation result to report object and close out report
        $envcheckerReport | Add-Member -MemberType NoteProperty -Name 'SBEHealth' -Value $allResult -Force
        $envcheckerReport = Close-AzStackHciEnvJob -Report $envcheckerReport
        Write-AzStackHciEnvReport -Report $envcheckerReport
        Write-AzStackHciFooter -invocation $MyInvocation -Exception $cmdletException -PassThru:$PassThru
    }
}
# SIG # Begin signature block
# MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBosnlc+0YWBeHw
# bP8C0DZhBfNG4K4HNGxfwylJVYktlqCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIDKl2jbD5yyhs4gPgrn7puz9
# DBeWgXmDZcd85HVi2PqyMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAtB5rlKtc5OS4jOCoexH4ACmjHr/QpRIzhAM6pUPRAuMUZfmmjL097fnE
# M3PeSW55/3hPVkqb808GOUJuTK8/zVgnPyHf/5SjRYHEEll36eEGiILvVnvSyD0b
# MkjRXgUySwgezFghrL6ob6E/9N+VyTVaX2VTG7y6a8kYEyi5ZPRw/F2q+Nz728h9
# Tt+5fF5M4ALdfPxceJnPtHtLn/wj/48UXye5TAwOniSLWfH+8iZ1mGFBD5uUtU0C
# EjJTjstyH/HqqGMNwbjWFSRDO8AjrdsTWvpknZDKdgUxJg4wAhv2lbyLOhrr3/Le
# 2q7zh+z6FBARypJpLxKpHQ2XU+uXGKGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC
# F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq
# hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAaT1Dj2hnOMNYddGU47SEqnc6xZ54o/C/V9no2FY9E4AIGZut9Knzs
# GBMyMDI0MTAwOTAxMTUwOS42NTNaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo2RjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAAB/Bigr8xpWoc6AAEAAAH8MA0G
# CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0
# MDcyNTE4MzExNFoXDTI1MTAyMjE4MzExNFowgdMxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w
# ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjZGMUEt
# MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp1DAKLxpbQcPVYPHlJHy
# W7W5lBZjJWWDjMfl5WyhuAylP/LDm2hb4ymUmSymV0EFRQcmM8BypwjhWP8F7x4i
# O88d+9GZ9MQmNh3jSDohhXXgf8rONEAyfCPVmJzM7ytsurZ9xocbuEL7+P7EkIwo
# OuMFlTF2G/zuqx1E+wANslpPqPpb8PC56BQxgJCI1LOF5lk3AePJ78OL3aw/Ndlk
# vdVl3VgBSPX4Nawt3UgUofuPn/cp9vwKKBwuIWQEFZ837GXXITshd2Mfs6oYfxXE
# tmj2SBGEhxVs7xERuWGb0cK6afy7naKkbZI2v1UqsxuZt94rn/ey2ynvunlx0R6/
# b6nNkC1rOTAfWlpsAj/QlzyM6uYTSxYZC2YWzLbbRl0lRtSz+4TdpUU/oAZSB+Y+
# s12Rqmgzi7RVxNcI2lm//sCEm6A63nCJCgYtM+LLe9pTshl/Wf8OOuPQRiA+stTs
# g89BOG9tblaz2kfeOkYf5hdH8phAbuOuDQfr6s5Ya6W+vZz6E0Zsenzi0OtMf5RC
# a2hADYVgUxD+grC8EptfWeVAWgYCaQFheNN/ZGNQMkk78V63yoPBffJEAu+B5xlT
# PYoijUdo9NXovJmoGXj6R8Tgso+QPaAGHKxCbHa1QL9ASMF3Os1jrogCHGiykfp1
# dKGnmA5wJT6Nx7BedlSDsAkCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSY8aUrsUaz
# hxByH79dhiQCL/7QdjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf
# BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww
# bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAT7ss/ZAZ0bTa
# FsrsiJYd//LQ6ImKb9JZSKiRw9xs8hwk5Y/7zign9gGtweRChC2lJ8GVRHgrFkBx
# ACjuuPprSz/UYX7n522JKcudnWuIeE1p30BZrqPTOnscD98DZi6WNTAymnaS7it5
# qAgNInreAJbTU2cAosJoeXAHr50YgSGlmJM+cN6mYLAL6TTFMtFYJrpK9TM5Ryh5
# eZmm6UTJnGg0jt1pF/2u8PSdz3dDy7DF7KDJad2qHxZORvM3k9V8Yn3JI5YLPuLs
# o2J5s3fpXyCVgR/hq86g5zjd9bRRyyiC8iLIm/N95q6HWVsCeySetrqfsDyYWStw
# L96hy7DIyLL5ih8YFMd0AdmvTRoylmADuKwE2TQCTvPnjnLk7ypJW29t17Yya4V+
# Jlz54sBnPU7kIeYZsvUT+YKgykP1QB+p+uUdRH6e79Vaiz+iewWrIJZ4tXkDMmL2
# 1nh0j+58E1ecAYDvT6B4yFIeonxA/6Gl9Xs7JLciPCIC6hGdliiEBpyYeUF0ohZF
# n7NKQu80IZ0jd511WA2bq6x9aUq/zFyf8Egw+dunUj1KtNoWpq7VuJqapckYsmvm
# mYHZXCjK1Eus7V1I+aXjrBYuqyM9QpeFZU4U01YG15uWwUCaj0uZlah/RGSYMd84
# y9DCqOpfeKE6PLMk7hLnhvcOQrnxP6kwggdxMIIFWaADAgECAhMzAAAAFcXna54C
# m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp
# Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy
# MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51
# yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY
# 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9
# cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN
# 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua
# Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74
# kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2
# K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5
# TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk
# i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
# BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri
# Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC
# BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y
# eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA
# YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw
# MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp
# b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm
# ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM
# 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW
# OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4
# FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw
# xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX
# fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX
# VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC
# onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU
# 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
# ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo2RjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUATkEpJXOaqI2wfqBsw4NLVwqYqqqggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF
# AAIFAOqvr74wIhgPMjAyNDEwMDgxMzE4NTRaGA8yMDI0MTAwOTEzMTg1NFowdzA9
# BgorBgEEAYRZCgQBMS8wLTAKAgUA6q+vvgIBADAKAgEAAgIaLQIB/zAHAgEAAgIU
# sjAKAgUA6rEBPgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow
# CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQAEAnaF4fTA
# XxZBtpscWKV1QQe1WFCg7AcIhB/9zWVZZMCwsFTuszZztk9qXqFlSeqvCaovRP2c
# 1U1r/NgBpID9fgeFizzz1IV7NX0TGgTrGqPQ7geHwE4CRXFhQKpdUBNk3Il8Y0x+
# dzvUx4IIyAViC36PkZmGRwbHYIPFaxeLJiK1ziJaUyf3m55c4m8mhX/u7RcVDiC/
# txcIDBTPc2dGj2ZSnSaQrSzEGt5zcYVX/C/tPA+PAJa/cDNW1xp3uWNQbrvfjRAD
# D7RIpOhV5gAaQ7aodtCR6gNLqu4oiGzyr2cZg5Cx8rU/rp9MzhOOPDCPo251+UEK
# Ou8SFhsjmUtJMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAH8GKCvzGlahzoAAQAAAfwwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgRBrkbS7x
# znZtr0mfoFiSCYQeTc3DsbpjMAfmDkbt77QwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCCVQq+Qu+/h/BOVP4wweUwbHuCUhh+T7hq3d5MCaNEtYjCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB/Bigr8xpWoc6AAEA
# AAH8MCIEIMxYfNmHhx9iKzrkDVNqTQiVCk7BuKPik5Q9vehPX2zcMA0GCSqGSIb3
# DQEBCwUABIICAAPFDz5gf+gHUoCr3G/cINxYrxPZzgQGTrWood1mVTCyYDHQYh2Q
# GVFmlt5BL4zViM/0XBDes0OK+w++VLoR/wTSH/VtdqGhWvO2jmBjBMSgO58wy15y
# 3dQjt2x8ApAuX+hM1uWJ5nBWJ/eIKQgNxgwmlbpXrrDn8iBjIOZEcFckZiCJqdz5
# YLzv5V6ymIpE7zBtHECWfSYz0g7nmjVdcIoCIT9t95XX0OZ3KfIueZuiN3vCZ6Nr
# VxIwmSL+U1gAE4mb4A9NlzhR35Nu7jCdXr8Zp98/OF+dKoGuSSBEt8VWk5BtlZ0i
# 8ZgaMots9M1LuTeMadXpWEG8MeEZFYnXNmHzVgJzGuJt6rWs1w2eWHqcGKfDcN/K
# 1Xo4g2CuDSu73V4X2OdapDRp938GM5JFCLczLpj95E6pyTUFquIFeXEYoJdTC6SV
# ilQ0+cVjJRFTO9KJWazXdn/m00KRvDA+Vtbe41FmnAFXFo7efnsIN1rvQ4VwNcbI
# eBZqG6z/o/2Iqrih+n+xtsjKktb00DPISVEDou+eNuGkHGpB0rQsl+A2lCulPMOk
# vFVlJEuiPS3NZ+dp68qX5U0Ek3+pBWbETTnqTjyEKXsPJpPO6NT/d3/rgA/DUGW/
# G1W3eJjBw23NMfeuR/7hWYTOiDfZL6mBEAH4UKNrARYqOfRPbEy8iLhV
# SIG # End signature block