AzStackHciOSImageRecipe/AzStackHci.OSImageRecipe.Helpers.psm1

Import-LocalizedData -BindingVariable lswTxt -FileName AzStackHci.OSImageRecipe.Strings.psd1

$global:recipeFilePath = "$env:windir/System32/AzureLocalImage/OSImageRecipe.xml"

$renderXMLvarScriptBloc = {
    <#
    .SYNOPSIS
        A script block that can be passed to another script block that happens
        to be running remotely. This is a cascaded script blocks concept.
 
    .DESCRIPTION
        The point of this script block is to validate that the Azure Local
        version is one that should have a local OS image recipe
        file on it. If it is and that file exists return a data structure
        that represents that XML file.
    #>

    Param($recipeFilePath)

    function isOfficialBuild {
        <#
        Return $true if the registry tells us this is an official build.
        Return $false if the registry tells us this is NOT an official build.
        Return $null if the we fail to find OFFICIAL_BUILD in the ComposedBuildInfo
        registry location.
        The shouldHaveRecipeFile() function should be called before isOfficialBuild().
        This is assure that we at least expect the hosts is from a composed build
        and that we should expect the registry to contain this information.
        #>

        try {
            $rawIsOfficial = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).OFFICIAL_BUILD
        }
        catch {
            $rawIsOfficial = $null
        }
        if ($null -eq $rawIsOfficial) {
            return $null
        } elseif ([int]$rawIsOfficial) {
            return $true
        } else {
            return $false
        }
    }

    function validateRecipeFileExistsAndIsNotCorrupt {
        <#
        Return the XML data structure if it renders correctly from the file.
        Return $false if the file simply does not exists OR
        if the file does not render a valid data structure and
        therefore considered corrupt.
        #>

        if (Test-Path -Path $recipeFilePath) {
            try {
                [xml]$recipeObj = Get-Content $recipeFilePath
                return $recipeObj
            }
            catch {
                # return False because although the XML file existed it appears to be corrupt
                return $false
            }
        } else {
            # return False because the xml should exist on this host, but it does not
            return $false
        }
    }

    function validateRecipeIsSigned {
        <#
        Validate that the signature in the recipe matches that particular file
        and that the file contents have not been manually manipulated.
        Return $true if the onbox xml recipe file is correctly signed.
        Return $false if the onbox xml recipe file is NOT correctly signed.
        isOfficialBuild() should be run before this function because
        only official builds contain signed recipe files and checking
        to see if it is an official build first guards the logic found
        in this func.
        #>

        Param($recipeFilePath)
        $recipeFilePath = $recipeFilePath -replace '\\|/', '='
        [string]$recipeFilePath = [string]$recipeFilePath -replace '=', '\\'
        $contentPath = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\EdgeArcBootstrapSetup\' -ErrorAction SilentlyContinue).ContentBinariesPath
        if (([System.String]::IsNullOrEmpty($contentPath)) -or (!(Test-Path $contentPath)))
        {
            $bootstrapDirectory = Get-ChildItem -Path "C:/windows/system32/Bootstrap" | Where-Object { $_.Name -like "*content*" } | Sort-Object -Property Name -Descending
            if ($null -ne $bootstrapDirectory)
            {
                $contentPath = $bootstrapDirectory[0].FullName
            }
        }
        if ([System.String]::IsNullOrEmpty($contentPath))
        {
            return $false
        }
        $validatorDll = "$contentPath/Microsoft.AzureStack.UpdateService.BootstrapValidation"
        $validatorDll += "/lib/net472/Microsoft.AzureStack.Services.Update.ResourceProvider.UpdateService.Security.dll"
        Import-Module $validatorDll
        $validatorInstance = [Microsoft.AzureStack.Services.Update.ResourceProvider.UpdateService.Security.SignedXmlValidator]::new()
        if (Test-Path -Path $recipeFilePath) {
            $isSigned = $validatorInstance.ValidateAsync("$recipeFilePath").GetAwaiter().GetResult()
            if ($isSigned) {
                return $true
            }
            return $false
        }
        return $false
    }

    function getOSBuildVersion {
        <#
        .SYNOPSIS
            Return a version object that represents the live installed version.
            (HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID
            NOTE: this func is scoped within a scriptBlock called by a scriptBlock
        .DESCRIPTION
            Discover the 'COMPOSED_BUILD_ID' version string.
            Convert the string into a [version] module object.
            For example if the string is '10.2502.0.6250' the object looks like this:
            Major Minor Build Revision
            ----- ----- ----- --------
            10 2502 0 6205
        #>

    $installedVersionString = $null
    try {
        $installedVersionString = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID
        if (-not $installedVersionString) {
            throw 'COMPOSED_BUILD_ID is not set'
        }
        $installedVersionString = $installedVersionString.split('.')[0..3] -join'.' # make sure no more than 4 chunks separated by periods because [Version]::Parse() does not like it
        $installedVersionString = [regex]::Replace($installedVersionString, '-\d*', '') # remove any hyphenated subversion string because [Version]::Parse() does not like it
        #assume subversion are backward and forward compatible with versions of similar major version
    }
    catch {
        # if the registry entry does not exist then this HCI instance can be considered a
        # nonComposed one and thus just set the version to 0
        # this will cause the downstream logic to skip the tests instead of fail them and
        # thus maintaining the integrity of the EnvironmentChecker for composed HCI
        # and the older nonComposed HCI.
        $installedVersionString = '0.0.0.0'
    }
    try {
        $installedVersionObj = [Version]::Parse($installedVersionString)
    }
    catch {
        $installedVersionString = '0.0.0.0'
        $installedVersionObj = [Version]::Parse($installedVersionString)
    }
        return $installedVersionObj
    }

    function shouldHaveRecipeFile {
        <#
        .SYNOPSIS
            Return True if the image running on the local host is an HCI image
            from an era that contained an embedded recipe XML file.
            NOTE: this func is scoped within a scriptBlock called by a scriptBlock
        .DESCRIPTION
            Check that the 'COMPOSED_BUILD_ID'
            version running on the local live host is >= 10.2502.0.3017.
            This minimum version is just a line in the sand when composed images
            started to contain the recipe xml file.
            Return True if the installed version is >= the minimum version.
            If True is returned the live host *should* have a valid recipe file.
            If False is returned then this live host should *not* have a recipe file.
        #>

        $minVersionString = '10.2502.0.0'
        $minVersionObj = [Version]::Parse($minVersionString)
        $installedVersionObj = getOSBuildVersion
        if ($installedVersionObj -ge $minVersionObj) {
            return $true
        }
        return $false
    }

    # The renderXMLvarScriptBloc can return 3 explicit things.
    # Each of these indicate a different outcome.
    # $recipeObj == This image should contain a recipe file
    # and the returned value is a data structure
    # representing the XML content in the recipe file.
    # $False == This image should contain a recipe file, but does NOT!
    # $Null == This image should NOT contain a recipe file, thus it
    # is OK to skip testing against it.
    # This Obj vs False vs Null response takes into account if the recipe file is signed
    # or not, but only if this live hosts was born from the official composed
    # image pipeline. If it came from the buddy pipeline do not take
    # into account if the recipe is signed or not.
    # Return False if this is an Official build and it is NOT signed.
    # This False response will correctly induce recipe validation test failures.
    if ($(shouldHaveRecipeFile)) {
        $isOfficial = $(isOfficialBuild)
        if ($isOfficial -or ($null -eq $isOfficial)) {
            if (-not $(validateRecipeIsSigned $recipeFilePath)) {
                # return False because this is an official build and the recipe file is not signed
                return $false
            }
        }
        return $(validateRecipeFileExistsAndIsNotCorrupt)
    } else {
        return $null
    }
} # end renderXMLvarScriptBloc

function TestResult {
    <#
    .SYNOPSIS
        Build up a params data structure to pass the
        New-AzStackHciResultObject function.
    .DESCRIPTION
        The New-AzStackHciResultObject function get information about the
        result of a test into the correct result files (log, json, etc.)
    #>

    Param([Parameter(Mandatory=$true,Position=0)] [array]$responses,
          [Parameter(Mandatory=$true,Position=1)] [string]$Name,
          [Parameter(Mandatory=$true,Position=2)] [string]$Title,
          [Parameter(Mandatory=$true,Position=3)] [string]$DisplayName,
          [Parameter(Mandatory=$true,Position=4)] [string]$Severity,
          [Parameter(Mandatory=$true,Position=5)] [string]$Description,
          [Parameter(Mandatory=$false,Position=6)] [string]$Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/deploy/deployment-tool-install-os',
          [Parameter(Mandatory=$false,Position=7)] [string]$TargetResourceType = 'OSImageRecipe',
          [Parameter(Mandatory=$false,Position=8)] [string]$Resource = 'OS Image Recipe')
    $instanceResults = @()
    foreach ($response in $responses) {
        $detailString = $($response.details) -join '; '
        foreach ($msg in $($response.logLines)) {
            $msgArray = $msg.split('|')
            Log-Info $msgArray[0] -Type $msgArray[1]
        }
        try {
            $Status = 'SUCCESS'
            if ($($response.rc)) {
                $Status = 'FAILURE'
            }
            $params = @{
                Name               = $Name
                Title              = $Title
                DisplayName        = $DisplayName
                Severity           = $Severity
                Description        = $Description
                Tags               = @{}
                Remediation        = $Remediation
                TargetResourceID   = $($response.computername)
                TargetResourceName = $($response.computername)
                TargetResourceType = $TargetResourceType
                Timestamp          = [datetime]::UtcNow
                Status             = $status
                HealthCheckSource  = $ENV:EnvChkrId
                AdditionalData     = @{Source    = $($response.computername)
                                       Resource  = $Resource
                                       Detail    = $detailString
                                       Status    = $status
                                       TimeStamp = [datetime]::UtcNow}}
            $instanceResults += New-AzStackHciResultObject @params
        }
        catch {
            throw $_
        }
    }
    return $instanceResults
}

function Test-InstalledPackages {
    <#
    .SYNOPSIS
        Validate that all the packages installed, including the bootStrap package,
        have the same version the OS image recipe requires.
    .DESCRIPTION
        Loop around the package defined in OSImageRecipe.xml
        and validate using 'Get-InstalledModule' that the version installed matches the version
        defined in the recipe XML file.
        This function used to use Get-Package to check to see if 'packages' as defined in
        the recipe were installed on a system with the correct version. Get-Package
        was used because it is a more generic cmdline tool than Get-InstalledModule.
        Get-Package would allow the user to see PowerShell modules install and other
        types of packages. As of 01/09/2025 the recipe does not contain any non-PowerShell
        packages other than The bootStrap one. BootStrap is already special cased in this
        test. Once there are non-PowerShell packages in the recipe we need to define what
        kind of package.
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession)
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $packageObj = New-Object System.Collections.Generic.List[System.Object]
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            if ($($XMLstructure.SelectNodes('/BuildInfo/Packages/Package'))) {
                $packageObj = $XMLstructure.BuildInfo.Packages.Package
            }
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = $resultSeverity
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        # the only thing we skip for now is the EC itself since how could we be running this code
        # unless EC is installed. We will run the ver validation that it is the right EC ver
        # during build time, but in the field just skip validating that EC is installed for now
        # this was mainly done because otherwise the CI runs that use ALLNUGETSHARES as a way to
        # install a specific EC version will fail every time
        $packagesToSkip = @('AzStackHci.EnvironmentChecker')
        foreach ($pkg in $packageObj) {
            $name = $pkg.Name
            $ver = $pkg.Version
            try {
                $ver = $ver.split('.')[0..3] -join'.' # make sure no more than 4 chunks separated by periods because [Version]::Parse() does not like it
                $ver = [regex]::Replace($ver, '-\d*', '') # remove any hyphenated subversion string because [Version]::Parse() does not like it
                #assume subversion are backward and forward compatible with versions of similar major version
            }
            catch {
                $ver = 'N/A'
            }
            if ($packagesToSkip -contains $name) {
                continue
            }
            if ($name -notlike '*Bootstrap.Setup*') {
                try {
                    $liveHostVer = ([string]((Get-InstalledModule -Name ${name} -ErrorAction SilentlyContinue).Version)).trim()
                    $liveHostVer = $liveHostver.split('.')[0..3] -join'.' # make sure no more than 4 chunks separated by periods because [Version]::Parse() does not like it
                    $liveHostver = [regex]::Replace($liveHostver, '-\d*', '') # remove any hyphenated subversion string because [Version]::Parse() does not like it
                }
                catch {
                    $liveHostVer = 'N/A'
                }
                if (-not $liveHostVer) {
                    $liveHostVer = 'N/A'
                }
                try {
                    $liveHostVerObj = [Version]::Parse($liveHostVer)
                }
                catch {
                    $liveHostVerObj = $null
                }
                try {
                    $verObj = [Version]::Parse($ver)
                }
                catch {
                    $verObj = $null
                }
                if ($liveHostVer -eq 'N/A') {
                    $rc += 1
                    $msg = $lswTxt.InstalledPackagesNotInstalled -f $name, $localHost
                    $logLines.Add("${msg}|${resultSeverity}")
                } elseif (-not $liveHostVerObj) {
                    $rc += 1
                    $msg = $lswTxt.InstalledPackagesLiveHostVerConversionFail -f $name, $localHost
                    $logLines.Add("${msg}|${resultSeverity}")
                } elseif (-not $verObj) {
                    $rc += 1
                    $msg = $lswTxt.InstalledPackagesRecipeVerConversionFail -f $name, $localHost
                    $logLines.Add("${msg}|${resultSeverity}")
                } elseif ($liveHostVerObj -lt $verObj) {
                    $rc += 1
                    $msg = $lswTxt.InstalledPackagesFail -f $name, $localHost, $liveHostVer, $ver
                    $logLines.Add("${msg}|${resultSeverity}")
                } else { # this assumes that ($liveHostVerObj -ge $verObj)
                    $msg = $lswTxt.InstalledPackagesPass -f $name, $localHost, $liveHostVer, $ver
                    $logLines.Add("${msg}|SUCCESS")
                }
                $detailList.Add($msg)
            } else {
                # checking both if the 'InstallCompleted' AND the version string exist
                # this is done because in the past I have seen bugs that prevented the bootstrap package from
                # successfully installing, but the version string was there.
                # It is best to validate that both bits of information exists and not just the version string!
                $outString = Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\EdgeArcBootstrapSetup\' -ErrorAction SilentlyContinue |Out-String
                if (-not [regex]::match($outString, 'InstallCompleted\s+:\s+1').Success) {
                    $rc += 1
                    $msg = $lswTxt.InstalledPackagesBootstrapInstalledFail -f $name, $localHost
                    $logLines.Add("${msg}|${resultSeverity}")
                } else {
                    $msg = $lswTxt.InstalledPackagesBootstrapInstalledPass -f $name, $localHost
                    $logLines.Add("${msg}|SUCCESS")
                }
                $detailList.Add($msg)
                if (-not [regex]::match($outString, $ver).Success) {
                    $rc += 1
                    $msg = $lswTxt.InstalledPackagesBootstrapVersionFail -f $name, $localHost, $ver
                    $logLines.Add("${msg}|${resultSeverity}")
                } else {
                    $msg = $lswTxt.InstalledPackagesBootstrapVersionPass -f $name, $localHost, $ver
                    $logLines.Add("${msg}|SUCCESS")
                }
                $detailList.Add($msg)
            }
        }
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_Package_Version"
               Title       = "Installed Packages match recipe."
               DisplayName = "Installed Packages match recipe."
               Severity    = $resultSeverity
               Description = "Validating that the packages installed on the host are the same versions defined in the OS image recipe."}
    return (TestResult @splat)
}

function Test-InstalledAdditionalFiles {
    <#
    .SYNOPSIS
        Validate that all the 'additional files' defined in the OS image recipe
        match those installed on the system.
    .DESCRIPTION
        Loop around the additionFiles defined in OSImageRecipe.xml
        and validate they exit on the system using '[System.IO.File]::Exists()'.
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $fileList = New-Object System.Collections.Generic.List[System.Object]
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            if ($($XMLstructure.SelectNodes('/BuildInfo/AdditionalFiles/File/DestinationPath'))) {
                $fileList = $XMLstructure.BuildInfo.AdditionalFiles.File.DestinationPath
            }
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = ${resultSeverity}
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        $fileList |foreach-object {
            $localFilePath = $_
            $filePath = "${localFilePath}"
            #$logLines.Add("Validate that additional file [${filePath}] is installed on host.|INFO")
            if (-not [System.IO.File]::Exists(${filePath})) {
                $rc += 1
                $msg = $lswTxt.InstalledFilesFail -f $filePath, $localHost
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.InstalledFilesPass -f $filePath, $localHost
                $msg += ' :: '
                $msg += ([System.IO.File]::GetCreationTime(${localFilePath}) |Out-string).trim()
                $msg += ' :: '
                $msg += ((([System.IO.File]::GetAccessControl(${localFilePath})).Access)[0]).FileSystemRights
                $logLines.Add("${msg}|SUCCESS")
            }
            $detailList.Add($msg)
        }
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_Additional_Installed_Files"
               Title       = "Installed Files match recipe."
               DisplayName = "Installed Files match recipe."
               Severity    = $resultSeverity
               Description = "Validating all additional files exist on the live host that are defined in the OS image recipe."}
    return (TestResult @splat)
}

function Test-BaseOSimage {
    <#
    .SYNOPSIS
        Validate that the system has a supported base OS version installed as defined in the OS image recipe file.
    .DESCRIPTION
        Validate that the Edition string and the Version string installed match what is in the OS image recipe.
        Use the following PS commands to perform this validation:
          Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | Select-Object -Property LCUVer).LCUVer
          (Get-ComputerInfo | Select-Object WindowsEditionId).WindowsEditionId
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $recipeBaseOSverString = $null
        $recipeEditionString = $null
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            $recipeBaseOSverString = $XMLstructure.BuildInfo.SupportedVersions.Version.Build
            # validate only the first 3 chunks of the version string as the last chunk represents
            # the LCU (HotFixes) and those differ post composed image creation
            $recipeBaseOSverString = ($recipeBaseOSverString.Split('.')[0..2]) -join('.')
            $recipeEditionString = $XMLstructure.BuildInfo.SupportedVersions.Version.Edition
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = $resultSeverity
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        if ($recipeBaseOSverString -and $recipeEditionString) {
            try {
                $installedBaseOSverString = [string](Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | Select-Object -Property LCUVer).LCUVer
            }
            catch {
                $installedBaseOSverString = 'N.A.0.0'
            }
            # validate only the first 3 chunks of the version string as the last chunk represents the LCU (HotFixes) and those differ post composed image creation
            $installedBaseOSverString = ($installedBaseOSverString.Split('.')[0..2]) -join('.')
            try {
                $installedEditionString = (Get-ComputerInfo | Select-Object WindowsEditionId).WindowsEditionId
            }
            catch {
                $installedEditionString = 'N/A'
            }
            #$logLines.Add("Validate that the base OS installed on host [${installedBaseOSverString}] matches recipe version [${recipeBaseOSverString}].|INFO")
            if ($recipeBaseOSverString -ne $installedBaseOSverString) {
                $rc += 1
                $msg = $lswTxt.BaseOSimageVersionFail -f $localHost, $recipeBaseOSverString, $installedBaseOSverString
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.BaseOSimageVersionPass -f $localHost, $recipeBaseOSverString, $installedBaseOSverString
                $logLines.Add("${msg}|SUCCESS")
            }
            $detailList.Add($msg)
            if ($recipeEditionString -ne $installedEditionString) {
                $rc += 1
                $msg = $lswTxt.BaseOSimageEditionStringFail -f $localHost, $recipeEditionString, $installedEditionString
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.BaseOSimageEditionStringPass -f $localHost, $recipeEditionString, $installedEditionString
                $logLines.Add("${msg}|SUCCESS")
            }
        }
        $detailList.Add($msg)
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_Base_OS"
               Title       = "BaseOS matches recipe."
               DisplayName = "BaseOS matches recipe."
               Severity    = $resultSeverity
               Description = "Validating that the base OS version string and edition string installed on the host match the OS image recipe."}
    return (TestResult @splat)
}

function Test-BuildId {
    <#
    .SYNOPSIS
        Validate that the system has a version that matches the one defined in the OS image recipe file.
    .DESCRIPTION
        Validate that the version burned into the registry matches what is in the OS image recipe.
        Use the following PS commands to perform this validation:
          (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $recipeBuildId = $null
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            $recipeBuildId = $XMLstructure.BuildInfo.BuildId
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = ${resultSeverity}
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        if ($recipeBuildId) {
            try {
                $installedBuildID = [string]((Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID).trim()
            }
            catch {
                $installedBuildID = 'N/A'
            }
            #$logLines.Add("Validate that OS image version [${recipeBuildId}] is installed on host.|INFO")
            if ($recipeBuildId -ne $installedBuildID) {
                $rc += 1
                $msg = $lswTxt.BuildIdFail -f $localHost, $recipeBuildId, $installedBuildID
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.BuildIdPass -f $localHost, $recipeBuildId, $installedBuildID
                $logLines.Add("${msg}|SUCCESS")
            }
            $detailList.Add($msg)
        }
        $detailList.Add($msg)
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_Version"
               Title       = "OS image version matches recipe."
               DisplayName = "OS image version matches recipe."
               Severity    = $resultSeverity
               Description = "Validating that the OS image version installed on the host matches the OS image recipe."}
    return (TestResult @splat)
}

function Test-ComposedBuildVersion {
    <#
    .SYNOPSIS
        Validate that the version running on the existing Azure Local node
        matches the version running on a node this is about to be added.
        The passed in PSsession is the session to the node that is about to be added.
    .DESCRIPTION
        Validate that the version burned into the registry on the existing
        node matches what is burned into the registry on the node that is about to be added.
        Use the following PS commands to perform this validation:
          (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($existingNodeInstalledBuildID, $existingNodeName, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        if ($existingNodeName -eq $localHost) {
            # if by chance you happen to have remote PS session to the localhost then skip this test
            $msg = $lswTxt.SkipTestNotNodeAdd
            $res = 'SUCCESS'
            $logLines.Add("${msg}|${res}")
        } else {
            try {
                $newNodeInstalledBuildID = [string]((Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID).trim()
            }
            catch {
                $newNodeInstalledBuildID = 'N/A'
            }
            #$logLines.Add("Validate that OS image version [${existingNodeInstalledBuildID}] is installed on the node you want to add to the cluster.|INFO")
            if ($newNodeInstalledBuildID -ne $existingNodeInstalledBuildID) {
                $rc += 1
                $msg = $lswTxt.NodeAddBuildIdFail -f $existingNodeName, $localHost, $existingNodeInstalledBuildID, $newNodeInstalledBuildID
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.NodeAddBuildIdPass -f $existingNodeName, $localHost, $existingNodeInstalledBuildID, $newNodeInstalledBuildID
                $logLines.Add("${msg}|SUCCESS")
            }
        }
        $detailList.Add($msg)
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    # this is where we get the buildID on the existing cluster, the buildID of the node you want to add is done in the scriptBlock Invoke-Command call below
    $existingNodeInstalledBuildID = [string]((Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\ComposedBuildInfo\Parameters).COMPOSED_BUILD_ID).trim()
    $responses = "" | Select-Object -Property rc, details, computername, logLines
    $existingNodeName = $ENV:ComputerName
    if ($PsSession) {
        $responses = Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$existingNodeInstalledBuildID, $existingNodeName, $lswTxt, $resultSeverity)
    } else {
        # if a remote PS session is not passed in then this is NOT a nodeAdd test, so skip
        $rc = 0
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $msg = $lswTxt.SkipTestNotNodeAdd
        $res = 'SUCCESS'
        $detailList.Add($msg)
        $logLines.Add("${msg}|${res}")
        $responses = "" | Select-Object -Property rc, details, computername, logLines
        $responses.rc = $rc
        $responses.details = $detailList
        $responses.computername = $existingNodeName
        $responses.logLines = $logLines
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_NodeAdd_Version"
               Title       = "Cluster OS image version matches the version on the node to add."
               DisplayName = "Cluster OS image version matches the version on the node to add."
               Severity    = $resultSeverity
               Description = "Validating that the OS image version installed on the cluster matches the version on the host to add to the cluster."}
    return (TestResult @splat)
}

function Test-SolutionVersion {
    <#
    .SYNOPSIS
        Validate that the system has a solution version that matches the one defined in the OS image recipe file.
    .DESCRIPTION
        Validate that the solution version burned into the registry matches what is in the OS image recipe.
        Use the following PS commands to perform this validation:
          (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\EdgeArcBootstrapSetup').VSRInfo
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $recipeSolutionVersion = $null
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            $recipeSolutionVersion = $XMLstructure.BuildInfo.VSRVersion
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = ${resultSeverity}
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        if ($recipeSolutionVersion) {
            #$logLines.Add("Validate that solution version [${recipeSolutionVersion}] is installed on host.|INFO")
            try {
                $regPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\VSR'
                if (-not (Test-Path $regPath))
                {
                    $regPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\EdgeArcBootstrapSetup'
                }
                $vsrInfoJson = [string](Get-ItemProperty -Path $regPath).VSRInfo
                $installedSolutionVersion = (($vsrInfoJson | ConvertFrom-Json | Select-Object -Property SolutionVersion).SolutionVersion).trim()
            }
            catch {
                $installedSolutionVersion = 'N/A'
            }
            if (($recipeSolutionVersion -ne $installedSolutionVersion) -or ($installedSolutionVersion -eq 'N/A')) {
                $rc += 1
                $msg = $lswTxt.SolutionVersionFail -f $localHost, $recipeSolutionVersion, $installedSolutionVersion
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.SolutionVersionPass -f $localHost, $recipeSolutionVersion, $installedSolutionVersion
                $logLines.Add("${msg}|SUCCESS")
            }
            $detailList.Add($msg)
        } else {
            $msg = $lswTxt.SkipTestMissingVSRVersion -f $localHost, $recipeSolutionVersion, $installedSolutionVersion
            $logLines.Add("${msg}|SUCCESS")
            $detailList.Add($msg)
        }
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_Solution_Version"
               Title       = "Solution version matches recipe."
               DisplayName = "Solution version matches recipe."
               Severity    = $resultSeverity
               Description = "Validating that the solution version installed on the host matches the OS image recipe."}
    return (TestResult @splat)
}

function Test-FeaturesOnDemand {
    <#
    .SYNOPSIS
        Validate that the system has the correct FOD enabled as defined in the OS image recipe file.
    .DESCRIPTION
        Validate that the correct version of the FODs are installed.
        Use the following PS command to perform this validation:
          ((Get-WindowsCapability -Online -Name '${name}*').Name.split('~')[-1]).TrimEnd()
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $recipeFODlist = New-Object System.Collections.Generic.List[System.Object]
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            if ($($XMLstructure.SelectNodes('/BuildInfo/FeatureOnDemands/Feature'))) {
                $recipeFODlist = $XMLstructure.BuildInfo.FeatureOnDemands.Feature
            }
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = ${resultSeverity}
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        $recipeFODlist |foreach-object {
            $fullNameString = $_.Name
            $name = ($fullNameString.split('~')[0]).TrimEnd()
            $ver = ($fullNameString.split('~')[-1]).TrimEnd()
            #$logLines.Add("Validate that FOD [${name}] is installed as version [${ver}] on host.|INFO")
            try {
                $liveHostVer = $(((Get-WindowsCapability -Online -Name "${name}*").Name.split('~')[-1]).TrimEnd())
            }
            catch {
                $liveHostVer = 'N/A'
            }
            if ($liveHostVer -ne $ver) {
                $rc += 1
                $msg = $lswTxt.FeaturesOnDemandFail -f $name, $localHost, $ver, $liveHostVer
                $logLines.Add("${msg}|${resultSeverity}")
            } else {
                $msg = $lswTxt.FeaturesOnDemandPass -f $name, $localHost, $ver, $liveHostVer
                $logLines.Add("${msg}|SUCCESS")
            }
            $detailList.Add($msg)
        }
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_FOD"
               Title       = "Features on Demand match recipe."
               DisplayName = "Features on Demand match recipe."
               Severity    = $resultSeverity
               Description = "Validating that the FODs installed on the host match the FODs defined in the OS image recipe."}
    return (TestResult @splat)
}

function Test-LatestCumulativeUpdate {
    <#
    .SYNOPSIS
        Validate that the system has the correct LCU as defined in the OS image recipe file.
    .DESCRIPTION
        Validate that the correct LCU version is installed.
        Use the following PS command to perform this validation:
          ((Get-HotFix -Id ${ver}).HotFixID).TrimEnd()
    #>

    [CmdletBinding()]
    Param([Parameter()]
          [System.Management.Automation.Runspaces.PSSession[]]
          $PsSession )
    $sb = {
        Param($recipeFilePath, $renderXML, $lswTxt, $resultSeverity)
        $rc = 0
        $localHost = $ENV:ComputerName
        $detailList = New-Object System.Collections.Generic.List[System.Object]
        $logLines = New-Object System.Collections.Generic.List[System.Object]
        $recipeLCUlist = New-Object System.Collections.Generic.List[System.Object]
        $innerSB = [ScriptBlock]::Create($renderXML)
        $XMLstructure = & $innerSB $recipeFilePath
        if ($XMLstructure) {
            if ($($XMLstructure.SelectNodes('/BuildInfo/LCUs/LCU'))) {
                $recipeLCUlist = $XMLstructure.BuildInfo.LCUs.LCU
            }
        } else {
            if ($false -eq $XMLstructure) {
                $rc += 1
                $msg = $lswTxt.failTestMissingRecipe
                $res = ${resultSeverity}
            } else {
                $msg = $lswTxt.skipTestMissingRecipe
                $res = 'SUCCESS'
            }
            $detailList.Add($msg)
            $logLines.Add("${msg}|${res}")
        }
        # for example: 9B is overridden by 12B
        $overriddenKBs = @('KB5043080')
        $recipeLCUlist |foreach-object {
            $name = $_.Name
            $_.Msu | foreach-Object {
                [string]$recipeHotFixID = ($_).split('-')[1]
                if (-not ($overriddenKBs -contains $recipeHotFixID)) {
                    #$logLines.Add("Validate that LCU [${name}] as version [${recipeHotFixID}] is installed on host.|INFO.|INFO")
                    $liveHotFixID = $null
                    $liveHotFixID = (Get-HotFix -Id ${recipeHotFixID} -ErrorAction SilentlyContinue).HotFixID
                    if (-not $liveHotFixID -or $liveHotFixID.TrimEnd() -ne $recipeHotFixID) {
                        $rc += 1
                        $msg = $lswTxt.LatestCumulativeUpdateFail -f $name, $localHost, $recipeHotFixID, $liveHotFixID
                        if (-not $liveHotFixID) {
                            $msg += " Installed Hot Fixes: ["
                            $msg += [string]((get-hotfix | Select-Object -Property HotFixID).HotFixID) -join(',')
                            $msg += "]."
                        }
                        $logLines.Add("${msg}|${resultSeverity}")
                    } else {
                        $msg = $lswTxt.LatestCumulativeUpdatePass -f $name, $localHost, $recipeHotFixID, $liveHotFixID
                        $logLines.Add("${msg}|SUCCESS")
                    }
                } else {
                    $msg = $lswTxt.LatestCumulativeUpdateSkip -f $name, $recipeHotFixID, $localHost
                    $logLines.Add("${msg}|INFO")
                }
                $detailList.Add($msg)
            }
        }
        $response = "" | Select-Object -Property rc, details, computername, logLines
        $response.rc = $rc
        $response.details = $detailList
        $response.computername = $localHost
        $response.logLines = $logLines
        return $response
    } #endSB
    $resultSeverity = "CRITICAL"
    $responses = if ($psSession) {
        Invoke-Command -Session $PsSession -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    } else {
        Invoke-Command -ScriptBlock $sb -ArgumentList (,$global:recipeFilePath, $renderXMLvarScriptBloc, $lswTxt, $resultSeverity)
    }
    $splat = @{responses   = $responses
               Name        = "AzStackHci_OSImageRecipeValidation_LCU"
               Title       = "Latest Cumulative Update matches recipe."
               DisplayName = "Latest Cumulative Update matches recipe."
               Severity    = $resultSeverity
               Description = "Validating that the LCUs installed on the host match the LCUs defined in the OS image recipe."}
    return (TestResult @splat)
}

Export-ModuleMember -Function Test-*
# SIG # Begin signature block
# MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDBaqtmH6fnyoU9
# vSSYUbaPmr/UGVIUrLynSXZpAz40FaCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0
# Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz
# NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo
# DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3
# a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF
# HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy
# 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC
# Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj
# L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp
# h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3
# cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X
# dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL
# E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi
# u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1
# sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq
# 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb
# DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/
# V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIK2MwKYcFWFWry+mMgOfuwUv
# 1OdPLB9oKLHHNhOUJRbcMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAJRNNTQKc4/oALTgckeHcKH1tzZlyOnLaYj1dA+v87ywlodCNoTyG7+Y5
# AP9Oejc/x6ZuWsbL6G+RymgK+Ygs+XTmAPlH+6HhBIzV9po5JnL2cT9ZJbiY4/JQ
# 1eUyk+29k7douWqtceSELs6iCgKEVkqhC8MiQ06Rw23i2+ZHYHayuIrjEQl1Pvlg
# Qhx0SYUCEscRLare1XDQ74GHFHkjP2ChFAMl9jVYYyB0rKsblz+cYNyjNsXg1Ojx
# Cw00QWNVcEgYe/Ss384r3sWMDhpCszOPyueHrYFHaq/VZ650XG28hcwFUakLQ1JA
# WSNiWgDXMSfuEScc5A9ZOONCijz7jKGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC
# F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq
# hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCArMr/uunKjVBIWnnuw7uYmah7wX8vGBZ+tSRcEHv9Z7AIGaC5MEqeo
# GBMyMDI1MDYxMDE1NDgzNS4zMDdaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo1NTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAACAdFFWZgQzEJPAAEAAAIBMA0G
# CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0
# MDcyNTE4MzEyMloXDTI1MTAyMjE4MzEyMlowgdMxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w
# ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjU1MUEt
# MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtWrf+HzDu7sk50y5YHhe
# CIJG0uxRSFFcHNek+Td9ZmyJj20EEjaU8JDJu5pWc4pPAsBI38NEAJ1b+KBnlStq
# U8uvXF4qnEShDdi8nPsZZQsTZDKWAgUM2iZTOiWIuZcFs5ZC8/+GlrVLM5h1Y9nf
# Mh5B4DnUQOXMremAT9MkvUhg3uaYgmqLlmYyODmba4lXZBu104SLAFsXOfl/TLhp
# ToT46y7lI9sbI9uq3/Aerh3aPi2knHvEEazilXeooXNLCwdu+Is6o8kQLouUn3Kw
# UQm0b7aUtsv1X/OgPmsOJi6yN3LYWyHISvrNuIrJ4iYNgHdBBumQYK8LjZmQaTKF
# acxhmXJ0q2gzaIfxF2yIwM+V9sQqkHkg/Q+iSDNpMr6mr/OwknOEIjI0g6ZMOymi
# vpChzDNoPz9hkK3gVHZKW7NV8+UBXN4G0aBX69fKUbxBBLyk2cC+PhOoUjkl6UC8
# /c0huqj5xX8m+YVIk81e7t6I+V/E4yXReeZgr0FhYqNpvTjGcaO2WrkP5XmsYS7I
# vMPIf4DCyIJUZaqoBMToAJJHGRe+DPqCHg6bmGPm97MrOWv16/Co6S9cQDkXp9vM
# SSRQWXy4KtJhZfmuDz2vr1jw4NeixwuIDGw1mtV/TdSI+vpLJfUiLl/b9w/tJB92
# BALQT8e1YH8NphdOo1xCwkcCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSwcq9blqLo
# PPiVrym9mFmFWbyyUjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf
# BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww
# bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAOjQAyz0cVztT
# FGqXX5JLRxFK/O/oMe55uDqEC8Vd1gbcM28KBUPgvUIPXm/vdDN2IVBkWHmwCp4A
# Icy4dZtkuUmd0fnu6aT9Mvo1ndsLp2YJcMoFLEt3TtriLaO+i4Grv0ZULtWXUPAW
# /Mn5Scjgn0xZduGPBD/Xs3J7+get9+8ZvBipsg/N7poimYOVsHxLcem7V5XdMNsy
# tTm/uComhM/wgR5KlDYTVNAXBxcSKMeJaiD3V1+HhNkVliMl5VOP+nw5xWF55u9h
# 6eF2G7eBPqT+qSFQ+rQCQdIrN0yG1QN9PJroguK+FJQJdQzdfD3RWVsciBygbYaZ
# lT1cGJI1IyQ74DQ0UBdTpfeGsyrEQ9PI8QyqVLqb2q7LtI6DJMNphYu+jr//0spr
# 1UVvyDPtuRnbGQRNi1COwJcj9OYmlkFgKNeCfbDT7U3uEOvWomekX60Y/m5utRcU
# PVeAPdhkB+DxDaev3J1ywDNdyu911nAVPgRkyKgMK3USLG37EdlatDk8FyuCrx4t
# iHyqHO3wE6xPw32Q8e/vmuQPoBZuX3qUeoFIsyZEenHq2ScMunhcqW32SUVAi5oZ
# 4Z3nf7dAgNau21NEPwgW+2wkrNqDg7Hp8yHyoOKbgEBu6REQbvSfZ5Kh4PV+S2gx
# f2uq6GoYDnlqABOMYwz309ISi0bPMh8wggdxMIIFWaADAgECAhMzAAAAFcXna54C
# 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
# Tjo1NTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUA1+26cR/yH100DiNFGWhuAv2rYBqggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF
# AAIFAOvyffYwIhgPMjAyNTA2MTAwOTQ4MzhaGA8yMDI1MDYxMTA5NDgzOFowdzA9
# BgorBgEEAYRZCgQBMS8wLTAKAgUA6/J99gIBADAKAgEAAgIGlQIB/zAHAgEAAgIU
# CjAKAgUA6/PPdgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow
# CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQAfi+MGnunW
# ybtUkrkwLDZX//z34ylWV1dRMzJyaqBTNnbPDaXxEl3FZpXbHM9+w+jLusEyONCH
# Pwrs1MBQ4pH7XAYyXbUhfq2mhK1ehRuSJLE8mq7MWv0Hz6Ry3VhHAIpuxXTFzy0Z
# nra2oeVxOzWdLwGrnAUyZJ0cBvz4ws5W28cVozT9zNEOj4LnHHFVZoOSEZYoDhzP
# DuGohvJsym+INRclCuWzfMbWyI9U352DlHXIcvkrjaOjN6T6zMB2g2UOYEFnRSGz
# y3TRIVLQCUGAl3BFmoeuM1zwRGod1LUEl1R6nROtHQobv/pmGT1NaNp/nTnHZQg2
# BKCIu1C+ohavMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAIB0UVZmBDMQk8AAQAAAgEwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgx1Pgk0u1
# ITeFX9UG3cHheYczP0aqaKwl3DcveJ60OHYwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCBYa7I6TJQRcmx0HaSTWZdJgowdrl9+Zrr0pIdqHtc4IzCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACAdFFWZgQzEJPAAEA
# AAIBMCIEIGXf2ArpJzaxZZF6XoFIb/jMjZpoRUaPtqukZ0JzHWTvMA0GCSqGSIb3
# DQEBCwUABIICAChFn4Fc9hmC2xzpck53sxKQLOJIgTFbouu/GXE4KHK3hLlFlBsl
# VognB6aVrnDeG+uH3FAJAvpLKaI+r+YT8aHZWdeKWRSx2EkXkwMLh5pTojZVr1OZ
# SV5MQ/2sZgjcPJZZnth2/o9Crd52ryHuUMuxxj9KHXwRx1JpKgPGDVMeajq2OU27
# xBrMJirJ2SqbjZe4vPZ0+Tc1YMwJuVIdb0d70idzcNUgPBa3CbnBxBmqd7jRU4QV
# LA0YPReeBz+am4GtjYBI3kf5BEMkMaoU1GVmb1tKt4FmeSHTXhYXvy9giXKzy9Ai
# 9NILxTQJylSbNtq6q7Iy6IO3i9T9YPCWx2/ZviYcxtEyVSl8jflhCH/FM0eXz0/I
# rBf05gAThLujBq2TH0Jkx+eCyzhg9ex7coduStoaZDGWayqioIRbHXbRCd5HM2oR
# +o0JJ2fIdhiFPMl0EgFpN6CiFdPDHhlKDNFU6JAPuzLPhb5X4IbOuBjx+QlYYI73
# N1r8a9qzIPH59a1IKS7ExkekQ1Ovwa/F5RG2V6WUpFefMUNjezL/xmSBh4qApm1g
# veQFduuaPTrwnAat9eeLbE8ObPRAOW3rsXdCoiOaXDqvYg+F93K9q7iM0qg0KeA2
# amyfccwrIOFxfFnGBzF14RtigzqjG9u/A8OsG++rthrKdQX1Po4DTzyA
# SIG # End signature block