ShieldedVMDeployment/ShieldedVMDeployment.psm1

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

function New-ShieldedVM {
    
    <#
    .SYNOPSIS
    Creates a new shielded virtual machine.
    
    .DESCRIPTION
    Creates a new shielded virtual machine using a prepared template disk and shielding data file.
    The virtual machine will go through shielded VM provisioning, which re-encrypts the OS volume
    and may take several minutes to complete. Use the -Wait parameter to observe the progress.
    
    .PARAMETER Name
    Name of the new virtual machine.
    
    .PARAMETER TemplateDiskPath
    Location of the template disk that has been prepared for use with shielded virtual machines.
    
    .PARAMETER ShieldingDataFilePath
    Location of the shielding data file used to configure the shielded VM.
    
    .PARAMETER SwitchName
    Name of network switch to which the VM should be connected.
    If no switch name is provided, and there is only one switch configured in Hyper-V, the VM will be connected to that switch.
    
    .PARAMETER Linux
    Indicates that the VM will run a Linux-based operating system.
    
    .PARAMETER MemoryStartupBytes
    Amount of memory to allocate to the VM (defaults to 2GB).
    
    .PARAMETER CpuCount
    Number of virtual processors to allocate to the VM (defaults to 2).
    
    .PARAMETER VMPath
    Location to store the resulting VM.
    If omitted, the default VM path configured in Hyper-V will be used for VM storage.
    
    .PARAMETER SpecializationValues
    Key-value pairs to replace in the shielding data answer file.
    @ComputerName@ is automatically set using the value of the -Name parameter, but can be overridden if desired.
    
    .PARAMETER Wait
    Shows the progress of the provisioning job and waits to return control until the VM is provisioned.
    
    .EXAMPLE
    New-ShieldedVM -Name 'CorpDC01' -TemplateDiskPath '.\WS2016-Template.vhdx' -ShieldingDataFilePath '.\DC.pdk' -SwitchName 'corpnet'

    Creates a new Windows shielded VM called "CorpDC01" using the specified templtae disk and shielding data file.

    .EXAMPLE
    New-ShieldedVM -Name 'ExampleVM' -TemplateDiskPath '.\template.vhdx' -ShieldingDataFilePath '.\myvm.pdk' -SpecializationValues @{ '@ComputerName@' = 'myVM01' }
    
    Creates a new Windows shielded VM with a custom replacement for the @ComputerName@ property in the shielding data answer file.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter(Mandatory=$true)]
        [string]
        $TemplateDiskPath,

        [Parameter(Mandatory=$true)]
        [string]
        $ShieldingDataFilePath,
        
        [string]
        $SwitchName,

        [switch]
        $Linux,

        [ValidateRange(1GB, 1TB)]
        [Int64]
        $MemoryStartupBytes = 2GB,

        [ValidateRange(1,240)]
        [int]
        $CpuCount = 2,

        [string]
        $VMPath,

        [System.Collections.IDictionary]
        $SpecializationValues,

        [switch]
        $Wait
    )

    ## Parameter validation
    # Check for invalid names (relating to file paths assumed later)
    if ($Name -match '"|<|>|\||\\|/|\*|\?') {
        throw [System.ArgumentException] "Name cannot contain the following characters: `", ?, *, |, \, /, <, >"
    }

    # Ensure template disk exists
    $TemplateDiskPath = Microsoft.PowerShell.Management\Resolve-Path $TemplateDiskPath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path
    if (-not (Microsoft.PowerShell.Management\Test-Path $TemplateDiskPath -PathType Leaf) -or $TemplateDiskPath -notlike "*.vhdx") {
        throw [System.IO.FileNotFoundException] "The template disk path is invalid."
    }

    # Ensure shielding data file exists
    $ShieldingDataFilePath = Microsoft.PowerShell.Management\Resolve-Path $ShieldingDataFilePath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path
    if (-not (Microsoft.PowerShell.Management\Test-Path $ShieldingDataFilePath -PathType Leaf) -or $ShieldingDataFilePath -notlike "*.pdk") {
        throw [System.IO.FileNotFoundException] "The shielding data file path is invalid."
    }

    # Ensure the switch name is valid
    if ($SwitchName -and -not (Hyper-V\Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue)) {
        throw [System.ArgumentException] ("A networking switch with the name '{0}' could not be found on the host." -f $SwitchName)
    }
    elseif (-not $SwitchName) {
        $switches = Hyper-V\Get-VMSwitch
        if ($switches.Count -eq 1)
        {
            Microsoft.PowerShell.Utility\Write-Verbose ("No switch name was provided. The VM will be connected to the '{0}' switch." -f $switches.Name)
            $SwitchName = $switches.Name
        }
        else {
            throw [System.ArgumentException] ("More than one VM switch was found. Re-run the command and specify one of the following switch names to the -SwitchName parameter: {0}" -f `
                [string]::Join(', ', $switches.Name))
        }
    }

    # Ensure the VM path is valid
    if ($VMPath) {
        $VMPath = Microsoft.PowerShell.Management\Resolve-Path $VMPath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path
        if (-not (Microsoft.PowerShell.Management\Test-Path $VMPath -PathType Container)) {
            throw [System.IO.DirectoryNotFoundException] "The VM path is not a valid directory."
        }

        $VhdDirectory = Microsoft.PowerShell.Management\Join-Path $VMPath "Virtual Hard Disks"
        if (-not (Microsoft.PowerShell.Management\Test-Path $VhdDirectory)) {
            Microsoft.PowerShell.Utility\Write-Verbose ("Creating directory for VHD at '{0}'" -f $VhdDirectory)
            $null = Microsoft.PowerShell.Management\New-Item $VhdDirectory -ItemType Directory
        }
    }
    else {
        $vmroot = (Hyper-V\Get-VMHost).VirtualMachinePath
        $VMPath = Microsoft.PowerShell.Management\Join-Path $vmroot $Name
        
        if ((Microsoft.PowerShell.Management\Test-Path $VMPath)) {
            foreach ($i in 1..999) {
                $samplePath = "{0}-{1:D3}" -f $Name, $i
                $VMPath = Microsoft.PowerShell.Management\Join-Path $vmroot $samplePath
                if (-not (Microsoft.PowerShell.Management\Test-Path $VMPath)) {
                    break
                }
            }
        }

        Microsoft.PowerShell.Utility\Write-Verbose ("Creating a VM directory at '{0}'" -f $VMPath)
        $null = Microsoft.PowerShell.Management\New-Item -Path $VMPath -ItemType Directory -ErrorAction Stop
        $VhdDirectory = Microsoft.PowerShell.Management\Join-Path $VMPath "Virtual Hard Disks"
        $null = Microsoft.PowerShell.Management\New-Item -Path $VhdDirectory -ItemType Directory -ErrorAction Stop
    }

    # Ensure specialization values are not null
    if ($SpecializationValues) {
        foreach ($key in $SpecializationValues.Keys) {
            if ($key -isnot [string] -or $key -notlike "@*@") {
                throw [System.ArgumentException] ("Specialization key '{0}' is invalid. All specialization keys must be in the form '@KeyName@'." -f $key)
            }

            $value = $SpecializationValues.$key
            if ($value -isnot [string] -or [string]::IsNullOrEmpty($value)) {
                throw [System.ArgumentException] ("The value for specialization key '{0}' is invalid. Values must be non-empty strings." -f $key)
            }
        }
    }

    ## Create the VM
    $VHDPath = Microsoft.PowerShell.Management\Join-Path $VhdDirectory "$Name-OS.vhdx"
    Microsoft.PowerShell.Utility\Write-Verbose ("Copying the template disk to '{0}'" -f $VHDPath)
    Microsoft.PowerShell.Management\Copy-Item -Path $TemplateDiskPath -Destination $VHDPath -ErrorAction Stop

    Microsoft.PowerShell.Utility\Write-Verbose "Creating the new VM"
    $vm = Hyper-V\New-VM -Name $Name -Generation 2 -Path $VMPath -VhdPath $VHDPath -SwitchName $SwitchName -MemoryStartupBytes $MemoryStartupBytes -ErrorAction Stop
    Hyper-V\Set-VMProcessor -VM $vm -Count $CpuCount

    if ($Linux) {
        Hyper-V\Set-VMFirmware -VM $vm -SecureBootTemplate OpenSourceShieldedVM -ErrorAction Stop
    }

    # Attach the key protector
    $kp = Get-KeyProtectorFromShieldingDataFile -ShieldingDataFilePath $ShieldingDataFilePath
    Hyper-V\Set-VMKeyProtector -VM $vm -KeyProtector $kp -ErrorAction Stop

    # Get the security data from the PDK file
    try {
        $pdk = CimCmdlets\Invoke-CimMethod -ClassName  Msps_ProvisioningFileProcessor -Namespace root\msps -MethodName PopulateFromFile -Arguments @{ FilePath = $ShieldingDataFilePath } -Verbose:$false -ErrorAction Stop
        $cimvm = CimCmdlets\Get-CimInstance  -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "Name = '$($vm.VMId)'" -Verbose:$false -ErrorAction Stop
        $vsd = CimCmdlets\Get-CimAssociatedInstance -InputObject $cimvm -ResultClassName "Msvm_VirtualSystemSettingData" -Verbose:$false -ErrorAction Stop
        $ssd = CimCmdlets\Get-CimAssociatedInstance -InputObject $vsd -ResultClassName "Msvm_SecuritySettingData" -Verbose:$false -ErrorAction Stop
        $ss = CimCmdlets\Get-CimAssociatedInstance -InputObject $cimvm -ResultClassName "Msvm_SecurityService" -Verbose:$false -ErrorAction Stop
        $cimSerializer = [Microsoft.Management.Infrastructure.Serialization.CimSerializer]::Create()
        $ssdString = [System.Text.Encoding]::Unicode.GetString($cimSerializer.Serialize($ssd, [Microsoft.Management.Infrastructure.Serialization.InstanceSerializationOptions]::None))
    }
    catch {
        throw "A security policy could not be created from the shielding data file.`n`n$($_.Message)"
    }

    # Apply the VM security policy and enable the VM TPM
    Microsoft.PowerShell.Utility\Write-Verbose "Enabling VM TPM"
    $null = CimCmdlets\Invoke-CimMethod -InputObject $ss -MethodName SetSecurityPolicy -Arguments @{ "SecuritySettingData" = $ssdString; "SecurityPolicy" = $pdk.ProvisioningFile.PolicyData } -Verbose:$false -ErrorAction Stop
    Hyper-V\Enable-VMTPM -VM $vm -ErrorAction Stop

    # Create the fabric specialization keyfile
    Microsoft.PowerShell.Utility\Write-Verbose "Creating specialization data file"
    $fskPath = Microsoft.PowerShell.Management\Join-Path $VMPath "SpecializationData.fsk"
    $simpleComputerName = $Name -replace '[^\w-]', ''
    $fskParams = @{ '@ComputerName@' = $simpleComputerName }
    
    if ($SpecializationValues) {
        foreach ($key in $SpecializationValues.Keys) {
            $fskParams.$key = $SpecializationValues.$key
        }
    }

    ShieldedVMProvisioning\New-ShieldedVMSpecializationDataFile -ShieldedVMSpecializationDataFilePath $fskPath -SpecializationDataPairs $fskParams -ErrorAction Stop

    # Provision the VM
    Microsoft.PowerShell.Utility\Write-Verbose "Initiating shielded VM provisioning process"
    $provisioningJob = ShieldedVMProvisioning\Initialize-ShieldedVM -VM $vm -ShieldingDataFilePath $ShieldingDataFilePath -ShieldedVMSpecializationDataFilePath $fskPath -ErrorAction Stop

    if ($Wait) {
        do {
            $status = ShieldedVMProvisioning\Get-ShieldedVMProvisioningStatus -VM $vm
            Write-Progress -Activity ("Provisioning shielded VM '{0}'" -f $Name) -PercentComplete $status.PercentComplete -Status ("{0}% complete" -f $Status.PercentComplete)
            Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds 1500
        }
        while ($status -and $status.Status -ne 'Error' -and $status.PercentComplete -lt 100)

        if ($status) {
            Microsoft.PowerShell.Utility\Write-Output $status.JobStatus

            if ($status.ErrorDescription) {
                Microsoft.PowerShell.Utility\Write-Error $status.ErrorDescription
            }
        }
        else {
            Microsoft.Powershell.Utility\Write-Output "Unable to check the status of the shielded VM provisioning process."
        }
    }
    else {
        return $provisioningJob
    }
}
# SIG # Begin signature block
# MIIkFwYJKoZIhvcNAQcCoIIkCDCCJAQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCSzQibPD549aci
# Fk5wo5wVe51UUlMVgKLctjmKoIzBiqCCDYMwggYBMIID6aADAgECAhMzAAAAxOmJ
# +HqBUOn/AAAAAADEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTcwODExMjAyMDI0WhcNMTgwODExMjAyMDI0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCIirgkwwePmoB5FfwmYPxyiCz69KOXiJZGt6PLX4kvOjMuHpF4+nypH4IBtXrL
# GrwDykbrxZn3+wQd8oUK/yJuofJnPcUnGOUoH/UElEFj7OO6FYztE5o13jhwVG87
# 7K1FCTBJwb6PMJkMy3bJ93OVFnfRi7uUxwiFIO0eqDXxccLgdABLitLckevWeP6N
# +q1giD29uR+uYpe/xYSxkK7WryvTVPs12s1xkuYe/+xxa8t/CHZ04BBRSNTxAMhI
# TKMHNeVZDf18nMjmWuOF9daaDx+OpuSEF8HWyp8dAcf9SKcTkjOXIUgy+MIkogCy
# vlPKg24pW4HvOG6A87vsEwvrAgMBAAGjggGAMIIBfDAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUy9ZihM9gOer/Z8Jc0si7q7fDE5gw
# UgYDVR0RBEswSaRHMEUxDTALBgNVBAsTBE1PUFIxNDAyBgNVBAUTKzIzMDAxMitj
# ODA0YjVlYS00OWI0LTQyMzgtODM2Mi1kODUxZmEyMjU0ZmMwHwYDVR0jBBgwFoAU
# SG5k5VAF04KqFzc3IrVtqMp1ApUwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljQ29kU2lnUENBMjAxMV8yMDEx
# LTA3LTA4LmNybDBhBggrBgEFBQcBAQRVMFMwUQYIKwYBBQUHMAKGRWh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljQ29kU2lnUENBMjAxMV8y
# MDExLTA3LTA4LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQAG
# Fh/bV8JQyCNPolF41+34/c291cDx+RtW7VPIaUcF1cTL7OL8mVuVXxE4KMAFRRPg
# mnmIvGar27vrAlUjtz0jeEFtrvjxAFqUmYoczAmV0JocRDCppRbHukdb9Ss0i5+P
# WDfDThyvIsoQzdiCEKk18K4iyI8kpoGL3ycc5GYdiT4u/1cDTcFug6Ay67SzL1BW
# XQaxFYzIHWO3cwzj1nomDyqWRacygz6WPldJdyOJ/rEQx4rlCBVRxStaMVs5apao
# pIhrlihv8cSu6r1FF8xiToG1VBpHjpilbcBuJ8b4Jx/I7SCpC7HxzgualOJqnWmD
# oTbXbSD+hdX/w7iXNgn+PRTBmBSpwIbM74LBq1UkQxi1SIV4htD50p0/GdkUieeN
# n2gkiGg7qceATibnCCFMY/2ckxVNM7VWYE/XSrk4jv8u3bFfpENryXjPsbtrj4Ns
# h3Kq6qX7n90a1jn8ZMltPgjlfIOxrbyjunvPllakeljLEkdi0iHv/DzEMQv3Lz5k
# pTdvYFA/t0SQT6ALi75+WPbHZ4dh256YxMiMy29H4cAulO2x9rAwbexqSajplnbI
# vQjE/jv1rnM3BrJWzxnUu/WUyocc8oBqAU+2G4Fzs9NbIj86WBjfiO5nxEmnL9wl
# iz1e0Ow0RJEdvJEMdoI+78TYLaEEAo5I+e/dAs8DojCCB3owggVioAMCAQICCmEO
# kNIAAAAAAAMwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj
# YXRlIEF1dGhvcml0eSAyMDExMB4XDTExMDcwODIwNTkwOVoXDTI2MDcwODIxMDkw
# OVowfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UE
# AxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKvw+nIQHC6t2G6qghBNNLrytlghn0IbKmvpWlCq
# uAY4GgRJun/DDB7dN2vGEtgL8DjCmQawyDnVARQxQtOJDXlkh36UYCRsr55JnOlo
# XtLfm1OyCizDr9mpK656Ca/XllnKYBoF6WZ26DJSJhIv56sIUM+zRLdd2MQuA3Wr
# aPPLbfM6XKEW9Ea64DhkrG5kNXimoGMPLdNAk/jj3gcN1Vx5pUkp5w2+oBN3vpQ9
# 7/vjK1oQH01WKKJ6cuASOrdJXtjt7UORg9l7snuGG9k+sYxd6IlPhBryoS9Z5JA7
# La4zWMW3Pv4y07MDPbGyr5I4ftKdgCz1TlaRITUlwzluZH9TupwPrRkjhMv0ugOG
# jfdf8NBSv4yUh7zAIXQlXxgotswnKDglmDlKNs98sZKuHCOnqWbsYR9q4ShJnV+I
# 4iVd0yFLPlLEtVc/JAPw0XpbL9Uj43BdD1FGd7P4AOG8rAKCX9vAFbO9G9RVS+c5
# oQ/pI0m8GLhEfEXkwcNyeuBy5yTfv0aZxe/CHFfbg43sTUkwp6uO3+xbn6/83bBm
# 4sGXgXvt1u1L50kppxMopqd9Z4DmimJ4X7IvhNdXnFy/dygo8e1twyiPLI9AN0/B
# 4YVEicQJTMXUpUMvdJX3bvh4IFgsE11glZo+TzOE2rCIF96eTvSWsLxGoGyY0uDW
# iIwLAgMBAAGjggHtMIIB6TAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQUSG5k
# 5VAF04KqFzc3IrVtqMp1ApUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYD
# VR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUci06AjGQQ7kU
# BU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2NybC5taWNyb3Nv
# ZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAxMV8yMDExXzAz
# XzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAxMV8yMDExXzAz
# XzIyLmNydDCBnwYDVR0gBIGXMIGUMIGRBgkrBgEEAYI3LgMwgYMwPwYIKwYBBQUH
# AgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvZG9jcy9wcmltYXJ5
# Y3BzLmh0bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBwAG8AbABpAGMA
# eQBfAHMAdABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAZ/KG
# pZjgVHkaLtPYdGcimwuWEeFjkplCln3SeQyQwWVfLiw++MNy0W2D/r4/6ArKO79H
# qaPzadtjvyI1pZddZYSQfYtGUFXYDJJ80hpLHPM8QotS0LD9a+M+By4pm+Y9G6XU
# tR13lDni6WTJRD14eiPzE32mkHSDjfTLJgJGKsKKELukqQUMm+1o+mgulaAqPypr
# WEljHwlpblqYluSD9MCP80Yr3vw70L01724lruWvJ+3Q3fMOr5kol5hNDj0L8giJ
# 1h/DMhji8MUtzluetEk5CsYKwsatruWy2dsViFFFWDgycScaf7H0J/jeLDogaZiy
# WYlobm+nt3TDQAUGpgEqKD6CPxNNZgvAs0314Y9/HG8VfUWnduVAKmWjw11SYobD
# HWM2l4bf2vP48hahmifhzaWX0O5dY0HjWwechz4GdwbRBrF1HxS+YWG18NzGGwS+
# 30HHDiju3mUv7Jf2oVyW2ADWoUa9WfOXpQlLSBCZgB/QACnFsZulP0V3HjXG0qKi
# n3p6IvpIlR+r+0cjgPWe+L9rt0uX4ut1eBrs6jeZeRhL/9azI2h15q/6/IvrC4Dq
# aTuv/DDtBEyO3991bWORPdGdVk5Pv4BXIqF4ETIheu9BCrE/+6jMpF3BoYibV3FW
# TkhFwELJm3ZbCoBIa/15n8G9bW1qyVJzEw16UM0xghXqMIIV5gIBATCBlTB+MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNy
# b3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExAhMzAAAAxOmJ+HqBUOn/AAAAAADE
# MA0GCWCGSAFlAwQCAQUAoIHcMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG
# CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBTQ/sP
# uJP1oeFG37+H9vqx5plWIJE42bN32bjjtXY4UTBwBgorBgEEAYI3AgEMMWIwYKAq
# gCgARwB1AGEAcgBkAGUAZAAgAEYAYQBiAHIAaQBjACAAVABvAG8AbABzoTKAMGh0
# dHBzOi8vZ2l0aHViLmNvbS9NaWNyb3NvZnQvR3VhcmRlZEZhYnJpY1Rvb2xzLzAN
# BgkqhkiG9w0BAQEFAASCAQAAjdTR8abyq3EdZg1oD8u4FgWTgJ+UQzppcoiHQWvV
# pEnTv93eixLa78dNr4qcp+yh9iLOEt2E43OvN+pAwr8TRRvZDZ2TBW+TuhHL4OxG
# ENV+hvyRtpUJSG5k0nKhijSs5BTbLN7wgpwPSxh6K9r+N/wrCWmMcQZ83sv8l/Tz
# OR/r1RTl0LzYh15TavOAsB7p/4lGvWkBQtuwPnQiYkWCjoahRiVgY/yTYdhVXfej
# xCYuG1RdjF+Pq5QsbenMH306KzR1HqK33kvvpBCbA9f+LrQl67PaJlxCOsY4s7x4
# kIpkg2AxkvXleuK7fofeqTHdSUgTqOEZ+voHZ2U5mayroYITRjCCE0IGCisGAQQB
# gjcDAwExghMyMIITLgYJKoZIhvcNAQcCoIITHzCCExsCAQMxDzANBglghkgBZQME
# AgEFADCCATkGCyqGSIb3DQEJEAEEoIIBKASCASQwggEgAgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIIr3tgp2OCQUE4n6bfn0yhrfe4WQZPHu7CuFIV8q
# GcNIAgZaTqtk+A4YEzIwMTgwMTI1MTcyMTI0Ljc3MVowBIACAfSggbikgbUwgbIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDDAKBgNVBAsTA0FP
# QzEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjZCRjYtMkQ1Mi05MkMxMSUwIwYD
# VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIOzTCCBnEwggRZoAMC
# AQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENl
# cnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcw
# MTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm
# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUK
# AIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCI
# hFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5Ff
# gVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx89
# 8Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg1
# 6HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHm
# MIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8Uz
# aFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G
# A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw
# VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j
# cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF
# BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br
# aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSB
# lTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwIC
# MDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBu
# AHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+um
# zPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM
# 9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84
# Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+
# tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32W
# apB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUP
# ei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAe
# b73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4
# hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanb
# lrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLF
# VeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLra
# NtvTX4/edIhJEjCCBNkwggPBoAMCAQICEzMAAACkvT3FljMeiUsAAAAAAKQwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMTYw
# OTA3MTc1NjUwWhcNMTgwOTA3MTc1NjUwWjCBsjELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVy
# IERTRSBFU046NkJGNi0yRDUyLTkyQzExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw
# K5fEm5bGITRq5cTlMrYt+/M/P40ZFqxpvwbqaxoFhCgUK329U99XH8xEfrWsafmN
# eBrTaJmGVZC60y0yqpwIEnyUiCaSvaZ6CXz3YzbWihPBKS6Wd82AREgnxMdGnGPh
# U8WnOAuNtbsSKU5twZl8QeAqAShXUP+QIq2/WCQ0z08xla6Wh9J6hOSV2zVlmndP
# AIUg003nlcbBAnqRdbCk1cj9wtoiabp9YqNvhIjRAOleW63vwRbSYyFf/lCVouw3
# 2uqaM1SXGkd9zsvsUt29tQXaZtWYU13b9OeAIJPY7sg1Buke/x+HgJN3CIrSzUAO
# 4jVHN7+pCkduYf11Q5qXAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQULm5uaXCOkRIv
# v3ogDMW2AdSC8sQwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADAT
# BgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAKZn2DHYGLLOG
# S1cjbuDpK1AzTgxlclx/ZvO1+v7MExqCN8ZHLEFOA0NTbCBhfTxLid3sC9XiVJul
# 34bNofldG8/WmoDVVAzyU5hYn52Sh811l/gtPpY3JmgRCrNSb8itGWLGuJQ059Bs
# oSq/ZtVGfDbs0yZQDwi36e0A8vGfIKwEMnTHjq7KYOGDzODhotCR9nO5m5g+KxhP
# Cl2+42WtbjxX4ZYooWAKGRbFnl5Xwi1XvJgHBIbROoj9I4NsLozTXALHArqUYakW
# aAeNoMbqWB3L0HX0XW+Fz4wQM0kheCM5Kuyeq6+WSR0H1bPZJH4Rr7PFItEpzSth
# qQ23LRtIVaGCA3cwggJfAgEBMIHioYG4pIG1MIGyMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMQwwCgYDVQQLEwNBT0MxJzAlBgNVBAsTHm5DaXBo
# ZXIgRFNFIEVTTjo2QkY2LTJENTItOTJDMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgU2VydmljZaIlCgEBMAkGBSsOAwIaBQADFQBhgZf3oouz70ZSQ5uJ
# fYbA9z/AdKCBwTCBvqSBuzCBuDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046
# MzIxQS02MTNGLTQwNjUxKzApBgNVBAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBN
# YXN0ZXIgQ2xvY2swDQYJKoZIhvcNAQEFBQACBQDeFI/PMCIYDzIwMTgwMTI1MTcx
# ODA3WhgPMjAxODAxMjYxNzE4MDdaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAN4U
# j88CAQAwCgIBAAICAaYCAf8wBwIBAAICGBswCgIFAN4V4U8CAQAwNgYKKwYBBAGE
# WQoEAjEoMCYwDAYKKwYBBAGEWQoDAaAKMAgCAQACAwehIKEKMAgCAQACAx6EgDAN
# BgkqhkiG9w0BAQUFAAOCAQEASApt/uwdytJusY+uKi1f7Sp+UQkMZDd66S0p8a2l
# E5a/C+D1THQybREel2KhgVUaXUeP48EJiVtE3rJ2q99+rWC2LjsYgxowvhGXpJSl
# EKh8i3domK1PnuZ3XEL/8Mdln8r0Krt4+AeHNWGr0NiiHnQZFsYvzlEkr/2UExdf
# zAQoQ9hAzMS7PfWNGMVPEwrNhH1HTFmvrOhU3CqmXyFmgp0jF85WZQn/LkJSvR9j
# KOSNH6OALk0rVfYp9AnEAu5ba5u+GNnbDZbM+MRBqYRdGEUBjyC/pRQ60HL5nyjK
# LRk1CacpwjT78PYblUrG0fTiNij/mTwFZGu1XprbCsQG7jGCAvUwggLxAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAApL09xZYzHolLAAAA
# AACkMA0GCWCGSAFlAwQCAQUAoIIBMjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEIDgmyV06oKlvimX+7Qrc1NG7IRFQGsTIi1i0gu4j
# zPMYMIHiBgsqhkiG9w0BCRACDDGB0jCBzzCBzDCBsQQUYYGX96KLs+9GUkObiX2G
# wPc/wHQwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# AKS9PcWWMx6JSwAAAAAApDAWBBQoTuLFWo5T+7qTSuerRx/3HONNZTANBgkqhkiG
# 9w0BAQsFAASCAQCTl4EONqk5iP17aW2HHXMYlwW626WSchA7EbJMRfJhrLFtfkf4
# dF5yrs1NyCZUykL9fWC3NOHWushYXarVS/do/6QdnwfYrNmNqu61MgUKoZRnSqPq
# /aBVwFMlXUvEf0lBZeFODgQViGdrGBGZaz2yxtrYbJPW+bgITes0iCcH/WRq1vGG
# cJbG4Zml1fhAGzHGbZO4V8PUmC40RTODMv26YUYSdEAcaoQECVxkXdBRAPTzF4WP
# 6n0vFqz4mrQunyBPDFKA7DVZyli7LHx32TLXyxzS4fkM+GOhRlvFRHwvu6WuHO0y
# 1Isn1qh1djRcic74RC1fuARLC/rngGg6deTY
# SIG # End signature block