Cdn.Autorest/custom/Start-AzFrontDoorCdnProfilePrepareMigration.ps1

# ----------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# 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.
# Code generated by Microsoft (R) AutoRest Code Generator.Changes may cause incorrect behavior and will be lost if the code
# is regenerated.
# ----------------------------------------------------------------------------------

<#
.Synopsis
Migrate the classic AFD instance to Azure Front Door(Standard/Premium) profile.
MigrationWebApplicationFirewallMapping should be associated if the front door has WAF policy. Managed Identity should be associated if the frontdoor has Customer Certificates.
The change need to be committed after this.
.Description
Migrate the classic AFD instance to Azure Front Door(Standard/Premium) profile.
The change need to be committed after this.
.Example
PS C:\> {{ Add code here }}
 
{{ Add output here }}
.Example
PS C:\> {{ Add code here }}
 
{{ Add output here }}
 
.Outputs
Microsoft.Azure.PowerShell.Cmdlets.Cdn.Models.Api20240201.IMigrateResult
.Notes
COMPLEX PARAMETER PROPERTIES
 
To create the parameters described below, construct a hash table containing the appropriate properties. For information on hash tables, run Get-Help about_Hash_Tables.
 
MIGRATIONWEBAPPLICATIONFIREWALLMAPPING <IMigrationWebApplicationFirewallMapping[]>: Waf mapping for the migrated profile
  [MigratedFromId <String>]: Resource ID.
  [MigratedToId <String>]: Resource ID.
.Link
https://learn.microsoft.com/powershell/module/az.cdn/start-azfrontdoorcdnprofilepreparemigration
 
#>

function Start-AzFrontDoorCdnProfilePrepareMigration {
    [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Runtime.PreviewMessageAttribute("This cmdlet is using a preview API version and is subject to breaking change in a future release.")]
    [OutputType([Microsoft.Azure.PowerShell.Cmdlets.Cdn.Models.Api20240201.IMigrateResult])]
    [CmdletBinding(PositionalBinding=$false, SupportsShouldProcess, ConfirmImpact='Medium')]
    param(
        [Parameter(Mandatory)]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Path')]
        [System.String]
        # Name of the Resource group within the Azure subscription.
        ${ResourceGroupName},

        [Parameter(Mandatory)]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Body')]
        [System.String]
        # Resource ID of the classic front door instance.
        ${ClassicResourceReferenceId},

        [Parameter(Mandatory)]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Body')]
        [System.String]
        # Name of the new AFD Standard/Premium profile that need to be created.
        ${ProfileName},

        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.Cdn.Support.SkuName])]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Support.SkuName]
        # Name of the pricing tier.
        ${SkuName},

        [Parameter()]
        [AllowEmptyCollection()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Models.Api20240201.IMigrationWebApplicationFirewallMapping[]]
        # Waf mapping for the migrated profile
        # To construct, see NOTES section for MIGRATIONWEBAPPLICATIONFIREWALLMAPPING properties and create a hash table.
        ${MigrationWebApplicationFirewallMapping},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Path')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Runtime.DefaultInfo(Script='(Get-AzContext).Subscription.Id')]
        [System.String]
        # Azure Subscription ID.
        ${SubscriptionId},

        [Parameter()]
        [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.Cdn.Support.ManagedServiceIdentityType])]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Support.ManagedServiceIdentityType]
        # Type of managed service identity (where both SystemAssigned and UserAssigned types are allowed).
        ${IdentityType},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Runtime.Info(PossibleTypes=([Microsoft.Azure.PowerShell.Cmdlets.Cdn.Models.Api40.IUserAssignedIdentities]))]
        [System.Collections.Hashtable]
        # The set of user assigned identities associated with the resource.
        # The userAssignedIdentities dictionary keys will be ARM resource ids in the form: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}.
        # The dictionary values can be empty objects ({}) in requests.
        ${IdentityUserAssignedIdentity},

        [Parameter()]
        [Alias('AzureRMContext', 'AzureCredential')]
        [ValidateNotNull()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Azure')]
        [System.Management.Automation.PSObject]
        # The credentials, account, tenant, and subscription used for communication with Azure.
        ${DefaultProfile},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [System.Management.Automation.SwitchParameter]
        # Run the command as a job
        ${AsJob},

        [Parameter(DontShow)]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [System.Management.Automation.SwitchParameter]
        # Wait for .NET debugger to attach
        ${Break},

        [Parameter(DontShow)]
        [ValidateNotNull()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Runtime.SendAsyncStep[]]
        # SendAsync Pipeline Steps to be appended to the front of the pipeline
        ${HttpPipelineAppend},

        [Parameter(DontShow)]
        [ValidateNotNull()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Runtime.SendAsyncStep[]]
        # SendAsync Pipeline Steps to be prepended to the front of the pipeline
        ${HttpPipelinePrepend},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [System.Management.Automation.SwitchParameter]
        # Run the command asynchronously
        ${NoWait},

        [Parameter(DontShow)]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [System.Uri]
        # The URI for the proxy server to use
        ${Proxy},

        [Parameter(DontShow)]
        [ValidateNotNull()]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [System.Management.Automation.PSCredential]
        # Credentials for a proxy server to use for the remote call
        ${ProxyCredential},

        [Parameter(DontShow)]
        [Microsoft.Azure.PowerShell.Cmdlets.Cdn.Category('Runtime')]
        [System.Management.Automation.SwitchParameter]
        # Use the default credentials for the proxy
        ${ProxyUseDefaultCredentials}
    )

    process {
        if (!(Get-Module -ListAvailable -Name Az.FrontDoor)) {
            Write-Host("Starting the install Az.FrontDoor module.")
            Install-Module -Name Az.FrontDoor
            # throw 'Please install Az.FrontDoor module by entering "Install-Module -Name Az.FrontDoor".'
        }
        Import-Module -Name Az.FrontDoor
        if (!(Get-Module -ListAvailable -Name Az.KeyVault)) {
            Write-Host("Starting the install Az.KeyVault module.")
            Install-Module -Name Az.KeyVault
            # throw 'Please install Az.KeyVault module by entering "Install-Module -Name Az.KeyVault".'
        }
        Import-Module -Name Az.KeyVault

        Write-Host("Starting the parameter validation process.")
        $classicResourceId = ${ClassicResourceReferenceId}.split("/")
        $frontDoorName = $classicResourceId[-1]
        $subId = $classicResourceId[2]
        $sku = ${SkuName}.ToString().ToLower()

        $context = Get-AzContext
        $localSubId = $context.Subscription.Id

        # If the ${SubscriptionId} is equal to localSubId, meaning user don't provide the SubscriptionId Parameter in the command.
        if ((${SubscriptionId} -ne $localSubId) -and (${SubscriptionId} -ne $subId)) {
            Write-Host("SubscriptionId provided is: '${SubscriptionId}")
            throw "The value of subscriptionId: '$subId' of the classic cdn in ClassicResourceReferenceId is different from the Subscription parameter provided."
        }
        
        if ($subId -ne $localSubId)
        {
            Write-Host("The subscription of classic cdn located is different from local subscription. Now set the value of subscription as: '$subId'")
            Set-AzContext -Subscription $subId
        }

        try {
            $frontDoorInfos = Get-AzFrontDoorFrontendEndpoint -ResourceGroupName ${ResourceGroupName} -FrontDoorName $frontDoorName
        } catch {
            throw "FrontDoorName: '$frontDoorName' dose not exist in the resource group: '${ResourceGroupName}'."
        }

        # Validate the parameter
        ValidateInputType
        ValidateWafPolicies
        ValidateIdentityType

        # 1. Validate whether the front door has waf policy
        # If associated with waf policy, then waf parameter should be typed.
        # 2. Validate whether the profile has BYOC
        # If associated with BYOC, then MSIIdentity parameter should be typed.
        
        # MigrationWebApplicationFirewallMapping parameter should only requires waf Mapping per waf instance, not the waf-endpoint association count!
        $allPoliciesWithWAF = New-Object System.Collections.Generic.HashSet[string]
        $allPoliciesWithVault = New-Object System.Collections.Generic.HashSet[string]
        foreach ($info in $frontDoorInfos) {
            $wafInfo = $info.WebApplicationFirewallPolicyLink
            if ($wafInfo) {
                $allPoliciesWithWAF.Add($wafInfo.ToLower())  | Out-Null
            }
            if ($info.Vault) {
                $vaultNameInfo = $info.Vault.split("/")[-1]
                $allPoliciesWithVault.Add($vaultNameInfo.ToLower()) | Out-Null
            }
        }

        Write-Debug("WAF linked to the frontdoor: $allPoliciesWithWAF")
        Write-Debug("Key vault name used for the frontdoor: $allPoliciesWithVault")

        if (${MigrationWebApplicationFirewallMapping}.count -ne $allPoliciesWithWAF.count) {
            throw "MigrationWebApplicationFirewallMapping parameter instance should be equal to the number of WAF policy instance in the profile."
        }

        # We should raise a complaint if the customer did not enable managed identity when they have BYOC enabled.
        # However, if the customer does not have BYOC but has specified a managed identity, we could ignore the validation for BYOC, no need to keep consisence with Portal behavior.
        if (($allPoliciesWithVault.count -gt 0) -and !($PSBoundParameters.ContainsKey('IdentityType')))
        {
            throw "IdentityType parameter should be provided when the front door has Customer Certificates."
        }

        Write-Host("The parameters have been validated successfully.")

        # Step1: Deal with Waf policy
        if ($PSBoundParameters.ContainsKey('MigrationWebApplicationFirewallMapping')) {
            Write-Host("Starting to configure WAF policy upgrades.")

            $hasManagedRule = $false
            $wafPolicies = $PSBoundParameters.MigrationWebApplicationFirewallMapping
            foreach ($policy in $wafPolicies) {
                # ToDo: allPoliciesWithWAF use map
                if ($allPoliciesWithWAF -NotContains $policy.MigratedFromId) {
                    throw "WAF policy: '$migrateFromId' does not exist in the profile. WAF policy provided should exist in the profile."
                }
                # Validate the Sku argument, if a managed waf associated, then the profile only can migrated to Premium tier.
                $migrateFromWafPreperty = GetMigrateWafProperty -MigrateWafResourceId $policy.MigratedFromId
                if ($migrateFromWafPreperty.ManagedRules) {
                    $hasManagedRule = $true
                    if (($sku -ne "premium_azurefrontdoor")) {
                        throw "The AFD (classic) instance has managed WAF rules associated, and it can only be migrated to Premium tier. If you want to migrate to Standard tier, please remove the association on AFD (classic) and migrate afterwards."
                    }
                }
            }
           
            # Validate whether MigratedToId policy already exists in the subsrciption;
            foreach ($policy in $wafPolicies) {
                $migrateToWafId = $policy.MigratedToId
                $migrateToWafArray = $policy.MigratedToId.split("/")
                try {
                    $existed = Get-AzFrontDoorWafPolicy -ResourceGroupName $migrateToWafArray[4] -Name $migrateToWafArray[8] -ErrorAction Stop
                }
                catch {
                    if ($migrateToWafArray[4] -ne ${ResourceGroupName}) {
                        throw "The copied new WAF policy should to be created in the same resource group with the classic front door's: '${ResourceGroupName}'."
                    }
                    $migrateFromWafPreperty = GetMigrateWafProperty -MigrateWafResourceId $policy.MigratedFromId
                    CreateNewWafPolicy -ResourceGroupName $migrateToWafArray[4] -Name $migrateToWafArray[8] -WafProperty $migrateFromWafPreperty -ManagedRuleMigrateFromWaf $hasManagedRule
                }
                if ($existed -and $hasManagedRule) { 
                    if ((!$existed.ManagedRules) -and ($existed.Sku.ToLower() -ne "premium_azurefrontdoor")) {
                        throw "Please check parameter migrateToId: '$migrateToWafId'. The AFD (classic) instance has managed WAF rules associated, and it can only be migrated to Premium tier."
                    }
                }
            }
            Write-Host("WAF policy upgrades have been configured successfully.")
        }

        # Step2: Create AFDx Profile
        # If create AfdX profile firstly, then an error ("Invalid migrated to waf reference.") will be thrown if the migrated-To-WAF is supposed to created. (not exists in current subscription)
        Write-Host("Your new Front Door profile is being created. Please wait until the process has finished completely. This may take several minutes.")
        $null = $PSBoundParameters.Remove('IdentityType')
        $null = $PSBoundParameters.Remove('IdentityUserAssignedIdentity')
        # # Deal with difference between PS5 and PS7.
        # No need to add this parameters here, cx may add this parameter when using this command.
        # $PSBoundParameters.Add('ErrorAction', 'Stop')

        # Upgrade subcriptionId
        $PSBoundParameters['SubscriptionId'] =  $subId
        Az.Cdn.internal\Move-AzCdnProfile @PSBoundParameters

        Write-Host("Your new Front Door profile with the configuration has been successfully created.")
        
        # Step 3: Deal with MSI parameter
        if (${IdentityType}) {
            Write-Host("Starting to enable managed identity.")

            # Waiting for results of profile created return
            Start-Sleep(20)

            # 1. Enable MSI: get "principalId" from RP
            $commandArgs = @{ ResourceGroupName = ${ResourceGroupName}; Name = ${ProfileName}; IdentityType = ${IdentityType}; ErrorAction = 'Stop'}
            if ($indentityType -ne "systemassigned") {
                $commandArgs.Add('IdentityUserAssignedIdentity', ${IdentityUserAssignedIdentity})
            }
            
            $enableMSISuccessMessage = 'Enabling managed identity succeeded.'
            $enableMSIRetryMessage = 'Retrying to enable managed identity...'
            $enableMSIErrorMessage = "Enabling managed identity failed."
            $profileIdentity = RetryCommand -Command 'Update-AzFrontDoorCdnProfile' -CommandArgs $commandArgs -RetryTimes 6 -SecondsDelay 20 -SuccessMessage $enableMSISuccessMessage -RetryMessage $enableMSIRetryMessage -ErrorMessage $enableMSIErrorMessage
            $identity = [System.Collections.ArrayList]@()
            foreach ($id in $profileIdentity.IdentityUserAssignedIdentity.Values.PrincipalId) {
                if ($id) {
                    $identity.Add($id) | Out-Null
                }
            }

            if ($profileIdentity.IdentityPrincipalId){
                $identity.Add($profileIdentity.IdentityPrincipalId) | Out-Null
            }

            # Waiting for Enabling managed identity...
            Start-Sleep(20)

            # When the classic front door has BYOC, need to grant managed identity to the key vault.
            if ($allPoliciesWithVault.count -gt 0)
            {
                Write-Host("Starting to grant managed identity to key vault.")
                foreach ($vault in $allPoliciesWithVault) {
                    foreach ($principal in $identity) {
                        $grantAccessSuccessMessage = 'Granting managed identity to key vault succeeded.'
                        $grantAccessRetryMessage = 'Retrying to grant managed identity to key vault...'
                        $grantAccessErrorMessage = 'Granting managed identity to key vault failed.'

                        $commandInfo = @{ VaultName = $vault; ObjectId = $principal; PermissionsToSecrets = 'Get'; PermissionsToCertificates = 'Get'; ErrorAction = 'Stop'; BypassObjectIdValidation = $true}

                        # Set-AzKeyVaultAccessPolicy -VaultName $vault -ObjectId $principal -PermissionsToSecrets Get -PermissionsToCertificates Get
                        # Adding the parameter `-BypassObjectIdValidation` to bypass the validation when using pipeline to do migration, the type of `-BypassObjectIdValidation` is 'SwitchParameter'.
                        RetryCommand -Command 'Set-AzKeyVaultAccessPolicy' -CommandArgs $commandInfo -RetryTimes 6 -SecondsDelay 20 -SuccessMessage $grantAccessSuccessMessage -RetryMessage $grantAccessRetryMessage -ErrorMessage $grantAccessErrorMessage
                    }
                }

                Write-Host("Your have successfully granted managed identity to key vault.")
            }
        } else {
            Write-Debug("IdentityType paramter not provided and no BYOC for the current front door, skip Managed Identity step.")
        }

        Write-Host("The change need to be committed after this.")
    }
}

function ValidateInputType {
    $validateResourceIdReg = "^/subscriptions/[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12}/resourcegroups/(?<resourceGroupName>[^/]+)/providers/microsoft.network/frontdoors/(?<frontDoorName>[^/]+)$"
    if (${ClassicResourceReferenceId} -notmatch $validateResourceIdReg) {
        throw "The format of ClassicResourceReferenceId: '${ClassicResourceReferenceId}', supposed to be like $validateResourceIdReg"
    }

    # $subscriptionId = $classicResourceId[2]
    # if (${SubscriptionId} -ne $subscriptionId)
    # {
    # throw "Subscription parameter:${SubscriptionId}, should be in the same subscriptionId in ClassicResourceReferenceId..."
    # }
    $resourceGroup = $classicResourceId[4]
    if (${ResourceGroupName} -ne $resourceGroup) {
        throw "ResourceGroupName parameter: ${ResourceGroupName}, should be in the same resourceGroup in ClassicResourceReferenceId..."
    }

    if (${ProfileName} -notmatch '^[a-zA-Z0-9]+(-*[a-zA-Z0-9])*$') {
        throw "The value must begin with a letter or number, and may contain only letters, numbers or hyphens. It must end with a letter or number..."
    }

    if (${ProfileName}.Length -gt 260)
    {
        throw "Maximum
         ProfileName property is: 260."

    }
    
    # Check if the profile already exists in the resourceGroup
    
    try {
        $existedProfile = Get-AzFrontDoorCdnProfile -ResourceGroupName $resourceGroup -Name ${ProfileName} -ErrorAction Stop
    } catch {
        Write-debug("Validation of the parameter 'ProfileName' was successful.")
    }

    if ($existedProfile) {
        throw "The profile name: '${ProfileName}' is already in use. Please use a new profile name."
    }

    if (($sku -ne "standard_azurefrontdoor") -and ($sku -ne "premium_azurefrontdoor"))
    {
        throw "'$sku' + is not a valid Sku. Only Standard_AzureFrontDoor and Premium_AzureFrontDoor Skus are allowed."
    }
}

function ValidateIdentityType {
    if (${IdentityType}) {
        $identityTypeArray =  ${IdentityType}.ToString().split(",")
        if (($identityTypeArray.Count -gt 2)) {
            throw "The IdentityType is invalid. The supported types are 'SystemAssigned,UserAssigned' when the front door has Customer Certificates during migration."
        }
        foreach($identity in $identityTypeArray) {
            $id = $identity.Trim().ToLower()
            if (($id -ne "userassigned") -and ($id -ne "systemassigned")) {
                throw "The IdentityType is invalid. The supported types are 'SystemAssigned,UserAssigned' when the front door has Customer Certificates during migration."
            }
            if ($id -eq "userassigned") {
                if (${IdentityUserAssignedIdentity}.count -eq 0) {
                    throw "Identities should not be empty or null to be assigned when using User Assigned type."
                }
            }
        }
    }
}

function ValidateWafPolicies{
    if (${MigrationWebApplicationFirewallMapping}.count -gt 0) {
        $wafPolicies = ${MigrationWebApplicationFirewallMapping}
        $theSubId = $wafPolicies[0].MigratedFromId.split("/")[2] 
        
        $validateWafIdReg = '/subscriptions/[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12}/resourceGroups/(?<resourceGroupName>[^/]+/providers/Microsoft.Network/frontdoorWebApplicationFirewallPolicies/*'
        $migrationFromError = "The format of the WAF MigrateFromId should be like '$validateWafIdReg'."
        $migrationToError = "The format of the WAF MigrateToId should be like '$validateWafIdReg'."

        # Validate the format of the waf policy and the migrateFrom.Id whether exists in the profile.
        $validateWafIdReg = "^/subscriptions/[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12}/resourcegroups/(?<resourceGroupName>[^/]+)/providers/microsoft.network/frontdoorwebapplicationfirewallpolicies/(?<policyName>[^/]+)$"
        foreach ($policy in $wafPolicies) {
            # add validation to check the migrateFrom and migrateTo cannot be null.
            if (-not $policy.MigratedFromId -or -not $policy.MigratedToId) {
                throw 'MigratedFrom and MigratedTo properties cannot be empty'
            }
            
            if ($policy.MigratedFromId.ToLower() -notmatch $validateWafIdReg) {
                throw $migrationFromError
            }
            if ($policy.MigratedToId.ToLower() -notmatch $validateWafIdReg) {
                throw $migrationToError
            }

            $migrateToSub = $policy.MigratedToId.split("/")[2]
            $migrateToName = $policy.MigratedToId.split("/")[8]
            if ($migrateToSub -ne $theSubId) {
                throw "The subscription of existing or created WAF policy should be in the same subscription as the classic AFD WAF policy's."
            }
            if ($migrateToName -notmatch '(^[a-zA-Z]+)\w+$') {
                throw "The WAF policy name must begin with a letter, and may only contain numbers and letters."
            }

            if ($migrateToName.Length -gt 260)
            {
                throw "Maximum WAF policy name property is: 260."
            }
        }
    }
}

# Get the property of waf policy associated with classic Afd
function GetMigrateWafProperty {
    param (
        [string]$MigrateWafResourceId
    )

    return Get-AzFrontDoorWafPolicy -ResourceGroupName $MigrateWafResourceId.split("/")[4] -Name $MigrateWafResourceId.split("/")[8]
}


# Corresponding to "Copy to a new waf policy"
function CreateNewWafPolicy {
    param (
        [string]$ResourceGroupName,
        [string]$Name,
        [Microsoft.Azure.Commands.FrontDoor.Models.PSTrackedResource]$WafProperty,
        [bool]$ManagedRuleMigrateFromWaf
    )

    # $sku = ${SkuName}.ToLower()
    if ($sku -eq "premium_azurefrontdoor") {
        $sku = "premium_azurefrontdoor"
    } elseif ($ManagedRuleMigrateFromWaf) {
        $sku = "premium_azurefrontdoor"
    } else {
        $sku = "standard_azurefrontdoor"
    }

    # Remove the null/empty property
    $validatedWafProperty = ValidateMigrationWafPolicyProperty -WafProperty $WafProperty

    # New a waf policy, copied from the Migrtae
    New-AzFrontDoorWafPolicy -ResourceGroupName $ResourceGroupName -Name $Name -Sku $sku @validatedWafProperty  | Out-Null
}

# Validate the property of a waf policy
function ValidateMigrationWafPolicyProperty {
    param (
        [Microsoft.Azure.Commands.FrontDoor.Models.PSTrackedResource]$WafProperty
    )

    $wafHash = @{}
    if ($WafProperty.PolicyEnabledState) {
        $wafHash.Add('EnabledState', $WafProperty.PolicyEnabledState)
    } 
    if ($WafProperty.PolicyMode) {
        $wafHash.Add('Mode', $WafProperty.PolicyMode)
    }
    if ($WafProperty.CustomRules) {
        $wafHash.Add('Customrule', $WafProperty.CustomRules)
    }
    if ($WafProperty.ManagedRules) {
        $wafHash.Add('ManagedRule', $WafProperty.ManagedRules)
    }
    if ($WafProperty.RedirectUrl) {
        $wafHash.Add('RedirectUrl', $WafProperty.RedirectUrl)
    }
    if ($WafProperty.CustomBlockResponseStatusCode) {
        $wafHash.Add('CustomBlockResponseStatusCode', $WafProperty.CustomBlockResponseStatusCode)
    }
    if ($WafProperty.CustomBlockResponseBody) {
        $wafHash.Add('CustomBlockResponseBody', $WafProperty.CustomBlockResponseBody)
    }
    if ($WafProperty.RequestBodyCheck) {
        $wafHash.Add('RequestBodyCheck', $WafProperty.RequestBodyCheck)
    }
    
    return $wafHash
}

function RetryCommand {
    param (
        [string]$Command,
        [hashtable]$CommandArgs, 
        [int]$RetryTimes,
        [int]$SecondsDelay,
        [string]$SuccessMessage,
        [string]$RetryMessage,
        [string]$ErrorMessage
    )

    $retryCount = 0
    $completed = $false

    while (-not $completed) {
        try {
            & $Command @CommandArgs
            Write-Host ("{0}" -f $SuccessMessage)
            $completed = $true
            return $res
        } 
        catch {
            if ($retryCount -ge $RetryTimes) {
                Write-Host ("{0}" -f $ErrorMessage)
                Write-Debug ("Command [{0}] failed the maximum number of {1} times." -f $Command, $retryCount)
                throw
            } else {
                Write-Host ("{0}" -f $RetryMessage)
                Write-Debug ("Command [{0}] loading..." -f $command)
                Start-Sleep $SecondsDelay
                $retryCount++
            }
        }
    }
}
# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBTp9AKDmQBjlm/
# i//CLULWxNnHRq8tl4vZUl2zjQmV16CCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIELmYL0d6yN5xmymdNC85gzt
# 0EHSpDphpIWILuG1qgocMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAaq9r8llLO/ifYdhqQCACYx6B4IzFhX+yDD0c6NvS3I71ATXbBr/G4VYq
# IHK6DggFUe5Be5p0as+68BgGOX8nHJRMuyZkN7a3GgJguUBsZXyPDmw63JGsaNa5
# OO7fb1bddfiaoM+SwD0PmmxHegmWxysho+AnsbfIaM52T1jtyuVSBu97ZR8TZgV5
# 0hzpGUSwRFqOdgyWfQMHrW4MKdHVU95uXsB4hGXHSXoze+a9gXwOrSuvyL+fkLhr
# 2nQmr3TdVwHw8obBUCOd8GTXyreQoRMnln6T4noe8yY8cmEf2yXPKiT1T7p6oRQm
# csqpMVfm+QOe7bWL4lyH5ibOufUgwaGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAWfv5pNMOESDRUqZbP9qQxFZCRb49BmNzqwrykaUuBkAIGZr4G5pu7
# GBMyMDI0MDgyOTAzMDgzMS4zOTlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODYwMy0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAfGzRfUn6MAW1gABAAAB8TANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# NTVaFw0yNTAzMDUxODQ1NTVaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODYwMy0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCxulCZttIf8X97rW9/J+Q4Vg9PiugB1ya1/DRxxLW2
# hwy4QgtU3j5fV75ZKa6XTTQhW5ClkGl6gp1nd5VBsx4Jb+oU4PsMA2foe8gP9bQN
# PVxIHMJu6TYcrrn39Hddet2xkdqUhzzySXaPFqFMk2VifEfj+HR6JheNs2LLzm8F
# DJm+pBddPDLag/R+APIWHyftq9itwM0WP5Z0dfQyI4WlVeUS+votsPbWm+RKsH4F
# QNhzb0t/D4iutcfCK3/LK+xLmS6dmAh7AMKuEUl8i2kdWBDRcc+JWa21SCefx5SP
# hJEFgYhdGPAop3G1l8T33cqrbLtcFJqww4TQiYiCkdysCcnIF0ZqSNAHcfI9SAv3
# gfkyxqQNJJ3sTsg5GPRF95mqgbfQbkFnU17iYbRIPJqwgSLhyB833ZDgmzxbKmJm
# dDabbzS0yGhngHa6+gwVaOUqcHf9w6kwxMo+OqG3QZIcwd5wHECs5rAJZ6PIyFM7
# Ad2hRUFHRTi353I7V4xEgYGuZb6qFx6Pf44i7AjXbptUolDcVzYEdgLQSWiuFajS
# 6Xg3k7Cy8TiM5HPUK9LZInloTxuULSxJmJ7nTjUjOj5xwRmC7x2S/mxql8nvHSCN
# 1OED2/wECOot6MEe9bL3nzoKwO8TNlEStq5scd25GA0gMQO+qNXV/xTDOBTJ8zBc
# GQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFLy2xe59sCE0SjycqE5Erb4YrS1gMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQDhSEjSBFSCbJyl3U/QmFMW2eLPBknnlsfI
# D/7gTMvANEnhq08I9HHbbqiwqDEHSvARvKtL7j0znICYBbMrVSmvgDxU8jAGqMyi
# LoM80788So3+T6IZV//UZRJqBl4oM3bCIQgFGo0VTeQ6RzYL+t1zCUXmmpPmM4xc
# ScVFATXj5Tx7By4ShWUC7Vhm7picDiU5igGjuivRhxPvbpflbh/bsiE5tx5cuOJE
# JSG+uWcqByR7TC4cGvuavHSjk1iRXT/QjaOEeJoOnfesbOdvJrJdbm+leYLRI67N
# 3cd8B/suU21tRdgwOnTk2hOuZKs/kLwaX6NsAbUy9pKsDmTyoWnGmyTWBPiTb2rp
# 5ogo8Y8hMU1YQs7rHR5hqilEq88jF+9H8Kccb/1ismJTGnBnRMv68Ud2l5LFhOZ4
# nRtl4lHri+N1L8EBg7aE8EvPe8Ca9gz8sh2F4COTYd1PHce1ugLvvWW1+aOSpd8N
# nwEid4zgD79ZQxisJqyO4lMWMzAgEeFhUm40FshtzXudAsX5LoCil4rLbHfwYtGO
# pw9DVX3jXAV90tG9iRbcqjtt3vhW9T+L3fAZlMeraWfh7eUmPltMU8lEQOMelo/1
# ehkIGO7YZOHxUqeKpmF9QaW8LXTT090AHZ4k6g+tdpZFfCMotyG+E4XqN6ZWtKEB
# QiE3xL27BDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg2MDMtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQD7
# n7Bk4gsM2tbU/i+M3BtRnLj096CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6npQZjAiGA8yMDI0MDgyOTAxNDE1
# OFoYDzIwMjQwODMwMDE0MTU4WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqelBm
# AgEAMAoCAQACAiRDAgH/MAcCAQACAhTqMAoCBQDqe6HmAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBAAaJ0AqDiIFLxzxPPjTPaZnwEkl6+Fon21eDsTfXwxNr
# zScgwo+Et00RmSub1keZoB70As53C1EgJhH3AUf/DUWC6lJbzw9BB8B6vxvqIirS
# qqNjkBFeapJIboOMo2YexrQM82H08mK3DaZKsW+AXnyWVc5asBFyX7jxH8oAu4JO
# tFpUjeBv8XPX4OC8c5a7Iqx3IBZFgDAoJI/Pyk13eUPBaG9A4bTQVu0YJYrabpGP
# /5YKXnay5mJJgzg5IKYiGyyBp6OSkASPWVWM8ta2bzW5TvMIMUIM751Y5rZc711z
# gjvjlb/B0OUhzZBq7VJ3AxiY5d39aQdaWGvjeLGr+FIxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAfGzRfUn6MAW1gABAAAB
# 8TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCAIM63wmR20cvtcI0ZmXa6VZZDomrvOvIQZ2VtKQ6t2
# vTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EINV3/T5hS7ijwao466RosB7w
# wEibt0a1P5EqIwEj9hF4MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAHxs0X1J+jAFtYAAQAAAfEwIgQg13lin3e5r3gSshZyGD5pWluc
# XX2qcujizzOr0BXFXCUwDQYJKoZIhvcNAQELBQAEggIAfpi6bXtFGmk0mFxmZwDZ
# Pw47zdkMy2T8QGQrEFE2liozm3RbO/cLRZGfxuNux9DIKmvboS87q3oTKSYtrwQg
# SML6g3hkkR/jvwzwlnKFXh9YELCTKVkxHkxSJJCSPzlTn3hfxue1kP7SZQTvWwEn
# fGecy6GiOz1UGqr/G5p/4zTYB46FWb7z3Yb0MKR6xLzqXbC4LTEiGh5/MPprIPTD
# V1Ya4MfBw1egyHFDWjcHNJRYIsL5owZrigf9c0eQbcfgtuhu+N9vX7ItH4iYoaKS
# txqzfvWgM+CnOV4osJiWfXkqWngiuhxxEr2vdc9L0nOOU0jAx9D+czsOS3jlNv83
# 7kbL4JQFPrn+RPSDIteRvEdzKzdZPLZLHj9Y6A1fAT3WyCyaO2eXCUxMP4xqoljr
# vbKg4hI0RKxi4C+uGIs+pqbUREUXTaObpw53LMhJinsvclTwieLZf640k7nyjVwR
# sGt8BWjc8AT4500dhtBmCHB1oB+tcTQjMv0w7lGctfWoVWMFG2yTCxwZJkPnQV7u
# dz4VYxqWtqHsZJnZCvWua/52DN3xCN6ogdKD6zv+/ZWYc/A8Yn2gtfriRtG2xn34
# DuChG1wd+2MJeCXJM1qbeKp0mXQkUpo8RIq9dGEFwttt3eeuvyjxjSrZ+DnxBOS3
# q7f5h3/j/m9bJLoMzZ2cY/4=
# SIG # End signature block