Obs/bin/ObsDep/content/Powershell/Roles/Common/DeployDirectCommon.psm1

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

$HostFile = "$Env:SystemRoot\System32\Drivers\Etc\Hosts"
# Set manually if needed
$ENABLE_DEBUGGING = $false
$DEBUG_CONNECTION_TYPE = '[DebugConnectionType]'
$DEBUG_SERIAL_PORT = '[DebugSerialPort]'
$DEBUG_SERIAL_BAUD_RATE = '[DebugSerialBaudRate]'
$DEBUG_NET_PORT_MAP_STRING = '[DebugNetPortMapString]'
$DEBUG_NET_HOST_IP = '[DebugNetHostIP]'
$DEBUG_NET_KEY = '[DebugNetKey]'
$DEBUG_NET_BUS_PARAMS = '[DebugNetBusParams]'
$ENABLE_SERIAL_CONSOLE = $false
$CONSOLE_SERIAL_PORT = '[ConsoleSerialPort]'
$CONSOLE_SERIAL_BAUD_RATE = '[ConsoleSerialBaudRate]'

# Starts all the services needed to intialize deployment on win PE
function Set-WinPEDeploymentPrerequisites
{
    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    if (-not (Get-Command wpeutil*)) {
        Write-Warning "This script is intended to be execute in WinPE only."
        return
    }

    $null = wpeutil InitializeNetwork
    $null = wpeutil EnableFirewall
    $null = wpeutil WaitForNetwork

    $null = Start-Service -Name LanmanWorkstation
}

function New-NetworkDrive
{
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $IPv4Address,

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

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

        [Parameter(Mandatory=$true)]
        [PSCredential]
        $Credential,

        [Parameter(Mandatory=$true)]
        [string]
        $DriveLetter
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    # Add Host Entry
    $hostEntry = "$IPv4Address $HostName"

    if (-not (Get-Content $HostFile).Contains($hostEntry)) {
        Write-Verbose "Add host entry: '$hostEntry'." -Verbose
        $hostEntry | Out-File -FilePath $HostFile -Append -Encoding ascii
    }

    if (Get-PSDrive | Where-Object Name -eq $DriveLetter) {
        throw [System.InvalidOperationException]::new("The letter $DriveLetter is already assigned to an existing PSDrive.")
    }

    $maxRetries = 5
    $retries = 1
    $successful = $false
    while ($retries -le $maxRetries)
    {
        try
        {
            # Set PS Drive
            if (-not (Get-PSDrive | Where-Object Name -eq $DriveLetter)) {
                Write-Verbose "Create PSDrive '$DriveLetter' to '$ShareRoot'." -Verbose
                $null = New-PSDrive -Name $DriveLetter -PSProvider FileSystem -Root $ShareRoot -Credential $Credential -Persist -Scope Global
                $successful = $true
                break
            }
        }
        catch
        {
           Write-Warning $_
           Write-Verbose "Failed to create PSDrive '$DriveLetter' to '$ShareRoot'. Sleep 60 seconds and retry $retries/$maxRetries." -Verbose
           Start-Sleep -Seconds 60
        }

        $retries ++
    }

    if ($successful) {
        Write-Verbose "Create PSDrive '$DriveLetter' to '$ShareRoot' successfully." -Verbose
    }
    else {
        throw "Failed to create PSDrive '$DriveLetter' to '$ShareRoot' after $maxRetries retries."
    }
}

# Returns back the SystemDrive
function Set-DiskConfiguration
{
    [CmdletBinding()]
    [OutputType([String])]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $LogPath,

        [Parameter(Mandatory=$false)]
        [string]
        $BootDiskConfigPath,

        [Parameter(Mandatory=$false)]
        [bool]
        $ClearExisting=$true,

        [Parameter(Mandatory=$false)]
        [bool]
        $BootFromPhysicalDisk=$false
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    (Get-Date).ToString('yyyy/MM/dd HH:mm:ss') | Add-Content $LogPath
    "Reset the disks and clean them of all data." | Add-Content $LogPath

    if ($ClearExisting) {
        Get-Partition | Remove-Partition -Confirm:$false -ErrorAction SilentlyContinue
        # account for change in Reset-PhysicalDisk parameters in WinPE with Windows cumulative update
        $PDParam = @{}
        if ((Get-Command -Name 'Reset-PhysicalDisk').Parameters['Confirm']) {
            $PDParam.Add('Confirm',$false)
        }
        Get-PhysicalDisk | Reset-PhysicalDisk @PDParam

        Get-Disk | Where-Object PartitionStyle -ne RAW | ForEach-Object {
            $_ | Set-Disk -IsOffline:$false -ErrorAction SilentlyContinue
            $_ | Set-Disk -IsReadOnly:$false -ErrorAction SilentlyContinue
            $_ | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false -ErrorAction SilentlyContinue
        }
    }

    Get-Disk | ForEach-Object {
        $_ | Set-Disk -IsReadOnly:$true -ErrorAction SilentlyContinue
        $_ | Set-Disk -IsOffline:$true -ErrorAction SilentlyContinue
    }

    Update-StorageProviderCache -DiscoveryLevel Full
    (Get-Date).ToString('yyyy/MM/dd HH:mm:ss') | Add-Content $LogPath
    "Select the disk to boot from." | Add-Content $LogPath

    Get-PhysicalDisk | Sort-Object DeviceId | Format-Table DeviceId, Model, BusType, MediaType, Size | Out-String | Add-Content $LogPath
    Get-Disk | Out-String | Add-Content $LogPath

    $allbootCandidateDisks = Get-PhysicalDisk
    if (-not $allbootCandidateDisks) {
        throw 'No suitable boot candidate disks found.'
    }

    # log the data about physical disks that filtering uses
    "All disks." | Add-Content $LogPath
    $allbootCandidateDisks | Sort-Object DeviceId | Select-Object FriendlyName,SerialNumber,BusType,DeviceId,Manufacturer,Model,MediaType,Size | Format-Table | Out-String | Add-Content $LogPath

    if ($bootDiskConfigPath -and (Test-Path $bootDiskConfigPath)) {
        "Boot disk configuration file '$bootDiskConfigPath' exists." | Add-Content $LogPath
        [xml] $config = Get-Content $bootDiskConfigPath
        $bootDiskConfigs = $config.disks.disk
        $filteredBootCandidateDisks = $null
        foreach ($bootDiskConfig in $bootDiskConfigs) {
            # only apply each filter if the previous filter did not return any disks
            if (-not $filteredBootCandidateDisks) {
                # log what if being used as a filter
                "Filter - BusType: $($bootDiskConfig.BusType), DeviceId: $($bootDiskConfig.DeviceId), Manufacturer: $($bootDiskConfig.Manufacturer), Model: $($bootDiskConfig.Model), MediaType: $($bootDiskConfig.MediaType), Size: $($bootDiskConfig.Size)" | Add-Content $LogPath
                $filteredBootCandidateDisks = $allbootCandidateDisks | Where-Object { ($_.BusType -like $bootDiskConfig.BusType) -and ($_.DeviceId -like $bootDiskConfig.DeviceId) -and ($_.Manufacturer -like $bootDiskConfig.Manufacturer) -and ($_.Model -like $bootDiskConfig.Model) -and ($_.MediaType -like $bootDiskConfig.MediaType) -and ($_.Size -like $bootDiskConfig.Size) }

                # if this filter returns disks, set the busTypeFilter so we can filter further for * below
                if ($filteredBootCandidateDisks) {
                    $busTypeFilter = $bootDiskConfig.BusType
                }
            }
            else {
                break
            }
        }

        # if no filtered disks after attempting all filters, we must fail
        if (-not $filteredBootCandidateDisks) {
            throw 'After filtering, no suitable boot candidate disks found.'
        }

        "Filtered disks." | Add-Content $LogPath
        $filteredBootCandidateDisks | Sort-Object DeviceId | Select-Object FriendlyName,SerialNumber,BusType,DeviceId,Manufacturer,Model,MediaType,Size | Format-Table | Out-String | Add-Content $LogPath

        ###############################################################################################
        # Temporary; after OEM extention is workin E2E, it will be removed

        $allTypeString = "*"

        if ($busTypeFilter -eq $allTypeString) {
            $filteredBootCandidateDisks = $filteredBootCandidateDisks | Where-Object BusType -in 'SATA', 'SAS', 'RAID'
        }

        "Filtered disks after the bus type filter 'SATA', 'SAS', 'RAID'." | Add-Content $LogPath
        $filteredBootCandidateDisks | Out-String | Add-Content $LogPath

        ###############################################################################################

        $bootCandidateDisks = $filteredBootCandidateDisks
    }
    else {
        $bootCandidateDisks = $allbootCandidateDisks | Where-Object BusType -in 'SATA', 'SAS', 'RAID'
    }

    if (-not $bootCandidateDisks) {
        throw 'No suitable boot candidate disk found.'
    }

    $bootCandidateDisks = $bootCandidateDisks | Where-Object DeviceId -in (Get-Disk).Number
    $bootCandidateDisks = $bootCandidateDisks | Sort-Object Size, DeviceId
    foreach ($currentDisk in $bootCandidateDisks) {
        $found = $(Get-Disk -Number $currentDisk.DeviceId | Get-Partition | Where-Object {$_.Type -eq "System"})
        if ($found) {
            "Found boot disk with system partition type" | Add-Content $LogPath
            $found | Out-String | Add-Content $LogPath
            $bootCandidateDisk = $currentDisk
            break
        }
    }
    if ($null -eq $bootCandidateDisk) {
        "Select the first candidate disk as boot disk" | Add-Content $LogPath
        $bootCandidateDisk = $bootCandidateDisks | Select-Object -First 1
    }
    $bootDiskNumber = $bootCandidateDisk.DeviceId

    if (-not $bootDiskNumber) {
        throw 'Not able to get the boot disk number.'
    }

    "Disk $bootDiskNumber will be used for boot partition." | Add-Content $LogPath

    if (-not($ClearExisting)) {
        # Remove the disk partition
        Get-Partition -DiskNumber $bootDiskNumber | Remove-Partition -Confirm:$false -ErrorAction SilentlyContinue

        # Reset only the boot disk
        $PDParam = @{}
        if ((Get-Command -Name 'Reset-PhysicalDisk').Parameters['Confirm']) {
            $PDParam.Add('Confirm',$false)
        }

        $bootCandidateDisk | Reset-PhysicalDisk @PDParam

        $disk = Get-Disk | Where-Object Number -eq $bootDiskNumber | Where-Object PartitionStyle -ne RAW

        "Disk about to be cleared:" | Add-Content $LogPath
        $disk | Out-String | Add-Content $LogPath

        $disk | ForEach-Object {
            $_ | Set-Disk -IsOffline:$false -ErrorAction SilentlyContinue
            $_ | Set-Disk -IsReadOnly:$false -ErrorAction SilentlyContinue
            $_ | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false -ErrorAction SilentlyContinue
            $_ | Set-Disk -IsReadOnly:$true -ErrorAction SilentlyContinue
            $_ | Set-Disk -IsOffline:$true -ErrorAction SilentlyContinue
        }
    }

    ###############################################################################################
    # Temporary for R730; after OEM extention is working E2E, this will be removed [9282124]

    $firstDisk = $bootCandidateDisk
    $secondDisk = $bootCandidateDisks | Where-Object { $($_.DeviceId -ne $bootCandidateDisk.DeviceId) -and $($_.Size -ge $bootCandidateDisk.Size) } | Sort-Object Size, DeviceId | Select-Object -First 1

    if ($firstDisk.Size -eq $secondDisk.Size) {
        $secondDiskNumber = $secondDisk.DeviceId
        $ssdDisks = Get-PhysicalDisk | Where-Object BusType -in 'SATA', 'SAS', 'RAID', 'NVMe' | Where-Object MediaType -eq SSD
        $nonOnboardSsdDisks = $ssdDisks | Where-Object DeviceId -notin $firstDisk.DeviceId, $secondDisk.DeviceId | Sort-Object Size

        if ($nonOnboardSsdDisks -and ($secondDisk.Size -lt $nonOnboardSsdDisks[0].Size / 1.01)) {
            throw "Disk $secondDiskNumber appears to be a secondary on-board drive; it needs to be removed."
        }
    }

    ##############################################################################################

    wpeutil UpdateBootInfo | Add-Content $LogPath

    $remove = @(Get-Volume | Where-Object DriveType -ne Fixed)
    foreach ($item in $remove) {
        $vol = Get-CimInstance -ClassName Win32_Volume -Filter "DriveLetter = '$($item.DriveLetter):'"
        if ($null -ne $vol) {
            try {
                "Remove drive letter assignment '$($item.DriveLetter):' from '$($item.DriveType)'." | Add-Content $LogPath
                $vol | Set-CimInstance -Property @{DriveLetter=$null}
            }
            catch {
                "WARNING: Failed to remove drive letter assignment '$($item.DriveLetter):' from '$($item.DriveType)'." | Add-Content $LogPath
            }
        }
    }

    $peFirmwareType = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control).PEFirmwareType
    # Returns 0x1 if the PC is booted into BIOS mode, or 0x2 if the PC is booted in UEFI mode.
    $isLegacyBoot = $peFirmwareType -eq 1

    if ($isLegacyBoot) {
        "Create new partitions for Legacy Boot." | Add-Content $LogPath
        $null = Initialize-Disk -Number $bootDiskNumber -PartitionStyle MBR -ErrorAction SilentlyContinue
        if ($true -eq $BootFromPhysicalDisk) {
            $winPartition = New-Partition -DiskNumber $bootDiskNumber -Size 60GB -DriveLetter C -IsActive
            if (-not $winPartition) {
                throw 'Unable to create partition for physical drive OS Installation.'
            }
            $partition = New-Partition -DiskNumber $bootDiskNumber -UseMaximumSize -DriveLetter D
        }
        else {
            $partition = New-Partition -DiskNumber $bootDiskNumber -UseMaximumSize -AssignDriveLetter -IsActive
        }
        if (-not $partition) {
            throw 'Unable to create partition for OS Installation.'
        }
        $systemDrive = $partition.DriveLetter + ':'
        if ($true -eq $BootFromPhysicalDisk) {
            $null = Format-Volume -Partition $winPartition -FileSystem NTFS -Confirm:$false
        }
        $osVolume = Format-Volume -Partition $partition -FileSystem NTFS -Confirm:$false
    }
    else {
        "Create new partitions for UEFI." | Add-Content $LogPath
        $null = Initialize-Disk -Number $bootDiskNumber -ErrorAction SilentlyContinue
        $msrPartition = New-Partition -DiskNumber $bootDiskNumber -Size 128MB -GptType "{e3c9e316-0b5c-4db8-817d-f92df00215ae}" # MSR
        if ($true -eq $BootFromPhysicalDisk) {
            "Create partitions for Boot From Physical Disk scenario." | Add-Content $LogPath
            $winPartition = New-Partition -DiskNumber $bootDiskNumber -Size 60GB -DriveLetter C -GptType "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" # WIN
            if (-not $winPartition) {
                throw 'Unable to create partition for physical drive OS Installation.'
            }
            $sysPartition = New-Partition -DiskNumber $bootDiskNumber -Size 350MB -DriveLetter E -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" # ESP
        }
        else {
            "Create partitions for Boot From Virtual Disk scenario." | Add-Content $LogPath
            $espPartition = New-Partition -DiskNumber $bootDiskNumber -Size 200MB -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" # ESP
            $espPartition | Add-PartitionAccessPath -AccessPath Q:
            $null = format Q: /fs:FAT32 /v:EFS /Y
        }
        $osPartition = New-Partition -DiskNumber $bootDiskNumber -UseMaximumSize -DriveLetter D -GptType "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" # OS
        if (-not $osPartition) {
            throw 'Unable to create the required partitions for OS Installation.'
        }
        $osVolume = Format-Volume -Partition $osPartition -FileSystem NTFS -Confirm:$false
        $systemDrive = $osPartition.DriveLetter + ':'
        "System drive letter is set to '$($systemDrive)'" | Add-Content $LogPath
        if ($null -ne $winPartition) {
            $null = Format-Volume -Partition $sysPartition -FileSystem FAT32 -Confirm:$false
            $null = Format-Volume -Partition $winPartition -FileSystem NTFS -Confirm:$false
            $env:WINDOWS_DRIVE = $winPartition.DriveLetter + ':'
            "Windows OS drive letter is set to '$($env:WINDOWS_DRIVE)'" | Add-Content $LogPath
        }
    }

    return [string]$systemDrive
}

function Set-HostVHDBoot
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $BootVHDFilePath,

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

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

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

        [Parameter(Mandatory=$false)]
        [string]
        $MacAddress=$null,

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

        [Parameter(Mandatory=$false)]
        [switch]
        $AddDeploymentIdToken,

        [Parameter(Mandatory=$false)]
        [string]
        $HypervisorSchedulerType='Core'
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    try
    {
        "Mounting VHD '$BootVHDFilePath'." | Add-Content $LogPath
        $null = Mount-DiskImage -ImagePath $BootVHDFilePath
        $virtualDiskDriveLetter = Get-Disk | Where-Object BusType -like 'File Backed Virtual' | Get-Partition | Where-Object Size -gt 2Gb | ForEach-Object DriveLetter
        $bootDrive = $virtualDiskDriveLetter + ':\'

        # Workaround for issue where script cannot find drive
        $null = New-PSDrive -Name $virtualDiskDriveLetter -Root $bootDrive -PSProvider FileSystem

        if ($AddDeploymentIdToken) {
            # Add a token file at a predefined location to detect host has booted up.
            # This will be relevant only in case of one-node stamp when it goes through baremetal deployment using WinPE
            $tempPath = "$($bootDrive)CloudBuilderTemp"
            "Inject deploymentId file '$tempPath\$($DeploymentId).txt'." | Add-Content $LogPath
            $null = New-Item -Path $tempPath -ItemType Directory -Force
            $null = Set-Content -Path "$tempPath\$($DeploymentId).txt" -Value ''
        }

        "Use-WindowsUnattend file '$UnattendPath' for offline values." | Add-Content $LogPath
        $null = Use-WindowsUnattend -Path $bootDrive  -UnattendPath $UnattendPath

        $unattendDirectory = "$($bootDrive)Windows\Panther\Unattend"
        "Inject Unattend file '$UnattendPath' to '$unattendDirectory'." | Add-Content $LogPath
        $null = New-Item -Path $unattendDirectory -ItemType Directory -Force

        if ($MacAddress) {
            $unattendContent = Get-Content $unattendPath
            "Writing MAC Address '$MacAddress' to Unattend file" | Add-Content $LogPath
            $unattendContent = $unattendContent.Replace('[MacAddress]', $MacAddress.Replace(':','-'))
            $null = Set-Content -Path "$unattendDirectory\unattend.xml" -Value $unattendContent
        }
        else {
            $null = Copy-Item -Path $unattendPath -Destination "$unattendDirectory\unattend.xml"
        }

        $computerName =  Get-ComputerNameFromUnattend -UnattendPath "$unattendDirectory\unattend.xml"

        Set-Debugging -LogPath $LogPath -ComputerName $computerName

        $peFirmwareType = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control).PEFirmwareType
        # Returns 0x1 if the PC is booted into BIOS mode, or 0x2 if the PC is booted in UEFI mode.
        $isLegacyBoot = $peFirmwareType -eq 1

        if ($isLegacyBoot) {
            "Set BCD Boot Legacy." | Add-Content $LogPath
            bcdboot "$($bootDrive)Windows" /s $SystemDrive | Add-Content $LogPath
        }
        else {
            "Set BCD Boot UEFI." | Add-Content $LogPath
            bcdboot "$($bootDrive)Windows" /s Q: /f UEFI /d /addlast /v | Add-Content $LogPath

            # Remove invalid Windows Boot Manager entries, left from the previous deployment.
            $bcdFirmware = bcdedit /enum firmware
            $bcdFirmware | Add-Content $LogPath
            $bcdFirmware = $bcdFirmware -join "`n"
            if ($bcdFirmware -match 'identifier\s*({\w*-[0-9a-z-]*})[^-]*?description\s*Windows Boot Manager') {
                for ($i = 0; $i -lt $matches.Count; $i++) {
                    if ($matches[$i] -like '{*') {
                        bcdedit /delete $matches[$i]
                    }
                }
            }

            bcdedit /enum firmware | Add-Content $LogPath
        }

        # https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/manage/manage-hyper-v-scheduler-types
        "Setting hypervisor scheduler type to $HypervisorSchedulerType" | Add-Content $LogPath
        $bcdOutput = & bcdedit /set `{default`} hypervisorschedulertype $HypervisorSchedulerType | Out-String
        if (-not ($bcdOutput -ilike "*successfully*")) {
            throw "BCDEdit failed to update the hypervisor scheduler type. Output: $bcdOutput"
        }

        # Output boot store entries to validate settings
        $bcdEditEnum = & bcdedit /enum | Out-String
        $bcdEdit | Add-Content $LogPath
    }
    finally
    {
        $mountedImages = Get-DiskImage -ImagePath $BootVHDFilePath
        if ($mountedImages) {
            $null = Dismount-DiskImage -ImagePath $BootVHDFilePath
        }
    }
}

function Set-HostPhysicalDiskBoot
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $SystemDrive,

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

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

        [Parameter(Mandatory=$false)]
        [string]
        $MacAddress=$null,

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

        [Parameter(Mandatory=$false)]
        [switch]
        $AddDeploymentIdToken,

        [Parameter(Mandatory=$false)]
        [string]
        $HypervisorSchedulerType='Core'
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    try
    {
        $bootDrive = $SystemDrive

        if ($AddDeploymentIdToken) {
            # Add a token file at a predefined location to detect host has booted up.
            # This will be relevant only in case of one-node stamp when it goes through baremetal deployment using WinPE
            $tempPath = "$($bootDrive)CloudBuilderTemp"
            "Inject deploymentId file '$tempPath\$($DeploymentId).txt'." | Add-Content $LogPath
            $null = New-Item -Path $tempPath -ItemType Directory -Force
            $null = Set-Content -Path "$tempPath\$($DeploymentId).txt" -Value ''
        }

        "Use-WindowsUnattend file '$UnattendPath' for offline values." | Add-Content $LogPath
        $null = Use-WindowsUnattend -Path $bootDrive  -UnattendPath $UnattendPath

        $unattendDirectory = "$($bootDrive)Windows\Panther\Unattend"
        "Inject Unattend file '$UnattendPath' to '$unattendDirectory'." | Add-Content $LogPath
        $null = New-Item -Path $unattendDirectory -ItemType Directory -Force

        if ($MacAddress) {
            $unattendContent = Get-Content $unattendPath
            "Writing MAC Address '$MacAddress' to Unattend file" | Add-Content $LogPath
            $unattendContent = $unattendContent.Replace('[MacAddress]', $MacAddress.Replace(':','-'))
            $null = Set-Content -Path "$unattendDirectory\unattend.xml" -Value $unattendContent
        }
        else {
            $null = Copy-Item -Path $unattendPath -Destination "$unattendDirectory\unattend.xml"
        }

        $computerName =  Get-ComputerNameFromUnattend -UnattendPath "$unattendDirectory\unattend.xml"

        Set-Debugging -LogPath $LogPath -ComputerName $computerName

        $peFirmwareType = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control).PEFirmwareType
        # Returns 0x1 if the PC is booted into BIOS mode, or 0x2 if the PC is booted in UEFI mode.
        $isLegacyBoot = $peFirmwareType -eq 1
        if ($isLegacyBoot) {
            "Set BCD Boot Legacy." | Add-Content $LogPath
            bcdboot "$($bootDrive)\Windows" /s $bootDrive | Add-Content $LogPath
        }
        else {
            "Set BCD Boot UEFI." | Add-Content $LogPath
            bcdboot "$($bootDrive)\Windows" /s E: /f UEFI /v | Add-Content $LogPath
            bcdedit /enum firmware | Add-Content $LogPath
        }

        # https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/manage/manage-hyper-v-scheduler-types
        "Setting hypervisor scheduler type to $HypervisorSchedulerType" | Add-Content $LogPath
        $bcdOutput = & bcdedit /set `{default`} hypervisorschedulertype $HypervisorSchedulerType | Out-String
        if (-not ($bcdOutput -ilike "*successfully*")) {
            throw "BCDEdit failed to update the hypervisor scheduler type. Output: $bcdOutput"
        }

        # Output boot store entries to validate settings
        $bcdEditEnum = & bcdedit /enum | Out-String
        $bcdEdit | Add-Content $LogPath
    }
    catch
    {
        throw $PSItem
    }
}

function Get-ComputerNameFromUnattend
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $UnattendPath
    )

    # Get host name from unattend.xml
    $computerNameSearch = Get-ChildItem -Path $unattendPath | Select-String '\<ComputerName\>([^<]*)\<'
    $computerName = $computernameSearch.Matches.Groups[1].Value

    return $computerName
}

<#
.Synopsis
     Function to test if an IP address is between two other IP addresses, inclusive
.Parameter IPAddress
     The address to test.
.Parameter BeginAddress
     The IP address at the begining of the range to test.
.Parameter EndAddress
     The IP address at the begining of the range to test.
.Example
    Test-IpAddressBetween -IPAddress 10.0.0.5 -BeginAddress 10.0.0.1 -EndAddress 10.0.0.10
    Returns True
#>

function Test-IpAddressBetween
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [String]
        $IPAddress,

        [Parameter(Mandatory=$true)]
        [String]
        $BeginAddress,

        [Parameter(Mandatory=$true)]
        [String]
        $EndAddress
    )

    # Convert all the addresses to Int32
    $originalAddressBytes = ([IPAddress]$IPAddress).GetAddressBytes()
    [array]::Reverse($originalAddressBytes)
    $ipAddressInt = [System.BitConverter]::ToInt32($originalAddressBytes, 0)

    $originalAddressBytes = ([IPAddress]$BeginAddress).GetAddressBytes()
    [array]::Reverse($originalAddressBytes)
    $beginAddressInt = [System.BitConverter]::ToInt32($originalAddressBytes, 0)

    $originalAddressBytes = ([IPAddress]$EndAddress).GetAddressBytes()
    [array]::Reverse($originalAddressBytes)
    $endAddressInt = [System.BitConverter]::ToInt32($originalAddressBytes, 0)

    $ipAddressInt -le $endAddressInt -and $ipAddressInt -ge $beginAddressInt
}

function Get-LogFilePath
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $UnattendPath
    )

    $computerName = Get-ComputerNameFromUnattend -UnattendPath $UnattendPath

    $logPath = [System.IO.Path]::GetDirectoryName($UnattendPath)
    $logFilePath = "$($logPath)\$($computerName).Log"

    return $logFilePath
}

function Set-Debugging
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $LogPath,

        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    if ($ENABLE_DEBUGGING) {
        Add-Content $LogPath "Current BCD settings:"
        bcdedit /enum | Add-Content $LogPath
        Add-Content $LogPath "Enabling debugging and setting dbgsettings..."
        bcdedit /debug '{default}' on | Add-Content $LogPath
        bcdedit /bootdebug '{default}' on | Add-Content $LogPath

        if ($DEBUG_CONNECTION_TYPE -eq 'serial') {
            bcdedit /dbgsettings serial debugport:$DEBUG_SERIAL_PORT baudrate:$DEBUG_SERIAL_BAUD_RATE | Add-Content $LogPath
        }
        elseif ($DEBUG_CONNECTION_TYPE -eq 'net') {
            $portMap = @{}
            $DEBUG_NET_PORT_MAP_STRING -split ';' | Where-Object { $_ } | ForEach-Object {
                $portRecord = $_ -split ','
                $portMap.($portRecord[0]) = $portRecord[1]
            }

            $port = $portMap.$ComputerName
            Add-Content $LogPath "Port: $port"
            bcdedit /dbgsettings net hostip:$DEBUG_NET_HOST_IP port:$port key:$DEBUG_NET_KEY | Add-Content $LogPath
            bcdedit /set '{dbgsettings}' busparams "$DEBUG_NET_BUS_PARAMS" | Add-Content $LogPath
        }
        else {
            Add-Content $LogPath "Debugging connection type '$DEBUG_CONNECTION_TYPE' is not expected."
        }

        Add-Content $LogPath "Debug settings are now set:"
        bcdedit /enum | Add-Content $LogPath
        bcdedit /dbgsettings | Add-Content $LogPath
    }

    if ($ENABLE_SERIAL_CONSOLE) {
        Add-Content $LogPath "Current BCD settings:"
        bcdedit /enum | Add-Content $LogPath
        Add-Content $LogPath "Enabling Emergency Management Services..."
        bcdedit /ems '{default}' on | Add-Content $LogPath

        bcdedit /emssettings emsport:$CONSOLE_SERIAL_PORT emsbaudrate:$CONSOLE_SERIAL_BAUD_RATE | Add-Content $LogPath
        Add-Content $LogPath "EMS settings are now set:"
        bcdedit /enum | Add-Content $LogPath
    }
}

<#
.Synopsis
     Function to get the local machine System Management BIOS Guid without dashes or brackets
.Example
    Get-LocalSMBIOSGuid
    Returns 1742B000CD3611E10000AC162D024F2F
#>

function Get-LocalSMBIOSGuid
{
    $smBiosGuid = Get-WmiObject Win32_ComputerSystemProduct UUID
    $smBiosGuid.UUID.Replace('-', '')
}

<#
.Synopsis
     Function to get a path to the file that will contain the MAC address on a deploying machine
.Example
    Get-MACAddressOutputPath -RemoteUnattendPath \\foo\bar
    Returns \\foo\bar\1742B000CD3611E10000AC162D024F2F.MACAddress.txt
#>

function Get-MACAddressOutputPath
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $RemoteUnattendPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    # Always use the smbios guid, since this is an externally discoverable identifier
    $smBiosGuid = Get-LocalSMBIOSGuid

    $macAddressOutputFilePath = $smBiosGuid | ForEach-Object { "$RemoteUnattendPath\$_.MACAddress.txt" }

    return $macAddressOutputFilePath
}


function Get-UnattendFilePath
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $RemoteUnattendPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    $macAddress = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object MACAddress | ForEach-Object MACAddress | ForEach-Object { $_ -replace ':', '-' } | Where-Object { "$remoteUnattendPath\$_.xml" | Where-Object {Test-Path $_} }

    if ($macAddress) {
        $unattendPath = $macAddress | ForEach-Object { "$RemoteUnattendPath\$_.xml" }
    }
    else {
        # No unattend file matching the MAC address was found, attempt to find an unattend file with the smbios guid instead
        $smBiosGuid = Get-LocalSMBIOSGuid

        $unattendPath = $smBiosGuid | ForEach-Object { "$RemoteUnattendPath\$_.xml" }
    }

    return $unattendPath
}


<#
.Synopsis
    This function returns the node specific RemoteUnattend path e.g. when passed RemoteUnattend, we will get back
    RemoteUnattend\<NodeName>. If node's unattend files are in RemoteUnattend *and* RemoteUnattend\NodeName, we will
    return the path containing the latest files. This scenario can happen during the following sequence
    Old build + FRU X (writes to RemoteUnattend) + PnU (new build that writes to NodeName) + FRU X during PnU
#>

function Get-NodeSpecificRemoteUnattendPath
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $UnattendPathToResolve
    )

    $macAddresses = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object MACAddress | ForEach-Object MACAddress | ForEach-Object { $_ -replace ':', '-' }

    foreach ($macAddress in $macAddresses) {
        $unattendFile = Get-ChildItem -Path $UnattendPathToResolve -Include "$macAddress.xml" -Recurse -Force -File | Sort-Object LastWriteTime -Descending | Select-Object -First 1
        if ($unattendFile)  {
            return $unattendFile.Directory.FullName
        }
    }

    $smBiosGuid = Get-LocalSMBIOSGuid

    $unattendFile = Get-ChildItem -Path $UnattendPathToResolve -Include "$smBiosGuid.xml" -Recurse -Force -File  | Sort-Object LastWriteTime -Descending | Select-Object -First 1
    if ($unattendFile)  {
        return $unattendFile.Directory.FullName
    }

    return $UnattendPathToResolve
}

function Get-BootDiskConfigPath
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $RemoteUnattendPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    $macAddress = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object MACAddress | ForEach-Object MACAddress | ForEach-Object { $_ -replace ':', '-' } | Where-Object { "$remoteUnattendPath\$_.BootDisk.xml" | Where-Object {Test-Path $_} }

    if ($macAddress) {
        $bootDiskConfigPath = $macAddress | ForEach-Object { "$RemoteUnattendPath\$_.BootDisk.xml" }
    }
    else {
        # No boot disk file matching the MAC address was found, attempt to find a boot disk file with the smbios guid instead
        $smBiosGuid = Get-LocalSMBIOSGuid

        $bootDiskConfigPath = $smBiosGuid | ForEach-Object { "$RemoteUnattendPath\$_.BootDisk.xml" }
    }

    return $bootDiskConfigPath
}

function Test-AzsHostTPMOwnership
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $LogPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    "Testing TPM ownership for AzureStack host" | Add-Content $LogPath

    try
    {
        "Getting Win32_TPM object in namespace root\cimv2\security\microsofttpm" | Add-Content $LogPath
        $tpmWMIObject = Get-WmiObject -Namespace "root\cimv2\security\microsofttpm" -Class Win32_TPM
        if ($null -eq $tpmWMIObject) {
            throw "Failed to get WMI object Win32_TPM"
        }

        "Calling TPM method IsOwned() to check if TPM is properly owned" | Add-Content $LogPath
        $tpmIsOwnedRet = $tpmWMIObject.IsOwned()
        if ($null -eq $tpmIsOwnedRet) {
            throw "Failed call to tpm method IsOwned()"
        }

        if ($tpmIsOwnedRet.ReturnValue -ne 0) {
            throw "Failed call to tpm method IsOwned(). ReturnValue is $($tpmIsOwnedRet.ReturnValue)"
        }

        if ($tpmIsOwnedRet.IsOwned -eq $true) {
            "TPM IsOwned() method confirmed that TPM is owned. No need to explicitly call TPM method TakeOwnerShip()" | Add-Content $LogPath
            return
        }

        "TPM is NOT owned. Will explicitly call TPM method TakeOwnerShip()" | Add-Content $LogPath
        "Calling TPM method TakeOwnerShip()" | Add-Content $LogPath
        $tpmTakeOwnerShipRet = $tpmWMIObject.TakeOwnerShip()
        if ($null -eq $tpmTakeOwnerShipRet) {
            throw "Failed call to TPM method TakeOwnerShip()"
        }

        if ($tpmTakeOwnerShipRet.ReturnValue -ne 0) {
            throw "Failed call to TPM method TakeOwnerShip(). ReturnValue is $($tpmTakeOwnerShipRet.ReturnValue)"
        }

        "Calling TPM method IsOwned() to check if TPM is properly owned after calling TPM method TakeOwnerShip()" | Add-Content $LogPath
        $tpmIsOwnedRet = $tpmWMIObject.IsOwned()
        if ($null -eq $tpmIsOwnedRet) {
            throw "Failed call to tpm method IsOwned()"
        }

        if ($tpmIsOwnedRet.ReturnValue -ne 0) {
            throw "Failed call to tpm method IsOwned(). ReturnValue is $($tpmIsOwnedRet.ReturnValue)"
        }

        if ($tpmIsOwnedRet.IsOwned -eq $false) {
            throw "Failure: TPM method IsOwned() shows that TPM is NOT owned even after calling TPM method TakeOwnerShip()"
        }

        "TPM is now owned after calling TPM method TakeOwnerShip()" | Add-Content $LogPath
    }
    catch
    {
        "Azs host test TPM ownership did not complete. Exception: $($_.Exception)" | Add-Content $LogPath
        "Test TPM ownership did not complete becaue of the following error/warning: $($_.Exception.Message)" | Add-Content $LogPath
    }
}

# Function Clear-AzsHostTPM
# It is a precautionary mechanism to avoid issues with
# TPM. If TPM could not be cleared
# an error will be reported but deployment
# should proceed. A common reason for TPM not to be cleared
# is a host that requies user confirmation upon reboot.
# In this case an error will be reported but deployment
# should proceed
function Clear-AzsHostTPM
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $LogPath
    )

    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    (Get-Date).ToString('yyyy/MM/dd HH:mm:ss') | Add-Content $LogPath
    "Clear TPM for AzureStack host" | Add-Content $LogPath

    try
    {
        "Retrieving a WMI object win32_TPM in namespace root\cimv2\security\microsofttpm" | Add-Content $LogPath
        $tpmWMIObject = Get-WmiObject -Namespace "root\cimv2\security\microsofttpm" -Class Win32_TPM
        if ($null -eq $tpmWMIObject) {
            throw "Aborting clear TPM. Failed to retrieve TPM WMI object. Make sure TPM is available and ready."
        }

        "Checking status of TPM" | Add-Content $LogPath
        if ((!$tpmWMIObject.IsActivated_InitialValue) -or (!$tpmWMIObject.IsEnabled_InitialValue)) {
            throw "Aborting clear TPM. TPM is either not activated or is not enabled"
        }

        "Testing TPM ownership. Calling Test-AzsHostTPMOwnership" | Add-Content $LogPath
        Test-AzsHostTPMOwnership -LogPath $LogPath

        "Retrieving a new WMI win32_TPM object after testing ownership in namespace root\cimv2\security\microsofttpm" | Add-Content $LogPath
        $tpmWMIObject = Get-WmiObject -Namespace "root\cimv2\security\microsofttpm" -Class Win32_TPM
        if ($null -eq $tpmWMIObject) {
            throw "Aborting clear TPM. Failed to retrieve TPM WMI object. Make sure TPM is available and ready."
        }

        "Starting process to clear TPM" | Add-Content $LogPath
        "Calling WMI TPM method GetPhysicalPresenceConfirmationStatus() with Operation input parameter set to 22 (Enable + Activate + Clear + Enable + Activate)" | Add-Content $LogPath
        $physicalPresenceConfRet = $tpmWMIObject.GetPhysicalPresenceConfirmationStatus(22)
        if ($null -eq $physicalPresenceConfRet) {
            throw "Aborting clear TPM. Failed call to WMI TPM method GetPhysicalPresenceConfirmationStatus()"
        }

        "Checking ReturnValue of WMI TPM method GetPhysicalPresenceConfirmationStatus()" | Add-Content $LogPath
        "ReturnValue of WMI TPM method GetPhysicalPresenceConfirmationStatus() is $($physicalPresenceConfRet.ReturnValue)" | Add-Content $LogPath
        if ($physicalPresenceConfRet.ReturnValue -ne 0) {
            throw "Aborting clear TPM. Failed call to TPM method GetPhysicalPresenceConfirmationStatus() with ReturnValue $($physicalPresenceConfRet.ReturnValue)"
        }

        "Checking ConfirmationStatus of WMI TPM method GetPhysicalPresenceConfirmationStatus()" | Add-Content $LogPath
        "ConfirmationStatus of WMI TPM method GetPhysicalPresenceConfirmationStatus() is $($physicalPresenceConfRet.ConfirmationStatus)" | Add-Content $LogPath
        if ($physicalPresenceConfRet.ConfirmationStatus -ne 4) {
            throw "Aborting clear TPM. Call to WMI TPM method GetPhysicalPresenceConfirmationStatus() returned ConfirmationStatus $($physicalPresenceConfRet.ConfirmationStatus). Host may require user intervention or BIOS change to enable clear TPM."
        }

        "Clearing TPM by calling WMI TPM method SetPhysicalPresenceRequest() with request input parameter set to 22 (Enable + Activate + Clear + Enable + Activate)" | Add-Content $LogPath
        $physicalPresenceRequestRet = $tpmWMIObject.SetPhysicalPresenceRequest(22)
        if ($null -eq $physicalPresenceRequestRet) {
            throw "Aborting clear TPM. Failed call to WMI TPM method SetPhysicalPresenceRequest()"
        }

        "Checking ReturnValue of WMI TPM method SetPhysicalPresenceRequest()" | Add-Content $LogPath
        "ReturnValue of WMI TPM method SetPhysicalPresenceRequest() is $($physicalPresenceRequestRet.ReturnValue)" | Add-Content $LogPath
        if ($physicalPresenceRequestRet.ReturnValue -ne 0) {
            throw "Aborting clear TPM. Failed call to WMI TPM method SetPhysicalPresenceRequest() with ReturnValue $($physicalPresenceRequestRet.ReturnValue)"
        }

        "Clearing host TPM configuration has completed. Changes will be applied after reboot" | Add-Content $LogPath
    }
    catch
    {
        "Azs host clear TPM did not complete. Exception: $($_.Exception)" | Add-Content $LogPath
        "Clear TPM did not complete becaue of the following error/warning: $($_.Exception.Message)" | Add-Content $LogPath
    }
}

Export-ModuleMember -Function Get-BootDiskConfigPath
Export-ModuleMember -Function Get-ComputerNameFromUnattend
Export-ModuleMember -Function Get-LocalSMBIOSGuid
Export-ModuleMember -Function Get-LogFilePath
Export-ModuleMember -Function Get-MACAddressOutputPath
Export-ModuleMember -Function Get-UnattendFilePath
Export-ModuleMember -Function Get-NodeSpecificRemoteUnattendPath
Export-ModuleMember -Function New-NetworkDrive
Export-ModuleMember -Function Set-Debugging
Export-ModuleMember -Function Set-DiskConfiguration
Export-ModuleMember -Function Set-HostVHDBoot
Export-ModuleMember -Function Set-HostPhysicalDiskBoot
Export-ModuleMember -Function Set-WinPEDeploymentPrerequisites
Export-ModuleMember -Function Test-IpAddressBetween
Export-ModuleMember -Function Clear-AzsHostTPM

# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDYHaoZD6xTLg+t
# zYiJguVagZQN++DpOkb6FZmxUQevSaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJIWoH7Y+riqrOTF2WH1gB+Z
# PtD7r+ALFrlYAchHbZXzMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAY2VyEplQNig30UvpYGSpjw1GY3Fb/6vHbj1WGKX1cKs961aB6/TW5KlG
# 86TiLq/+V+9dBKw/Wd0B0QXOcgYH7I6skbLdhog04bU18gvlPZqkfeqE51bX2bgn
# OWHAHS1sixj0gRschHG34HFy79Yn4f/6aC1Lp4mE/gBtwQWFC+cLYsptXPddq5tm
# iIL+aANIWE5fCbSFT4PUXgkhIxstOvnNORFV5ik9zMyNKADpOb83fpf1gmyLEhyv
# yA08Tz0InH90ltI8QEpbuaQ7X39pD/tzDAV/4Vwd46Ye8luQeeWGEGLzwipuCJMk
# vKhGE/Y6VqVKkgT9b5h9kgJq3KQ9NaGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCDn+aV8TyVIvuZPGNMj2jFIevMr+vqc64dwX+m5ywd2BQIGZMvhvAuy
# GBMyMDIzMDgwNzIxMzMzOC4yNDZaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAdIhJDFKWL8tEQABAAAB0jANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
# MjFaFw0yNDAyMDExOTEyMjFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDcYIhC0QI/SPaT5+nYSBsSdhBPO2SXM40Vyyg8Fq1T
# PrMNDzxChxWUD7fbKwYGSsONgtjjVed5HSh5il75jNacb6TrZwuX+Q2++f2/8CCy
# u8TY0rxEInD3Tj52bWz5QRWVQejfdCA/n6ZzinhcZZ7+VelWgTfYC7rDrhX3TBX8
# 9elqXmISOVIWeXiRK8h9hH6SXgjhQGGQbf2bSM7uGkKzJ/pZ2LvlTzq+mOW9iP2j
# cYEA4bpPeurpglLVUSnGGQLmjQp7Sdy1wE52WjPKdLnBF6JbmSREM/Dj9Z7okxRN
# UjYSdgyvZ1LWSilhV/wegYXVQ6P9MKjRnE8CI5KMHmq7EsHhIBK0B99dFQydL1vd
# uC7eWEjzz55Z/DyH6Hl2SPOf5KZ4lHf6MUwtgaf+MeZxkW0ixh/vL1mX8VsJTHa8
# AH+0l/9dnWzFMFFJFG7g95nHJ6MmYPrfmoeKORoyEQRsSus2qCrpMjg/P3Z9WJAt
# FGoXYMD19NrzG4UFPpVbl3N1XvG4/uldo1+anBpDYhxQU7k1gfHn6QxdUU0TsrJ/
# JCvLffS89b4VXlIaxnVF6QZh+J7xLUNGtEmj6dwPzoCfL7zqDZJvmsvYNk1lcbyV
# xMIgDFPoA2fZPXHF7dxahM2ZG7AAt3vZEiMtC6E/ciLRcIwzlJrBiHEenIPvxW15
# qwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFCC2n7cnR3ToP/kbEZ2XJFFmZ1kkMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCw5iq0Ey0LlAdz2PcqchRwW5d+fitNISCv
# qD0E6W/AyiTk+TM3WhYTaxQ2pP6Or4qOV+Du7/L+k18gYr1phshxVMVnXNcdjecM
# tTWUOVAwbJoeWHaAgknNIMzXK3+zguG5TVcLEh/CVMy1J7KPE8Q0Cz56NgWzd9ur
# G+shSDKkKdhOYPXF970Mr1GCFFpe1oXjEy6aS+Heavp2wmy65mbu0AcUOPEn+hYq
# ijgLXSPqvuFmOOo5UnSV66Dv5FdkqK7q5DReox9RPEZcHUa+2BUKPjp+dQ3D4c9I
# H8727KjMD8OXZomD9A8Mr/fcDn5FI7lfZc8ghYc7spYKTO/0Z9YRRamhVWxxrIsB
# N5LrWh+18soXJ++EeSjzSYdgGWYPg16hL/7Aydx4Kz/WBTUmbGiiVUcE/I0aQU2U
# /0NzUiIFIW80SvxeDWn6I+hyVg/sdFSALP5JT7wAe8zTvsrI2hMpEVLdStFAMqan
# FYqtwZU5FoAsoPZ7h1ElWmKLZkXk8ePuALztNY1yseO0TwdueIGcIwItrlBYg1Xp
# Pz1+pMhGMVble6KHunaKo5K/ldOM0mQQT4Vjg6ZbzRIVRoDcArQ5//0875jOUvJt
# Yyc7Hl04jcmvjEIXC3HjkUYvgHEWL0QF/4f7vLAchaEZ839/3GYOdqH5VVnZrUIB
# QB6DTaUILDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkRDMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCJ
# ptLCZsE06NtmHQzB5F1TroFSBqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6HumKDAiGA8yMDIzMDgwNzE3MTkz
# NloYDzIwMjMwODA4MTcxOTM2WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDoe6Yo
# AgEAMAoCAQACAhDjAgH/MAcCAQACAhNjMAoCBQDofPeoAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBAF3O9ZIy+ReDG9zcjledOS/Pvwp64zcZzXIU8a66LPmm
# xSLJcsyPRq/UfgdFYeSRR5zwlaeNb2Q7Vgi3foNXsoIhAQDKCq1evNyF0lGUqLnG
# U+ZdbvMhts1FsXlPn7NQCybSUd7ZeW0WhF0ZCE9r051QkTumXtOAueBBtTHlac7Y
# Hg3jeLRYLxWZKJn5v/mNGUFOwkz8aSMxR4GsrulBJgVlovvZnxLo6t1md0Aj1nit
# cPQCaBdzmI0wKSIbZjBztus/YuawnAKn9yocB0xDo3NcTH9B0B9W7I8bmbE+yUuJ
# i2JD1KQh9ITWj/smbeoFAecNzRF+CuIb52uFwW0MMicxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdIhJDFKWL8tEQABAAAB
# 0jANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCCWw98fxqjTqOmk9J/Y0HPV0ZUVs+9c6GSFoAOKadxg
# XTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMeAIJPf30i9ZbOExU557GwW
# NaLH0Z5s65JFga2DeaROMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAHSISQxSli/LREAAQAAAdIwIgQg2cELRqGf2GEm5p7GuIiVTGko
# LY0wRXu0l+4iPAXTgkYwDQYJKoZIhvcNAQELBQAEggIAOqTNIfboSSj6ttquu3qN
# Wmbio5Hq4BdQ275DZG/yTJlL5vXAOK7eLttGv0QgzSX7iOiV3Y4b7QXGE4mJzQYR
# c9g8E+mpKu7BayAqIcWhv8hVqJHtSLM/OadXt57IFqiCMWZCqH3Poxa6TuyKImJ6
# /1nY+taUx++WZuZ86HTGJwU5wd0CtJPemU9iriob+B9JUkhgYSgIJPX3jofrJ4+V
# Z2cXphxW26FvBw9rNH8RiKH07LITAZBWF71smjAXi9eN7jp9Cyyr05SR0SYrMc4m
# +ftUgG6zmZ/yLkzOooPO1GxiQhypw6X0W5iMSPkS0uKNHea5fXzJsYu0Twq1zNjf
# YkJJt4tc7xEM0wCWXw1jdGOApM3KmpFo0r7oAUyYCXHFQrxEpalFXRPX/Si6Cchj
# 4Mki6sSk4rxYjdLYbNNjaTVQ/T+AHdjCkJiahPaSudq6Nc01GNcYUFLxzwq6gEga
# Je0C1L4+ibIpb8xez+tQav+mQ+WqkIXD4YoecaqzCs7NrPHw8ZYGhb6VfG0wBg+O
# 48LF4+3Gob+1a+Sd24lFUOobhBX4kacfuABWLzQ09899GTEhv8W2dc8KxNFYMzMJ
# 0ZVyJo9OVn+38tBV0CZNt9TWUpbA7K+sUbx+TnnXG6b1h7pdJpmqqecb5ZKgf/iJ
# ANgxPP/77YBohovAH58PwcU=
# SIG # End signature block