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
        }

        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
# MIIjhgYJKoZIhvcNAQcCoIIjdzCCI3MCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBS5jprnsLab6SY
# O4OpdZYquWpuQUVePKNZP2o1nvfSAaCCDYEwggX/MIID56ADAgECAhMzAAAB32vw
# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn
# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw
# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS
# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG
# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh
# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH
# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS
# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp
# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok
# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4
# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao
# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD
# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt
# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G
# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+
# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82
# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWzCCFVcCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgKUphMSbM
# ECCG/ERyc4PIJ0ihXrfeM/CTe1E9VlXy/fswQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBCnD2gWt3Abd6qXybkKKoh9SGs/phBsR9Zu2JYj+1K
# dxo36l4UX9rD0+x1B9iQipa5WSMz8IElc4xKPFPG+SJJI3ioLB8MqXl52Eh39yIc
# 0sJSnrnIOhTL0QQKSmy0efvSTebqb3OPX75HhYJTxX/OB0AhUXIIA7A26+lsCJXD
# JIKg5q9eflTPKNIhrJhhNnpu3F/LNVbf01IrGaAfzqhOhVN8bfqtGxTzwEHQXTCS
# whjSN/UgPIbASbqRzaxMi8w9msTWuIeqCTM8Xfl9swNpnUOD22o1X5eQQqvz17K1
# v/SFsAMYsFY4yFz1bRIWYg/BDiCYQNavKpBBCRK+yleLoYIS5TCCEuEGCisGAQQB
# gjcDAwExghLRMIISzQYJKoZIhvcNAQcCoIISvjCCEroCAQMxDzANBglghkgBZQME
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIJEfnoOBz7cky5HzypdytpQCjBD+8QMGAd7ulVoB
# pWJIAgZhefLK3YgYEzIwMjExMDI5MDc1NTM4LjQ4OFowBIACAfSggdCkgc0wgcox
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOkREOEMtRTMzNy0yRkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIOPDCCBPEwggPZoAMCAQICEzMAAAFOjLHr7dey4wAAAAAAAU4w
# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
# MjAxMTEyMTgyNjAxWhcNMjIwMjExMTgyNjAxWjCByjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046REQ4Qy1FMzM3LTJG
# QUUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCG+5vo9Ur9k7nCE6alU9k1Av/D5G0b
# RLOSQRLfl76/siJwDVvlJs9rsnxmXoq2Vu/5BCVnAi69b0nUIrJNXQRPrxBby1kL
# 2WWjgAy4OWNlhTzYWN8SYLA1OqwjvBNncr1VejeHI018G1e5w8YwqwBhuK/IahIC
# M/t8UoTBIhKPsbG3NCInczU5GgHerG0Myp7ug9+8Es6joAl2pu88GefHg48ROnCG
# Avmb3xPppdhUHzpSwPhjLvMHPnilQAN2IjQcnArxdBQ3I6llOEIWwJdoin2GG/Fi
# VMyvK92bWOCwZSj42pcBXNNsob0So9yxRJXfHSuyU/fMgfrXTOq0ho2pAgMBAAGj
# ggEbMIIBFzAdBgNVHQ4EFgQUojVREyZC4/ay6+fmwmlq2qZgGeEwHwYDVR0jBBgw
# FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB
# XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx
# MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN
# BgkqhkiG9w0BAQsFAAOCAQEAo4mcyK2Sr4FlF5VgTkRd1POeVebEWCvJjhs1IqbV
# fSJefNWXL5iYLxc2fJscNe7i86yrbBfsThj8uvQV7lx0JEGt/NT6nlUnYxyJB2ZK
# N1pPACcKMmHLeXUL6BMrgaE9Vl5zJQyr5hGfa6GLQeXert/8WxK45fusANXFqzEO
# B8pgwydlhxaFr+R7YH8ec++EJm+yIIF6tC1n5YvWy4mQNKBkFuk52FxDKoISQ02u
# txzLVmK3wRE3SVbaGQ0OixF65cymVOWmLIEFmyi0mGkI5kvKQBpbgl8foOKNrw0F
# 8+Q5Us6AfoJ11rbK5HUm3Utq975SKwcAVzAJCeM6YZW5lzCCBnEwggRZoAMCAQIC
# CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp
# ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx
# NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF
# ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD
# DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx
# z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1
# rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc
# sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB
# 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF
# bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
# EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB
# kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe
# MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA
# LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx
# vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS
# inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1
# L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO
# M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4
# pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45
# V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x
# 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe
# gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn
# QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp
# 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT
# X4/edIhJEqGCAs4wggI3AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP
# cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpERDhDLUUzMzctMkZB
# RTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG
# BSsOAwIaAxUAg8uPxL0/+sO+NO9xWDx5US8QfgKggYMwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOUlwqkwIhgPMjAy
# MTEwMjkwODQ1MjlaGA8yMDIxMTAzMDA4NDUyOVowdzA9BgorBgEEAYRZCgQBMS8w
# LTAKAgUA5SXCqQIBADAKAgEAAgIgJQIB/zAHAgEAAgIRLjAKAgUA5ScUKQIBADA2
# BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIB
# AAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAAAZfy02s+mEFaAhzy+dKyVjJhgE6Vbw
# uGwViiGVHMK1v3uSHDjCmyqcU7pUBgO7dZEPRRyHD5rks+QYf9ecp5qiOnKtPObD
# PYEFvEV/rpXAApecbm6JDsPHKkDCSohN9EZGuEgGe8csnpqV5mnnmhDNAHlVGTHI
# ohmvqZlcr78LMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAFOjLHr7dey4wAAAAAAAU4wDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgpoX5DO6T
# PdUkIk/DEJoGYU5bkILMpZF+fJamTp1jYr8wgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCAI/g3imEuLgecw/rodQgpE3e8yMSuIAo7+6n3jyiUvkjCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABToyx6+3XsuMAAAAA
# AAFOMCIEIP6NThKt6MNV/x/oRKuKtOMtDDWi/xGISXgw/+tcJwA0MA0GCSqGSIb3
# DQEBCwUABIIBAEkajT2E2Ru8uamzoAeAn+caeJEeA3V3dDKqRYmyqmfhSGXa8J9B
# HsLZzlVhSDCW9Lr9EKHv7/qNL5gxvV7MKG6cNNGENJfVdvc4Y6txGN75NOAr+djI
# 3NiEhMQHOyRY6EnJXT9q4sadJ6AcdCS2c9EIXZD6TsvyfSVQSsYAcmfZEZ14mR0T
# ktySNtdFeKbzepPBxN77JCGjOF7krb85AmFQR5BdRua8aJhtVY2F7nU5VuV1N/Qp
# TvqI1gy1JD5Cd2zUYARPdHW3g59WfbS6tGS3CmETR+1EozxonpdA5VDa9AdMQ0uW
# QOhAmdL2+ZmdW4uIXTegQsG0pG8p+22zNQk=
# SIG # End signature block