Obs/bin/ObsDep/content/Powershell/Roles/Common/Servicing/Scripts/Modules/AzureStackUpdate.psm1

<###################################################
 # #
 # Copyright (c) Microsoft. All rights reserved. #
 # #
 ##################################################>


<#
This file implements the copying of the Azure Stack Update
payload into the respective product VHDs attached to the VMs.
#>


#region CustomerConfig functions
<#
.SYNOPSIS
 Finds the CustomerConfigFile relative to the current location of the module.
 The modules path is:
 \\su1_fileserver\SU1_ManagementLibrary_1\CloudMedia\Roles\Common\Servicing\Modules
 
 The CustomerConfigFile exists at:
 \\su1_fileserver\SU1_ManagementLibrary_1\CloudMedia
#>

function Get-CustomerConfigFilePath
{
    [CmdletBinding()]
    param ()

    $cloudMediaPath = Join-Path $PSScriptRoot "..\..\..\.."
    Write-Verbose "cloudMediaPath: $cloudMediaPath."

    $customerConfigFilePath = Join-Path $cloudMediaPath "CustomerConfig.xml"
    if (Test-Path $customerConfigFilePath)
    {
        Write-Verbose "Found the CustomerConfig file at: '$customerConfigFilePath'."
        return $customerConfigFilePath
    }
    throw "CustomerConfig file not found at '$customerConfigFilePath'."    
}

<#
.SYNOPSIS
 This function processes CustomerConfig file and returns a hash table with all the parsed values.
 Any data we would need later from CustomerConfig file should all be processed here and add to the hash table.
#>

function Import-CustomerConfigFile
{
    [CmdletBinding()]
    param ()

    $customerConfigFilePath = Get-CustomerConfigFilePath
    $customerConfigXML = [xml] (Get-Content $customerConfigFilePath)
    $fabricRole = $customerConfigXML.CustomerConfiguration.Role.Roles.Role | ? id -eq 'Fabric' 
    $virtualMachines = $fabricRole.Roles.Role | ? id -eq 'VirtualMachines'
    $vmNames = $virtualMachines.Nodes.Node | % Name
   
    $vmToVhdMap = @{} 
    foreach ($vmName in $vmNames)
    {
        $vmNode = $virtualMachines.Nodes.Node | ? Name -eq $vmName
        $vmToVhdMap.$vmName = $vmNode.Vhds.Vhd.Path
    }

    $customerConfigData = @{
        LocalSourceImagePath = $virtualMachines.PrivateInfo.LocalSourceImagePath.Path
        VmToVhdMap = $vmToVhdMap
        LibrarySharePath = $virtualMachines.PrivateInfo.LibraryShare.Path
        InfrastructureSharePath = $virtualMachines.PrivateInfo.InfrastructureShare.Path
    }

    return $customerConfigData
}
#endregion CustomerConfig functions

<#
.SYNOPSIS
 Creates a mapped drive of the network path with the drive name provided, using "net use" command.
 Throws an exception, if the command fails.
#>

function New-MappedDrive
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [string]
        $Drive,

        [Parameter(Mandatory=$true, Position=1)]
        [ValidateScript({ Test-Path $_ })]
        [string]
        $Path
    )
    
    $output = net use $Drive $Path  2>&1 | % ToString | Out-String
    if ($LASTEXITCODE)
    {
        throw "net use '$Drive' '$Path' failed with error code '$LASTEXITCODE'.`r`nThe command output was:`r`n$output."
    }

    # Workaround for a PowerShell issue. The newly mapped drive is not visible sometimes.
    # Enumerating the drives rebuilds the cache and makes the new drive visible.
    Get-PSDrive | Out-Null
}

<#
.SYNOPSIS
 Deletes a mapped drive of the network path with the drive name provided, using "net use" command.
 Throws an exception, if the command fails.
#>

function Remove-MappedDrive
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $Drive
    )

    $output = net use $Drive /d 2>&1 | % ToString | Out-String
    if ($LASTEXITCODE)
    {
        throw "net use '$Drive' /d failed with error code '$LASTEXITCODE'.`r`nThe command output was:`r`n$output."
    }
}

<#
.SYNOPSIS
 Parse the manifest file in the Update and find the VHDs which need to be updated.
 Based on the VHDs find the VMs, whose mounted VHDs need to be updated.
 Then copy the directories/files from the respective nuget directories into the respective paths of mounted VHDs.
 We access the mounted VHDs using the VM name and the d drive. say the VM name is MAS-XRP01, then the path of the mounted
 VHD is \\MAS-XRP01\D$. The content from the update overrides the content in the mounted VHD (if it exists).
#>

function Copy-AzureStackUpdatePayload
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [parameter (Mandatory=$true)]
        [ValidateScript({ Test-Path $_ })]
        [string]
        $ExtractedUpdatePath
    )

    Import-Module (Join-Path $PSScriptRoot "Get-FreeDriveLetter.psm1")
    $Verbose = (Get-PSCallStack).Arguments -join '' -match 'Verbose=True' 
    try 
    {
        # Process the CustomerConfig data to get the vm to vhd map and other information
        $customerConfigData = Import-CustomerConfigFile
        $vmToVhdMap =  $customerConfigData["VmToVhdMap"]
        Write-Verbose "VM to VHD Path after processing CustomerConfig file: $($vmToVhdMap | Out-String)"
        $manifestFile = "manifest.xml"
        $manifestFilePath = Join-Path $ExtractedUpdatePath $manifestFile

        if (-not (Test-Path $manifestFilePath))
        {
            throw "Manifest file $manifestFilePath missing from the package."
        }

        $manifestxml = [xml] (Get-Content $manifestFilePath)
        $dataImages = $manifestxml.ImageBuildManifest.DataImage | Select Name, FileName, NuGetPackage


        foreach ($dataImage in $dataImages)
        {
            $vhdName = [System.IO.Path]::GetFileNameWithoutExtension($dataImage.FileName)
            $vhdExtension = [System.IO.Path]::GetExtension($dataImage.FileName)
            $vhdName = $vhdName+"-Product"+$vhdExtension

            Write-Verbose "VHD being updated: '$vhdName'."
            # TODO : Offline update VHDs...
            # $vhdPath = Join-Path $customerConfigData["localSourceImagePath"] $vhdname

            $vmsToUpdate = $vmToVhdMap.Keys | where { $vmToVhdMap.$_ -contains $vhdname } 
            Write-Verbose "VMs to be updated: '$vmsToUpdate'."

            foreach ($vm in $vmsToUpdate)
            {
                # The product VHDs are guaranteed to be mounted as D drive.
                # If that changes, this part of code needs to change.
                $updatePath = "\\" + $vm +"\D$"
                Write-Verbose "VM being updated: '$vm' and the path being updated: '$updatePath'."

                foreach ($nuget in $dataImage.NuGetPackage)
                {
                    $nugetPath = $nuget.Name
                    if ($nuget.Version)
                    {
                        $nugetPath = $nugetPath +"."+ $nuget.Version
                    }
                    $nugetSource = Join-Path $ExtractedUpdatePath $nugetPath

                    # Due to long path issues with the filenames, direct copy
                    # of the files to the destination fails. Hence we create mapped
                    # drives to both source and destination and use them for the copy.
                    $sourceDrive = $null
                    $destinationDrive = $null

                    if ($nuget.Directory)
                    {
                        foreach ($directory in $nuget.Directory)
                        {
                            $source = Join-Path $nugetSource $directory.SourceSubdirectory

                            $sourceDrive = Get-FreeDriveLetter -MakePath
                            New-MappedDrive -Drive $sourceDrive -Path $source
                            $sourceDrivePath = Join-Path $sourceDrive "\*"

                            if (!(Test-Path $sourceDrivePath))
                            {
                                throw "Unable to access the path '$sourceDrivePath' ($source)."
                            }

                            $destination = Join-Path $updatePath  $directory.Destination

                            $destinationDrive = Get-FreeDriveLetter -MakePath
                            New-MappedDrive -Drive $destinationDrive -Path $destination
                            
                            if ($PSCmdlet.ShouldProcess("Copy-Item -Path $sourceDrivePath ($source) -Destination $destinationDrive ($destination) -Recurse -Force", "Update Directory on $vm."))
                            {
                                Write-Verbose "Copying '$sourceDrivePath' ($source) recursively to '$destinationDrive' ($destination)."
                                Copy-Item -Path $sourceDrivePath -Destination $destinationDrive -Recurse -Force
                            }
                      
                            if ($sourceDrive)
                            {
                                Remove-MappedDrive -Drive $sourceDrive
                                $sourceDrive = $null
                            }

                            if ($destinationDrive)
                            {
                                Remove-MappedDrive -Drive $destinationDrive
                                $destinationDrive = $null
                            }
                        }
                    }
                    elseif ($nuget.File)
                    {
                        foreach ($file in $nuget.File)
                        {

                            $source = Join-Path $nugetSource $file.Source

                            $sourceDirectory = Split-Path $source -Parent
                            $sourceFileName = Split-Path $source -Leaf
                            $sourceDrive = Get-FreeDriveLetter -MakePath
                            New-MappedDrive -Drive $sourceDrive -Path $sourceDirectory
                            

                            $sourcePath = Join-Path $sourceDrive $sourceFileName
                            $destination = Join-Path $updatePath  $directory.Destination

                            $destinationDrive = Get-FreeDriveLetter -MakePath
                            New-MappedDrive -Drive $destinationDrive -Path $destination

                            if ($PSCmdlet.ShouldProcess("Copy-Item -Path $sourceDrivePath -Destination $destinationDrive -Force", "Update file on $vm."))
                            {
                                Write-Verbose "Copying '$sourcePath' ($source) to '$destinationDrive' ($destination)."
                                Copy-Item -path $sourcePath -Destination $destinationDrive -Force
                            }
                            
                            if ($sourceDrive)
                            {
                                Remove-MappedDrive -Drive $sourceDrive
                                $sourceDrive = $null
                            }

                            if ($destinationDrive)
                            {
                                Remove-MappedDrive -Drive $destinationDrive
                                $destinationDrive = $null
                            }
                        }
                    }
                }
            }
        }
    }
    catch
    {
        $ex = $_ | Select-Object *
        throw "Copying the payload for the Azure Stack Update failed.`r`n$ex."
    }
    finally
    {
        if ($sourceDrive)
        {
            Remove-MappedDrive -Drive $sourceDrive
        }

        if ($destinationDrive)
        {
            Remove-MappedDrive -Drive $destinationDrive
        }
    } 
}

<#
.SYNOPSIS
Expands the Azure Stack Update Package.
 
.DESCRIPTION
Given a Package, expand package to directory of same name (without the .zip extension) in the same location as the package.
 
.PARAMETER Path
The full path to the Azure Stack Update Package.
#>

function Expand-AzureStackUpdate
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [parameter (Mandatory=$true)]
        [ValidateScript({ Test-Path $_ })]
        [string]
        $Path
    )

    Import-Module (Join-Path $PSScriptRoot "ZipFiles.psm1")
    Import-Module (Join-Path $PSScriptRoot "Get-FreeDriveLetter.psm1")
    $drive = $null
    try
    {
        $Verbose = (Get-PSCallStack).Arguments -join '' -match 'Verbose=True' 

        $dirName = [System.IO.Path]::GetDirectoryName($Path)
        Write-Verbose "Directory name is: '$dirName'."

        $fileName = [System.IO.Path]::GetFileName($Path)
        Write-Verbose "File name is: '$fileName'."

          $fileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Path)  
        Write-Verbose "File name without extension is: '$fileNameWithoutExtension'." 

        $drive = Get-FreeDriveLetter -MakePath
        New-MappedDrive -Drive $drive -Path $dirName

        $shortPath = Join-Path $drive $fileName
        Write-Verbose "Short path name is: '$shortPath'."
        
        $destShortPath = Join-Path $drive $fileNameWithoutExtension

        Write-Verbose "Expanding the package '$shortPath' to '$destShortPath'."
        Expand-ZipFile -Source $shortPath -Destination $destShortPath -Force

        $destOriginalPath = Join-Path $dirName $fileNameWithoutExtension 

        if (Test-Path $destOriginalPath)
        {
            return $destOriginalPath
        }
        else
        {
            throw "Expanding package '$Path' failed to extract to '$destOriginalPath'."
        }
    }
    catch
    {
        $ex = $_ | Select-Object *
        throw "Expanding the Azure Stack Update failed.`r`n$ex."
    }
    finally
    {
        if ($drive)
        {
            Remove-MappedDrive -Drive $drive
        }
    }
}

Export-ModuleMember -Function Copy-AzureStackUpdatePayload
Export-ModuleMember -Function Expand-AzureStackUpdate
# SIG # Begin signature block
# MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAuyVyaoyfeJofS
# Y1cF1kCAWoNmxRrpCoWlO6LiXQjrvqCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFJCtNZ+O2BgPRNe4dcrXfzA
# UcaUQxKewF47JM8ASh/sMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEANc+tK+bnkLX3ZnYkJEeOP6Ja7eg/Kx28dIE/8NZwc9wrYx2j3VXfW+r7
# dSW3B8ZXmxWQuPz7dOtTmjO9sxOv+mJmNLRAfP96wE1a7p3s0rgFr18yNg6afVsE
# 0mXTRGCcbU89jCZPDkrDbT3cPJIn2vzd4b9lLFDyxhYcY/CsGvzZz1RCTYZL0oIn
# pNSlzg2UbVQ8y5Q+NTTbryK34UvzhJ8lezgSS5VxHAcQeiSzOpcnUVfIAj2LKMe5
# QAwtgBboxqfYu7GdiCx928bgSw0xxZoPeB3zGzraqztV4nmngrqceF3DBY6TEqwu
# Pm3qSmYXnjIugy7qYPxZ/mE5Gj7jBKGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCC857LR+wrOEJLs7C97cvyx/Gz/8VbaOfkepPE77ymy1gIGZjOp275z
# GBMyMDI0MDUxNjE4NDQ0MS41NjVaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAHajtXJWgDREbEAAQAAAdowDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx
# MDEyMTkwNjU5WhcNMjUwMTEwMTkwNjU5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJOQBgh2tVFR1j8jQA4NDf8b
# cVrXSN080CNKPSQo7S57sCnPU0FKF47w2L6qHtwm4EnClF2cruXFp/l7PpMQg25E
# 7X8xDmvxr8BBE6iASAPCfrTebuvAsZWcJYhy7prgCuBf7OidXpgsW1y8p6Vs7sD2
# aup/0uveYxeXlKtsPjMCplHkk0ba+HgLho0J68Kdji3DM2K59wHy9xrtsYK+X9er
# bDGZ2mmX3765aS5Q7/ugDxMVgzyj80yJn6ULnknD9i4kUQxVhqV1dc/DF6UBeuzf
# ukkMed7trzUEZMRyla7qhvwUeQlgzCQhpZjz+zsQgpXlPczvGd0iqr7lACwfVGog
# 5plIzdExvt1TA8Jmef819aTKwH1IVEIwYLA6uvS8kRdA6RxvMcb//ulNjIuGceyy
# kMAXEynVrLG9VvK4rfrCsGL3j30Lmidug+owrcCjQagYmrGk1hBykXilo9YB8Qyy
# 5Q1KhGuH65V3zFy8a0kwbKBRs8VR4HtoPYw9z1DdcJfZBO2dhzX3yAMipCGm6Smv
# mvavRsXhy805jiApDyN+s0/b7os2z8iRWGJk6M9uuT2493gFV/9JLGg5YJJCJXI+
# yxkO/OXnZJsuGt0+zWLdHS4XIXBG17oPu5KsFfRTHREloR2dI6GwaaxIyDySHYOt
# vIydla7u4lfnfCjY/qKTAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUoXyNyVE9ZhOV
# izEUVwhNgL8PX0UwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBALmDVdTtuI0jAEt4
# 1O2OM8CU237TGMyhrGr7FzKCEFaXxtoqk/IObQriq1caHVh2vyuQ24nz3TdOBv7r
# cs/qnPjOxnXFLyZPeaWLsNuARVmUViyVYXjXYB5DwzaWZgScY8GKL7yGjyWrh78W
# JUgh7rE1+5VD5h0/6rs9dBRqAzI9fhZz7spsjt8vnx50WExbBSSH7rfabHendpeq
# bTmW/RfcaT+GFIsT+g2ej7wRKIq/QhnsoF8mpFNPHV1q/WK/rF/ChovkhJMDvlqt
# ETWi97GolOSKamZC9bYgcPKfz28ed25WJy10VtQ9P5+C/2dOfDaz1RmeOb27Kbeg
# ha0SfPcriTfORVvqPDSa3n9N7dhTY7+49I8evoad9hdZ8CfIOPftwt3xTX2RhMZJ
# CVoFlabHcvfb84raFM6cz5EYk+x1aVEiXtgK6R0xn1wjMXHf0AWlSjqRkzvSnRKz
# FsZwEl74VahlKVhI+Ci9RT9+6Gc0xWzJ7zQIUFE3Jiix5+7KL8ArHfBY9UFLz4sn
# boJ7Qip3IADbkU4ZL0iQ8j8Ixra7aSYfToUefmct3dM69ff4Eeh2Kh9NsKiiph58
# 9Ap/xS1jESlrfjL/g/ZboaS5d9a2fA598mubDvLD5x5PP37700vm/Y+PIhmp2fTv
# uS2sndeZBmyTqcUNHRNmCk+njV3nMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAQqIfIYljHUbNoY0/wjhXRn/sSA2ggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOnwnAIwIhgPMjAyNDA1MTYyMjUxNDZaGA8yMDI0MDUxNzIyNTE0NlowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA6fCcAgIBADAHAgEAAgIRpjAHAgEAAgIRbzAKAgUA
# 6fHtggIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAI4pnLR+QYde5f+qZ1u4
# h+ow3MB7jtruhTIST+f8uLk1SBLm0niiqCuxdSUDAch40loud3DfBZ54rDiV7Hu2
# mhY8FHfZXI273lSdVTsgc2/aGRpFoT66b7nNxxQdgs/ULZYKHpGw6pPOhPyWkG6B
# S8tSffAJU3wB7PyOmV+K12zQMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAHajtXJWgDREbEAAQAAAdowDQYJYIZIAWUDBAIB
# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# IgQgzS19CSHEpLTx099QT3zN8IfnJ8ZQ9ecCa2dTmfDup5wwgfoGCyqGSIb3DQEJ
# EAIvMYHqMIHnMIHkMIG9BCAipaNpYsDvnqTe95Dj1C09020I5ljibrW/ndICOxg9
# xjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB2o7V
# yVoA0RGxAAEAAAHaMCIEIMUK/484Qzmn64FWwcwP/fhtuvZmZGOkRd8YJwNgaiE8
# MA0GCSqGSIb3DQEBCwUABIICAB0c9bVLm7jd6oSEuvv8yKfNYEU5QCLjM1Gaxt7m
# d7E1Pk5QYAFzj2tDe0e1PCoDQI6RGFYShJTg7TnuduM66m4vfXLi3bTcD7+0XFVf
# zSI2nYRXmugH2+b58v2cd9+8bQEZbQIfY7uj9iIVbW2Xxrk7nCwBkRMFuv3OKfY0
# odGCDZm20I4LndZ6eCw/x4WfJPMMrhpU9h8NaslUgAroeMCZ1/osw4UcrpsJkNM9
# FyZ8adx9goEygWgIHh/PucCotORk/d6Vf5InDcinUHOih8r4flYHCR5L3V2YSMks
# Qgjo8c20HiGlI7v/s90iHCF0EdA5tVJLEketnjGnQ/2cqI/fVhG9kNEFdwpdNy9d
# 8aKPI70vcNF87MiAsFij9COvj6THt1wmQuPPk7KpI/Q0haNEoDfKu0CByCp3Zuqg
# dWJ/46DC2EY0uZlkHuWz0xOUXOKEjzSUHNT4aNPjgpjARx0SYtmVcgBDnjycEIZD
# QDWCbSjBUoT34D/2IEJEtQI6MPMF+k6ZgBo5dInrkQltL4Ykww+kJlbs1oyOzgOj
# cILmpyBDH67c4gpDI1bdXroocyl0pyhlIsb92eKX2hk/T2B5lRDklx8cm/hlDkiO
# RNqp2IIZSfFSjfzq+KgVs7xS02yacqHmiYcoURqS3/pEoRGlcKqrAK3QAkGBIzDj
# vCKa
# SIG # End signature block