custom/New-AzMySqlFlexibleServer.ps1
# ---------------------------------------------------------------------------------- # # Copyright Microsoft Corporation # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ---------------------------------------------------------------------------------- <# .Synopsis Creates a new MySQL flexible server .Description Creates a new MySQL flexible server #> $DEFAULT_DB_NAME = 'flexibleserverdb' $DELEGATION_SERVICE_NAME = "Microsoft.DBforMySQL/flexibleServers" $DEFAULT_VNET_PREFIX = '10.0.0.0/16' $DEFAULT_SUBNET_PREFIX = '10.0.0.0/24' $AZURE_ARMNAME = '^[^<>%&:\\?/]{1,260}$' function New-AzMySqlFlexibleServer { [OutputType([Microsoft.Azure.PowerShell.Cmdlets.MySql.Models.Api20210501.IServerAutoGenerated])] [CmdletBinding(DefaultParameterSetName='CreateExpanded', PositionalBinding=$false, SupportsShouldProcess, ConfirmImpact='Medium')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Description('Creates a new MySQL flexible server.')] param( [Parameter(HelpMessage = 'The name of the server.')] [Alias('ServerName')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Path')] [System.String] ${Name}, [Parameter(HelpMessage = 'The name of the resource group that contains the resource, You can obtain this value from the Azure Resource Manager API or the portal.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Path')] [System.String] ${ResourceGroupName}, [Parameter(HelpMessage='The subscription ID that identifies an Azure subscription.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Path')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Runtime.DefaultInfo(Script='(Get-AzContext).Subscription.Id')] [System.String] ${SubscriptionId}, [Parameter(HelpMessage = 'The location the resource resides in.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.String] ${Location}, [Parameter(HelpMessage = 'Availability zone into which to provision the resource.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.String] ${Zone}, [Parameter(HelpMessage = 'Administrator username for the server. Once set, it cannot be changed.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.String] ${AdministratorUserName}, [Parameter(HelpMessage = 'The password of the administrator. Minimum 8 characters and maximum 128 characters. Password must contain characters from three of the following categories: English uppercase letters, English lowercase letters, numbers, and non-alphanumeric characters.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.Security.SecureString] [ValidateNotNullOrEmpty()] ${AdministratorLoginPassword}, [Parameter(HelpMessage = 'The name of the sku, typically, tier + family + cores, e.g. Standard_B1ms, Standard_D2ds_v4.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.String] ${Sku}, [Parameter(HelpMessage = 'Compute tier of the server. Accepted values: Burstable, GeneralPurpose, Memory Optimized. Default: Burstable.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [Validateset('Burstable', 'GeneralPurpose', 'MemoryOptimized')] [System.String] ${SkuTier}, [Parameter(HelpMessage = "Backup retention days for the server. Day count is between 1 and 35.")] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.Int32] ${BackupRetentionDay}, [Parameter(HelpMessage = 'Max storage allowed for a server.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.Int32] ${StorageInMb}, [Parameter(HelpMessage='Enable or disable Storage Auto Grow. The default value is Disabled')] [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.StorageAutogrow])] [Validateset('Enabled', 'Disabled')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.StorageAutogrow] ${StorageAutogrow}, [Parameter(HelpMessage = "Number of IOPS to be allocated for this server. You will get certain amount of free IOPS based on compute and storage provisioned. The default value for IOPS is free IOPS.")] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [System.Int32] ${Iops}, [Parameter(HelpMessage = 'Application-specific metadata in the form of key-value pairs.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Runtime.Info(PossibleTypes=([Microsoft.Azure.PowerShell.Cmdlets.MySql.Models.Api20171201.IServerForCreateTags]))] [System.Collections.Hashtable] ${Tag}, [Parameter(HelpMessage = 'Server version.')] [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.ServerVersion])] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Body')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.ServerVersion] ${Version}, [Parameter(HelpMessage = 'The subnet IP address prefix to use when creating a new vnet in CIDR format. Default value is 10.0.0.0/24.')] [System.String] ${SubnetPrefix}, [Parameter(HelpMessage = 'The Name or Id of an existing Subnet or name of a new one to create. Use resource ID if you want to use a subnet from different resource group. Please note that the subnet will be delegated to Microsoft.DBforMySQL/flexibleServers. After delegation, this subnet cannot be used for any other type of Azure resources.')] [System.String] ${Subnet}, [Parameter(HelpMessage = 'The IP address prefix to use when creating a new vnet in CIDR format. Default value is 10.0.0.0/16.')] [System.String] ${VnetPrefix}, [Parameter(HelpMessage = 'The Name or Id of an existing virtual network or name of a new one to create. The name must be between 2 to 64 characters. The name must begin with a letter or number, end with a letter, number or underscore, and may contain only letters, numbers, underscores, periods, or hyphens.')] [System.String] ${Vnet}, [Parameter(HelpMessage = 'The id of an existing private dns zone. The suffix of dns zone has to be same as that of fully qualified domain of the server.')] [System.String] ${PrivateDnsZone}, [Parameter(HelpMessage = "Determines the public access. Allowed values: All, None, IP address range (e.g., 1.1.1.1-1.1.1.5, 1.1.1.1) Specifying 0.0.0.0 allows public access from any resources deployed within Azure to access your server. Specifying no IP address sets the server in public access mode but does not create a firewall rule.")] [System.String] ${PublicAccess}, [Parameter(HelpMessage = "Enable or disable high availability feature. Allowed values are 'ZoneRedundant', 'SameZone', and 'Disabled'. Default value is Disabled.")] [Validateset('ZoneRedundant', 'SameZone', 'Disabled')] [Alias('HaEnabled')] [System.String] ${HighAvailability}, [Parameter(HelpMessage = 'The credentials, account, tenant, and subscription used for communication with Azure.')] [Alias('AzureRMContext', 'AzureCredential')] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Azure')] [System.Management.Automation.PSObject] ${DefaultProfile}, [Parameter(HelpMessage = 'Run the command as a job.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [System.Management.Automation.SwitchParameter] ${AsJob}, [Parameter(DontShow, HelpMessage = 'Wait for .NET debugger to attach.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [System.Management.Automation.SwitchParameter] ${Break}, [Parameter(DontShow)] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Runtime.SendAsyncStep[]] # SendAsync Pipeline Steps to be appended to the front of the pipeline. ${HttpPipelineAppend}, [Parameter(DontShow)] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Runtime.SendAsyncStep[]] # SendAsync Pipeline Steps to be prepended to the front of the pipeline. ${HttpPipelinePrepend}, [Parameter(HelpMessage = 'Run the command asynchronously.')] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [System.Management.Automation.SwitchParameter] ${NoWait}, [Parameter(DontShow)] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [System.Uri] # The URI for the proxy server to use. ${Proxy}, [Parameter(DontShow)] [ValidateNotNull()] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [System.Management.Automation.PSCredential] # Credentials for a proxy server to use for the remote call. ${ProxyCredential}, [Parameter(DontShow)] [Microsoft.Azure.PowerShell.Cmdlets.MySql.Category('Runtime')] [System.Management.Automation.SwitchParameter] # Use the default credentials for the proxy. ${ProxyUseDefaultCredentials} ) process { try { if (!$PSBoundParameters.ContainsKey('Location')) { $PSBoundParameters.Location = 'westus2' } if ($PSBoundParameters.ContainsKey('Zone')) { $PSBoundParameters.AvailabilityZone = $PSBoundParameters.Zone $null = $PSBoundParameters.Remove('Zone') } if (!$PSBoundParameters.ContainsKey('AdministratorLoginPassword')) { $Password = Get-GeneratePassword $PSBoundParameters.AdministratorLoginPassword = $Password | ConvertTo-SecureString -AsPlainText -Force } Import-Module -Name Az.Resources if(!$PSBoundParameters.ContainsKey('ResourceGroupName')) { $PSBoundParameters.ResourceGroupName = Get-RandomNumbers -Prefix 'group' -Length 10 $Msg = "Creating Resource Group {0}..." -f $PSBoundParameters.ResourceGroupName Write-Host $Msg if($PSCmdlet.ShouldProcess($PSBoundParameters.ResourceGroupName)) { $null = New-AzResourceGroup -Name $PSBoundParameters.ResourceGroupName -Location $PSBoundParameters.Location -Force } } else { $Msg = 'Checking the existence of the resource group {0} ...' -f $PSBoundParameters.ResourceGroupName Write-Host $Msg try { $null = Get-AzResourceGroup -Name $PSBoundParameters.ResourceGroupName -ErrorAction Stop $Msg = 'Resource group {0} exists ? : True' -f $PSBoundParameters.ResourceGroupName Write-Host $Msg } catch { $Msg = 'Resource group {0} exists ? : False' -f $PSBoundParameters.ResourceGroupName Write-Host $Msg $Msg = "Creating Resource Group {0}..." -f $PSBoundParameters.ResourceGroupName Write-Host $Msg if($PSCmdlet.ShouldProcess($PSBoundParameters.ResourceGroupName)) { $null = New-AzResourceGroup -Name $PSBoundParameters.ResourceGroupName -Location $PSBoundParameters.Location -Force } } } if (!$PSBoundParameters.ContainsKey('Name')) { $PSBoundParameters.Name = Get-RandomNumbers -Prefix 'server' -Length 10 } if (!$PSBoundParameters.ContainsKey('SkuTier')) { $PSBoundParameters.SkuTier = 'Burstable' } if ($PSBoundParameters.ContainsKey('Sku')) { $PSBoundParameters.SkuName = $PSBoundParameters['Sku'] $null = $PSBoundParameters.Remove('Sku') } else { $PSBoundParameters.SkuName = 'Standard_B1ms' } if (!$PSBoundParameters.ContainsKey('BackupRetentionDay')) { $PSBoundParameters.BackupRetentionDay = 7 } if ($PSBoundParameters.ContainsKey('StorageInMb')) { $PSBoundParameters.StorageSizeGb = [Math]::floor($PSBoundParameters['StorageInMb'] / 1024) $null = $PSBoundParameters.Remove('StorageInMb') } else { $PSBoundParameters.StorageSizeGb = 32 } if ($PSBoundParameters.ContainsKey('StorageAutogrow') -And $PSBoundParameters['StorageAutogrow'] -eq 'Enabled') { $PSBoundParameters.StorageAutoGrow = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.StorageAutogrow]::Enabled } else { $PSBoundParameters.StorageAutoGrow = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.StorageAutogrow]::Disabled } if ($PSBoundParameters.ContainsKey('Iops')) { $PSBoundParameters.StorageIop = $PSBoundParameters.Iops $null = $PSBoundParameters.Remove('Iops') } if ($PSBoundParameters.ContainsKey('HighAvailability')){ if($PSBoundParameters['HighAvailability'].ToLower() -eq 'disabled'){ $PSBoundParameters["HighAvailabilityMode"] = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.HighAvailabilityMode]::Disabled } elseif($PSBoundParameters['HighAvailability'].ToLower() -eq 'zoneredundant') { if ($PSBoundParameters.SkuTier -eq 'Burstable') { throw "Zone redundant high availability cannot be enabled for Burstable tier." } $PSBoundParameters["HighAvailabilityMode"] = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.HighAvailabilityMode]::ZoneRedundant $PSBoundParameters.StorageAutoGrow = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.StorageAutogrow]::Enabled } elseif($PSBoundParameters['HighAvailability'].ToLower() -eq 'samezone') { $PSBoundParameters.HighAvailabilityMode = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.HighAvailabilityMode]::SameZone $PSBoundParameters.StorageAutoGrow = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.StorageAutogrow]::Enabled } $null = $PSBoundParameters.Remove('HighAvailability') } if (!$PSBoundParameters.ContainsKey('Version')) { $PSBoundParameters.Version = '5.7' } if ($PSBoundParameters.ContainsKey('AdministratorUserName')) { $PSBoundParameters.AdministratorLogin = $PSBoundParameters['AdministratorUserName'] $null = $PSBoundParameters.Remove('AdministratorUserName') } else { $PSBoundParameters.AdministratorLogin = Get-RandomName } $PSBoundParameters.CreateMode = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Support.CreateMode]::Default # Handling Vnet & Subnet $NetworkKeys = 'PublicAccess', 'Subnet', 'Vnet', 'SubnetPrefix', 'VnetPrefix', 'PrivateDnsZone' $NetworkParameters = @{} foreach($Key in $NetworkKeys){ if ($PSBoundParameters.ContainsKey($Key)){ $NetworkParameters[$Key] = $PSBoundParameters[$Key] $null = $PSBoundParameters.Remove($Key) } } $RequiredKeys = 'SubscriptionId', 'ResourceGroupName', 'Name', 'Location' foreach($Key in $RequiredKeys){ $NetworkParameters[$Key] = $PSBoundParameters[$Key] } if ($NetworkParameters.ContainsKey('Vnet') -Or $NetworkParameters.ContainsKey('Subnet')){ $VnetSubnetParameters = CreateNetworkResource $NetworkParameters $SubnetId = GetSubnetId $VnetSubnetParameters.ResourceGroupName $VnetSubnetParameters.VnetName $VnetSubnetParameters.SubnetName $VnetId = [string]::Join("/",$SubnetId.split("/")[0..8]) $PSBoundParameters.NetworkDelegatedSubnetResourceId = $SubnetId if ([string]::IsNullOrEmpty($PSBoundParameters.NetworkDelegatedSubnetResourceId)) { $null = $PSBoundParameters.Remove('NetworkDelegatedSubnetResourceId') } if ($NetworkParameters.ContainsKey('PrivateDnsZone')){ if (!(Get-Module -ListAvailable -Name Az.PrivateDns)) { throw 'Please install Az.Network module by entering "Install-Module -Name Az.PrivateDns"' } else { Import-Module -Name Az.PrivateDns } $ZoneName = $NetworkParameters["PrivateDnsZone"].split("/")[-1] $DnsResourceGroup = $NetworkParameters["PrivateDnsZone"].split("/")[4] $Links = Get-AzPrivateDnsVirtualNetworkLink -ZoneName $ZoneName -ResourceGroupName $DnsResourceGroup $LinkedFlag = $false foreach($Link in $Links){ if ($Link.VirtualNetworkId -eq $VnetId){ $LinkedFlag = $true break } } if (!$LinkedFlag){ Write-Host "Adding virtual network link to the DNS zone..." New-AzPrivateDnsVirtualNetworkLink -ZoneName $ZoneName -ResourceGroupName $DnsResourceGroup -Name $PSBoundParameters["Name"] -VirtualNetworkId $VnetId } $PSBoundParameters.NetworkPrivateDnsZoneResourceId = $NetworkParameters["PrivateDnsZone"] $null = $PSBoundParameters.Remove('PrivateDnsZone') } else{ throw "To provision a server with private access, you need to provide private DNS zone." } } else{ $RuleName, $StartIp, $EndIp = ParseFirewallRule $NetworkParameters.PublicAccess } $Msg = 'Creating MySQL server {0} in group {1}...' -f $PSBoundParameters.Name, $PSBoundParameters.resourceGroupName Write-Host $Msg $Msg = 'Your server {0} is using sku {1} (Paid Tier). Please refer to https://aka.ms/mysql-pricing for pricing details' -f $PSBoundParameters.Name, $PSBoundParameters.SkuName Write-Host $Msg $Server = Az.MySql.internal\New-AzMySqlFlexibleServer @PSBoundParameters # # Create Database $DatabaseParameter = [Microsoft.Azure.PowerShell.Cmdlets.MySql.Models.Api20171201.Database]::new() $DatabaseParameter.Charset = "utf8" $DatabaseParameter.Collation = "utf8_general_ci" $Msg = 'Creating database {0}...' -f $DEFAULT_DB_NAME Write-Host $Msg $null = New-AzMySqlFlexibleServerDatabase -Name $DEFAULT_DB_NAME -ResourceGroupName $PSBoundParameters.ResourceGroupName -ServerName $PSBoundParameters.Name -Parameter $DatabaseParameter # Create Firewallrules if (![string]::IsNullOrEmpty($RuleName)) { $FirewallRuleName = CreateFirewallRule $RuleName $StartIp $EndIp $PSBoundParameters.ResourceGroupName $PSBoundParameters.Name $Server.FirewallRuleName = $FirewallRuleName } $Server.DatabaseName = $DEFAULT_DB_NAME $Server.SecuredPassword = $PSBoundParameters.AdministratorLoginPassword return $Server } catch { throw } } } function CreateNetworkResource($NetworkParameters) { [OutputType([hashtable])] $WarningPreference = 'silentlycontinue' if (!(Get-Module -ListAvailable -Name Az.Network)) { throw 'Please install Az.Network module by entering "Install-Module -Name Az.Network"' } else { Import-Module -Name Az.Network } # 1. Error Handling # Raise error when user passes values for both parameters if ($NetworkParameters.Containskey('Subnet') -And $NetworkParameters.ContainsKey('PublicAccess')) { throw "Incorrect usage : A combination of the parameters -Subnet and -PublicAccess is invalid. Use either one of them." } # # When address space parameters are passed, the only valid combination is : -Vnet -Subnet -VnetPrefix -SubnetPrefix if ($NetworkParameters.ContainsKey('Vnet') -Or $NetworkParameters.ContainsKey('Subnet')) { if (($NetworkParameters.ContainsKey('VnetPrefix') -And !$NetworkParameters.ContainsKey('SubnetPrefix')) -Or (!$NetworkParameters.ContainsKey('VnetPrefix') -And $NetworkParameters.ContainsKey('SubnetPrefix')) -Or ($NetworkParameters.ContainsKey('VnetPrefix') -And $NetworkParameters.ContainsKey('SubnetPrefix') -And (!$NetworkParameters.ContainsKey('Vnet') -Or !$NetworkParameters.ContainsKey('Subnet')))){ throw "Incorrect usage : -Vnet -Subnet -VnetPrefix -SubnetPrefix must be supplied together." } } #Handle Vnet, Subnet scenario # Only the Subnet ID provided.. if (!$NetworkParameters.ContainsKey('Vnet') -And $NetworkParameters.ContainsKey('Subnet')) { if (IsValidSubnetId $NetworkParameters.Subnet) { Write-Host "You have supplied a subnet Id. Verifying its existence..." $ParsedResult = ParseResourceId $NetworkParameters.Subnet $NetworkParameters.VnetName = $ParsedResult.VnetName $NetworkParameters.SubnetName = $ParsedResult.SubnetName $NetworkParameters.ResourceGroupName = $ParsedResult.ResourceGroupName $SubnetFlag = $true try { # Valid Subnet ID is provided $Subnet = Get-AzVirtualNetworkSubnetConfig -ResourceId $NetworkParameters.Subnet -ErrorAction Stop } catch { # Invalid subnet ID is provided, creating a new one. $SubnetFlag = $false Write-Host "The subnet doesn't exist. Creating the subnet" $Subnet = CreateVnetSubnet $NetworkParameters } if ($SubnetFlag){ $Delegations = Get-AzDelegation -Subnet $Subnet if ($null -ne $Delegations){ # Valid but incorrect delegation $Delegations | ForEach-Object {if ($PSItem.ServiceName -ne $DELEGATION_SERVICE_NAME) { $Msg = "Can not use subnet with existing delegations other than {0}" -f $DELEGATION_SERVICE_NAME throw $Msg }} } else { # Valid but no delegation $Vnet = Get-AzVirtualNetwork -ResourceGroupName $NetworkParameters.ResourceGroupName -Name $NetworkParameters.VnetName $Subnet = Get-AzVirtualNetworkSubnetConfig -Name $NetworkParameters.SubnetName -VirtualNetwork $Vnet $Subnet = Add-AzDelegation -Name $DELEGATION_SERVICE_NAME -ServiceName $DELEGATION_SERVICE_NAME -Subnet $Subnet $Vnet | Set-AzVirtualNetwork } } } else { throw "The Subnet ID is not a valid form of resource id." } } elseif ($NetworkParameters.ContainsKey('Vnet') -And !$NetworkParameters.ContainsKey('Subnet')) { if (IsValidVnetId $NetworkParameters.Vnet){ Write-Host "You have supplied a vnet Id. Verifying its existence..." IsValidRgLocation $NetworkParameters.Vnet $NetworkParameters $ParsedResult = ParseResourceId $NetworkParameters.Vnet $NetworkParameters.VnetName = $ParsedResult.VnetName $NetworkParameters.SubnetName = 'Subnet' + $NetworkParameters.Name $Subnet = CreateVnetSubnet $NetworkParameters } elseif ($NetworkParameters.Vnet -Match $AZURE_ARMNAME) { Write-Host "You have supplied a vnet Name. Verifying its existence..." $NetworkParameters.VnetName = $NetworkParameters.Vnet $NetworkParameters.SubnetName = 'Subnet' + $NetworkParameters.Name $Subnet = CreateVnetSubnet $NetworkParameters IsValidRgLocation $Subnet.Id $NetworkParameters } else { throw "Incorrectly formed Vnet id or Vnet name" } } else { # Both Vnet and Subnet provided if ($NetworkParameters.Vnet -Match $AZURE_ARMNAME -And $NetworkParameters.Subnet -Match $AZURE_ARMNAME) { $NetworkParameters.VnetName = $NetworkParameters.Vnet $NetworkParameters.SubnetName = $NetworkParameters.Subnet $Subnet = CreateVnetSubnet $NetworkParameters } else { if ($NetworkParameters.ContainsKey('SubnetPrefix') -And $NetworkParameters.ContainsKey('VnetPrefix')) { $Msg = "If you pass an address prefix, please consider passing a name (instead of Id) for a subnet or vnet." } else { $Msg = "If you pass both --vnet and --subnet, consider passing names instead of ids." } throw $Msg } } return $NetworkParameters } function GetSubnetId($ResourceGroupName, $VnetName, $SubnetName){ if (!($ResourceGroupName -is [String])){ $ResourceGroupName = $ResourceGroupName[0]} $Vnet = Get-AzVirtualNetwork -Name $VnetName -ResourceGroupName $ResourceGroupName $Subnet = Get-AzVirtualNetworkSubnetConfig -Name $SubnetName -VirtualNetwork $Vnet return $Subnet.Id } function CreateVnetSubnet($Parameters){ if (!$Parameters.ContainsKey('SubnetPrefix')){$Parameters.SubnetPrefix = $DEFAULT_SUBNET_PREFIX} if (!$Parameters.ContainsKey('VnetPrefix')){$Parameters.VnetPrefix = $DEFAULT_VNET_PREFIX} try { $Vnet = Get-AzVirtualNetwork -Name $Parameters.VnetName -ResourceGroupName $Parameters.ResourceGroupName -ErrorAction Stop $prefixes = $Vnet.AddressSpace.AddressPrefixes if (!($prefixes -Contains $Parameters.VnetPrefix)){ $prefixes.Add($Parameters.VnetPrefix) $Vnet.AddressSpace.AddressPrefixes = $prefixes $Vnet | Set-AzVirtualNetwork } } catch { $Msg = "Creating new vnet {0} in resource group {1}" -f $Parameters.VnetName, $Parameters.ResourceGroupName Write-Host $Msg if($PSCmdlet.ShouldProcess($Parameters.VnetName)) { New-AzVirtualNetwork -Name $Parameters.VnetName -ResourceGroupName $Parameters.ResourceGroupName -Location $Parameters.Location -AddressPrefix $Parameters.VnetPrefix -Force } } $Subnet = CreateAndDelegateSubnet $Parameters return $Subnet } function CreateAndDelegateSubnet($Parameters) { $SubnetFlag = $true $Vnet = Get-AzVirtualNetwork -Name $Parameters.VnetName -ResourceGroupName $Parameters.ResourceGroupName -ErrorAction Stop try { $Subnet = Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $Vnet -Name $Parameters.SubnetName -ErrorAction Stop } catch { $SubnetFlag = $false $Msg = 'Creating new subnet {0} in resource group {1} and delegating it to {2}' -f $Parameters.SubnetName, $Parameters.ResourceGroupName, $DELEGATION_SERVICE_NAME Write-Host $Msg } if (!$SubnetFlag) { $Delegation = New-AzDelegation -Name $DELEGATION_SERVICE_NAME -ServiceName $DELEGATION_SERVICE_NAME Add-AzVirtualNetworkSubnetConfig -Name $Parameters.SubnetName -VirtualNetwork $Vnet -AddressPrefix $Parameters.SubnetPrefix -Delegation $Delegation | Set-AzVirtualNetwork } else { # check if existing subnet is delegated $Delegations = Get-AzDelegation -Subnet $Subnet if ($null -ne $Delegations){ $Delegations | ForEach-Object {If ($PSItem.ServiceName -ne $DELEGATION_SERVICE_NAME) { $Msg = "Can not use subnet with existing delegations other than {0}" -f $DELEGATION_SERVICE_NAME throw $Msg }} } else { # Valid but no delegation $Subnet = Add-AzDelegation -Name $DELEGATION_SERVICE_NAME -ServiceName $DELEGATION_SERVICE_NAME -Subnet $Subnet $Vnet | Set-AzVirtualNetwork } } return $Subnet } function CreateFirewallRule($RuleName, $StartIp, $EndIp, $ResourceGroupName, $ServerName) { $FirewallRule = New-AzMySqlFlexibleServerFirewallRule -Name $RuleName -ResourceGroupName $ResourceGroupName -ServerName $ServerName -EndIPAddress $EndIp -StartIPAddress $StartIp return $FirewallRule.Name } function ParseFirewallRule($PublicAccess){ $PublicAccess = [string]$PublicAccess if ([string]::IsNullOrEmpty($PublicAccess)) { $PublicAccess = 'none' } if ($PublicAccess.ToLower() -ne 'none') { $Date = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" if ($PublicAccess.ToLower() -eq 'all'){ $StartIp = '0.0.0.0' $EndIp = '255.255.255.255' $RuleName = "AllowAll_" + $Date } else { $Parsed = $PublicAccess -split "-" if ($Parsed.length -eq 1) { $StartIp = $Parsed[0] $EndIp = $Parsed[0] } elseif ($Parsed.length -eq 2) { $StartIp = $Parsed[0] $EndIp = $Parsed[1] } else { throw "Incorrect usage: --public-access. Acceptable values are \'all\', \'none\',\'<startIP>\' and \'<startIP>-<destinationIP>\' where startIP and destinationIP ranges from 0.0.0.0 to 255.255.255.255" } if ($StartIp -eq '0.0.0.0' -And $EndIp -eq '0.0.0.0') { $RuleName = "AllowAllAzureServicesAndResourcesWithinAzureIps_" + $Date $Msg = 'Configuring server firewall rule to accept connections from all Azure resources...' } elseif ($StartIP -eq $EndIP) { $Msg = 'Configuring server firewall rule to accept connections from ' + $StartIP $RuleName = "FirewallIPAddress_" + $Date } else { $Msg = 'Configuring server firewall rule to accept connections from {0} to {1}' -f $StartIP, $EndIp $RuleName = "FirewallIPAddress_" + $Date } Write-Host $Msg } } else{ $StartIp = $null $EndIp = $null $RuleName = $null } return $RuleName, $StartIp, $EndIp } function IsValidVnetId($Rid){ $VnetFormat = "\/subscriptions\/[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}\/resourceGroups\/[-\w\._\(\)]+\/providers\/Microsoft.Network\/virtualNetworks\/[^<>%&:\\?/]{1,260}$" if ( $Rid -match $VnetFormat ) { return $True } return $False } function IsValidSubnetId($Rid){ $SubnetFormat = "\/subscriptions\/[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}\/resourceGroups/[-\w\._\(\)]+\/providers\/Microsoft.Network\/virtualNetworks\/[^<>%&:\\?/]{1,260}\/subnets\/[^<>%&:\\?/]{1,260}$" if ( $Rid -match $SubnetFormat ) { return $True } return $False } function ParseResourceId($Rid){ $Splits = $Rid -split "/" $ParsedResults = @{} if ($Splits.length -gt 1){ $ParsedResults["SubscriptionId"] = $Splits[2] $ParsedResults["ResourceGroupName"] = $Splits[4] $ParsedResults["VnetName"] = $Splits[8] if ($Splits.length -eq 11) { $ParsedResults["SubnetName"] = $Splits[10] } } return $ParsedResults } function IsValidRgLocation($ResourceId, $Parameters){ $ParsedResults = ParseResourceId $ResourceId $Group = Get-AzResourceGroup -Name $ParsedResults["ResourceGroupName"] $ParsedResults["Location"] = $Group.Location if ($Parameters.SubscriptionId -eq $ParsedResults.SubscriptionId -And $Parameters.Location -eq $ParsedResults.Location) { return $True } throw "Incorrect Usage : The location and subscription of the server, Vnet and Subnet should be same." } function Get-RandomNumbers($Prefix, $Length) { $Generated = "" for($i = 0; $i -lt $Length; $i++){ $Generated += Get-Random -Maximum 10 } return $Prefix + $Generated } function Get-RandomName() { $Noun = Get-Content -Path (Join-Path $PSScriptRoot ".\nouns.txt") | Get-Random $Adjective = Get-Content -Path (Join-Path $PSScriptRoot ".\adjectives.txt") | Get-Random $Number = Get-Random -Maximum 10 $RandomName = $Adjective + $Noun + $Number return $RandomName } function Get-GeneratePassword() { $Password = '' $Chars = 'abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890' for ($i = 0; $i -lt 16; $i++ ) { $Password += $Chars[(Get-Random -Minimum 0 -Maximum $Chars.Length)] } $Password = ($Password -split '' | Sort-Object {Get-Random}) -join '' return $Password } # SIG # Begin signature block # MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBrw0MAnwk/INIq # jC62wAhB3nQL6UE80z31JfwRUnKGdaCCDYUwggYDMIID66ADAgECAhMzAAADTU6R # phoosHiPAAAAAANNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI4WhcNMjQwMzE0MTg0MzI4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDUKPcKGVa6cboGQU03ONbUKyl4WpH6Q2Xo9cP3RhXTOa6C6THltd2RfnjlUQG+ # Mwoy93iGmGKEMF/jyO2XdiwMP427j90C/PMY/d5vY31sx+udtbif7GCJ7jJ1vLzd # j28zV4r0FGG6yEv+tUNelTIsFmmSb0FUiJtU4r5sfCThvg8dI/F9Hh6xMZoVti+k # bVla+hlG8bf4s00VTw4uAZhjGTFCYFRytKJ3/mteg2qnwvHDOgV7QSdV5dWdd0+x # zcuG0qgd3oCCAjH8ZmjmowkHUe4dUmbcZfXsgWlOfc6DG7JS+DeJak1DvabamYqH # g1AUeZ0+skpkwrKwXTFwBRltAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUId2Img2Sp05U6XI04jli2KohL+8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMDUxNzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACMET8WuzLrDwexuTUZe9v2xrW8WGUPRQVmyJ1b/BzKYBZ5aU4Qvh5LzZe9jOExD # YUlKb/Y73lqIIfUcEO/6W3b+7t1P9m9M1xPrZv5cfnSCguooPDq4rQe/iCdNDwHT # 6XYW6yetxTJMOo4tUDbSS0YiZr7Mab2wkjgNFa0jRFheS9daTS1oJ/z5bNlGinxq # 2v8azSP/GcH/t8eTrHQfcax3WbPELoGHIbryrSUaOCphsnCNUqUN5FbEMlat5MuY # 94rGMJnq1IEd6S8ngK6C8E9SWpGEO3NDa0NlAViorpGfI0NYIbdynyOB846aWAjN # fgThIcdzdWFvAl/6ktWXLETn8u/lYQyWGmul3yz+w06puIPD9p4KPiWBkCesKDHv # XLrT3BbLZ8dKqSOV8DtzLFAfc9qAsNiG8EoathluJBsbyFbpebadKlErFidAX8KE # usk8htHqiSkNxydamL/tKfx3V/vDAoQE59ysv4r3pE+zdyfMairvkFNNw7cPn1kH # Gcww9dFSY2QwAxhMzmoM0G+M+YvBnBu5wjfxNrMRilRbxM6Cj9hKFh0YTwba6M7z # ntHHpX3d+nabjFm/TnMRROOgIXJzYbzKKaO2g1kWeyG2QtvIR147zlrbQD4X10Ab # rRg9CpwW7xYxywezj+iNAc+QmFzR94dzJkEPUSCJPsTFMIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXIwghluAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA # A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIDT2 # rLP5tNPsM14AyZhAFFCFcHlb4sY8NdwkKUlUFUVMMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEACBFW9VIRIwIJBy1XqA1khN1zOmt5U+lG15oL # +bQST7Qpj202tGEpL0PzQnQn41cJj3xp2TfBY5Q9sL/v0CNh1cSQjZFBj3mBpemV # tbdP8KET5LGsCT+zWl0dHE0JKI1LeQHUyNQME/vhKOPJ+NuFxWtpUtZH3T9eUF9d # FNt0N/VwvUr7dEZU0EBoUiIlVFAw+xZ0Vz7WK8XhApLUwZwp5iUjJi/xRslaFsw/ # di8r9LnU2DZBdxDontoullvfC9TITSmG87JkoORpP3AFB7K/QWNj2GgEi3hx4y+N # lNoRPglmOtRvADqPO2vON2ESwSrwSF0SoeQbl1pG6jweNMVopqGCFvwwghb4Bgor # BgEEAYI3AwMBMYIW6DCCFuQGCSqGSIb3DQEHAqCCFtUwghbRAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFQBgsqhkiG9w0BCRABBKCCAT8EggE7MIIBNwIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCBqjgZPyAyGwD1ZHBhJs1ycTjncj9Zhf82c # jUVDrXJwSQIGZIr5nXisGBIyMDIzMDYyOTA4MDQ1NS41NVowBIACAfSggdCkgc0w # gcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsT # HE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBU # U1MgRVNOOkREOEMtRTMzNy0yRkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T # dGFtcCBTZXJ2aWNloIIRVDCCBwwwggT0oAMCAQICEzMAAAHFA83NIaH07zkAAQAA # AcUwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw # HhcNMjIxMTA0MTkwMTMyWhcNMjQwMjAyMTkwMTMyWjCByjELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp # Y2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046REQ4Qy1FMzM3 # LTJGQUUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCrSF2zvR5fbcnulqmlopdGHP5N # Psknc69V/f43x82nFGzmNjiES/cFX/DkRZdtl07ibfGPTWVMj/EOSr7K2O6I97zE # ZexnEOe2/svUTMx3mMhKon55i7ySBXTnqaqzx0GjnnFk889zF/m7X3OfThoxAXk9 # dX8LhktKMVr0gU1yuJt06beUZbWtBEVraNSy6nqC/rfirlTAfT1YYa7TPz1Fu1vI # znm+YGBZXx53ptkJmtyhgiMwvwVFO8aXOeqboe3Bl1czAodPdr+QtRI+IYCysiAT # PPs2kGl46yCz1OvDJZNkE1sHDIgAKZDfiP65Hh63aFmT40fj0qEQnJgPb504hoMY # HYRQ0VJhzLUySC1m3V5GoEHSb5g9jPseOhw/KQpg1BntO/7OCU598KJrHWM5vS7o # hgLlfUmvwDBNyxoPK7eoCHHxwVA30MOCJVnD5REVnyjKgOTqwhXWfHnNkvL6E21q # R49f1LtjyfWpZ8COhc8TorT91tPDzsQ4kv8GUkZwqgVPK2vTM+D8w0lJvp/Zr/AO # RegYIZYmJCsZPGM4/5H3r+cggbTl4TUumTLYU51gw8HgOFbu0F1lq616lNO5KGaC # f4YoRHwCgDWBJKTUQLllfhymlWeAmluUwG7yv+0KF8dV1e+JjqENKEfBAKZmpl5u # BJgeceXi6sT7grpkLwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFFTquzi/WbE1gb+u # 2kvCtXB6TQVrMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud # HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr # BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ # KoZIhvcNAQELBQADggIBAIyo3nx+swc5JxyIr4J2evp0rx9OyBAN5n1u9CMK7E0g # lkn3b7Gl4pEJ/derjup1HKSQpSdkLp0eEvC3V+HDKLL8t91VD3J/WFhn9GlNL7PS # Gdqgr4/8gMCJQ2bfY1cuEMG7Q/hJv+4JXiM641RyYmGmkFCBBWEXH/nsliTUsJ2M # h57/8atx9uRC2Jihv05r3cNKNuwPWOpqJwSeRyVQ3+YSb1mycKcDX785AOn/xDhw # 98f3gszgnpfQ200F5XLC9YfTC4xo4nMeAMsJ4lSQUT0cTywENV52aPrM8kAj7ujM # uNirDuLhEVuJK19ZlIaPC36UslBlFZQJxPdodi9OjVhYNmySiFaDvvD18XZBuI70 # N+eqhntCjMeLtGI+luOCQkwCGuGl5N/9q3Z734diQo5tSaA8CsfVaOK/CbV3s9ha # xqsvu7mpm6TfoZvWYRNLWgDZdff4LeuC3NGiE/z2plV/v2VW+OaDfg20gIr+kyT3 # 1IG62CG2KkVIxB1tdSdLah4u31wq6/Uwm76AnzepdM2RDZCqHG01G9sT1CqaolDD # lVb/hJnN7Wk9fHI5M7nIOr6JEhS5up5DOZRwKSLI24IsdaHw4sIjmYg4LWIu1UN/ # aXD15auinC7lIMm1P9nCohTWpvZT42OQ1yPWFs4MFEQtpNNZ33VEmJQj2dwmQaD+ # MIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsF # ADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UE # AxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcN # MjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzn # tHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3 # lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFE # yHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+ # jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4x # yDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBc # TyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9 # pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ # 8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pn # ol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYG # NRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cI # FRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEE # AYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E # 7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwr # BgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUF # BwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNV # HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYG # A1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3Js # L3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcB # AQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kv # Y2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUA # A4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2 # P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J # 6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfak # Vqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/AL # aoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtP # u4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5H # LcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEua # bvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvB # QUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb # /wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETR # kPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAsswggI0AgEB # MIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQL # Ex1UaGFsZXMgVFNTIEVTTjpERDhDLUUzMzctMkZBRTElMCMGA1UEAxMcTWljcm9z # b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAIQAa9hdkkrtx # Sjrb4u8RhATHv+eggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDANBgkqhkiG9w0BAQUFAAIFAOhHQn4wIhgPMjAyMzA2MjkwNzM2MzBaGA8yMDIz # MDYzMDA3MzYzMFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA6EdCfgIBADAHAgEA # AgInfzAHAgEAAgIRxTAKAgUA6EiT/gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor # BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA # A4GBAEysGgB6mIu7BO+RmFqvMa33VSbtj96aYhkdatUT1O1WPvBO1WIYKPiSijIi # UvS114r0shAi+s/lHhJcwb5HslYCxvsg2ANvl25cIQ+YdNTYyczemCA20119cpmg # rOqfjVJfWnCuitJHKmubCfHBosv+fduMvkDlkRqKuArwnQxnMYIEDTCCBAkCAQEw # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHFA83NIaH07zkA # AQAAAcUwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B # CRABBDAvBgkqhkiG9w0BCQQxIgQgNJqM0TeClRwk0rKQEg+ejP9Yc2yfyNvtjdrb # Qwwjo5IwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAZAbGR9iR3TAr5XT3A # 7Sw76ybyAAzKPkS4o+q81D98sTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAABxQPNzSGh9O85AAEAAAHFMCIEIKXafVUH7v4pvoO5VYvr # 4VJz9BSyCR7CGFZxEt+f1jpiMA0GCSqGSIb3DQEBCwUABIICAHa9Gy59P3OY83RS # hxxWwhy5jn9dgC/Qik/9tviDtNz4Htodd9XCB+JtG5eTGKs8/hIRwL6H3TV7fQia # U2I4TlPdoKZsi8Tb+2zqFXouXiaDqrLyMTsAQ8niGRaliDCl7lf7Qei/SsNtXz1U # wEUzHddCuJAXfT2xXOKNyxOpp13rCeyxTFlRFTr1Coug9BYI7B5a5QFiBWjcQift # 2TYVs1dZwMYvgXCtdNz4BzRxqEHXbDlJbE8xJ8UA2z4r/Ff7SSV2F/cVU7JbfyAM # Y/44cKccEODbYcCLgEmuA/Az+div4v5HUwKyVN7Cf9qoLyg9chZx4odZRyc5qWxN # 5cJLkvCOUJhD4ILPrNlYn43ZSnStVbig2Pa2ihkoLal5g5IVbs0GZaPfQGJe0raq # 5TH3lgaoMC/RJosDOQ+nZuuryk2K8uXillDaWA+MH8l2wc1mVqiO3wkjmuFS3+VT # bsB0ZvSvbDGa1ZCglkLjTHBegANfEZjO1fm0qoIMNwOX29ninZiGobFpT+lFB6fy # 7+nDO48+HPwUVabmDDz5vmJNvJY4fSYIwi5FeZ4IzsX0Us94wLrkG/BWxh9KdafQ # OVV72nfE4nPXLYt9Vl+s/Ue/R3SIQlkjJp/eCTQa1enG9FeSGWb2dWmRMHk7hXeL # OSP2cAkad9XFtnsLfPpid/r+ucKh # SIG # End signature block |