Skip to content

Instantly share code, notes, and snippets.

@mritsurgeon
Created January 8, 2020 10:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mritsurgeon/f06587b9161711fb40fa91bf595a8af9 to your computer and use it in GitHub Desktop.
Save mritsurgeon/f06587b9161711fb40fa91bf595a8af9 to your computer and use it in GitHub Desktop.
ASDK Bare Metal test Netsed edit
<###################################################
# #
# Copyright (c) Microsoft. All rights reserved. #
# #
##################################################>
# These tests are expected to run only as part of initial deployment of a stamp.
Import-LocalizedData CommonLocalizedStrings -BaseDirectory $PSScriptRoot\..\..\Common -FileName Roles.Strings.psd1 -ErrorAction SilentlyContinue
Import-LocalizedData BareMetalLocalizedStrings -BaseDirectory $PSScriptRoot\.. -FileName PhysicalMachines.Strings.psd1 -ErrorAction SilentlyContinue
Import-Module -Name "$PSScriptRoot\..\..\Common\RoleHelpers.psm1"
Import-Module -Name "$PSScriptRoot\..\..\..\ECEngine\Telemetry.dll"
# Disk information of the physical host.
# Some fields are used telemetry/logging only:
# FriendlyName/SerialNumber/DeviceId/Manufacturer/Model/MediaType/FirmwareVersion
class Disk : System.IComparable
{
[string] $FriendlyName
[string] $SerialNumber
[string] $BusType
[string] $DeviceId
[string] $Manufacturer
[string] $Model
[string] $MediaType
[string] $HealthStatus
[long] $Size
[string] $FirmwareVersion
[string] $UniqueId
[int] $IndexWithinSimilarDisks
[int] CompareTo([object] $obj)
{
[Disk] $other = $obj -as [Disk]
if (-not $other)
{
throw new System.ArgumentException("The object to compare with is not of type Disk.")
}
[int] $busTypeComparisonResult = $this.BusType.CompareTo($other.BusType)
if ($busTypeComparisonResult -ne 0)
{
return $busTypeComparisonResult
}
[int] $indexComparisonResult = $this.IndexWithinSimilarDisks.CompareTo($other.IndexWithinSimilarDisks)
if ($indexComparisonResult -ne 0)
{
return $indexComparisonResult
}
[int] $sizeComparisonResult = $this.Size.CompareTo($other.Size)
return $sizeComparisonResult
}
[bool] Equals([object] $other)
{
return $this.CompareTo($other) -eq 0
}
[int] GetHashCode()
{
return [Tuple]::Create($this.BusType, $this.Size, $this.IndexWithinSimilarDisks).GetHashCode()
}
}
class NetworkAdapter : System.IComparable
{
[long] $Speed
[string] $IsRdmaEnabled
[string] $Name
[string] $InterfaceIndex
[string] $InterfaceDescription
[string] $DriverInformation
[string] $DriverName
[int] $MtuSize
[int] CompareTo([object] $obj)
{
[NetworkAdapter] $other = $obj -as [NetworkAdapter]
if (-not $other)
{
throw new System.ArgumentException("The object to compare with is not of type NetworkAdapter.")
}
[int] $speedComparisonResult = $this.Speed.CompareTo($other.Speed)
if ($speedComparisonResult -ne 0)
{
return $speedComparisonResult
}
[int] $isRdmaEnabledComparisonResult = $this.IsRdmaEnabled.CompareTo($other.IsRdmaEnabled)
return $isRdmaEnabledComparisonResult
}
[bool] Equals([object] $other)
{
return $this.CompareTo($other) -eq 0
}
[int] GetHashCode()
{
return [Tuple]::Create($this.Speed, $this.IsRdmaEnabled).GetHashCode()
}
}
class ScsiAdapter
{
[string] $DeviceName
[string] $Description
[string] $DriverVersion
[string] $DriverDate
[string] $Location
[string] $HardWareID
}
class Processor
{
[string] $NumberOfEnabledCores
[bool] $HasVMMonitorModeExtensions
[bool] $IsVirtualizationEnabled
[bool] $SupportsSecondLevelAddressTranslation
[string] $Name
[string] $Caption
[string] $Model
[string] $Socket
[string] $DriverVersion
[string] $DriverDate
[string] $TotalCores
[string] $TotalThreads
[string] $MicrocodeVersion
}
# BIOS information of the physical host. Used for telemetry only
class Bios
{
[string] $Name
[string] $Manufacturer
[string] $SerialNumber
[string] $Version
[string] $SMBIOSBIOSVersion
[string] $SMBIOSMajorVersion
[string] $SMBIOSMinorVersion
[string] $UUID
}
class Memory
{
[string] $Name
[string] $Manufacturer
[uint64] $Capacity
[string] $SerialNumber
[string] $MemoryType
[string] $PartNumber
[string] $DeviceLocator
[uint32] $ConfiguredClockSpeed
[string] $Model
[string] $PoweredOn
[string] $Status
}
class PcieDevices
{
[string] $Name
[string] $Id
[string] $ManufacturerName
[string] $DriverVersion
[string] $PNPDeviceID
[string] $Status
}
class PhysicalMachine
{
[string] $ComputerName
[string] $IPAddress
[bool] $IsVirtualMachine
[bool] $IsHyperVServiceRunning
[bool] $IsDataExecutionPreventionEnabled
[string] $OSVersion
[int] $OSSku
[bool] $IsPartOfDomain
[long] $TotalPhysicalMemory
[Processor[]] $Processors
[Disk] $SystemDisk
[Disk[]] $NonSystemDisks
[NetworkAdapter[]] $NetworkAdapters
[Disk[]] $PhysicalDisks
[Bios] $Bios
[ScsiAdapter[]] $ScsiAdapters
[Memory[]] $Memories
[PcieDevices[]] $Gpu
}
# Validation is done on three stages.
# First, data about the configuration of the machines is retrieved from all of the machines.
# Second, the hardware is emitted for telemetry purpose. (not related to the validation itself).
# After that, the data is checked to find potential problems.
Describe "Validate-Role" `
{
# Before we start, validate that the expected parameters have been passed.
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "MachineNameToIPAddress") `
{
$Parameters.MachineNameToIPAddress | Should Not BeNullOrEmpty
}
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "Credential") `
{
($Parameters.Credential -eq $null) | Should Be $false
}
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "Topology") `
{
$Parameters.Topology | Should Not BeNullOrEmpty
}
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "PhysicalMachinesRole") `
{
$Parameters.PhysicalMachinesRole | Should Not BeNullOrEmpty
}
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "DeploymentGuid") `
{
$Parameters.DeploymentGuid | Should Not BeNullOrEmpty
}
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "ClusterRole") `
{
$Parameters.OEMPublisher | Should Not BeNullOrEmpty
}
It ($CommonLocalizedStrings.TestParameterShouldBeProvided -f "OEMPublisher") `
{
$Parameters.OEMPublisher | Should Not BeNullOrEmpty
}
$isOneNode = ($Parameters.Topology -eq 'OneNode')
$isVirtualizedDeployment = ($Parameters.OEMModel -eq 'Hyper-V')
$physicalMachinesRole = $Parameters.PhysicalMachinesRole
$clusterRole = $Parameters.ClusterRole
# Validate that the expected settings that are needed for validation have been specified in CustomerConfig.xml.
It ($BareMetalLocalizedStrings.TestCustomerConfigParameters) `
{
$script:minimumOperatingSystemVersion = New-Object Version($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumOperatingSystemVersion)
$script:minimumNumberOfCoresPerMachine = [int]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumNumberOfCoresPerMachine)
$script:minimumPhysicalMemoryPerMachineGB = [long]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumPhysicalMemoryPerMachineGB)
$script:minimumNumberOfAdaptersPerMachine = [int]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumNumberOfAdaptersPerMachine)
$script:minimumAdapterSpeedBitsPerSecond = [long]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumAdapterSpeedBitsPerSecond)
$script:minimumSizeOfSystemDiskGB = [long]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumSizeOfSystemDiskGB)
$script:minimumSizeOfDataDisksGB = [long]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumSizeOfDataDisksGB)
$script:minimumNumberOfDataDisksPerMachine = [int]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MinimumNumberOfDataDisksPerMachine)
$script:maximumNumberOfMissingDisksPerMachine = [int]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.MaximumNumberOfMissingDisksPerMachine)
$script:sizeComparisonTolerancePercentage = [int]::Parse($physicalMachinesRole.PrivateInfo.ValidationRequirements.SizeComparisonTolerancePercentage)
$script:toleranceMultiplier = 1 + ($sizeComparisonTolerancePercentage / 100.0)
}
# Validate the number of machines and that, in the one-node case, the test is running on the physical machine.
if ($isOneNode)
{
It ($BareMetalLocalizedStrings.TestExactlyOneMachine) `
{
$Parameters.MachineNameToIPAddress.Count | Should Be 1
}
foreach ($physicalMachineName in $Parameters.MachineNameToIPAddress.Keys)
{
It ($BareMetalLocalizedStrings.TestRunningOnPhysicalMachine -f $physicalMachineName) `
{
$physicalMachineName | Should Be $env:COMPUTERNAME
}
}
# For OneNode, there is no need to use the IPv4 address because there will not be any IPv6 address problems. Use
# "localhost" for the host address instead of the IPv4 address so that CredSSP is not needed.
$Parameters.MachineNameToIPAddress[$env:COMPUTERNAME] = "localhost"
}
###################################################################################################################################################
# Stage 1: Query for machine information
# Querying for machine information will be kept separate from the checks that use this information, because in the future this machine information
# may be retrieved as part of a discovery stage.
###################################################################################################################################################
$physicalMachines = [PhysicalMachine[]]@()
[System.Collections.Hashtable[]] $physicalMachinesInventoryInfo = @()
foreach ($machineName in $Parameters.MachineNameToIPAddress.Keys)
{
$ipAddress = $Parameters.MachineNameToIPAddress[$machineName];
$physicalMachine = New-Object PhysicalMachine -Property @{ ComputerName=$machineName; IPAddress=$ipAddress }
Trace-Execution "Querying machine information for machine $machineName with address $ipAddress"
It ($CommonLocalizedStrings.TestConnection -f $machineName) `
{
if($machineName -eq $env:COMPUTERNAME)
{
$script:cimSession = New-CimSession -Credential $Parameters.Credential
}
else
{
$script:cimSession = New-CimSession -ComputerName $ipAddress -Credential $Parameters.Credential
}
$currentMachineIdString = Invoke-CimMethod -CimSession $cimSession -NameSpace "root/cimv2" -Class "StdRegProv" -MethodName "GetStringValue" -Arguments @{
'hDefKey' = [uint32]'0x80000002'
'sSubKeyName' = "Software\Microsoft\SQMClient"
'sValueName' = "MachineId"
}
[System.Guid] $tmpGuid = [System.Guid]::Empty
if ([System.Guid]::TryParse($currentMachineIdString.sValue, [ref]$tmpGuid))
{
$script:machineId = $tmpGuid.ToString()
}
else
{
$script:machineId = ""
}
}
#
# Start: Computer and OS information
#
It ($BareMetalLocalizedStrings.TestRetrieveComputerSystemInformation -f $machineName) `
{
Trace-Execution "Querying Win32_ComputerSystem."
$computerSystemWmiResults = Get-CimInstance -CimSession $cimSession -Query "select TotalPhysicalMemory, PartOfDomain, Manufacturer from Win32_ComputerSystem"
$physicalMachine.TotalPhysicalMemory = $computerSystemWmiResults.TotalPhysicalMemory
$physicalMachine.IsPartOfDomain = $computerSystemWmiResults.PartOfDomain
$physicalMachine.IsVirtualMachine = $computerSystemWmiResults.Manufacturer.ToLower() -match 'microsoft corporation' -or `
$computerSystemWmiResults.Manufacturer.ToLower() -match 'vmware'
Trace-Execution "Manufacturer reported by Win32_ComputerSystem is $($computerSystemWmiResults.Manufacturer)"
Trace-Execution "Total physical memory is $($computerSystemWmiResults.TotalPhysicalMemory)"
Trace-Execution "PartOfDomain is $($computerSystemWmiResults.PartOfDomain)"
}
It ($BareMetalLocalizedStrings.TestRetrieveOperatingSystemInformation -f $machineName) `
{
Trace-Execution "Querying Win32_OperatingSystem."
$operatingSystemWmiResults = Get-CimInstance -CimSession $cimSession -Query "select Version, OperatingSystemSKU, DataExecutionPrevention_Available from Win32_OperatingSystem"
$physicalMachine.OSVersion = $operatingSystemWmiResults.Version
$physicalMachine.OSSku = $operatingSystemWmiResults.OperatingSystemSKU
$physicalMachine.IsDataExecutionPreventionEnabled = $operatingSystemWmiResults.DataExecutionPrevention_Available
Trace-Execution "OS version is $($operatingSystemWmiResults.Version)"
Trace-Execution "OS SKU is $($operatingSystemWmiResults.OperatingSystemSKU)"
Trace-Execution "DataExecutionPreventionAvailable is $($operatingSystemWmiResults.DataExecutionPrevention_Available)"
}
It ($BareMetalLocalizedStrings.TestRetrieveHyperVServiceInformation -f $machineName) `
{
Trace-Execution "Querying Win32_Service."
$hyperVServiceWmiResults = Get-CimInstance -CimSession $cimSession -Query "select State from Win32_Service where name='vmms'"
$physicalMachine.IsHyperVServiceRunning = $false
if ($hyperVServiceWmiResults)
{
Trace-Execution "Hyper-V service was found."
Trace-Execution "State of Hyper-V service is $($hyperVServiceWmiResults.State)"
$physicalMachine.IsHyperVServiceRunning = ($hyperVServiceWmiResults.State -eq 'Running')
}
}
#
# End: Computer and OS information
#
#
# Start: Processor information
#
It ($BareMetalLocalizedStrings.TestRetrieveProcessorInformation -f $machineName) `
{
function GetCpuMicrocodeVersionString
{
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[Byte[]] $littleEndianBytes
)
$retStr = ""
if ($null -eq $littleEndianBytes -or $littleEndianBytes.Length -eq 0 -or $littleEndianBytes.Length % 4 -ne 0)
{
return $retStr
}
# We are expecting an array that represents one INT32 integer.
# For some OEM, the array might contains multiple INT32 numbers. In that case the leading number should
# be 0: the 1st 4 bytes represents 1st integer, the 2nd 4 bytes represents 2nd integer, etc.
# So cannot just revert the byte array to get the correct HEX string: should do some workaround here.
[String] $littleEndianString = [System.BitConverter]::ToString($littleEndianBytes).Replace("-", "")
[int] $littleEndianNumber = 0
try
{
$littleEndianNumber = [System.Convert]::ToInt32($littleEndianString, 16)
}
catch [System.OverflowException]
{
# In case the input array contains multiple INT32 numbers, but the leading numbers are not 0
return $retStr
}
[Byte[]] $bytes = [System.BitConverter]::GetBytes($littleEndianNumber)
foreach ($b in $bytes)
{
$retStr += $b.ToString("X2");
}
return $retStr
}
Trace-Execution "Querying processor information."
$currentCpuMicrocodeBinaryArray = Invoke-CimMethod -CimSession $cimSession -NameSpace "root/cimv2" -Class "StdRegProv" -MethodName "GetBinaryValue" -Arguments @{
'hDefKey' = [uint32]'0x80000002'
'sSubKeyName' = "HARDWARE\DESCRIPTION\System\CentralProcessor\0"
'sValueName' = "Update Revision"
}
$currentCpuMicrocodeVersion = GetCpuMicrocodeVersionString -littleEndianBytes $currentCpuMicrocodeBinaryArray.uValue
$processorWmiResults = Get-CimInstance -CimSession $cimSession -Class Win32_Processor
$physicalMachine.Processors = [Processor[]]@()
$processorWmiResults | % `
{
$physicalMachine.Processors += (New-Object Processor -Property @{ NumberOfEnabledCores=$_.NumberOfEnabledCore;
HasVMMonitorModeExtensions=$_.VMMonitorModeExtensions;
IsVirtualizationEnabled=$_.VirtualizationFirmwareEnabled;
SupportsSecondLevelAddressTranslation=$_.SecondLevelAddressTranslationExtensions;
Name = $_.DeviceId;
Caption = $_.Caption;
Model = $_.Name;
Socket = $_.SocketDesignation;
TotalCores = $_.NumberOfCores;
TotalThreads = $_.NumberOfLogicalProcessors;
MicrocodeVersion = $currentCpuMicrocodeVersion
})
}
$pnpSignedDrivers = Get-CimInstance -CimSession $cimSession -ClassName "Win32_PnPSignedDriver" `
| Where-Object FriendlyName -ne $null `
| Select-Object FriendlyName, DriverVersion, DriverDate
foreach ($processor in $physicalMachine.Processors)
{
foreach ($pnpSignedDriver in $pnpSignedDrivers)
{
if ($pnpSignedDriver.FriendlyName -eq $processor.Model)
{
$processor.DriverVersion = $pnpSignedDriver.DriverVersion
$processor.DriverDate = $pnpSignedDriver.DriverDate
}
}
}
Trace-Execution "Found processors with the following properties:"
Trace-Execution ($physicalMachine.Processors | out-string)
}
#
# End: Processor information
#
#
# Start: Disk information
#
It ($BareMetalLocalizedStrings.TestRetrieveDiskInformation -f $machineName) `
{
Trace-Execution "Querying disk information."
$systemDisk = Get-Disk -CimSession $cimSession | ? { $_.IsSystem }
$physicalMachine.SystemDisk = New-Object Disk -Property @{ BusType=$systemDisk.BusType; Size=$systemDisk.Size }
$physicalMachine.NonSystemDisks = [Disk[]]@()
Get-PhysicalDisk -CimSession $cimSession | ? DeviceId -ne $systemDisk.Number | % `
{
$physicalMachine.NonSystemDisks += New-Object Disk -Property @{
BusType=$_.BusType
Size=$_.Size
FriendlyName = $_.FriendlyName
SerialNumber = $_.SerialNumber
Manufacturer = $_.Manufacturer
DeviceId = $_.DeviceId
Model = $_.Model
MediaType = $_.MediaType
HealthStatus = $_.HealthStatus
FirmwareVersion = $_.FirmwareVersion
}
}
$physicalMachine.NonSystemDisks = ($physicalMachine.NonSystemDisks | sort)
$physicalMachine.NonSystemDisks[0].IndexWithinSimilarDisks = 0
for ($i = 1; $i -lt $physicalMachine.NonSystemDisks.Count; $i++)
{
if ($physicalMachine.NonSystemDisks[$i].BusType -eq $physicalMachine.NonSystemDisks[$i-1].BusType -and
$physicalMachine.NonSystemDisks[$i].Size * $toleranceMultiplier -ge $physicalMachine.NonSystemDisks[$i-1].Size -and
$physicalMachine.NonSystemDisks[$i].Size -le $physicalMachine.NonSystemDisks[$i-1].Size * $toleranceMultiplier)
{
$physicalMachine.NonSystemDisks[$i].IndexWithinSimilarDisks = $physicalMachine.NonSystemDisks[$i-1].IndexWithinSimilarDisks + 1
}
else
{
$physicalMachine.NonSystemDisks[$i].IndexWithinSimilarDisks = 0
}
}
# Get information check for physical disk that attached to current machine only
$hostLocalStorageSubSystems = Get-StorageSubSystem -CimSession $cimSession | Where-Object { $_.FriendlyName -like "Cluster*" }
if ($hostLocalStorageSubSystems.Count -eq 0)
{
# No SOFS enabled, try to get Windows Storage itself
$hostLocalStorageSubSystems = Get-StorageSubSystem -CimSession $cimSession | Where-Object { $_.FriendlyName -like "Windows Storage*" }
}
$hostLocalStorageNodes = Get-StorageNode -StorageSubSystem $hostLocalStorageSubSystems[0] -CimSession $cimSession
$currentHostStorageNode = $hostLocalStorageNodes | Where-Object { $_.Name -like "$($machineName)*" }
Get-PhysicalDisk -PhysicallyConnected -StorageNode $currentHostStorageNode -CimSession $cimSession | Where-Object Model -ne "Virtual Disk" | ForEach-Object `
{
$physicalMachine.PhysicalDisks += New-Object Disk -Property @{
BusType=$_.BusType
Size=$_.Size
FriendlyName = $_.FriendlyName
SerialNumber = $_.SerialNumber
Manufacturer = $_.Manufacturer
DeviceId = $_.DeviceId
Model = $_.Model
MediaType = $_.MediaType
HealthStatus = $_.HealthStatus
FirmwareVersion = $_.FirmwareVersion
UniqueId = $_.UniqueId.Trim()
}
}
Trace-Execution "Found system disk with the following properties:"
Trace-Execution ($physicalMachine.SystemDisk | out-string)
Trace-Execution "Found non-system disks with the following properties:"
Trace-Execution ($physicalMachine.NonSystemDisks | out-string)
}
#
# End: Disk information
#
#
# Start: BIOS information
#
It ($BareMetalLocalizedStrings.TestRetrieveBIOSInformation -f $machineName) `
{
Trace-Execution "Querying BIOS information."
# BIOS properties need to be collected
Get-CimInstance -CimSession $cimSession -ClassName "Win32_BIOS" | Select-Object -First 1 | ForEach-Object `
{
$physicalMachine.Bios = New-Object Bios -Property @{
Name = $_.Name
Manufacturer = $_.Manufacturer
SerialNumber = $_.SerialNumber
Version = $_.Version
SMBIOSBIOSVersion = $_.SMBIOSBIOSVersion
SMBIOSMajorVersion = $_.SMBIOSMajorVersion
SMBIOSMinorVersion = $_.SMBIOSMinorVersion
}
}
Get-CimInstance -CimSession $cimSession -ClassName "Win32_ComputerSystemProduct" | Select-Object -First 1 | ForEach-Object `
{
$physicalMachine.Bios.UUID = $_.UUID
}
}
#
# End: BIOS information
#
#
# Start: Network adapter information
#
#
# Start: Memory information
#
{
# Get memory type string from CIM memory type number
# Refer to https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-physicalmemory
function Get-MemoryTypeString
{
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[string] $MemoryType
)
switch ($MemoryType)
{
"0"
{
$retVal = "Unknown"
break
}
"1"
{
$retVal = "Other"
break
}
"2"
{
$retVal = "DRAM"
break
}
"3"
{
$retVal = "Synchronous DRAM"
break
}
"4"
{
$retVal = "Cache DRAM"
break
}
"5"
{
$retVal = "EDO"
break
}
"6"
{
$retVal = "EDRAM"
break
}
"7"
{
$retVal = "VRAM"
break
}
"8"
{
$retVal = "SRAM"
break
}
"9"
{
$retVal = "RAM"
break
}
"10"
{
$retVal = "ROM"
break
}
"11"
{
$retVal = "Flash"
break
}
"12"
{
$retVal = "EEPROM"
break
}
"13"
{
$retVal = "FEPROM"
break
}
"14"
{
$retVal = "EPROM"
break
}
"15"
{
$retVal = "CDRAM"
break
}
"16"
{
$retVal = "3DRAM"
break
}
"17"
{
$retVal = "SDRAM"
break
}
"18"
{
$retVal = "SGRAM"
break
}
"19"
{
$retVal = "RDRAM"
break
}
"20"
{
$retVal = "DDR"
break
}
"21"
{
$retVal = "DDR2"
break
}
"22"
{
$retVal = "DDR2 FB-DIMM"
break
}
"24"
{
$retVal = "DDR3"
break
}
"25"
{
$retVal = "FBD2"
break
}
default
{
$retVal = "Unknown"
break
}
}
return $retVal
}
Trace-Execution "Querying Memory information."
$memoryWmiResults = Get-CimInstance -CimSession $cimSession -Class Win32_PhysicalMemory
$physicalMachine.Memories = [Memory[]]@()
$memoryWmiResults | ForEach-Object `
{
$currentMemoryTypeString = Get-MemoryTypeString -MemoryType $_.MemoryType
$physicalMachine.Memories += (New-Object Memory -Property @{ Name = $_.Name;
Manufacturer = $_.Manufacturer;
Capacity = $_.Capacity;
SerialNumber = $_.SerialNumber;
MemoryType = $currentMemoryTypeString;
DeviceLocator = $_.DeviceLocator;
ConfiguredClockSpeed = $_.ConfiguredClockSpeed;
Model = $_.Model;
PoweredOn = $_.PoweredOn;
Status = $_.Status })
}
Trace-Execution "Found memories with the following properties:"
Trace-Execution ($physicalMachine.Memories | out-string)
}
#
# Start: SCSI Adapter information
#
It ($BareMetalLocalizedStrings.TestRetrieveScsiAdapterInformation -f $machineName) `
{
Trace-Execution "Querying SCSI Adapter information."
# SCSI properties need to be collected
$scsiAdapterWmiResults = Get-CimInstance -CimSession $cimSession -ClassName "Win32_PnPSignedDriver" `
| Where-Object DeviceClass -eq "SCSIADAPTER" `
| Where-Object Location -ne $null
$physicalMachine.ScsiAdapters = [ScsiAdapter[]]@()
$scsiAdapterWmiResults | ForEach-Object `
{
$physicalMachine.ScsiAdapters += (New-Object ScsiAdapter -Property @{
DeviceName = $_.DeviceName
Description = $_.Description
DriverVersion = $_.DriverVersion
DriverDate = $_.DriverDate
HardWareID = $_.HardWareID
})
}
Trace-Execution "Found SCSI adapters with the following properties:"
Trace-Execution ($physicalMachine.ScsiAdapters | out-string)
Trace-Execution "Completed querying machine information for machine $machineName with address $physicalMachine"
}
#
# Start: GPU Adapter information
#
It ($BareMetalLocalizedStrings.TestRetrieveGPUInformation -f $machineName) `
{
Trace-Execution "Querying GPU information."
# GPU properties need to be collected
$gpuWmiResults = Get-CimInstance -CimSession $cimSession -ClassName "Win32_VideoController" `
| Where-Object Name -NotLike "*Microsoft*"
$physicalMachine.Gpu = [PcieDevices[]]@()
$gpuWmiResults | ForEach-Object `
{
$physicalMachine.Gpu += (New-Object PcieDevices -Property @{
Id = $_.DeviceId
Name = $_.Name
DriverVersion = $_.DriverVersion
PNPDeviceID = $_.PNPDeviceID
Status = $_.Status
ManufacturerName = $_.AdapterCompatibility
})
}
Trace-Execution "Found GPU adapters with the following properties:"
Trace-Execution ($gpuData | out-string)
Remove-CimSession -CimSession $cimSession
Trace-Execution "Completed querying machine information for machine $machineName with address $physicalMachine"
}
$physicalMachines += $physicalMachine
# BIOS JSON be saved into hardware inventory information (ECE store and stamp info file)
$biosJson = [PSCustomObject]@{
'@odata.context' = '/redfish/v1/$metadata#ComputerSystem.ComputerSystem'
'@odata.id' = "/redfish/v1/Systems/$machineName"
'@odata.type' = "#ComputerSystem.v1_5_0.ComputerSystem"
Id = $machineName
Name = $machineName
SerialNumber = $physicalMachine.Bios.SerialNumber
BiosVersion = $physicalMachine.Bios.SMBIOSBIOSVersion
UUID = $physicalMachine.Bios.UUID
Oem = [PSCustomObject]@{
$Parameters.oemPublisher = [PSCustomObject]@{
Name = $physicalMachine.Bios.Name
BIOS_Manufacturer = $physicalMachine.Bios.Manufacturer
BIOS_Version = $physicalMachine.Bios.Version
BIOS_SMBIOSMajorVersion = $physicalMachine.Bios.SMBIOSMajorVersion
BIOS_SMBIOSMinorVersion = $physicalMachine.Bios.SMBIOSMinorVersion
MachineId = $machineId
}
}
}
$processorsJson = [PSCustomObject[]]@()
foreach ($processor in $physicalMachine.Processors)
{
$processorsJson += [PSCustomObject]@{
'@odata.context' = '/redfish/v1/$metadata#Processor.Processor'
'@odata.id' = "/redfish/v1/Systems/$machineName/Processors/$($processor.Name)"
'@odata.type' = "#Processor.v1_3_0.Processor"
Id = $processor.Name
Name = $processor.Name
Model = $processor.Model
Socket = $processor.Socket
TotalCores = $processor.TotalCores
TotalThreads = $processor.TotalThreads
Oem = [PSCustomObject]@{
$Parameters.oemPublisher = [PSCustomObject]@{
Caption = $processor.Caption
NumberOfEnabledCores = $processor.NumberOfEnabledCores
MicrocodeVersion = $processor.MicrocodeVersion
DriverDate = $processor.DriverDate
DriverVersion = $processor.DriverVersion
}
}
}
}
$netAdaptersJson = [PSCustomObject[]] @()
foreach ($netAdapter in $physicalMachine.NetworkAdapters)
{
$netAdaptersJson += [PSCustomObject]@{
'@odata.context' = '/redfish/v1/$metadata#EthernetInterface.EthernetInterface'
'@odata.id' = "/redfish/v1/Systems/$machineName/EthernetInterfaces/$($netAdapter.InterfaceIndex)"
'@odata.type' = "#EthernetInterface.v1_4_0.EthernetInterface"
Id = $netAdapter.InterfaceIndex
Name = $netAdapter.InterfaceDescription
SpeedMbps = $netAdapter.Speed / 1000000
MtuSize = $netAdapter.MtuSize
Oem = [PSCustomObject]@{
$Parameters.oemPublisher = [PSCustomObject]@{
DriverName = $netAdapter.DriverName
IsRdmaEnabled = $netAdapter.IsRdmaEnabled
DriverInformation = $netAdapter.DriverInformation
}
}
}
}
$diskDrivesJson = [PSCustomObject[]] @()
foreach ($diskDrive in $physicalMachine.PhysicalDisks)
{
$diskStatus = "Warning"
switch ($diskDrive.HealthStatus)
{
"Healthy" { $diskStatus = "OK" }
"Warning" { $diskStatus = "Warning" }
"Unhealthy" { $diskStatus = "Critical" }
Default { $diskStatus = "Warning" }
}
$diskDrivesJson += [PSCustomObject]@{
'@odata.context' = '/redfish/v1/$metadata#Drive.Drive'
'@odata.id' = "/redfish/v1/Systems/$machineName/Storage/1/Drives/$($diskDrive.SerialNumber)"
'@odata.type' = "#Drive.v1_4_0.Drive"
Id = $diskDrive.SerialNumber
Name = $diskDrive.FriendlyName
SerialNumber = $diskDrive.SerialNumber
Protocol = $diskDrive.BusType
Manufacturer = $diskDrive.Manufacturer
Model = $diskDrive.Model
MediaType = $diskDrive.MediaType
Status = [PSCustomObject] @{
Health = $diskStatus
}
CapacityBytes = $diskDrive.Size
Revision = $diskDrive.FirmwareVersion
Oem = [PSCustomObject]@{
$Parameters.oemPublisher = [PSCustomObject]@{
DeviceId = $diskDrive.DeviceId
UniqueId = $diskDrive.UniqueId
}
}
}
}
$memoriesJson = [PSCustomObject[]] @()
foreach ($memory in $physicalMachine.Memories)
{
$currentMemoryCapacityMiB = $memory.Capacity/(1024*1024)
$memoriesJson += [PSCustomObject] @{
'@odata.context' = '/redfish/v1/$metadata#Memory.Memory'
'@odata.id' = "/redfish/v1/Systems/$machineName/Memory/$($memory.DeviceLocator)"
'@odata.type' = "#Memory.v1_5_0.Memory"
Id = $memory.DeviceLocator
Name = $memory.Name
CapacityMiB = $currentMemoryCapacityMiB
DeviceLocator = $memory.DeviceLocator
Manufacturer = $memory.Manufacturer
MemoryType = $memory.MemoryType
OperatingSpeedMhz = $memory.ConfiguredClockSpeed
PartNumber = $memory.PartNumber
SerialNumber = $memory.SerialNumber
Oem = [PSCustomObject]@{
$Parameters.oemPublisher = [PSCustomObject]@{
Model = $memory.Model
PoweredOn = $memory.PoweredOn
Status = $memory.Status
}
}
}
}
$gpuDataJson = [PSCustomObject[]] @()
foreach ($gpudata in $physicalMachine.GPU)
{
$gpuDataJson += [PSCustomObject] @{
'@odata.context' = '/redfish/v1/$metadata#PCIeDevice.PCIeDevice'
'@odata.id' = "/redfish/v1/Systems/$machineName/PCIeDevice/$($gpudata.Id)"
'@odata.type' = "#PCIeDevice.v1_3_1.PCIeDevice"
Id = $gpudata.Id
Name = $gpudata.Name
Oem = [PSCustomObject]@{
$gpudata.ManufacturerName = [PSCustomObject]@{
DriverVersion = $gpudata.DriverVersion
PNPDeviceID = $gpudata.PNPDeviceID
Status = $gpudata.Status
}
}
}
}
$physicalMachinesInventoryInfo += @{
"$machineName" = @{
"ComputerSystem" = $biosJson
"Memory" = $memoriesJson
"Processor" = $processorsJson
"Drive" = $diskDrivesJson
"EthernetInterface" = $netAdaptersJson
"ScsiAdapters" = $physicalMachine.ScsiAdapters
"PcieDevices" = $gpuDataJson
}
}
}
# Got inventory info for all hosts in $physicalMachinesInventoryInfo. Now output to JSON
$hostInventoryLog = $physicalMachinesRole.PublicInfo.HardwareInventoryInformaton.Path
$hostInventoryLog = $ExecutionContext.InvokeCommand.ExpandString($hostInventoryLog)
$hostInventoryLogFolder = Split-Path -Path $hostInventoryLog -Parent
if (-not (Test-Path -Path $hostInventoryLogFolder -PathType Container))
{
New-Item -Path $hostInventoryLogFolder -ItemType Container
}
if (Test-Path -Path $hostInventoryLog -PathType Leaf)
{
Trace-Execution "Remove existing hardware inventory log file [$hostInventoryLog]."
Remove-Item -Path $hostInventoryLog -Force -ErrorAction Ignore | Out-null
}
# Now getting the information formatted as expected. For example
# "TimeOfCollection":
# "HardwareInventoryInfo": {
# "s-cluster" : {
# "NODE1": {
# ...
# },
# "NODE2": {
# ...
# },
# ...
# "NODEx": {
# ...
# }
# }
# }
$clusters = $ClusterRole.Clusters.Node
$finalInventoryJsonData = @{}
foreach ($cluster in $clusters)
{
# Get machines' name in current cluster
$currentClusterMachines = ($physicalMachinesRole.Nodes.Node | Where-Object {$_.RefClusterId -eq $cluster.Id}).Name
# Prepare information for current cluster
$currentClusterInfo = @{}
foreach ($machine in $currentClusterMachines)
{
if ($physicalMachinesInventoryInfo.$machine)
{
$currentClusterInfo += @{ $machine = $physicalMachinesInventoryInfo.$machine }
}
}
$finalInventoryJsonData += @{ $cluster.Name = $currentClusterInfo}
}
Trace-Execution "Saving new host hardware inventory information at [$hostInventoryLog]..."
$hardwareInventoryInfoJson = @{
"TimeOfCollection" = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss")
"HardwareInventoryInfo" = $finalInventoryJsonData
}
ConvertTo-Json $hardwareInventoryInfoJson -Depth 99 | Add-Content -Path $hostInventoryLog
###################################################################################################################################################
# Stage 2: Emit the hardware information for telemetry purpose
###################################################################################################################################################
$physicalMachines | ForEach-Object {
Send-HardwareTelemetry -DeploymentGuid $Parameters.DeploymentGuid -ValidationRequirements ($physicalMachinesRole.PrivateInfo.ValidationRequirements.InnerXml) -HardwareInformation ($_ | ConvertTo-Json -Depth 10 -Compress)
}
Trace-Execution "Hardware information for Deployment $($Parameters.DeploymentGuid) has been emitted."
###################################################################################################################################################
# Stage 3: Run validations based on the machine information that has been retrieved.
###################################################################################################################################################
# Validate this is not a virtual machine.
if ($isVirtualizedDeployment)
{
foreach ($physicalMachine in $physicalMachines)
{
It ($BareMetalLocalizedStrings.TestNotVirtualMachine -f @($physicalMachine.ComputerName)) `
{
$physicalMachine.IsVirtualMachine | Should Be $true
}
}
}
# Validate Hyper-V support. Validation is based partially on code in http://wincode/View.aspx?File=%2f%2fdepot%2fth2%2fvm%2fsetup%2fServerManager%2fHyper-V%2fPlugin%2fHardwareDetectionUtilities.cs%232&SDPort=vmdepot.sys-ntgroup.ntdev.microsoft.com:2022&Query=VirtualizationFirmwareEnabled&Catalog=th2(dev)
foreach ($physicalMachine in $physicalMachines)
{
if ($physicalMachine.IsHyperVServiceRunning)
{
Trace-Execution "The Hyper-V service is running on physical machine $($physicalMachine.ComputerName). There is no need to check for virtualization support on this machine."
continue
}
It ($BareMetalLocalizedStrings.TestVMMonitorModeExtensions -f @($($physicalMachine.ComputerName))) `
{
($physicalMachine.Processors.HasVMMonitorModeExtensions | Measure-Object -Minimum).Minimum | Should Be $true
}
It ($BareMetalLocalizedStrings.TestVirtualizationIsEnabled -f @($($physicalMachine.ComputerName))) `
{
($physicalMachine.Processors.IsVirtualizationEnabled | Measure-Object -Minimum).Minimum | Should Be $true
}
It ($BareMetalLocalizedStrings.TestSecondLevelAddressTranslation -f @($($physicalMachine.ComputerName))) `
{
($physicalMachine.Processors.SupportsSecondLevelAddressTranslation | Measure-Object -Minimum).Minimum | Should Be $true
}
It ($BareMetalLocalizedStrings.TestDataExecutionPrevention -f @($($physicalMachine.ComputerName))) `
{
$physicalMachine.IsDataExecutionPreventionEnabled | Should Be $true
}
}
# Validate the OS version
foreach ($physicalMachine in $physicalMachines)
{
It ($BareMetalLocalizedStrings.TestOperatingSystemVersion -f @($physicalMachine.ComputerName)) `
{
New-Object Version($physicalMachine.OSVersion) | Should Not BeLessThan $minimumOperatingSystemVersion
}
}
# In the case of OneNode, validate the OS SKU is a Datacenter SKU.
if ($isOneNode)
{
It ($BareMetalLocalizedStrings.TestDatacenterSku -f @($env:COMPUTERNAME)) `
{
$serverDatacenterSku = 8
$serverDatacenterEvalSku = 80
($physicalMachine.OSSku -in @($serverDatacenterSku, $serverDatacenterEvalSku)) | Should Be $true
}
}
# In the case of OneNode, validate that the computer name is not AzureStack, because this is used as the domain name.
if ($isOneNode)
{
It ($BareMetalLocalizedStrings.TestComputerNameIsNotAzureStack -f @($env:COMPUTERNAME)) `
{
$env:COMPUTERNAME | Should Not Be 'AzureStack'
}
}
# In the case of OneNode, validate that the computer is not already joined to a domain.
if ($isOneNode)
{
It ($BareMetalLocalizedStrings.TestNotPartOfDomain -f @($env:COMPUTERNAME)) `
{
$physicalMachine.IsPartOfDomain | Should Be $false
}
}
# Validate the number of cores on each of the machines meets the required minimum.
if ($isVirtualizedDeployment)
{
foreach ($physicalMachine in $physicalMachines)
{
It ($BareMetalLocalizedStrings.TestMinimumNumberOfCores -f @($minimumNumberOfCoresPerMachine.ToString(), $physicalMachine.ComputerName)) `
{
($physicalMachine.Processors.NumberOfEnabledCores | Measure-Object -Sum).Sum | Should Not BeLessThan $minimumNumberOfCoresPerMachine
}
}
}
# Validate the amount of memory on each of the machines meets the required minimum.
if ($isVirtualizedDeployment)
{
foreach ($physicalMachine in $physicalMachines)
{
It ($BareMetalLocalizedStrings.TestMinimumPhysicalMemory -f @($minimumPhysicalMemoryPerMachineGB.ToString(), $physicalMachine.ComputerName)) `
{
($physicalMachine.TotalPhysicalMemory * $toleranceMultiplier) | Should Not BeLessThan ($minimumPhysicalMemoryPerMachineGB * 1GB)
}
}
}
# For multi-node, validate that each machine has the minimum required number of network adapters with the required speed.
if (-not $isOneNode)
{
foreach ($physicalMachine in $physicalMachines)
{
It ($BareMetalLocalizedStrings.TestMinimumNumberOfNetworkAdapters -f @($minimumNumberOfAdaptersPerMachine.ToString(),
$minimumAdapterSpeedBitsPerSecond.ToString(),
$($physicalMachine.ComputerName))) `
{
($physicalMachine.NetworkAdapters | ? Speed -ge $minimumAdapterSpeedBitsPerSecond).Count |
Should Not BeLessThan $minimumNumberOfAdaptersPerMachine
}
}
}
# For multi-node, validate that all machines have the same network adapter configuration in terms of speed and whether RDMA is enabled.
if (-not $isOneNode)
{
foreach ($physicalMachine in $physicalMachines)
{
if ($physicalMachine -eq $physicalMachines[0])
{
continue;
}
It ($BareMetalLocalizedStrings.TestSameNetworkAdapterConfiguration -f @($($physicalMachine.ComputerName),
$($physicalMachines[0].ComputerName))) `
{
$physicalMachine.NetworkAdapters.Count | Should Be $physicalMachines[0].NetworkAdapters.Count
# Adapters have already been sorted. We only need to compare each network adapter to the one with the same index on the other machine.
for ($i = 0; $i -lt $physicalMachine.NetworkAdapters.Count; $i++)
{
$physicalMachine.NetworkAdapters[$i] | Should Be $physicalMachines[0].NetworkAdapters[$i]
}
}
}
}
# Validate the size of the system disk.
It ($BareMetalLocalizedStrings.TestSystemDiskMeetsMinimumSize -f @($physicalMachine.ComputerName,
$minimumSizeOfSystemDiskGB.ToString())) `
{
($physicalMachine.SystemDisk.Size * $toleranceMultiplier) | Should Not BeLessThan ($minimumSizeOfSystemDiskGB * 1GB)
}
# $disksUnion is the minimum-size superset of all disks. It is calculated to allow for performing checks even if some machines are missing some disks.
foreach ($physicalMachine in $physicalMachines)
{
$combinedListOfDisksOnAllMachines += $physicalMachine.NonSystemDisks
}
$combinedListOfDisksOnAllMachines = ($combinedListOfDisksOnAllMachines | sort)
$disksUnion = @($combinedListOfDisksOnAllMachines[0])
for ($i = 1; $i -lt $combinedListOfDisksOnAllMachines.Count; $i++)
{
if ($combinedListOfDisksOnAllMachines[$i].BusType -ne $combinedListOfDisksOnAllMachines[$i-1].BusType -or
$combinedListOfDisksOnAllMachines[$i].IndexWithinSimilarDisks -ne $combinedListOfDisksOnAllMachines[$i-1].IndexWithinSimilarDisks -or
($combinedListOfDisksOnAllMachines[$i].Size * $toleranceMultiplier) -lt $combinedListOfDisksOnAllMachines[$i-1].Size -or
$combinedListOfDisksOnAllMachines[$i].Size -gt ($combinedListOfDisksOnAllMachines[$i-1].Size * $toleranceMultiplier))
{
# Disk does not have the same properties as the previous disks (Comparison with only one
# disk is sufficent because the disks have been sorted). Include disk in union.
$disksUnion += $combinedListOfDisksOnAllMachines[$i]
}
}
# Validate that the machines have at least the minimum number of required data disks that meet the minimum required size.
It ($BareMetalLocalizedStrings.TestNumberOfDisksMeetingMinimumSize -f @($minimumNumberOfDataDisksPerMachine.ToString(),
$minimumSizeOfDataDisksGB.ToString(),
$physicalMachine.ComputerName)) `
{
if ($isOneNode)
{
$supportedBusTypes = @('SATA', 'SAS', 'RAID', 'NVMe')
}
else
{
$supportedBusTypes = @('SATA', 'SAS', 'NVMe')
}
(($disksUnion | ? BusType -in $supportedBusTypes | sort -Property Size -Descending |
select -Skip ($minimumNumberOfDataDisksPerMachine-1) -First 1 -ExpandProperty Size) * $toleranceMultiplier) |
Should Not BeLessThan ($minimumSizeOfDataDisksGB * 1GB)
}
# For multi-node, validate that the number of disks that may be missing from any machine compared to other machines is not more than the maximum.
if (-not $isOneNode)
{
foreach ($physicalMachine in $physicalMachines)
{
It ($BareMetalLocalizedStrings.TestMaximumMissingDisksPerMachine -f @($maximumNumberOfMissingDisksPerMachine.ToString(),
$physicalMachine.ComputerName)) `
{
($disksUnion.Count - $physicalMachine.NonSystemDisks.Count) | Should Not BeGreaterThan $maximumNumberOfMissingDisksPerMachine
}
}
}
}
# SIG # Begin signature block
# MIIkWAYJKoZIhvcNAQcCoIIkSTCCJEUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBjDMN+D61uPMUu
# bVMHPDOv4RiJSm/OfpegnFZy7IAj9KCCDYEwggX/MIID56ADAgECAhMzAAABUZ6N
# j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1
# oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os
# +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU
# 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg
# B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ
# IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A
# rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS
# se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf
# NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL
# crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs
# 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF
# sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD
# 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK
# YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j
# 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu
# Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY
# HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIWLTCCFikCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgN8Bc9FSk
# 8kjAYMW4BmgBoTkeWLixZPUESgxCzA9pE+wwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQAa3huY7aEY7gU0UzcAS2uYeuSJUdpjGDXZXAiMHFQa
# YDGonlsABE1sW+aOmVZJu+jWy6DmB4oQRaepY+vWgYTYwGUqGxS3F9fJU/zqyIsd
# CTIjhxbeU540rS9U+yEfhDKuQDhD/M0U6+r6qYd2BdS8LiJ5TguNiE+HK9kkXtcx
# KfjsG7RT60sYv0BRwYzhAPD+JayzGGsA6G5d1/1vDFNQybAfbneGb72I9JrNjK9O
# 4vkXGQv0ZTN+3AVtqpxeFBSCzTkqaEF6uX7lS3j2gF8eHuW4LFJnIdJCWEcuPiLx
# cpbujrX4j4fTurBTeSA2ERirPow6krsYP6hinSf+VVEUoYITtzCCE7MGCisGAQQB
# gjcDAwExghOjMIITnwYJKoZIhvcNAQcCoIITkDCCE4wCAQMxDzANBglghkgBZQME
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIBJo7Ly5gCr25XZQDrroahGZ6Pq+67+gFRsFyAXj
# ghlIAgZdPyaQfvQYEzIwMTkwODE1MDgyODEwLjE0M1owBIACAfSggdSkgdEwgc4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjoxNDhDLUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZaCCDyIwggT1MIID3aADAgECAhMzAAAA1acj5XiVagn/AAAA
# AADVMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTE4MDgyMzIwMjY0NVoXDTE5MTEyMzIwMjY0NVowgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxNDhD
# LUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIUCSuuF/E0gjL87onw
# du50M1BwUxQwtrZWEQYCmLMhGhNR/3eQxBD9lqY4FYxhRZ1bxfsIjs7SKvcF0m1Y
# fhgK5j9BdZvzueedkEY8MVUu41zB18dn+Nmaelz5vFz9BLq5hJPAEdZg3ZR+4dP7
# 19tVXJP/iVkslLTTKH/YqT8VciemXOUaCsK/Re7xdBe8qBShsOwbgD7xVYwgmevz
# +bSZ3UsWClccBB9kMhNHhniIKkuMo4BNJ5te+yp8KBVyzontKmtOhS1XKNSIywEW
# AzRGKZtlP36DVGOy9SGajweV0RjHkqZMUD1tEiIaDoBpiZXacHPne1FQGwSjK4NM
# lEsCAwEAAaOCARswggEXMB0GA1UdDgQWBBQpaKMxlHZD2V9dDZuf07KE9mI3hDAf
# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0
# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB52cvC6cTRwKstDtImv9pnoAv99sC2
# wQLy4rmBzWL2kI/sleBNXo74cqdAWoYfLRCQo+DuW2m5/0mMe4DpvPDLYCQSSJkA
# tu/hUL80wik5ZqaNvIfp7h1/CJ/KMuIgd8WbhNZxLyG5S4W7As667QNifweCrjFq
# ZMV9XtBO2wjz9Qti1RjdOImhX7V4kdCYKStdQg2xJhUU9ZtPqGprthaPqto9Fheq
# s37Cw2vO3tAjYqMXTZCvybz+4bUHxIWvqvJIXLpuPPz1zCvCotLfBzzBiV9miIEB
# Xsnv2qIIkdDnISqlrIZ1nXdRHdXl4ZhBf3wAZR/EwILvKaksKFYzrk4LMIIGcTCC
# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv
# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN
# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw
# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0
# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw
# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe
# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx
# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G
# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA
# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7
# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g
# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB
# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA
# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh
# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS
# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK
# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon
# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi
# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/
# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII
# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0
# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a
# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ
# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+
# NR4Iuto229Nfj950iEkSoYIDsDCCApgCAQEwgf6hgdSkgdEwgc4xCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjox
# NDhDLUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIlCgEBMAkGBSsOAwIaBQADFQCtwyS80dl18F3Q3UUfOTGebtaesaCB3jCB
# 26SB2DCB1TELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcG
# A1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJzAlBgNVBAsT
# Hm5DaXBoZXIgTlRTIEVTTjo1N0Y2LUMxRTAtNTU0QzErMCkGA1UEAxMiTWljcm9z
# b2Z0IFRpbWUgU291cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAOD/
# ZMYwIhgPMjAxOTA4MTUxMjU3NDJaGA8yMDE5MDgxNjEyNTc0MlowdzA9BgorBgEE
# AYRZCgQBMS8wLTAKAgUA4P9kxgIBADAKAgEAAgIXtQIB/zAHAgEAAgIYWDAKAgUA
# 4QC2RgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMBoAowCAIBAAID
# B6EgoQowCAIBAAIDB6EgMA0GCSqGSIb3DQEBBQUAA4IBAQDBgvAxiKFLeeT0hB1Y
# Tx6QE7j+AZnFUzikHZ63ysIelO3sSOIlL95ZcMFhpFibvTBldIqXttijClwsrX08
# cZzj7mI7SkZf4MueKY7fXNu2oAMaQci67y+Geo9uoN2RI0Sy32de0Ha+Xnb/PM7M
# 2hwBibw2nYKEF7BKH9jx7kX/mho9HAoc5VzawuUrlufVsWqV8dsvFcBqaOq7gShx
# kxejh6paJz0UCrO6L+5ePXZ3EYLGHLHyOvZOBIHdkSeUrrPPZyFTxnZHwSUtC5fS
# HyetvEcZ6US7299P5JIWEO9+QzeoCSzAkSskC2s3CbMdGtY/q35PwY/CIusUc0l1
# SGUcMYIC9TCCAvECAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAADVpyPleJVqCf8AAAAAANUwDQYJYIZIAWUDBAIBBQCgggEyMBoGCSqGSIb3
# DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgyGlMFOmhYMKcYevJ
# 462GJKJPvp/b6OkHp2cm5QgdxxcwgeIGCyqGSIb3DQEJEAIMMYHSMIHPMIHMMIGx
# BBStwyS80dl18F3Q3UUfOTGebtaesTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwAhMzAAAA1acj5XiVagn/AAAAAADVMBYEFNFDfAWvn2fpcC4T
# kL+KEq5Te55SMA0GCSqGSIb3DQEBCwUABIIBABnq/RH7vrYyv8D9r/kNRjzdXqZ7
# QLf4Zc0dD912qL/c5D5FA/oK0bNcHPP9Ag04IBQC7LU9HJoegpONjtaq5uKADofk
# AnJjLTHR5gJaVmgpVMVo+29GWYQfncIpK4vbFlJTgSpAHf50rXC7NBwm+MsL0dFW
# X48bKLceVC6+bPHXYcqvWK1CQUBgXNnN9MgzDD0kgJSyHvBuMTYnTdkYBojRnRHO
# dVxY2+UXwle12VSmK3xhGHYT4+luH2K1Hne04wttUx5nJD+peXx7wjsX5RCXZUlc
# vTus+izrjXKqzyLsiYEb/54Q8XDxwgtGfza26rQJGKC6YoLqdF5mMcJij0c=
# SIG # End signature block
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment