AzureRM.Compute.Experiments.psm1
<#
.ExternalHelp AzureRM.Compute.Experiments-help.xml #> function New-AzVm { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory=$true, Position=0)][string] $Name = "VM", [Parameter(Mandatory=$true)][PSCredential] $Credential, [Parameter()][string] $ResourceGroupName = $Name, [Parameter()][string] $Location, [Parameter()][string] $VirtualNetworkName = $Name, [Parameter()][string] $AddressPrefix = "192.168.0.0/16", [Parameter()][string] $SubnetName = $Name, [Parameter()][string] $SubnetAddressPrefix = "192.168.1.0/24", [Parameter()][string] $PublicIpAddressName = $Name, [Parameter()][string] $DomainNameLabel = $ResourceGroupName + $Name, [Parameter()][string] $AllocationMethod = "Static", [Parameter()][string] $SecurityGroupName = $Name, [Parameter()][int[]] $OpenPorts = @(3389, 5985), [Parameter()][string] $ImageName = "Win2016Datacenter", [Parameter()][string] $Size = "Standard_DS1_v2", [Parameter()][object] $AzureRmContext, [Parameter()][switch] $AsJob ) PROCESS { # TODO: make sure it's logged in. $context = if ($AzureRmContext) { Get-AzureRmContext -AzureRmContext $AzureRmContext } else { Get-AzureRmContext } $rgi = [ResourceGroup]::new($ResourceGroupName) $vni = [VirtualNetwork]::new($VirtualNetworkName, $rgi, $AddressPrefix) $subnet = [Subnet]::new($SubnetName, $vni, $SubnetAddressPrefix) $piai = [PublicIpAddress]::new($PublicIpAddressName, $rgi, $DomainNameLabel, $AllocationMethod) $sgi = [SecurityGroup]::new($SecurityGroupName, $rgi, $OpenPorts) # we don't allow to reuse NetworkInterface so $name is $null. $nii = [NetworkInterface]::new( $Name, $rgi, $subnet, $piai, $sgi) # the purpouse of the New-AzVm cmdlet is to create (not get) a VM so $name is $null. $vmi = [VirtualMachine]::new( $Name, $rgi, $nii, $Credential, $ImageName, $images, $Size) # infer a location $locationi = [Location]::new() if (-not $Location) { $vmi.UpdateLocation($locationi, $context) if (-not $locationi.Value) { $locationi.Value = "eastus" } } else { $locationi.Value = $Location } $createParams = [CreateParams]::new($locationi.Value, $context) if ($PSCmdlet.ShouldProcess($Name, "Creating a virtual machine")) { if ($AsJob) { $boundParams = $PSCmdlet.MyInvocation.BoundParameters $arguments = @{ 'AzureRmContext' = $context } foreach ($argName in $boundParams.Keys) { if ($argName -ne 'AsJob' -and $argName -ne 'AzureRmContext') { $arguments[$argName] = $boundParams[$argName] } } $script = { [hashtable] $params = $args[0] New-AzVm @params } $jobName = "Creating VM $Name" return Start-Job -Name $jobName -ScriptBlock $script -ArgumentList $arguments } else { $vm = $vmi.GetOrCreate($createParams, [ProgressRange]::new(0.0, 1.0)) Write-Progress "Done." -Completed return [PSAzureVm]::new( $vm, $piai.DomainNameLabel + "." + $locationi.Value + ".cloudapp.azure.com") } } } } class PSAzureVm { [Microsoft.Azure.Commands.Compute.Models.PSVirtualMachine] $Vm; [string] $Name; [string] $ResourceGroupName; [string] $Fqdn; PSAzureVm([Microsoft.Azure.Commands.Compute.Models.PSVirtualMachine] $vm, [string] $fqdn) { $this.Vm = $vm $this.Name = $vm.Name $this.ResourceGroupName = $vm.ResourceGroupName $this.Fqdn = $fqdn } } class Location { [int] $Priority; [string] $Value; Location() { $this.Priority = 0 $this.Value = $null } } class CreateParams { [string] $Location; [object] $Context; CreateParams( [string] $location, [object] $context) { $this.Location = $location $this.Context = $context } } class ProgressRange { [double] $Start; [double] $Size; ProgressRange([double] $start, [double] $size) { $this.Start = $start; $this.Size = $size; } } class AzureObject { [string] $Name; [AzureObject[]] $Children; [int] $Priority; [int] $ObjectSize; [bool] $GetInfoCalled = $false; [object] $info = $null; AzureObject([string] $name, [AzureObject[]] $children) { $this.Name = $name $this.Children = $children $this.Priority = 0 $this.ObjectSize = 1 foreach ($child in $this.Children) { if ($this.Priority -lt $child.Priority) { $this.Priority = $child.Priority } $this.ObjectSize += $child.ObjectSize } $this.Priority++ } [string] GetResourceType() { return $null } [object] GetInfoOrThrow([object] $context) { return $null } [object] Create([CreateParams] $p) { return $null } [object] GetInfo([object] $context) { if (!$this.GetInfoCalled) { $this.GetInfoCalled = $true try { $this.Info = $this.GetInfoOrThrow($context) } catch { # ignore all errors } } return $this.Info; } [void] UpdateLocation([Location] $location, [object] $context) { if ($this.Priority -gt $location.Priority) { if ($this.Name) { $i = $this.GetInfo($context) if ($i) { $location.Value = $i.Location $location.Priority = $this.Priority return; } } foreach ($child in $this.Children) { $child.UpdateLocation($location, $context) } } } [object] GetOrCreate([CreateParams] $p, [ProgressRange] $progressRange) { $i = $this.GetInfo($p.Context) if ($i) { return $i } $pSize = $progressRange.Size / $this.ObjectSize $offset = $progressRange.Start foreach ($child in $this.Children) { $pChildSize = $pSize * $child.ObjectSize $pc = [ProgressRange]::new($offset, $pChildSize) $child.GetOrCreate($p, $pc) | Out-Null $offset += $pChildSize } $message = "Creating '" + $this.Name + "' " + $this.GetResourceType() + "." $percent = [convert]::ToInt32($offset * 100) Write-Progress $message -PercentComplete $percent -Status "$percent% Complete:" Write-Verbose $message $this.Info = $this.Create($p) return $this.Info } } class ResourceGroup: AzureObject { ResourceGroup([string] $name): base($name, @()) { } [string] GetResourceType() { return "Resource Group" } [object] GetInfoOrThrow([object] $context) { return Get-AzureRmResourceGroup ` -Name $this.Name ` -AzureRmContext $context ` -ErrorAction Stop } [object] Create([CreateParams] $p) { return New-AzureRmResourceGroup ` -Name $this.Name ` -Location $p.Location ` -AzureRmContext $p.Context ` -WarningAction SilentlyContinue ` -ErrorAction Stop } } class Resource1: AzureObject { [ResourceGroup] $ResourceGroup; Resource1( [string] $name, [ResourceGroup] $resourceGroup, [AzureObject[]] $children ): base($name, $children) { $this.ResourceGroup = $resourceGroup } Resource1( [string] $name, [ResourceGroup] $resourceGroup ): base($name, @($resourceGroup)) { $this.ResourceGroup = $resourceGroup } [string] GetResourceGroupName() { return $this.ResourceGroup.Info.ResourceGroupName; } } class VirtualNetwork: Resource1 { [string] $AddressPrefix; VirtualNetwork( [string] $name, [ResourceGroup] $resourceGroup, [string] $addressPrefix ): base($name, $resourceGroup) { $this.AddressPrefix = $addressPrefix } [string] GetResourceType() { return "Virtual Network" } [object] GetInfoOrThrow([object] $context) { return Get-AzureRmVirtualNetwork ` -ResourceGroupName $this.ResourceGroup.Name ` -Name $this.Name ` -AzureRmContext $context ` -ErrorAction Stop } [object] Create([CreateParams] $p) { return New-AzureRmVirtualNetwork ` -ResourceGroupName $this.GetResourceGroupName() ` -Location $p.Location ` -Name $this.Name ` -AddressPrefix $this.AddressPrefix ` -AzureRmContext $p.Context ` -WarningAction SilentlyContinue ` -ErrorAction Stop } } class PublicIpAddress: Resource1 { [string] $DomainNameLabel; [string] $AllocationMethod; PublicIpAddress( [string] $name, [ResourceGroup] $resourceGroup, [string] $domainNameLabel, [string] $allocationMethod ): base($name, $resourceGroup) { $this.DomainNameLabel = $domainNameLabel.ToLower() $this.AllocationMethod = $allocationMethod } [string] GetResourceType() { return "Public IP Address" } [object] GetInfoOrThrow([object] $context) { return Get-AzureRMPublicIpAddress ` -ResourceGroupName $this.ResourceGroup.Name ` -Name $this.Name ` -AzureRmContext $context ` -ErrorAction Stop } [object] Create([CreateParams] $p) { return New-AzureRmPublicIpAddress ` -ResourceGroupName $this.GetResourceGroupName() ` -Location $p.Location ` -Name $this.Name ` -DomainNameLabel $this.DomainNameLabel ` -AllocationMethod $this.AllocationMethod ` -AzureRmContext $p.Context ` -WarningAction SilentlyContinue ` -ErrorAction Stop } } class SecurityGroup: Resource1 { [int[]] $OpenPorts; SecurityGroup( [string] $name, [ResourceGroup] $resourceGroup, [int[]] $OpenPorts ): base($name, $resourceGroup) { $this.OpenPorts = $OpenPorts } [string] GetResourceType() { return "Security Group" } [object] GetInfoOrThrow([object] $context) { return Get-AzureRmNetworkSecurityGroup ` -ResourceGroupName $this.ResourceGroup.Name ` -Name $this.Name ` -AzureRmContext $context ` -ErrorAction Stop } [object] Create([CreateParams] $p) { $rules = New-Object ` "System.Collections.Generic.List[Microsoft.Azure.Commands.Network.Models.PSSecurityRule]" $priority = 1000 foreach ($port in $this.OpenPorts) { $name = $this.Name + $port $securityRuleConfig = New-AzureRmNetworkSecurityRuleConfig ` -Name $name ` -Protocol "Tcp" ` -Priority $priority ` -Access "Allow" ` -Direction "Inbound" ` -SourcePortRange "*" ` -SourceAddressPrefix "*" ` -DestinationPortRange $port ` -DestinationAddressPrefix "*" ` -ErrorAction Stop $rules.Add($securityRuleConfig) ++$priority } return New-AzureRmNetworkSecurityGroup ` -ResourceGroupName $this.GetResourceGroupName() ` -Location $p.Location ` -Name $this.Name ` -SecurityRules $rules ` -AzureRmContext $p.Context ` -WarningAction SilentlyContinue ` -ErrorAction Stop } } class Subnet: AzureObject { [VirtualNetwork] $VirtualNetwork; [string] $SubnetAddressPrefix; Subnet([string] $name, [VirtualNetwork] $virtualNetwork, [string] $subnetAddressPrefix): base($name, @($virtualNetwork)) { $this.VirtualNetwork = $virtualNetwork $this.SubnetAddressPrefix = $subnetAddressPrefix } [string] GetResourceType() { return "Subnet" } [object] GetInfoFromVirtualNetworkInfo([object] $virtualNetworkInfo) { return $virtualNetworkInfo ` | Get-AzureRmVirtualNetworkSubnetConfig -Name $this.Name -ErrorAction Stop } [object] GetInfoOrThrow([object] $context) { $virtualNetworkInfo = $this.VirtualNetwork.GetInfo($context) if ($virtualNetworkInfo) { return $this.GetInfoFromVirtualNetworkInfo($virtualNetworkInfo) } return $null } [object] Create([CreateParams] $p) { $virtualNetworkInfo = $this.VirtualNetwork.Info try { return $this.GetInfoFromVirtualNetworkInfo($virtualNetworkInfo) } catch { } $virtualNetworkInfo = Add-AzureRmVirtualNetworkSubnetConfig ` -VirtualNetwork $virtualNetworkInfo ` -Name $this.Name ` -AddressPrefix $this.SubnetAddressPrefix $virtualNetworkInfo = Set-AzureRmVirtualNetwork ` -VirtualNetwork $virtualNetworkInfo ` -AzureRmContext $p.Context ` -ErrorAction Stop return $this.GetInfoFromVirtualNetworkInfo($virtualNetworkInfo) } } class NetworkInterface: Resource1 { [Subnet] $Subnet; [PublicIpAddress] $PublicIpAddress; [SecurityGroup] $SecurityGroup; NetworkInterface( [string] $name, [ResourceGroup] $resourceGroup, [Subnet] $subnet, [PublicIpAddress] $publicIpAddress, [SecurityGroup] $securityGroup ): base($name, $resourceGroup, @($subnet, $publicIpAddress, $securityGroup)) { $this.Subnet = $subnet $this.PublicIpAddress = $publicIpAddress $this.SecurityGroup = $securityGroup } [string] GetResourceType() { return "Network Interface" } [object] GetInfoOrThrow([object] $context) { return Get-AzureRMNetworkInterface ` -ResourceGroupName $this.ResourceGroup.Name ` -Name $this.Name ` -AzureRmContext $context ` -ErrorAction Stop } [object] Create([CreateParams] $p) { $publicIpAddressInfo = $this.PublicIpAddress.Info $subnetInfo = $this.Subnet.Info $securityGroupInfo = $this.SecurityGroup.Info return New-AzureRmNetworkInterface ` -ResourceGroupName $this.GetResourceGroupName() ` -Location $p.Location ` -Name $this.Name ` -PublicIpAddressId $publicIpAddressInfo.Id ` -SubnetId $subnetInfo.Id ` -NetworkSecurityGroupId $securityGroupInfo.Id ` -AzureRmContext $p.Context ` -WarningAction SilentlyContinue ` -ErrorAction Stop } } class VirtualMachine: Resource1 { [NetworkInterface] $NetworkInterface; [pscredential] $Credential; [string] $ImageName; [object] $Images; [string] $Size; VirtualMachine( [string] $name, [ResourceGroup] $resourceGroup, [NetworkInterface] $networkInterface, [PSCredential] $credential, [string] $imageName, [object] $images, [string] $size): base($name, $resourceGroup, @($networkInterface)) { $this.Credential = $credential $this.ImageName = $imageName $this.NetworkInterface = $networkInterface $this.Images = $images $this.Size = $size } [string] GetResourceType() { return "Virtual Machine" } [object] GetInfoOrThrow([object] $context) { return Get-AzureRmVM ` -ResourceGroupName $this.ResourceGroup.Name ` -Name $this.Name ` -AzureRmContext $context ` -ErrorAction Stop } [object] Create([CreateParams] $p) { $networkInterfaceInstance = $this.NetworkInterface.Info $vmImage = $this.Images | Where-Object { $_.Name -eq $this.ImageName } | Select-Object -First 1 if (-not $vmImage) { throw "Unknown image: " + $this.ImageName } $vmConfig = New-AzureRmVMConfig -VMName $this.Name -VMSize $this.Size -ErrorAction Stop $vmComputerName = $this.Name switch ($vmImage.Type) { "Windows" { $vmConfig = $vmConfig | Set-AzureRmVMOperatingSystem ` -Windows ` -ComputerName $vmComputerName ` -Credential $this.Credential ` -ErrorAction Stop } "Linux" { $vmConfig = $vmConfig | Set-AzureRmVMOperatingSystem ` -Linux ` -ComputerName $vmComputerName ` -Credential $this.Credential ` -ErrorAction Stop } } $vmImageImage = $vmImage.Image $vmConfig = $vmConfig ` | Set-AzureRmVMSourceImage ` -PublisherName $vmImageImage.publisher ` -Offer $vmImageImage.offer ` -Skus $vmImageImage.sku ` -Version $vmImageImage.version ` -ErrorAction Stop ` | Add-AzureRmVMNetworkInterface ` -Id $networkInterfaceInstance.Id ` -ErrorAction Stop $rgName = $this.GetResourceGroupName() New-AzureRmVm ` -ResourceGroupName $rgName ` -Location $p.Location ` -VM $vmConfig ` -AzureRmContext $p.Context ` -WarningAction SilentlyContinue ` -ErrorAction Stop return Get-AzureRmVM -ResourceGroupName $rgName -Name $this.Name -AzureRmContext $p.Context } } function New-PsObject { param([hashtable] $property) New-Object psobject -Property $property } $staticImages = New-PsObject @{ Linux = New-PsObject @{ CentOS = New-PsObject @{ publisher = "OpenLogic"; offer = "CentOS"; sku = "7.3"; version = "latest"; }; CoreOS = New-PsObject @{ publisher = "CoreOS"; offer = "CoreOS"; sku = "Stable"; version = "latest"; }; Debian = New-PsObject @{ publisher = "credativ"; offer = "Debian"; sku = "8"; version = "latest"; }; "openSUSE-Leap" = New-PsObject @{ publisher = "SUSE"; offer = "openSUSE-Leap"; sku = "42.2"; version = "latest"; }; RHEL = New-PsObject @{ publisher = "RedHat"; offer = "RHEL"; sku = "7.3"; version = "latest"; }; SLES = New-PsObject @{ publisher = "SUSE"; offer = "SLES"; sku = "12-SP2"; version = "latest"; }; UbuntuLTS = New-PsObject @{ publisher = "Canonical"; offer = "UbuntuServer"; sku = "16.04-LTS"; version = "latest"; }; }; Windows = New-PsObject @{ Win2016Datacenter = New-PsObject @{ publisher = "MicrosoftWindowsServer"; offer = "WindowsServer"; sku = "2016-Datacenter"; version = "latest"; }; Win2012R2Datacenter = New-PsObject @{ publisher = "MicrosoftWindowsServer"; offer = "WindowsServer"; sku = "2012-R2-Datacenter"; version = "latest"; }; Win2012Datacenter = New-PsObject @{ publisher = "MicrosoftWindowsServer"; offer = "WindowsServer"; sku = "2012-Datacenter"; version = "latest"; }; Win2008R2SP1 = New-PsObject @{ publisher = "MicrosoftWindowsServer"; offer = "WindowsServer"; sku = "2008-R2-SP1"; version = "latest"; }; }; } # Images # an array of @{ Type = ...; Name = ...; Image = ... } # $images = $jsonImages.outputs.aliases.value.psobject.Properties | ForEach-Object { $images = $staticImages.psobject.Properties | ForEach-Object { # e.g. "Linux" $type = $_.Name $_.Value.psobject.Properties | ForEach-Object { New-Object -TypeName psobject -Property @{ # e.g. "Linux" Type = $type; # e.g. "CentOs" Name = $_.Name; # e.g. @{ publisher = "OpenLogic"; offer = "CentOS"; sku = "7.3"; version = "latest" } Image = $_.Value } } } Export-ModuleMember -Function New-AzVm # SIG # Begin signature block # MIIkBgYJKoZIhvcNAQcCoIIj9zCCI/MCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDZ94lM46wbzEcp # bHgnkeucij5IDhMCVKZEiFv17ZaUcKCCDYIwggYAMIID6KADAgECAhMzAAAAww6b # p9iy3PcsAAAAAADDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTcwODExMjAyMDI0WhcNMTgwODExMjAyMDI0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC7V9c40bEGf0ktqW2zY596urY6IVu0mK6N1KSBoMV1xSzvgkAqt4FTd/NjAQq8 # zjeEA0BDV4JLzu0ftv2AbcnCkV0Fx9xWWQDhDOtX3v3xuJAnv3VK/HWycli2xUib # M2IF0ZWUpb85Iq2NEk1GYtoyGc6qIlxWSLFvRclndmJdMIijLyjFH1Aq2YbbGhEl # gcL09Wcu53kd9eIcdfROzMf8578LgEcp/8/NabEMC2DrZ+aEG5tN/W1HOsfZwWFh # 8pUSoQ0HrmMh2PSZHP94VYHupXnoIIJfCtq1UxlUAVcNh5GNwnzxVIaA4WLbgnM+ # Jl7wQBLSOdUmAw2FiDFfCguLAgMBAAGjggF/MIIBezAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUpxNdHyGJVegD7p4XNuryVIg1Ga8w # UQYDVR0RBEowSKRGMEQxDDAKBgNVBAsTA0FPQzE0MDIGA1UEBRMrMjMwMDEyK2M4 # MDRiNWVhLTQ5YjQtNDIzOC04MzYyLWQ4NTFmYTIyNTRmYzAfBgNVHSMEGDAWgBRI # bmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEt # MDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAE2X # TzR+8XCTnOPVGkucEX5rJsSlJPTfRNQkurNqCImZmssx53Cb/xQdsAc5f+QwOxMi # 3g7IlWe7bn74fJWkkII3k6aD00kCwaytWe+Rt6dmAA6iTCXU3OddBwLKKDRlOzmD # rZUqjsqg6Ag6HP4+e0BJlE2OVCUK5bHHCu5xN8abXjb1p0JE+7yHsA3ANdkmh1// # Z+8odPeKMAQRimfMSzVgaiHnw40Hg16bq51xHykmCRHU9YLT0jYHKa7okm2QfwDJ # qFvu0ARl+6EOV1PM8piJ858Vk8gGxGNSYQJPV0gc9ft1Esq1+fTCaV+7oZ0NaYMn # 64M+HWsxw+4O8cSEQ4fuMZwGADJ8tyCKuQgj6lawGNSyvRXsN+1k02sVAiPGijOH # OtGbtsCWWSygAVOEAV/ye8F6sOzU2FL2X3WBRFkWOCdTu1DzXnHf99dR3DHVGmM1 # Kpd+n2Y3X89VM++yyrwsI6pEHu77Z0i06ELDD4pRWKJGAmEmWhm/XJTpqEBw51sw # THyA1FBnoqXuDus9tfHleR7h9VgZb7uJbXjiIFgl/+RIs+av8bJABBdGUNQMbJEU # fe7K4vYm3hs7BGdRLg+kF/dC/z+RiTH4p7yz5TpS3Cozf0pkkWXYZRG222q3tGxS # /L+LcRbELM5zmqDpXQjBRUWlKYbsATFtXnTGVjELMIIHejCCBWKgAwIBAgIKYQ6Q # 0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh # dGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5 # WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQD # Ex9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4 # BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe # 0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato # 88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v # ++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDst # rjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN # 91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4ji # JV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmh # D+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbi # wZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8Hh # hUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaI # jAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTl # UAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV # HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQF # TuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29m # dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNf # MjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNf # MjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcC # ARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnlj # cHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5 # AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oal # mOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0ep # o/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1 # HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtY # SWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInW # H8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZ # iWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMd # YzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7f # QccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKf # enoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOpp # O6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZO # SEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFdowghXWAgEBMIGVMH4xCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAADDDpun2LLc9ywAAAAAAMMw # DQYJYIZIAWUDBAIBBQCggcwwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK # KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIONEB3Db # +d9Jnn0J0J+QcSy9HoUWlzx84rVtNnv8ansqMGAGCisGAQQBgjcCAQwxUjBQoDaA # NABNAGkAYwByAG8AcwBvAGYAdAAgAEEAegB1AHIAZQAgAFAAbwB3AGUAcgBTAGgA # ZQBsAGyhFoAUaHR0cDovL0NvZGVTaWduSW5mbyAwDQYJKoZIhvcNAQEBBQAEggEA # NHkygghQY1jKG0PVl+6H03hr25lV7T86KsWMcUPukCh+0EGBsUs/MVrP/PJr9+VP # /E6Cf4v8qsdOUmcaSt4K7TaYIUSYpvGNlDoJyV3SQ5R+7vY5Q77HZw/+SKhEJxPE # /xz7jNmXBU+1xcNierRmuFbEkuB7hy24Ur797KYhDDgvumCEHwJReyK0s/2dyzCo # kmwAXphIwqF3YTcMmK7RaXastUEJFBw73JwPORukqqpiDDDiCcA4DxxyKEzmQPZs # 5pK3CeXVxZ0r18xVkPPsNr5e4Tnx+vpMH3+2vspWduBOccGQDX7pmyhiLjgqUfx/ # nz7p0CVEFrWCOfdrVPmeiaGCE0YwghNCBgorBgEEAYI3AwMBMYITMjCCEy4GCSqG # SIb3DQEHAqCCEx8wghMbAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggE8BgsqhkiG9w0B # CRABBKCCASsEggEnMIIBIwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUA # BCAKxsOdmgQC9xBtFhq66AtO481uQ8oSL0tRFWZd8pHm5QIGWckEQW32GBMyMDE3 # MDkyNTE5MzgyOS43NjNaMAcCAQGAAgH0oIG4pIG1MIGyMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMQwwCgYDVQQLEwNBT0MxJzAlBgNVBAsTHm5D # aXBoZXIgRFNFIEVTTjo3QUI1LTJERjItREEzRjElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaCCDsowggZxMIIEWaADAgECAgphCYEqAAAAAAAC # MA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRo # b3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0yNTA3MDEyMTQ2NTVaMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A # MIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWlCgCChfvtfGhLLF/Fw+Vh # wna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/FgiIRUQwzXTbg4CLNC3ZOs # 1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeRX4FUsc+TTJLBxKZd0WET # bijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/XcfPfBXday9ikJNQFHRD5wG # Pmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogINeh4HLDpmc085y9Euqf0 # 3GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQABo4IB5jCCAeIwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8RhvFM2hahW1VMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/ # MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ # oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p # Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB # BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v # Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSABAf8EgZUwgZIwgY8GCSsGAQQB # gjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL1BL # SS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBh # AGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG # 9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Ehb7Prpsz1Mb7PBeKp/vpXbRkw # s8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7uVOMzPRgEop2zEBAQZvcXBf/ # XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqRUgCvOA8X9S95gWXZqbVr5MfO # 9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9Va8v/rbljjO7Yl+a21dA6fHO # mWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8+n99lmqQeKZt0uGc+R38ONiU # 9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+Y1klD3ouOVd2onGqBooPiRa6 # YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh2rBQHm+98eEA3+cxB6STOvdl # R3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRyzR30uIUBHoD7G4kqVDmyW9rI # DVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoouLGp25ayp0Kiyc8ZQU3ghvkq # mqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx16HSxVXjad5XwdHeMMD9zOZN # +w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341Hgi62jbb01+P3nSISRIwggTZ # MIIDwaADAgECAhMzAAAAq15Ane5G3yxsAAAAAACrMA0GCSqGSIb3DQEBCwUAMHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE2MDkwNzE3NTY1NFoXDTE4 # MDkwNzE3NTY1NFowgbIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xDDAKBgNVBAsTA0FPQzEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjdBQjUt # MkRGMi1EQTNGMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs+Z+UJigRgmwZgEmijTT # Kcl9mDWaDCzdM8oDzw4C1wvvu+HILkmk09DZ3HPTu0WFnF3lAfX8K6Zim+GAAqfz # NyhFSzXeuLpu+4aQ7eGrEPI48HfCW89AE+45ShvSKnoe/IsB+Km8mo95A8PjcNGs # APOmwMsh3XTU1IeEKIGtGllSYt7s/czFA4lMTQezoNQennsBwjrNY2kJkIgTk8Mt # lKEsBCm4N9VKtTGK5L5ukTPbfO7MMnxcwS3huLDWIokhKiR54uSjzy4Z1lXJSBEk # pKjJ9UjIUsIxkpD6ZjYA+aEwGmgPrWlkS1rx62mfc4Z6tmAflJFejiC91N8OKpDI # SwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFA3HR9AcYMvpwGuUzF8GDmK42GqnMB8G # A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG # RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp # bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH # MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh # UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB # BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAD+f/rouYfFyKq2qFWaVoARLfbPCRt3Q # 61tAPPSMdQWtoYFvJHsNLtKjibrwNiv5yMV3Dnq4fN5U4v0PJl88DHYt+hjRohCo # iF5LKlmVyF93f/5EJLgCmYSRlK8Cx6YqhoSxi/iRJMfQbqM2v/v4uAeabz5p7bbx # m2FnVK026Nvy2uVipjvIbcxDmiYi80SM6y1HJnXTwuab1oqdFU+T6IAgzg4tlQNT # 80DgGtxpXEwh10oTKC9F1YL9pMHfITvJ6z84pT8y+s0GS7gfvJsas5Gxk13i/3mh # FtG4Nfbhkcr3cez9/orD4GU1WMukXg+W5qcir2jzPs/epI2q2O4SpOKhggN0MIIC # XAIBATCB4qGBuKSBtTCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046N0FC # NS0yREYyLURBM0YxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZp # Y2WiJQoBATAJBgUrDgMCGgUAAxUAyey7SC012ZS+to73JqkxboqHjjKggcEwgb6k # gbswgbgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDDAKBgNV # BAsTA0FPQzEnMCUGA1UECxMebkNpcGhlciBOVFMgRVNOOjI2NjUtNEMzRi1DNURF # MSswKQYDVQQDEyJNaWNyb3NvZnQgVGltZSBTb3VyY2UgTWFzdGVyIENsb2NrMA0G # CSqGSIb3DQEBBQUAAgUA3XPS0zAiGA8yMDE3MDkyNTE5MDkwN1oYDzIwMTcwOTI2 # MTkwOTA3WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDdc9LTAgEAMAcCAQACAgJW # MAcCAQACAho/MAoCBQDddSRTAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQB # hFkKAwGgCjAIAgEAAgMW42ChCjAIAgEAAgMHoSAwDQYJKoZIhvcNAQEFBQADggEB # AEnnuy6WUPgGDoX3J2Rr8NEbT1h58uPKBtZt8vhMRfc9nl0rAb6sgBLod6cxc9Iw # x0+HmRoivWNKZg+wB6AF5f1PeqcXM8I6r4YxixXKdhx784O1CaC664AWGv4fqtL1 # 9t/gr84VTvi+AGoIN3WWuQSGnxzVaiugH+LGC9ExHcftExtBKeOiNdT/zD0e41xa # 3ieyi4njVtHUGLmogr+JK4HZQe58koAG7aBSHprOlLx5kkmb/8JLH+c8lXxeZipy # j6wnTWOIKM1Cjnen0VAAO2xQPGa7wXHHK/JnWa77+5xuGrRR8TxrEYXY17P3RhFb # ILh/oB1Ugme13g9CbgdhXj0xggL1MIIC8QIBATCBkzB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMAITMwAAAKteQJ3uRt8sbAAAAAAAqzANBglghkgBZQMEAgEF # AKCCATIwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi # BCAe10SqZpZkT5sQlbc/vHLLqDnoj+jqH8knxxFSRydNxzCB4gYLKoZIhvcNAQkQ # AgwxgdIwgc8wgcwwgbEEFMnsu0gtNdmUvraO9yapMW6Kh44yMIGYMIGApH4wfDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAACrXkCd7kbfLGwAAAAAAKsw # FgQU9ntu9Jt3EBM5GEtNkG0/+KigSAkwDQYJKoZIhvcNAQELBQAEggEAfncOFB0B # Sqs6ygWT8uqXVsgzfKcMIFG5vENzNc70kZ0uV1aonwRhIH7G++P0zpEDgnJi3kio # IXi4EACXh5P8g4nXO1oIZSqnQ2UqdGW67dvPve++gpyn8sDKyQZwytrCW6DA+rWX # 2SEIOriHpZ2LOSKBMXdTAQg+7xdLy49VZg89/i7hqvzf3TpXIIXaRmUlzk1swMcV # ymXSTP2YTM+ZeKaRC+abbPbHsTnnlhLoFArLs51EZq4tsG04JMXD2oSLWH2mNdGU # 2FMkYzHJSX7yzxKZ14IPL1xeePR43qouP1dTquBzm1FXQFQF2TV1sXazA8zY8TbW # 7U+7PGF3KptuUw== # SIG # End signature block |