Public/Manage-HyperVVM.ps1
<# .SYNOPSIS Manages a HyperV VM. This is a refactor of the PowerShell Script used to deploy a MobyLinux VM on Hyper-V during a Docker CE install. The refactor was done mostly to fix permissions issues that occur when running Hyper-V on a Guest VM in order to deploy a Nested VM, but it also works just fine on baremetal Hyper-V. .DESCRIPTION Creates/Destroys/Starts/Stops A HyperV VM This function is a refactored version of MobyLinux.ps1 that is bundled with a DockerCE install. This function deploys newly created VMs to "C:\Users\Public\Documents". This location is hardcoded for now. .PARAMETER VmName If passed, use this name for the HyperV VM .PARAMETER IsoFile Path to the ISO image, must be set for Create/ReCreate .PARAMETER SwitchName Name of the switch you want to attatch to your new VM. .PARAMETER VMGen Generation of the VM you would like to create. Can be either 1 or 2. Defaults to 2. .PARAMETER PreferredIntegrationServices List of Hyper-V Integration Services you would like enabled for your new VM. Valid values are: "Heartbeat","Shutdown","TimeSynch","GuestServiceInterface","KeyValueExchange","VSS" Defaults to enabling: "Heartbeat","Shutdown","TimeSynch","GuestServiceInterface","KeyValueExchange" .PARAMETER VhdPathOverride By default, VHD file(s) for the new VM are stored under "C:\Users\Public\Documents\HyperV". If you want VHD(s) stored elsewhere, provide this parameter with a full path to a directory. .PARAMETER NoVhd This parameter is a switch. Use it to create a new VM without a VHD. For situations where you want to attach a VHD later. .PARAMETER Create Create a HyperV VM .PARAMETER CPUs CPUs used in the VM (optional on Create, default: min(2, number of CPUs on the host)) .PARAMETER Memory Memory allocated for the VM at start in MB (optional on Create, default: 2048 MB) .PARAMETER Destroy Remove a HyperV VM .PARAMETER KeepVolume If passed, will not delete the VHD on Destroy .PARAMETER Start Start an existing HyperV VM .PARAMETER Stop Stop a running HyperV VM .EXAMPLE # Open an elevated PowerShell Session, import the module, and - PS C:\Users\zeroadmin> Manage-HyperVVM -VMName "TestVM" -SwitchName "ToMgmt" -IsoFile .\mobylinux.iso -VMGen 1 -Create .EXAMPLE # Open an elevated PowerShell Session, import the module, and - PS C:\Users\zeroadmin> Manage-HyperVVM -VMName "TestVM" -SwitchName "ToMgmt" -VHDPathOverride "C:\Win1016Serv.vhdx" -VMGen 2 -Memory 4096 -Create #> function Manage-HyperVVM { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [string]$VmName, [Parameter( Mandatory=$False, ParameterSetName='Create' )] [string]$IsoFile, [Parameter( Mandatory=$True, ParameterSetName='Create' )] [string]$SwitchName, [Parameter( Mandatory=$False, ParameterSetName='Create' )] [ValidateSet(1,2)] [int]$VMGen = 2, [Parameter( Mandatory=$False, ParameterSetName='Create' )] [ValidateSet("Heartbeat","Shutdown","Time Synchronization","Guest Service Interface","Key-Value Pair Exchange","VSS")] [string[]]$PreferredIntegrationServices = @("Heartbeat","Shutdown","Time Synchronization","Guest Service Interface","Key-Value Pair Exchange"), [Parameter(Mandatory=$False)] [string]$VhdPathOverride, [Parameter(Mandatory=$False)] [switch]$NoVhd, [Parameter( Mandatory=$False, ParameterSetName='Create' )] [switch]$Create, [Parameter( Mandatory=$False, ParameterSetName='Create' )] [int]$CPUs = 1, [Parameter( Mandatory=$False, ParameterSetName='Create' )] [long]$Memory = 2048, [Parameter( Mandatory=$False, ParameterSetName='Destroy' )] [switch]$Destroy, [Parameter( Mandatory=$False, ParameterSetName='Destroy' )] [switch]$KeepVolume, [Parameter( Mandatory=$False, ParameterSetName='Start' )] [switch]$Start, [Parameter( Mandatory=$False, ParameterSetName='Stop' )] [switch]$Stop ) ##### BEGIN Variable/Parameter Transforms and PreRun Prep ##### # This is only a problem for Windows_Server_2016_14393.0.160715-1616.RS1_RELEASE_SERVER_EVAL_X64FRE_EN-US (technet_official).ISO <# if ($IsoFile) { if ($IsoFile -notmatch "C:\\Users\\Public") { Write-Error "The ISO File used to install the new VM's Operating System must be placed somewhere under 'C:\Users\Public' due to permissions issues! Halting!" $global:FunctionResult = "1" return } } #> # Make sure we stop at Errors unless otherwise explicitly specified $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" # Explicitly disable Module autoloading and explicitly import the # Modules this script relies on. This is not strictly necessary but # good practise as it prevents arbitrary errors # More Info: https://blogs.msdn.microsoft.com/timid/2014/09/02/psmoduleautoloadingpreference-and-you/ $PSModuleAutoloadingPreference = 'None' # Check to see if Hyper-V is installed: if ($(Get-Module).Name -notcontains "Dism") { # Using full path to Dism Module Manifest because sometimes there are issues with just 'Import-Module Dism' $DismModuleManifestPaths = $(Get-Module -ListAvailable -Name Dism).Path foreach ($MMPath in $DismModuleManifestPaths) { try { Import-Module $MMPath -ErrorAction Stop break } catch { continue } } } if ($(Get-Module).Name -notcontains "Dism") { Write-Error "Problem importing the Dism PowerShell Module! Halting!" $global:FunctionResult = "1" return } $HyperVCheck = Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V -Online if ($HyperVCheck.State -ne "Enabled") { Write-Error "Please install Hyper-V before proceeding! Halting!" $global:FunctionResult = "1" return } Write-Output "Script started at $(Get-Date -Format "HH:mm:ss.fff")" <# # Explicitly import the Modules we need for this function try { Import-Module Microsoft.PowerShell.Utility Import-Module Microsoft.PowerShell.Management Import-Module Hyper-V Import-Module NetAdapter Import-Module NetTCPIP Import-Module PackageManagement Import-Module PowerShellGet if ($(Get-Module -ListAvailable).Name -notcontains "NTFSSecurity") { Install-Module NTFSSecurity } try { if ($(Get-Module).Name -notcontains "NTFSSecurity") {Import-Module NTFSSecurity} } catch { if ($_.Exception.GetType().FullName -eq "System.Management.Automation.RuntimeException") { Write-Verbose "NTFSSecurity Module is already loaded..." } else { throw "There was a problem loading the NTFSSecurity Module! Halting!" } } } catch { Write-Error $_ $global:FunctionResult = "1" return } Write-Host "Modules loaded at $(Get-Date -Format "HH:mm:ss.fff")" #> # Hard coded for now $global:VhdSize = 60*1024*1024*1024 # 60GB ##### END Variable/Parameter Transforms and PreRun Prep ##### ##### BEGIN Helper Functions ##### function Get-Vhd-Root { if($VhdPathOverride){ return $VhdPathOverride } # Default location for VHDs $VhdRoot = "$((Hyper-V\Get-VMHost -ComputerName localhost).VirtualHardDiskPath)".TrimEnd("\") # Where we put the Nested VM return "$VhdRoot\$VmName.vhdx" } function New-Switch { $ipParts = $SwitchSubnetAddress.Split('.') [int]$switchIp3 = $null [int32]::TryParse($ipParts[3] , [ref]$switchIp3 ) | Out-Null $Ip0 = $ipParts[0] $Ip1 = $ipParts[1] $Ip2 = $ipParts[2] $Ip3 = $switchIp3 + 1 $switchAddress = "$Ip0.$Ip1.$Ip2.$Ip3" $vmSwitch = Hyper-V\Get-VMSwitch $SwitchName -SwitchType Internal -ea SilentlyContinue $vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $SwitchName -ea SilentlyContinue if ($vmSwitch -and $vmNetAdapter) { Write-Output "Using existing Switch: $SwitchName" } else { # There seems to be an issue on builds equal to 10586 (and # possibly earlier) with the first VMSwitch being created after # Hyper-V install causing an error. So on these builds we create # Dummy switch and remove it. $buildstr = $(Get-WmiObject win32_operatingsystem).BuildNumber $buildNumber = [convert]::ToInt32($buildstr, 10) if ($buildNumber -le 10586) { Write-Output "Enabled workaround for Build 10586 VMSwitch issue" $fakeSwitch = Hyper-V\New-VMSwitch "DummyDesperatePoitras" -SwitchType Internal -ea SilentlyContinue $fakeSwitch | Hyper-V\Remove-VMSwitch -Confirm:$false -Force -ea SilentlyContinue } Write-Output "Creating Switch: $SwitchName..." Hyper-V\Remove-VMSwitch $SwitchName -Force -ea SilentlyContinue Hyper-V\New-VMSwitch $SwitchName -SwitchType Internal -ea SilentlyContinue | Out-Null $vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $SwitchName Write-Output "Switch created." } # Make sure there are no lingering net adapter $netAdapters = Get-NetAdapter | ? { $_.Name.StartsWith("vEthernet ($SwitchName)") } if (($netAdapters).Length -gt 1) { Write-Output "Disable and rename invalid NetAdapters" $now = (Get-Date -Format FileDateTimeUniversal) $index = 1 $invalidNetAdapters = $netAdapters | ? { $_.DeviceID -ne $vmNetAdapter.DeviceId } foreach ($netAdapter in $invalidNetAdapters) { $netAdapter ` | Disable-NetAdapter -Confirm:$false -PassThru ` | Rename-NetAdapter -NewName "Broken Docker Adapter ($now) ($index)" ` | Out-Null $index++ } } # Make sure the Switch has the right IP address $networkAdapter = Get-NetAdapter | ? { $_.DeviceID -eq $vmNetAdapter.DeviceId } if ($networkAdapter | Get-NetIPAddress -IPAddress $switchAddress -ea SilentlyContinue) { $networkAdapter | Disable-NetAdapterBinding -ComponentID ms_server -ea SilentlyContinue $networkAdapter | Enable-NetAdapterBinding -ComponentID ms_server -ea SilentlyContinue Write-Output "Using existing Switch IP address" return } $networkAdapter | Remove-NetIPAddress -Confirm:$false -ea SilentlyContinue $networkAdapter | Set-NetIPInterface -Dhcp Disabled -ea SilentlyContinue $networkAdapter | New-NetIPAddress -AddressFamily IPv4 -IPAddress $switchAddress -PrefixLength ($SwitchSubnetMaskSize) -ea Stop | Out-Null $networkAdapter | Disable-NetAdapterBinding -ComponentID ms_server -ea SilentlyContinue $networkAdapter | Enable-NetAdapterBinding -ComponentID ms_server -ea SilentlyContinue Write-Output "Set IP address on switch" } function Remove-Switch { Write-Output "Destroying Switch $SwitchName..." # Let's remove the IP otherwise a nasty bug makes it impossible # to recreate the vswitch $vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -ManagementOS -SwitchName $SwitchName -ea SilentlyContinue if ($vmNetAdapter) { $networkAdapter = Get-NetAdapter | ? { $_.DeviceID -eq $vmNetAdapter.DeviceId } $networkAdapter | Remove-NetIPAddress -Confirm:$false -ea SilentlyContinue } Hyper-V\Remove-VMSwitch $SwitchName -Force -ea SilentlyContinue } function New-HyperVVM { <# if (!(Test-Path $IsoFile)) { Fatal "ISO file at $IsoFile does not exist" } #> $CPUs = [Math]::min((Hyper-V\Get-VMHost -ComputerName localhost).LogicalProcessorCount, $CPUs) $vm = Hyper-V\Get-VM $VmName -ea SilentlyContinue if ($vm) { if ($vm.Length -ne 1) { Fatal "Multiple VMs exist with the name $VmName. Delete invalid ones and try again." } } else { # Create the Snapshot Directory if it doesn't already exist $SnapShotDir = $($VhdPathOverride -split "Virtual Hard Disks")[0] + "Snapshots" if (!$(Test-Path $SnapShotDir)) { $null = New-Item -ItemType Directory -Path $SnapShotDir -Force } Write-Output "Creating VM $VmName..." $vm = Hyper-V\New-VM -Name $VmName -Generation $VMGen -NoVHD $SetVMSplatParams = @{ Name = $VmName AutomaticStartAction = "Nothing" AutomaticStopAction = "ShutDown" CheckpointType = "Production" SnapShotFileLocation = $SnapShotDir } $null = Hyper-V\Set-VM @SetVMSplatParams } <# if ($vm.Generation -ne 2) { Fatal "VM $VmName is a Generation $($vm.Generation) VM. It should be a Generation 2." } #> if ($vm.State -ne "Off") { Write-Output "VM $VmName is $($vm.State). Cannot change its settings." return } Write-Output "Setting CPUs to $CPUs and Memory to $Memory MB" $Memory = ([Math]::min($Memory, (Hyper-V\Get-VMMemory -VMName $VMName).MaximumPerNumaNode)) Hyper-V\Set-VM -Name $VMName -MemoryStartupBytes ($Memory*1024*1024) -ProcessorCount $CPUs -StaticMemory if (!$NoVhd) { $VmVhdFile = Get-Vhd-Root $vhd = Get-VHD -Path $VmVhdFile -ea SilentlyContinue if (!$vhd) { Write-Output "Creating dynamic VHD: $VmVhdFile" $vhd = New-VHD -ComputerName localhost -Path $VmVhdFile -Dynamic -SizeBytes $global:VhdSize } ## BEGIN Try and Update Permissions ## if ($($VMVhdFile -split "\\")[0] -eq $env:SystemDrive) { if ($VMVhdFile -match "\\Users\\") { $UserDirPrep = $VMVHdFile -split "\\Users\\" $UserDir = $UserDirPrep[0] + "\Users\" + $($UserDirPrep[1] -split "\\")[0] # We can assume there is at least one folder under $HOME before getting to the .vhd file $DirectoryThatMayNeedPermissionsFixPrep = $UserDir + '\' + $($UserDirPrep[1] -split "\\")[1] # If $DirectoryThatMayNeedPermissionsFixPrep isn't a SpecialFolder typically found under $HOME # then assume we can mess with permissions. Else, target one directory deeper. $HomeDirCount = $($HOME -split '\\').Count $SpecialFoldersDirectlyUnderHomePrep = [enum]::GetNames('System.Environment+SpecialFolder') | foreach { [environment]::GetFolderPath($_) } | Sort-Object | Get-Unique | Where-Object {$_ -match "$($HOME -replace '\\','\\')"} $SpecialFoldersDirectlyUnderHome = $SpecialFoldersDirectlyUnderHomePrep | Where-Object {$($_ -split '\\').Count -eq $HomeDirCount+1} if ($SpecialFoldersDirectlyUnderHome -notcontains $DirectoryThatMayNeedPermissionsFixPrep) { $DirectoryThatMayNeedPermissionsFix = $DirectoryThatMayNeedPermissionsFixPrep } else { # Go one folder deeper... $DirectoryThatMayNeedPermissionsFix = $UserDir + '\' + $($UserDirPrep[1] -split "\\")[1] + '\' + $($UserDirPrep[1] -split "\\")[2] } try { FixNTVirtualMachinesPerms -Directorypath $DirectoryThatMayNeedPermissionsFix } catch { Write-Error $_ Write-Error "The FixNTVirtualMachinesPerms function failed! Halting!" $global:FunctionResult = "1" return } } else { $DirectoryThatMayNeedPermissionsFix = $VMVhdFile | Split-Path -Parent try { FixNTVirtualMachinesPerms -DirectoryPath $DirectoryThatMayNeedPermissionsFix } catch { Write-Error $_ Write-Error "The FixNTVirtualMachinesPerms function failed! Halting!" $global:FunctionResult = "1" return } } } # Also fix permissions on "$env:SystemDrive\Users\Public" and "$env:SystemDrive\ProgramData\Microsoft\Windows\Hyper-V" # the because lots of software (like Docker) likes throwing stuff in these locations <# $PublicUserDirectoryPath = "$env:SystemDrive\Users\Public" $HyperVConfigDir = "$env:SystemDrive\ProgramData\Microsoft\Windows\Hyper-V" [System.Collections.ArrayList]$DirsToPotentiallyFix = @($PublicUserDirectoryPath,$HyperVConfigDir) foreach ($dir in $DirsToPotentiallyFix) { try { FixNTVirtualMachinesPerms -DirectoryPath $dir } catch { Write-Error $_ Write-Error "The FixNTVirtualMachinesPerms function failed! Halting!" $global:FunctionResult = "1" return } } #> ## END Try and Update Permissions ## if ($vm.HardDrives.Path -ne $VmVhdFile) { if ($vm.HardDrives) { Write-Output "Remove existing VHDs" Hyper-V\Remove-VMHardDiskDrive $vm.HardDrives -ea SilentlyContinue } Write-Output "Attach VHD $VmVhdFile" $null = Hyper-V\Add-VMHardDiskDrive -VMName $VMName -Path $VmVhdFile } } $vmNetAdapter = Hyper-V\Get-VMNetworkAdapter -VMName $VMName if (!$vmNetAdapter) { Write-Output "Attach Net Adapter" $vmNetAdapter = Hyper-V\Add-VMNetworkAdapter -VMName $VMName -SwitchName $SwitchName -Passthru } Write-Output "Connect Switch $SwitchName" Hyper-V\Connect-VMNetworkAdapter -VMName $VMName -SwitchName $SwitchName if ($IsoFile) { if ($vm.DVDDrives.Path -ne $IsoFile) { if ($vm.DVDDrives) { Write-Output "Remove existing DVDs" $ExistingDvDDriveInfo = Get-VMDvdDrive -VMName $VMName Hyper-V\Remove-VMDvdDrive -VMName 'CentOS7Test2' -ControllerNumber $ExistingDvDDriveInfo.ControllerNumber -ControllerLocation $ExistingDvDDriveInfo.ControllerLocation } Write-Output "Attach DVD $IsoFile" Hyper-V\Add-VMDvdDrive -VMName $VMName -Path $IsoFile } } #$iso = $vm | Hyper-V\Get-VMFirmware | select -ExpandProperty BootOrder | ? { $_.FirmwarePath.EndsWith("Scsi(0,1)") } #$vm | Hyper-V\Set-VMFirmware -EnableSecureBoot Off -FirstBootDevice $iso ##$vm | Hyper-V\Set-VMComPort -number 1 -Path "\\.\pipe\docker$VmName-com1" # Enable only prefered VM integration services [System.Collections.ArrayList]$intSvc = @() foreach ($integrationService in $PreferredIntegrationServices) { switch ($integrationService) { 'Heartbeat' { $null = $intSvc.Add("Microsoft:$($vm.Id)\84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47") } 'Shutdown' { $null = $intSvc.Add("Microsoft:$($vm.Id)\9F8233AC-BE49-4C79-8EE3-E7E1985B2077") } 'Time Synchronization' { $null = $intSvc.Add("Microsoft:$($vm.Id)\2497F4DE-E9FA-4204-80E4-4B75C46419C0") } 'Guest Service Interface' { $null = $intSvc.Add("Microsoft:$($vm.Id)\6C09BB55-D683-4DA0-8931-C9BF705F6480") } 'Key-Value Pair Exchange' { $null = $intSvc.Add("Microsoft:$($vm.Id)\2A34B1C2-FD73-4043-8A5B-DD2159BC743F") } 'VSS' { $null = $intSvc.Add("Microsoft:$($vm.Id)\5CED1297-4598-4915-A5FC-AD21BB4D02A4") } } } Hyper-V\Get-VMIntegrationService -VMName $VMName | foreach { if ($PreferredIntegrationServices -contains $_.Name) { Hyper-V\Enable-VMIntegrationService -VMName $VMName -Name $_.Name Write-Output "Enabled $($_.Name)" } else { Hyper-V\Disable-VMIntegrationService -VMName $VMName -Name $_.Name Write-Output "Disabled $($_.Name)" } } #$vm | Hyper-V\Disable-VMConsoleSupport Hyper-V\Enable-VMConsoleSupport -VMName $VMName Write-Output "VM created." } function Remove-HyperVVM { Write-Output "Removing VM $VmName..." Hyper-V\Remove-VM $VmName -Force -ea SilentlyContinue if (!$KeepVolume) { $VmVhdFile = Get-Vhd-Root Write-Output "Delete VHD $VmVhdFile" Remove-Item $VmVhdFile -ea SilentlyContinue } } function Start-HyperVVM { Write-Output "Starting VM $VmName..." Hyper-V\Start-VM -VMName $VmName } function Stop-HyperVVM { $vms = Hyper-V\Get-VM $VmName -ea SilentlyContinue if (!$vms) { Write-Output "VM $VmName does not exist" return } foreach ($vm in $vms) { Stop-VM-Force($vm) } } function Stop-VM-Force { Param($vm) if ($vm.State -eq 'Off') { Write-Output "VM $VmName is stopped" return } $code = { Param($vmId) # Passing the $vm ref is not possible because it will be disposed already $vm = Hyper-V\Get-VM -Id $vmId -ea SilentlyContinue if (!$vm) { Write-Output "VM with Id $vmId does not exist" return } $shutdownService = Hyper-V\Get-VMIntegrationService -VMName $vm.Name -Name Shutdown -ea SilentlyContinue if ($shutdownService -and $shutdownService.PrimaryOperationalStatus -eq 'Ok') { Write-Output "Shutdown VM $VmName..." Hyper-V\Stop-VM -VMName $vm.Name -Confirm:$false -Force -ea SilentlyContinue if ($vm.State -eq 'Off') { return } } Write-Output "Turn Off VM $VmName..." Hyper-V\Stop-VM -VMName $vm.Name -Confirm:$false -TurnOff -Force -ea SilentlyContinue } Write-Output "Stopping VM $VmName..." $job = Start-Job -ScriptBlock $code -ArgumentList $vm.VMId.Guid if (Wait-Job $job -Timeout 20) { Receive-Job $job } Remove-Job -Force $job -ea SilentlyContinue if ($vm.State -eq 'Off') { Write-Output "VM $VmName is stopped" return } # If the VM cannot be stopped properly after the timeout # then we have to kill the process and wait till the state changes to "Off" for ($count = 1; $count -le 10; $count++) { $ProcessID = (Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "Name = '$($vm.Id.Guid)'").ProcessID if (!$ProcessID) { Write-Output "VM $VmName killed. Waiting for state to change" for ($count = 1; $count -le 20; $count++) { if ($vm.State -eq 'Off') { Write-Output "Killed VM $VmName is off" #Remove-Switch $oldKeepVolumeValue = $KeepVolume $KeepVolume = $true Remove-HyperVVM $KeepVolume = $oldKeepVolumeValue return } Start-Sleep -Seconds 1 } Fatal "Killed VM $VmName did not stop" } Write-Output "Kill VM $VmName process..." Stop-Process $ProcessID -Force -Confirm:$false -ea SilentlyContinue Start-Sleep -Seconds 1 } Fatal "Couldn't stop VM $VmName" } function Fatal { throw "$args" return 1 } # Main entry point Try { Switch ($PSBoundParameters.GetEnumerator().Where({$_.Value -eq $true}).Key) { 'Stop' { Stop-HyperVVM } 'Destroy' { Stop-HyperVVM; Remove-HyperVVM } 'Create' { New-HyperVVM } 'Start' { Start-HyperVVM } } } Catch { throw return 1 } } # SIG # Begin signature block # MIIMiAYJKoZIhvcNAQcCoIIMeTCCDHUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUUJEBd2FGOPgkqzN4eZEPMWWb # Q+mgggn9MIIEJjCCAw6gAwIBAgITawAAAB/Nnq77QGja+wAAAAAAHzANBgkqhkiG # 9w0BAQsFADAwMQwwCgYDVQQGEwNMQUIxDTALBgNVBAoTBFpFUk8xETAPBgNVBAMT # CFplcm9EQzAxMB4XDTE3MDkyMDIxMDM1OFoXDTE5MDkyMDIxMTM1OFowPTETMBEG # CgmSJomT8ixkARkWA0xBQjEUMBIGCgmSJomT8ixkARkWBFpFUk8xEDAOBgNVBAMT # B1plcm9TQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCwqv+ROc1 # bpJmKx+8rPUUfT3kPSUYeDxY8GXU2RrWcL5TSZ6AVJsvNpj+7d94OEmPZate7h4d # gJnhCSyh2/3v0BHBdgPzLcveLpxPiSWpTnqSWlLUW2NMFRRojZRscdA+e+9QotOB # aZmnLDrlePQe5W7S1CxbVu+W0H5/ukte5h6gsKa0ktNJ6X9nOPiGBMn1LcZV/Ksl # lUyuTc7KKYydYjbSSv2rQ4qmZCQHqxyNWVub1IiEP7ClqCYqeCdsTtfw4Y3WKxDI # JaPmWzlHNs0nkEjvnAJhsRdLFbvY5C2KJIenxR0gA79U8Xd6+cZanrBUNbUC8GCN # wYkYp4A4Jx+9AgMBAAGjggEqMIIBJjASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsG # AQQBgjcVAgQWBBQ/0jsn2LS8aZiDw0omqt9+KWpj3DAdBgNVHQ4EFgQUicLX4r2C # Kn0Zf5NYut8n7bkyhf4wGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDgYDVR0P # AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUdpW6phL2RQNF # 7AZBgQV4tgr7OE0wMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovL3BraS9jZXJ0ZGF0 # YS9aZXJvREMwMS5jcmwwPAYIKwYBBQUHAQEEMDAuMCwGCCsGAQUFBzAChiBodHRw # Oi8vcGtpL2NlcnRkYXRhL1plcm9EQzAxLmNydDANBgkqhkiG9w0BAQsFAAOCAQEA # tyX7aHk8vUM2WTQKINtrHKJJi29HaxhPaHrNZ0c32H70YZoFFaryM0GMowEaDbj0 # a3ShBuQWfW7bD7Z4DmNc5Q6cp7JeDKSZHwe5JWFGrl7DlSFSab/+a0GQgtG05dXW # YVQsrwgfTDRXkmpLQxvSxAbxKiGrnuS+kaYmzRVDYWSZHwHFNgxeZ/La9/8FdCir # MXdJEAGzG+9TwO9JvJSyoGTzu7n93IQp6QteRlaYVemd5/fYqBhtskk1zDiv9edk # mHHpRWf9Xo94ZPEy7BqmDuixm4LdmmzIcFWqGGMo51hvzz0EaE8K5HuNvNaUB/hq # MTOIB5145K8bFOoKHO4LkTCCBc8wggS3oAMCAQICE1gAAAH5oOvjAv3166MAAQAA # AfkwDQYJKoZIhvcNAQELBQAwPTETMBEGCgmSJomT8ixkARkWA0xBQjEUMBIGCgmS # JomT8ixkARkWBFpFUk8xEDAOBgNVBAMTB1plcm9TQ0EwHhcNMTcwOTIwMjE0MTIy # WhcNMTkwOTIwMjExMzU4WjBpMQswCQYDVQQGEwJVUzELMAkGA1UECBMCUEExFTAT # BgNVBAcTDFBoaWxhZGVscGhpYTEVMBMGA1UEChMMRGlNYWdnaW8gSW5jMQswCQYD # VQQLEwJJVDESMBAGA1UEAxMJWmVyb0NvZGUyMIIBIjANBgkqhkiG9w0BAQEFAAOC # AQ8AMIIBCgKCAQEAxX0+4yas6xfiaNVVVZJB2aRK+gS3iEMLx8wMF3kLJYLJyR+l # rcGF/x3gMxcvkKJQouLuChjh2+i7Ra1aO37ch3X3KDMZIoWrSzbbvqdBlwax7Gsm # BdLH9HZimSMCVgux0IfkClvnOlrc7Wpv1jqgvseRku5YKnNm1JD+91JDp/hBWRxR # 3Qg2OR667FJd1Q/5FWwAdrzoQbFUuvAyeVl7TNW0n1XUHRgq9+ZYawb+fxl1ruTj # 3MoktaLVzFKWqeHPKvgUTTnXvEbLh9RzX1eApZfTJmnUjBcl1tCQbSzLYkfJlJO6 # eRUHZwojUK+TkidfklU2SpgvyJm2DhCtssFWiQIDAQABo4ICmjCCApYwDgYDVR0P # AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBS5d2bhatXq # eUDFo9KltQWHthbPKzAfBgNVHSMEGDAWgBSJwtfivYIqfRl/k1i63yftuTKF/jCB # 6QYDVR0fBIHhMIHeMIHboIHYoIHVhoGubGRhcDovLy9DTj1aZXJvU0NBKDEpLENO # PVplcm9TQ0EsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNl # cnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9emVybyxEQz1sYWI/Y2VydGlmaWNh # dGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlv # blBvaW50hiJodHRwOi8vcGtpL2NlcnRkYXRhL1plcm9TQ0EoMSkuY3JsMIHmBggr # BgEFBQcBAQSB2TCB1jCBowYIKwYBBQUHMAKGgZZsZGFwOi8vL0NOPVplcm9TQ0Es # Q049QUlBLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENO # PUNvbmZpZ3VyYXRpb24sREM9emVybyxEQz1sYWI/Y0FDZXJ0aWZpY2F0ZT9iYXNl # P29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwLgYIKwYBBQUHMAKG # Imh0dHA6Ly9wa2kvY2VydGRhdGEvWmVyb1NDQSgxKS5jcnQwPQYJKwYBBAGCNxUH # BDAwLgYmKwYBBAGCNxUIg7j0P4Sb8nmD8Y84g7C3MobRzXiBJ6HzzB+P2VUCAWQC # AQUwGwYJKwYBBAGCNxUKBA4wDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOC # AQEAszRRF+YTPhd9UbkJZy/pZQIqTjpXLpbhxWzs1ECTwtIbJPiI4dhAVAjrzkGj # DyXYWmpnNsyk19qE82AX75G9FLESfHbtesUXnrhbnsov4/D/qmXk/1KD9CE0lQHF # Lu2DvOsdf2mp2pjdeBgKMRuy4cZ0VCc/myO7uy7dq0CvVdXRsQC6Fqtr7yob9NbE # OdUYDBAGrt5ZAkw5YeL8H9E3JLGXtE7ir3ksT6Ki1mont2epJfHkO5JkmOI6XVtg # anuOGbo62885BOiXLu5+H2Fg+8ueTP40zFhfLh3e3Kj6Lm/NdovqqTBAsk04tFW9 # Hp4gWfVc0gTDwok3rHOrfIY35TGCAfUwggHxAgEBMFQwPTETMBEGCgmSJomT8ixk # ARkWA0xBQjEUMBIGCgmSJomT8ixkARkWBFpFUk8xEDAOBgNVBAMTB1plcm9TQ0EC # E1gAAAH5oOvjAv3166MAAQAAAfkwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwx # CjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGC # NwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFAVat6ITzrdskzjZ # o8owFbD74eTzMA0GCSqGSIb3DQEBAQUABIIBAE0lAP0ejHo9sE0sS7lNLjhGsSZC # 5JlrhAvtWK7uNB7JsA41ij5nMJgLdOs5mikes4ByZckmfnUxFCKoqqN+PUI1T71m # DYSyBi/kMGx33M8lCeqFyk0+hbt6Nje7QEesMFvYXzb1wTGRbz2k6/DPxhVayz3b # NIBOB3F/Ud0LlxX5u5fZLi730+DSaXy/naspUPCIYUaaOfcCNMJDoXErobAonmOV # UXUNlCBbGc+Se0WUCNqzJEQUph3XAedyKJjB2yNfzMizUUMbQVOU2nP2TI79hy0T # C5YRni0OWNcDb4D3earaoL7Cn4Owrr64dIv1kOaNvtjVV9XQU2HLnzPcA2o= # SIG # End signature block |