Artifacts/Download-Artifacts.ps1

<#
 .Synopsis
  Download Artifacts
 .Description
  Download artifacts from artifacts storage
 .Parameter artifactUrl
  Url for artifact to use.
 .Parameter includePlatform
  Add this switch to include the platform artifact in the download
 .Parameter force
  Add this switch to force download artifacts even though they already exists
 .Parameter forceRedirection
  Add this switch to force download redirection artifacts even though they already exists
 .Parameter basePath
  Load the artifacts into a file structure below this path. (default is c:\bcartifacts.cache)
 .Parameter timeout
  Timeout in seconds for each file download.
 .Example
  $artifactPaths = Download-Artifacts -artifactUrl $artifactUrl -includePlatform
  $appArtifactPath = $artifactPaths[0]
  $platformArtifactPath = $artifactPaths[1]
 .Example
  $appArtifactPath = Download-Artifacts -artifactUrl $artifactUrl
#>

function Download-Artifacts {
    Param (
        [Parameter(Mandatory=$true)]
        [string] $artifactUrl,
        [switch] $includePlatform,
        [switch] $force,
        [switch] $forceRedirection,
        [string] $basePath = "",
        [int]    $timeout = $bccontainerHelperConfig.artifactDownloadTimeout
    )

    function DownloadPackage {
        Param(
            [string] $artifactUrl,
            [string] $destinationPath,
            [int]    $timeout = 300
        )

        $tmpFolder = Join-Path ([System.IO.Path]::GetDirectoryName($destinationPath)) ([System.IO.Path]::GetRandomFileName())
        $zipFile = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString()).zip"
        $retry = $false
        do {
            Download-File -sourceUrl $artifactUrl -destinationFile $zipFile -timeout $timeout
            Write-Host "Unpacking artifact to tmp folder " -NoNewline
            try {
                Expand-7zipArchive -Path $zipFile -DestinationPath $tmpFolder -use7zipIfAvailable:(!$retry)
                $retry = $false
            }
            catch {
                Remove-Item -path $zipFile -force
                if (Test-Path $tmpFolder) {
                    Remove-Item $tmpFolder -Recurse -Force
                }
                if ($retry) {
                    throw "Error trying to unpack artifact, downloaded package is corrupt"
                }
                else {
                    if ($artifactUrl -match '^https:\/\/(.+)\.azureedge\.net\/(.*)$') {
                        Write-Host "Error unpacking platform artifact downloaded from CDN, retrying download from direct download URL"
                        $artifactUrl = "https://$($Matches[1]).blob.core.windows.net/$($Matches[2])"
                        $retry = $true
                    }
                }
            }
        } while ($retry)
        $result = $false
        try {
            $attempts = 0
            while (!(Test-Path $destinationPath)) {
                try {
                    Rename-Item -Path $tmpFolder -NewName ([System.IO.Path]::GetFileName($destinationPath)) -Force
                    $result = $true
                }
                catch {
                    if ($attempts++ -eq 5) {
                        throw
                    }
                    $waittime = 5*$attempts
                    Write-Host "Could not rename '$tmpFolder' retrying in $waittime seconds."
                    Start-Sleep -Seconds $waittime
                    Write-Host "Retrying..."
                }
            }
        }
        finally {
            if (Test-Path $zipFile) {
                Remove-Item -path $zipFile -force
            }
            if (Test-Path $tmpFolder) {
                Remove-Item $tmpFolder -Recurse -Force
            }
        }
        return $result
    }


$telemetryScope = InitTelemetryScope -name $MyInvocation.InvocationName -parameterValues $PSBoundParameters -includeParameters @("artifactUrl","includePlatform")
try {

    if ($basePath -eq "") {
        $basePath = $bcContainerHelperConfig.bcartifactsCacheFolder
    }

    if (-not (Test-Path $basePath)) {
        New-Item $basePath -ItemType Directory | Out-Null
    }

    $appMutexName = "dl-$($artifactUrl.Split('?')[0].Substring(8).Replace('/','_'))"
    $appMutex = New-Object System.Threading.Mutex($false, $appMutexName)
    try {
        try {
            if (!$appMutex.WaitOne(1000)) {
                Write-Host "Waiting for other process downloading artifact '$($artifactUrl.Split('?')[0])'"
                $appMutex.WaitOne() | Out-Null
                Write-Host "Other process completed download"
            }
        }
        catch [System.Threading.AbandonedMutexException] {
           Write-Host "Other process terminated abnormally"
        }
        do {
            $redir = $false
            $appUri = [Uri]::new($artifactUrl)
    
            $appArtifactPath = Join-Path $basePath $appUri.AbsolutePath
            $appManifestPath = Join-Path $appArtifactPath "manifest.json"
            $exists = Test-Path $appArtifactPath

            # if the force switch is set, we remove the existing artifact and redownload it
            if ($exists -and $force) {
                Remove-Item $appArtifactPath -Recurse -Force
                $exists = $false
            }

            # If the artifact exists and includePlatform or forceRedirection is set, we also need the manifest file
            if ($exists -and ($includePlatform -or $forceRedirection) -and (-not (Test-Path $appManifestPath))) {
                Remove-Item $appArtifactPath -Recurse -Force
                $exists = $false
            }

            # Test if the manifest file exists and is not corrupt
            if ($exists -and (Test-Path $appManifestPath)) {
                try {
                    Get-Content $appManifestPath | ConvertFrom-Json | Out-Null
                }
                catch {
                    Write-Host "ERROR: Manifest file is corrupt, removing $appArtifactPath and redownloading"
                    Remove-Item $appArtifactPath -Recurse -Force
                    $exists = $false
                }
            }

            if ($exists -and $forceRedirection) {
                $appManifest = Get-Content $appManifestPath | ConvertFrom-Json
                if ($appManifest.PSObject.Properties.name -eq "applicationUrl") {
                    # redirect artifacts are always downloaded
                    Remove-Item $appArtifactPath -Recurse -Force
                    $exists = $false
                }
            }

            if (-not $exists) {
                Write-Host "Downloading artifact $($appUri.AbsolutePath)"
                DownloadPackage -artifactUrl $artifactUrl -destinationPath $appArtifactPath -timeout $timeout | Out-Null
            }
            try { [System.IO.File]::WriteAllText((Join-Path $appArtifactPath 'lastused'), "$([datetime]::UtcNow.Ticks)") } catch {}

            if (Test-Path $appManifestPath) {
                $appManifest = Get-Content $appManifestPath | ConvertFrom-Json

                # Patch wrong license file in ONPREM AU version 20.5.45456.45889
                if ($artifactUrl -like '*/onprem/20.5.45456.45889/au') {
                    Write-Host "INFO: Patching wrong license file in ONPREM AU version 20.5.45456.45889"
                    Download-File -sourceUrl 'https://bcartifacts-exdbf9fwegejdqak.b02.azurefd.net/prerequisites/21demolicense/au/3048953.flf' -destinationFile (Join-Path $appArtifactPath 'database/Cronus.flf')
                }
                
                $cuFixMapping = @{
                    '11.0.48794.0' = 'cu53';
                    '11.0.48962.0' = 'cu54';
                    '11.0.49061.0' = 'cu55';
                    '11.0.49175.0' = 'cu56';
                    '11.0.49240.0' = 'cu57';
                    '11.0.49345.0' = 'cu58';
                    '11.0.49497.0' = 'cu59';
                    '11.0.49618.0' = 'cu60';
                }
                if ($appManifest.version -in $cuFixMapping.Keys) {
                    $appManifest.cu = $cuFixMapping[$appManifest.version]
                    $appManifest | ConvertTo-Json | Set-Content -Path $appManifestPath
                }
        
                if ($appManifest.PSObject.Properties.name -eq "applicationUrl") {
                    $redir = $true
                    $artifactUrl = $appManifest.ApplicationUrl
                    if ($artifactUrl -notlike 'https://*') {
                        $artifactUrl = "https://$($appUri.Host)/$artifactUrl$($appUri.Query)"
                    }
                }
            }
    
        } while ($redir)
    
        $appArtifactPath
    
        if ($includePlatform) {
            if ($appManifest.PSObject.Properties.name -eq "platformUrl") {
                $platformUrl = $appManifest.platformUrl
            }
            else {
                $platformUrl = "$($appUri.AbsolutePath.Substring(0,$appUri.AbsolutePath.LastIndexOf('/')))/platform".TrimStart('/')
            }
        
            if ($platformUrl -notlike 'https://*') {
                $platformUrl = "https://$($appUri.Host.TrimEnd('/'))/$platformUrl$($appUri.Query)"
            }
            $platformUri = [Uri]::new($platformUrl)
    
            $PlatformMutexName = "dl-$($platformUrl.Split('?')[0].Substring(8).Replace('/','_'))"
            $PlatformMutex = New-Object System.Threading.Mutex($false, $PlatformMutexName)
            try {
                try {
                    if (!$PlatformMutex.WaitOne(1000)) {
                        Write-Host "Waiting for other process downloading platform artifact '$($platformUrl.Split('?')[0])'"
                        $PlatformMutex.WaitOne() | Out-Null
                        Write-Host "Other process completed download"
                    }
                }
                catch [System.Threading.AbandonedMutexException] {
                   Write-Host "Other process terminated abnormally"
                }
    
                $platformArtifactPath = Join-Path $basePath $platformUri.AbsolutePath
                $exists = Test-Path $platformArtifactPath
                if ($exists -and $force) {
                    Remove-Item $platformArtifactPath -Recurse -Force
                    $exists = $false
                }
                if (-not $exists) {
                    Write-Host "Downloading platform artifact $($platformUri.AbsolutePath)"
                    $downloadprereqs = DownLoadPackage -ArtifactUrl $platformUrl -DestinationPath $platformArtifactPath -timeout $timeout
                    if ($downloadprereqs) {
                        $prerequisiteComponentsFile = Join-Path $platformArtifactPath "Prerequisite Components.json"
                        if (Test-Path $prerequisiteComponentsFile) {
                            $prerequisiteComponents = Get-Content $prerequisiteComponentsFile | ConvertFrom-Json
                            Write-Host "Downloading Prerequisite Components"
                            $prerequisiteComponents.PSObject.Properties | % {
                                $skip = ($_.Name -eq "Prerequisite Components\Open XML SDK 2.5 for Microsoft Office\OpenXMLSDKv25.msi") -and (([System.Version]$appManifest.Version).Major -ge 21)
                                $path = Join-Path $platformArtifactPath $_.Name
                                if ((-not $skip) -and (-not (Test-Path $path))) {
                                    $dirName = [System.IO.Path]::GetDirectoryName($path)
                                    if (-not (Test-Path $dirName)) {
                                        New-Item -Path $dirName -ItemType Directory | Out-Null
                                    }
                                    $url = $_.Value
                                    Download-File -sourceUrl $url -destinationFile $path -timeout $timeout
                                }
                            }
                            $dotnetCoreFolder = Join-Path $platformArtifactPath "Prerequisite Components\DotNetCore"
                            if (!(Test-Path $dotnetCoreFolder)) {
                                New-Item $dotnetCoreFolder -ItemType Directory | Out-Null
                                Download-File -sourceUrl "https://go.microsoft.com/fwlink/?LinkID=844461" -destinationFile (Join-Path $dotnetCoreFolder "DotNetCore.1.0.4_1.1.1-WindowsHosting.exe") -timeout $timeout
                            }
                        }
                        # Patch potential wrong version of Newtonsoft.Json.dll
                        $newtonSoftDllPath = Join-Path $platformArtifactPath 'ServiceTier\program files\Microsoft Dynamics NAV\210\Service\Newtonsoft.Json.dll'
                        if (Test-Path $newtonSoftDllPath) {
                            'Applications\testframework\TestRunner\Internal\Newtonsoft.Json.dll','Test Assemblies\Newtonsoft.Json.dll' | ForEach-Object {
                                $dstFile = Join-Path $platformArtifactPath $_
                                $file = Get-item -Path $dstFile -ErrorAction SilentlyContinue
                                if ($file -and $file.Length -eq 686000) {
                                    Write-Host "INFO: Patching wrong version of Newtonsoft.Json.dll in $dstFile"
                                    Copy-Item -Path $newtonSoftDllPath -Destination $dstFile -Force 
                                }
                            }
                        }
                        $ad1DLL = Join-Path $platformArtifactPath 'Applications\testframework\TestRunner\Internal\Microsoft.IdentityModel.Clients.ActiveDirectory.dll'
                        $ad2DLL = Join-Path $platformArtifactPath 'ServiceTier\*\Microsoft Dynamics NAV\*\Service\Management\Microsoft.IdentityModel.Clients.ActiveDirectory.dll'
                        if ((Test-Path $ad1DLL) -and (Test-Path $ad2DLL)) {
                            if ((Get-Item $ad1DLL).Length -ne (Get-Item $ad2DLL).Length) {
                                Write-Host "INFO: Patching wrong version of Microsoft.IdentityModel.Clients.ActiveDirectory.dll in $ad1DLL"
                            }
                            Copy-Item -Path $ad2DLL -Destination $ad1DLL -Force
                        } 
                    }
                }
                try { [System.IO.File]::WriteAllText((Join-Path $platformArtifactPath 'lastused'), "$([datetime]::UtcNow.Ticks)") } catch {}
                $platformArtifactPath
            }
            finally {
                $platformMutex.ReleaseMutex()
            }
        }
    }
    finally {
        $appMutex.ReleaseMutex()
    }
}
catch {
    TrackException -telemetryScope $telemetryScope -errorRecord $_
    throw
}
finally {
    TrackTrace -telemetryScope $telemetryScope
}
}
Export-ModuleMember -Function Download-Artifacts
# SIG # Begin signature block
# MIIoCgYJKoZIhvcNAQcCoIIn+zCCJ/cCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDiZkP4tj9wQLvo
# PyJDTBRACqEXNX3rkFNefeBNLgwrD6CCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z
# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy
# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi
# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ
# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ
# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe
# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk
# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj
# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS
# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns
# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO
# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71
# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9
# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk
# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm
# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn
# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo
# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGeowghnmAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB
# BQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIE
# IG1kQotPH2lzV5CRk7H7xweruHsnBFk7W4rOqCawXBU5MEIGCisGAQQBgjcCAQwx
# NDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20wDQYJKoZIhvcNAQEBBQAEggEALjr6VDGihdERa36S3NWwCXv4nvi0jice
# k93Q/ixGY6KF6dww1lLl1w2VJNdew35gJC+AoMNwa8ivjfVNMeEJeAQhEO/cjQ8b
# vNvxK/uYwzUJgfht6+wu3aKFnzpxz4B6jVKGRPE8dChu4zzRZ/1Ot5GrhRkJotl3
# J2qVDA/JUm4dpvXlU7CAZTEzPB7O9dAS+sGVRFUsXoOa8LC+uA+nL+yo6U7mAfbk
# 1x1+yr02ffT+SwawgTi93bfBF+AMiMDRg3adrfFkTrkB49Zu/kNs5KvSHjeWU20k
# gBAqSrf3VMHe2dvHSZBS9Op5ft7V3RpAnR6/yubgYr32hDmWJn9vnaGCF5IwgheO
# BgorBgEEAYI3AwMBMYIXfjCCF3oGCSqGSIb3DQEHAqCCF2swghdnAgEDMQ8wDQYJ
# YIZIAWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYB
# BAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCA6gtL4OFIt+xeMZnYZXuA4ES0wCzgB
# +xPb+HQpVZvwpQIGaSTl5msWGBIyMDI1MTIwODEyNDcyOC41MVowBIACAfSggdGk
# gc4wgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNV
# BAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGll
# bGQgVFNTIEVTTjo5MjAwLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgU2VydmljZaCCEekwggcgMIIFCKADAgECAhMzAAACCQgH4PlcjOZV
# AAEAAAIJMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy
# MDEwMB4XDTI1MDEzMDE5NDI1NVoXDTI2MDQyMjE5NDI1NVowgcsxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBB
# bWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo5MjAw
# LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMKUSjD3Lgzd/VL3PXG0
# 0QRPBYvW8SKLDSgPtJcR2/ix0/TGxXKJ2/ojauYSXw9iz0txmPOxY4cjt1CREvbw
# Y/cJdy9jRmrqdawdjZBqYJkUsXYiVEfoEfHZGQ3tlEMqazsE6jggYFGUIyRS/033
# +3A7MCSlY2wzdv8FDFzCFWCxCq1Dw0Q9S6JH4ZXmt1AdRPimOKFlOQnCtqWLPRlt
# ilRMfk6SLd3cGnH2qI+uIHqGE18Y+OXQ8inbcPnv2ulbpmY+o9PyPXYpfvJJnA27
# Gzc9i8X/DXcaxFeTMhsjIsoQ/OP2XOaasXbCO+9SvH0BnDsYtJeTbwOfVdJ/raFu
# QW5QbA8UuncRtGohWYFnjbBzPmZIggLLdCz+HCERiFSd2cAGA2kPlq8As5XuxR8m
# scNldfp/2CBuMgDqPaeFIBIiqXwXkuwoHDRE+0O7LePYI/G1OZmjNssrxMy3EOIw
# KDFOl+DmJhS/KFXhqpoMvBEGygFGE7/6HDJsqdjBfEp546uw7BAudo4TkGYUlhYE
# 4XPd3zwsEr1BEGB0QfkItWHvCSAwh6H3pwfn4fTES+aDq3u7O2VdfZJXvF1Rg/ED
# e+ONXcSRXtptIcPkcdBlOt3cWqwP9U5gAJRUE+vEX6RStkZfFgidlOmtgxgSrpQg
# bUNPikJU/0NxoIsYg5gQnWDTAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUSYvo0cRd
# OOW98C9AzbV3MxaTytIwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIw
# XwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w
# cy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3Js
# MGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
# JTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEF
# BQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAFxefG84PCTi
# H+NtQGycWUW2tK4EFlvvBJl9rmUpExM182WZoALht3tajQjmEzGwQlTK6kfCHiQP
# mqRFlzMhzSMgAFBDXENQFr5ZPGun9QCoLXuKMUJ49kphWM2sd/8GaPPsVo4jjWTG
# 55GHAs0hxDaCYGoNHlbhNLaG1EljJkCzuN8mZsO1NxQ4yESXU5aXH8We9xBui3lU
# /NpTCJPo2J7yXo9mOhCy7GJqy5ICbEohB2wecnlCiSrB3KpeLUVkO0RNW9td8Oyh
# /NO1rh6fap/jyHMRnBS9uTPmya3z3SdUAruTPZyuvM3eGmd8W5+2n+tctZO/E9Bx
# 9ZeIS4hR3YaDt5HxC3Iq0kNTz48PAQKTOhomNsYIqrH0RKAUnPOtc3CGFfpFzyDY
# RT/7reaapZ4IX+Qk4WDZ4nDtq79psRKCrcRrPIPVWUv4dpf4wEcbNCYe286bdCXj
# BVM3darxfxsJHryqIXmsVqybhHEXrNqNl5IcL+pLnffr/howOqxXo7zpGU88JgYk
# 4+1/Yxso7tckl4v9RA3Rze6LHlExOjrp1sBPE9QUQbk+Hg8fMaNRsQ7sPfku4QGK
# IbxiuUxE6QaXd8FCX1tZuDD0IhRBvCrlxNoTGV8Skx1KjJ0miVRNAPkQsobPVMlq
# FOJ13bTCXCLkGTfpcibOwfhizXmJdF8CMIIHcTCCBVmgAwIBAgITMwAAABXF52ue
# AptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgz
# MjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxO
# dcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQ
# GOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq
# /XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVW
# Te/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7
# mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De
# +JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM
# 9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEz
# OUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2
# ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqv
# UAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q
# 4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcV
# AgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXS
# ZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcC
# ARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRv
# cnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1
# AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA
# FNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8y
# MDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8
# qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7p
# Zmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2C
# DPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BA
# ljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJ
# eBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1
# MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz
# 138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1
# V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLB
# gqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0l
# lOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFx
# BmoQtB1VM1izoXBm8qGCA0wwggI0AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wi
# IwoBATAHBgUrDgMCGgMVAHzvras9NB3sicMJB1vWSAUpCQJEoIGDMIGApH4wfDEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj
# cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDs4S8D
# MCIYDzIwMjUxMjA4MTEwMzMxWhgPMjAyNTEyMDkxMTAzMzFaMHMwOQYKKwYBBAGE
# WQoEATErMCkwCgIFAOzhLwMCAQAwBgIBAAIBKjAHAgEAAgITEDAKAgUA7OKAgwIB
# ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQow
# CAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQA6yeqrP7fauTlaczjAonOh+JW6
# Xx+4WQ+FlTT7ub9r9J+18EzxPMiL7WFEzv2JYkojYcTXUIsFwBrm27NJmRwtr414
# kWO2ICp3rEjJMSYWJSHj4OuxK/xRL0iXfIM3CYJ8i02vgAwoX7Ux1Y/gGWkL8s6U
# tK8Bu5vHALdBgD3R/IJsYq/n4dx59zTt4TWLZwM1f1WHCYaJ9e3zUK0bRN6OwTxc
# GGI5yGJrjXpmi7l+oQcWlOJ6+zEK5dnjf0LKRn9lR36mAwDRDqLgKyzs2rR0dvja
# 5jOloVI9LnaIh4m4TICnt9PegCt5QzImiwH4sOPA9TcHNWUIu8neyPmupMy5MYIE
# DTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIJ
# CAfg+VyM5lUAAQAAAgkwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzEN
# BgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgFsce89b1wK+0awow/3JkuIxc
# EupozhS4WQ/2W7nNP+kwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBoGywe
# yL30Ai5lDlGYY3VKivG4pHwemVVXaE4zcIUTPjCBmDCBgKR+MHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACCQgH4PlcjOZVAAEAAAIJMCIEICs66vMJ
# 4s1PV6HLZat88cbsEF0D+GtbBU7Uh/JNLqNHMA0GCSqGSIb3DQEBCwUABIICAHob
# 4wjrP4M6vhD11Z3875hGQXTCBrnHgVCHfBreRG5lfsKIsKie2YW2xlqMRloKU4Aw
# AVd/AN0/005XIhD01AxziAhhIGcglfMXUi9/c2q8sY+rSpkov+xXYwyeS2rAnYVO
# Q3vnsabVVLRJAp59+MU9dQJd4VaMr0f2jLg7Pk6yMPUSVxJ4BFHHb4ILn9CGSzOl
# Lj65M+T7hZ1WsqxYc2r0GSQbT/s/tfA4S6R1dMjJEY9h5qZWL53B4H0RYFp0lIZp
# LqOfwIVIvA9jgl3QTbATs+LVnG9o7ft+MOhaSKNMpG432FRE1UTJoP2IWnnMKe9i
# +QiJxpbSOKUoiMtq7/zenMuGrUf00kXtDdkW6YprDbHk3S6OHaNRyLzJilTy5Lmw
# 3YVnTgekP31IZQ5A5e0kI/ZgfVSnjCexvaf6KpGhf8x/RVA5XiPN1OrJXunmON6O
# KHb5E3k8zG8yl/L1BW2H619zbor3lHcXWZsHZE9lImY7HyCqdJGjRLMxUROnN6I3
# bWcNTQ2EtrMpVLV+w14Ni1EolqvUXvSPMcZYSIfVQF62hUO2gqERLmHGpAMx2fqz
# F23UXZMrCM6gvPwpgDsZ7SdSl+1XJsWuAYmBwtFrgDkMcfzGm2Vdm/oNMHxedGh5
# 1SRjaK2I/ynexduxMHunph7GMS5XBpIz+x3tGjrW
# SIG # End signature block