Functions.Autorest/custom/HelperFunctions.ps1
# Load Az.Functions module constants $constants = @{} $constants["AllowedStorageTypes"] = @('Standard_GRS', 'Standard_RAGRS', 'Standard_LRS', 'Standard_ZRS', 'Premium_LRS', 'Standard_GZRS') $constants["RequiredStorageEndpoints"] = @('PrimaryEndpointFile', 'PrimaryEndpointQueue', 'PrimaryEndpointTable') $constants["DefaultFunctionsVersion"] = '4' $constants["RuntimeToFormattedName"] = @{ 'dotnet' = 'DotNet' 'dotnet-isolated' = 'DotNet-Isolated' 'custom' = 'Custom' 'node' = 'Node' 'python' = 'Python' 'java' = 'Java' 'powershell' = 'PowerShell' } $constants["RuntimeToDefaultOSType"] = @{ 'DotNet'= 'Windows' 'DotNet-Isolated' = 'Windows' 'Custom' = 'Windows' 'Node' = 'Windows' 'Java' = 'Windows' 'PowerShell' = 'Windows' 'Python' = 'Linux' } $constants["ReservedFunctionAppSettingNames"] = @( 'FUNCTIONS_WORKER_RUNTIME' 'DOCKER_CUSTOM_IMAGE_NAME' 'FUNCTION_APP_EDIT_MODE' 'WEBSITES_ENABLE_APP_SERVICE_STORAGE' 'DOCKER_REGISTRY_SERVER_URL' 'DOCKER_REGISTRY_SERVER_USERNAME' 'DOCKER_REGISTRY_SERVER_PASSWORD' 'WEBSITES_ENABLE_APP_SERVICE_STORAGE' 'WEBSITE_NODE_DEFAULT_VERSION' 'AzureWebJobsStorage' 'AzureWebJobsDashboard' 'FUNCTIONS_EXTENSION_VERSION' 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 'WEBSITE_CONTENTSHARE' 'APPINSIGHTS_INSTRUMENTATIONKEY' ) $constants["SetDefaultValueParameterWarningMessage"] = "This default value is subject to change over time. Please set this value explicitly to ensure the behavior is not accidentally impacted by future changes." $constants["DEBUG_PREFIX"] = '[Stacks API] - ' $constants["DefaultCentauriImage"] = 'mcr.microsoft.com/azure-functions/dotnet8-quickstart-demo:1.0' foreach ($variableName in $constants.Keys) { if (-not (Get-Variable $variableName -ErrorAction SilentlyContinue)) { Set-Variable $variableName -value $constants[$variableName] -option ReadOnly } } # These are used to hold the types for the tab completers $RuntimeToVersionLinux = @{} $RuntimeToVersionWindows = @{} $AllRuntimeVersions = @{} $global:StacksAndTabCompletersInitialized = $false $AllFunctionsExtensionVersions = New-Object System.Collections.Generic.List[[String]] function GetConnectionString { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $StorageAccountName, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("StorageAccountName")) { $PSBoundParameters.Remove("StorageAccountName") | Out-Null } $storageAccountInfo = GetStorageAccount -Name $StorageAccountName @PSBoundParameters if (-not $storageAccountInfo) { $errorMessage = "Storage account '$StorageAccountName' does not exist." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "StorageAccountNotFound" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } if ($storageAccountInfo.ProvisioningState -ne "Succeeded") { $errorMessage = "Storage account '$StorageAccountName' is not ready. Please run 'Get-AzStorageAccount' and ensure that the ProvisioningState is 'Succeeded'" $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "StorageAccountNotFound" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } $skuName = $storageAccountInfo.SkuName if (-not ($AllowedStorageTypes -contains $skuName)) { $storageOptions = $AllowedStorageTypes -join ", " $errorMessage = "Storage type '$skuName' is not allowed'. Currently supported storage options: $storageOptions" $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "StorageTypeNotSupported" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } foreach ($endpoint in $RequiredStorageEndpoints) { if ([string]::IsNullOrEmpty($storageAccountInfo.$endpoint)) { $errorMessage = "Storage account '$StorageAccountName' has no '$endpoint' endpoint. It must have table, queue, and blob endpoints all enabled." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "StorageAccountRequiredEndpointNotAvailable" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } $resourceGroupName = ($storageAccountInfo.Id -split "/")[4] $keys = Az.Functions.internal\Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountInfo.Name @PSBoundParameters -ErrorAction SilentlyContinue if (-not $keys) { $errorMessage = "Failed to get key for storage account '$StorageAccountName'." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FailedToGetStorageAccountKey" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } if ([string]::IsNullOrEmpty($keys[0].Value)) { $errorMessage = "Storage account '$StorageAccountName' has no key value." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "StorageAccountHasNoKeyValue" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } $suffix = GetEndpointSuffix $accountKey = $keys[0].Value $connectionString = "DefaultEndpointsProtocol=https;AccountName=$StorageAccountName;AccountKey=$accountKey" + $suffix return $connectionString } function GetEndpointSuffix { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param() $environmentName = (Get-AzContext).Environment.Name switch ($environmentName) { "AzureUSGovernment" { ';EndpointSuffix=core.usgovcloudapi.net' } "AzureChinaCloud" { ';EndpointSuffix=core.chinacloudapi.cn' } "AzureCloud" { ';EndpointSuffix=core.windows.net' } default { '' } } } function NewAppSetting { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory=$false)] [System.String] $Value ) $setting = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20231201.NameValuePair $setting.Name = $Name $setting.Value = $Value return $setting } function GetServicePlan { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Name, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("Name")) { $PSBoundParameters.Remove("Name") | Out-Null } $plans = @(Az.Functions\Get-AzFunctionAppPlan @PSBoundParameters) foreach ($plan in $plans) { if ($plan.Name -eq $Name) { return $plan } } # The plan name was not found, error out $errorMessage = "Service plan '$Name' does not exist." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "ServicePlanDoesNotExist" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } function GetStorageAccount { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Name, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("Name")) { $PSBoundParameters.Remove("Name") | Out-Null } $storageAccounts = @(Az.Functions.internal\Get-AzStorageAccount @PSBoundParameters -ErrorAction SilentlyContinue) foreach ($account in $storageAccounts) { if ($account.Name -eq $Name) { return $account } } } function GetApplicationInsightsProject { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Name, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("Name")) { $PSBoundParameters.Remove("Name") | Out-Null } $projects = @(Az.Functions.internal\Get-AzAppInsights @PSBoundParameters) foreach ($project in $projects) { if ($project.Name -eq $Name) { return $project } } } function CreateApplicationInsightsProject { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $ResourceName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $ResourceGroupName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Location, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) $paramsToRemove = @( "ResourceGroupName", "ResourceName", "Location" ) foreach ($paramName in $paramsToRemove) { if ($PSBoundParameters.ContainsKey($paramName)) { $PSBoundParameters.Remove($paramName) | Out-Null } } # Create a new ApplicationInsights $maxNumberOfTries = 3 $tries = 1 while ($true) { try { $newAppInsightsProject = Az.Functions.internal\New-AzAppInsights -ResourceGroupName $ResourceGroupName ` -ResourceName $ResourceName ` -Location $Location ` -Kind web ` -RequestSource "AzurePowerShell" ` -ErrorAction Stop ` @PSBoundParameters if ($newAppInsightsProject) { return $newAppInsightsProject } } catch { # Ignore the failure and continue } if ($tries -ge $maxNumberOfTries) { break } # Wait for 2^(tries-1) seconds between retries. In this case, it would be 1, 2, and 4 seconds, respectively. $waitInSeconds = [Math]::Pow(2, $tries - 1) Start-Sleep -Seconds $waitInSeconds $tries++ } } function ConvertWebAppApplicationSettingToHashtable { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Object] $ApplicationSetting, [System.Management.Automation.SwitchParameter] $ShowAllAppSettings, [System.Management.Automation.SwitchParameter] $RedactAppSettings, [System.Management.Automation.SwitchParameter] $ShowOnlySpecificAppSettings, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [String[]] $AppSettingsToShow ) if ($RedactAppSettings.IsPresent) { Write-Warning "App settings have been redacted. Use the Get-AzFunctionAppSetting cmdlet to view them." } # Create a key value pair to hold the function app settings $applicationSettings = @{} foreach ($keyName in $ApplicationSetting.Property.Keys) { if($ShowAllAppSettings.IsPresent) { $applicationSettings[$keyName] = $ApplicationSetting.Property[$keyName] } elseif ($RedactAppSettings.IsPresent) { # When RedactAppSettings is present, all app settings are set to null $applicationSettings[$keyName] = $null } elseif($ShowOnlySpecificAppSettings.IsPresent) { # When ShowOnlySpecificAppSettings is present, only show the app settings in this list AppSettingsToShow if ($AppSettingsToShow.Contains($keyName)) { $applicationSettings[$keyName] = $ApplicationSetting.Property[$keyName] } } } return $applicationSettings } function GetRuntime { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Object] $Settings, [Parameter(Mandatory=$false)] [String] $AppKind ) $appSettings = ConvertWebAppApplicationSettingToHashtable -ApplicationSetting $Settings -ShowAllAppSettings $runtimeName = $appSettings["FUNCTIONS_WORKER_RUNTIME"] $runtime = "" if (($null -ne $runtimeName) -and ($RuntimeToFormattedName.ContainsKey($runtimeName))) { $runtime = $RuntimeToFormattedName[$runtimeName] } elseif ($appSettings.ContainsKey("DOCKER_CUSTOM_IMAGE_NAME")) { if ($AppKind -match "azurecontainerapps") { $runtime = "Container App" } else { $runtime = "Custom Image" } } return $runtime } function AddFunctionAppSettings { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Object] $App, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("App")) { $PSBoundParameters.Remove("App") | Out-Null } if ($App.kind.ToString() -match "azurecontainerapps") { if ($App.ManagedEnvironmentId) { $App.AppServicePlan = ($App.ManagedEnvironmentId -split "/")[-1] } } else { $App.AppServicePlan = ($App.ServerFarmId -split "/")[-1] } $App.OSType = if ($App.kind.ToString() -match "linux"){ "Linux" } else { "Windows" } if ($App.Type -eq "Microsoft.Web/sites/slots") { return $App } $currentSubscription = $null $resetDefaultSubscription = $false try { $settings = Az.Functions.internal\Get-AzWebAppApplicationSetting -Name $App.Name ` -ResourceGroupName $App.ResourceGroup ` -ErrorAction SilentlyContinue ` @PSBoundParameters if ($null -eq $settings) { Write-Warning -Message "Failed to retrieve function app settings. 1st attempt" Write-Warning -Message "Setting session context to subscription id '$($App.SubscriptionId)'" $resetDefaultSubscription = $true $currentSubscription = (Get-AzContext).Subscription.Id $null = Select-AzSubscription $App.SubscriptionId $settings = Az.Functions.internal\Get-AzWebAppApplicationSetting -Name $App.Name ` -ResourceGroupName $App.ResourceGroup ` -ErrorAction SilentlyContinue ` @PSBoundParameters if ($null -eq $settings) { # We are unable to get the app settings, return the app Write-Warning -Message "Failed to retrieve function app settings. 2nd attempt." return $App } } } finally { if ($resetDefaultSubscription) { Write-Warning -Message "Resetting session context to subscription id '$currentSubscription'" $null = Select-AzSubscription $currentSubscription } } # Add application settings and runtime $App.ApplicationSettings = ConvertWebAppApplicationSettingToHashtable -ApplicationSetting $settings -RedactAppSettings $App.Runtime = GetRuntime -Settings $settings -AppKind $App.kind # Get the app site config $config = GetAzWebAppConfig -Name $App.Name -ResourceGroupName $App.ResourceGroup @PSBoundParameters # Add all site config properties as a hash table $SiteConfig = @{} foreach ($property in $config.PSObject.Properties) { if ($property.Name) { $SiteConfig.Add($property.Name, $property.Value) } } $App.SiteConfig = $SiteConfig return $App } function GetFunctionApps { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [AllowEmptyCollection()] [Object[]] $Apps, [System.String] $Location, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) $paramsToRemove = @( "Apps", "Location" ) foreach ($paramName in $paramsToRemove) { if ($PSBoundParameters.ContainsKey($paramName)) { $PSBoundParameters.Remove($paramName) | Out-Null } } if ($Apps.Count -eq 0) { return } $activityName = "Getting function apps" for ($index = 0; $index -lt $Apps.Count; $index++) { $app = $Apps[$index] $percentageCompleted = [int]((100 * ($index + 1)) / $Apps.Count) $status = "Complete: $($index + 1)/$($Apps.Count) function apps processed." Write-Progress -Activity "Getting function apps" -Status $status -PercentComplete $percentageCompleted if ($app.kind -match "functionapp") { if ($Location) { if ($app.Location -eq $Location) { $app = AddFunctionAppSettings -App $app @PSBoundParameters $app } } else { $app = AddFunctionAppSettings -App $app @PSBoundParameters $app } } } Write-Progress -Activity $activityName -Status "Completed" -Completed } function AddFunctionAppPlanWorkerType { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $AppPlan, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("AppPlan")) { $PSBoundParameters.Remove("AppPlan") | Out-Null } # The GetList api for service plan that does not set the Reserved property, which is needed to figure out if the OSType is Linux. # TODO: Remove this code once https://msazure.visualstudio.com/Antares/_workitems/edit/5623226 is fixed. if ($null -eq $AppPlan.Reserved) { # Get the service plan by name does set the Reserved property $planObject = Az.Functions.internal\Get-AzFunctionAppPlan -Name $AppPlan.Name ` -ResourceGroupName $AppPlan.ResourceGroup ` -ErrorAction SilentlyContinue ` @PSBoundParameters $AppPlan = $planObject } $AppPlan.WorkerType = if ($AppPlan.Reserved){ "Linux" } else { "Windows" } return $AppPlan } function GetFunctionAppPlans { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [AllowEmptyCollection()] [Object[]] $Plans, [System.String] $Location, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) $paramsToRemove = @( "Plans", "Location" ) foreach ($paramName in $paramsToRemove) { if ($PSBoundParameters.ContainsKey($paramName)) { $PSBoundParameters.Remove($paramName) | Out-Null } } if ($Plans.Count -eq 0) { return } $activityName = "Getting function app plans" for ($index = 0; $index -lt $Plans.Count; $index++) { $plan = $Plans[$index] $percentageCompleted = [int]((100 * ($index + 1)) / $Plans.Count) $status = "Complete: $($index + 1)/$($Plans.Count) function apps plans processed." Write-Progress -Activity $activityName -Status $status -PercentComplete $percentageCompleted try { if ($Location) { if ($plan.Location -eq $Location) { $plan = AddFunctionAppPlanWorkerType -AppPlan $plan @PSBoundParameters $plan } } else { $plan = AddFunctionAppPlanWorkerType -AppPlan $plan @PSBoundParameters $plan } } catch { continue; } } Write-Progress -Activity $activityName -Status "Completed" -Completed } function ValidateFunctionName { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Name, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) $result = Az.Functions.internal\Test-AzNameAvailability -Type Site @PSBoundParameters if (-not $result.NameAvailable) { $errorMessage = "Function name '$Name' is not available. Please try a different name." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FunctionAppNameIsNotAvailable" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } function NormalizeSku { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Sku ) if ($Sku -eq "SHARED") { return "D1" } return $Sku } function CreateFunctionsIdentity { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $InputObject ) if (-not ($InputObject.Name -and $InputObject.ResourceGroupName -and $InputObject.SubscriptionId)) { $errorMessage = "Input object '$InputObject' is missing one or more of the following properties: Name, ResourceGroupName, SubscriptionId" $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FailedToCreateFunctionsIdentity" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } $functionsIdentity = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.FunctionsIdentity $functionsIdentity.Name = $InputObject.Name $functionsIdentity.SubscriptionId = $InputObject.SubscriptionId $functionsIdentity.ResourceGroupName = $InputObject.ResourceGroupName return $functionsIdentity } function GetSkuName { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Sku ) if (($Sku -eq "D1") -or ($Sku -eq "SHARED")) { return "SHARED" } elseif (($Sku -eq "B1") -or ($Sku -eq "B2") -or ($Sku -eq "B3") -or ($Sku -eq "BASIC")) { return "BASIC" } elseif (($Sku -eq "S1") -or ($Sku -eq "S2") -or ($Sku -eq "S3")) { return "STANDARD" } elseif (($Sku -eq "P1") -or ($Sku -eq "P2") -or ($Sku -eq "P3")) { return "PREMIUM" } elseif (($Sku -eq "P1V2") -or ($Sku -eq "P2V2") -or ($Sku -eq "P3V2")) { return "PREMIUMV2" } elseif (($Sku -eq "PC2") -or ($Sku -eq "PC3") -or ($Sku -eq "PC4")) { return "PremiumContainer" } elseif (($Sku -eq "EP1") -or ($Sku -eq "EP2") -or ($Sku -eq "EP3")) { return "ElasticPremium" } elseif (($Sku -eq "I1") -or ($Sku -eq "I2") -or ($Sku -eq "I3")) { return "Isolated" } $guidanceUrl = 'https://learn.microsoft.com/azure/azure-functions/functions-premium-plan#plan-and-sku-settings' $errorMessage = "Invalid sku (pricing tier), please refer to '$guidanceUrl' for valid values." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "InvalidSkuPricingTier" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } function ThrowTerminatingError { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $ErrorId, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $ErrorMessage, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Management.Automation.ErrorCategory] $ErrorCategory, [Exception] $Exception, [object] $TargetObject ) if (-not $Exception) { $Exception = New-Object -TypeName System.Exception -ArgumentList $ErrorMessage } $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList ($Exception, $ErrorId, $ErrorCategory, $TargetObject) #$PSCmdlet.ThrowTerminatingError($errorRecord) throw $errorRecord } function GetErrorMessage { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] $Response ) if ($Response.Exception.ResponseBody) { try { $details = ConvertFrom-Json $Response.Exception.ResponseBody if ($details.Message) { return $details.Message } } catch { # Ignore the deserialization error } } } function GetSupportedRuntimes { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType ) if ($OSType -eq "Linux") { return $RuntimeToVersionLinux } elseif ($OSType -eq "Windows") { return $RuntimeToVersionWindows } throw "Unknown OS type '$OSType'" } function ValidateFunctionsVersion { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionsVersion ) # if ($SupportedFunctionsVersion -notcontains $FunctionsVersion) if ($AllFunctionsExtensionVersions -notcontains $FunctionsVersion) { $currentlySupportedFunctionsVersions = $AllFunctionsExtensionVersions -join ' and ' $errorMessage = "Functions version not supported. Currently supported version are: $($currentlySupportedFunctionsVersions)." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FunctionsVersionNotSupported" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } function GetDefaultOSType { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime ) $defaultOSType = $RuntimeToDefaultOSType[$Runtime] if (-not $defaultOSType) { # The specified runtime did not match, error out $runtimeOptions = FormatListToString -List @($RuntimeToDefaultOSType.Keys | Sort-Object) $errorMessage = "Runtime '$Runtime' is not supported. Currently supported runtimes: " + $runtimeOptions + "." ThrowRuntimeNotSupportedException -Message $errorMessage -ErrorId "RuntimeNotSupported" } return $defaultOSType } # Returns the stack definition for the given runtime name # function GetStackDefinitionForRuntime { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionsVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime, [Parameter(Mandatory=$false)] [System.String] $RuntimeVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType ) $supportedRuntimes = GetSupportedRuntimes -OSType $OSType $runtimeJsonDefinition = $null $functionsExtensionVersion = "~$FunctionsVersion" if (-not $supportedRuntimes.ContainsKey($Runtime)) { $runtimeOptions = FormatListToString -List @($supportedRuntimes.Keys | Sort-Object) $errorMessage = "Runtime '$Runtime' on '$OSType' is not supported. Currently supported runtimes: " + $runtimeOptions + "." ThrowRuntimeNotSupportedException -Message $errorMessage -ErrorId "RuntimeNotSupported" } # If runtime version is not provided, iterate through the list to find the default version (if available) if (($Runtime -ne 'Custom') -and (-not $RuntimeVersion)) { # Try to get the default version $defaultVersionFound = $false $RuntimeVersion = $supportedRuntimes[$Runtime] | ForEach-Object { if ($_.IsDefault -and ($_.SupportedFunctionsExtensionVersions -contains $functionsExtensionVersion)) { $_.Version } } if ($RuntimeVersion) { $defaultVersionFound = $true Write-Debug "$DEBUG_PREFIX Runtime '$Runtime' has a default version '$RuntimeVersion'" } else { Write-Debug "$DEBUG_PREFIX Runtime '$Runtime' does not have a default version. Finding the latest version." # Iterate through the list to find the latest non preview version $latestVersion = $supportedRuntimes[$Runtime] | Sort-Object -Property Version -Descending | Where-Object { $_.SupportedFunctionsExtensionVersions -contains $functionsExtensionVersion -and (-not $_.IsPreview) } | Select-Object -First 1 -ExpandProperty Version if ($latestVersion) { # Set the runtime version to the latest version $RuntimeVersion = $latestVersion } } # Error out if we could not find a default or latest version for the given runtime (except for 'Custom'), functions extension version, and os type if ((-not $latestVersion) -and (-not $defaultVersionFound) -and ($Runtime -ne 'Custom')) { $errorMessage = "Runtime '$Runtime' in Functions version '$FunctionsVersion' on '$OSType' is not supported." ThrowRuntimeNotSupportedException -Message $errorMessage -ErrorId "RuntimeVersionNotSupported" } Write-Warning "RuntimeVersion not specified. Setting default value to '$RuntimeVersion'. $SetDefaultValueParameterWarningMessage" } if ($Runtime -eq 'Custom') { # Custom runtime does not have a version $runtimeJsonDefinition = $supportedRuntimes[$Runtime] } else { $runtimeJsonDefinition = $supportedRuntimes[$Runtime] | Where-Object { $_.Version -eq $RuntimeVersion } } if (-not $runtimeJsonDefinition) { $errorMessage = "Runtime '$Runtime' version '$RuntimeVersion' in Functions version '$FunctionsVersion' on '$OSType' is not supported." $supporedVersions = @($supportedRuntimes[$Runtime] | Sort-Object -Property Version -Descending | Where-Object { $_.SupportedFunctionsExtensionVersions -contains $functionsExtensionVersion } | Select-Object -ExpandProperty Version) if ($supporedVersions.Count -gt 0) { $runtimeVersionOptions = $supporedVersions -join ", " $errorMessage += " Currently supported runtime versions for '$($Runtime)' are: $runtimeVersionOptions." } ThrowRuntimeNotSupportedException -Message $errorMessage -ErrorId "RuntimeVersionNotSupported" } if ($runtimeJsonDefinition.IsPreview) { # Write a verbose message to the user if the current runtime is in Preview Write-Verbose "Runtime '$Runtime' version '$RuntimeVersion' is in Preview for '$OSType'." -Verbose } return $runtimeJsonDefinition } function ThrowRuntimeNotSupportedException { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $ErrorId ) $Message += [System.Environment]::NewLine $Message += "For supported languages, please visit 'https://learn.microsoft.com/azure/azure-functions/functions-versions#languages'." $exception = [System.InvalidOperationException]::New($Message) ThrowTerminatingError -ErrorId $ErrorId ` -ErrorMessage $Message ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } function FormatListToString { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [System.String[]] $List ) if ($List.Count -eq 0) { return } $result = "" if ($List.Count -eq 1) { $result = "'" + $List[0] + "'" } else { for ($index = 0; $index -lt ($List.Count - 1); $index++) { $item = $List[$index] $result += "'" + $item + "', " } $result += "'" + $List[$List.Count - 1] + "'" } return $result } function ValidatePlanLocation { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Location, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] [ValidateSet("Dynamic", "ElasticPremium")] $PlanType, [Parameter(Mandatory=$false)] [System.Management.Automation.SwitchParameter] $OSIsLinux, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) $paramsToRemove = @( "PlanType", "OSIsLinux", "Location" ) foreach ($paramName in $paramsToRemove) { if ($PSBoundParameters.ContainsKey($paramName)) { $PSBoundParameters.Remove($paramName) | Out-Null } } $Location = $Location.Trim() $locationContainsSpace = $Location.Contains(" ") $availableLocations = @(Az.Functions.internal\Get-AzFunctionAppAvailableLocation -Sku $PlanType ` -LinuxWorkersEnabled:$OSIsLinux ` @PSBoundParameters | ForEach-Object { $_.Name }) if (-not $locationContainsSpace) { $availableLocations = @($availableLocations | ForEach-Object { $_.Replace(" ", "") }) } if (-not ($availableLocations -contains $Location)) { $errorMessage = "Location is invalid. Use 'Get-AzFunctionAppAvailableLocation' to see available locations for running function apps." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "LocationIsInvalid" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } function ValidatePremiumPlanLocation { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Location, [Parameter(Mandatory=$false)] [System.Management.Automation.SwitchParameter] $OSIsLinux, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) ValidatePlanLocation -PlanType ElasticPremium @PSBoundParameters } function ValidateConsumptionPlanLocation { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Location, [Parameter(Mandatory=$false)] [System.Management.Automation.SwitchParameter] $OSIsLinux, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) ValidatePlanLocation -PlanType Dynamic @PSBoundParameters } function GetParameterKeyValues { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [System.Collections.Generic.Dictionary[string, object]] [ValidateNotNull()] $PSBoundParametersDictionary, [Parameter(Mandatory=$true)] [System.String[]] [ValidateNotNull()] $ParameterList ) $params = @{} if ($ParameterList.Count -gt 0) { foreach ($paramName in $ParameterList) { if ($PSBoundParametersDictionary.ContainsKey($paramName)) { $params[$paramName] = $PSBoundParametersDictionary[$paramName] } } } return $params } function NewResourceTag { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [hashtable] $Tag ) $resourceTag = [Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20231201.ResourceTags]::new() foreach ($tagName in $Tag.Keys) { $resourceTag.Add($tagName, $Tag[$tagName]) } return $resourceTag } function ParseDockerImage { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $DockerImageName ) # Sample urls: # myacr.azurecr.io/myimage:tag # mcr.microsoft.com/azure-functions/powershell:2.0 if ($DockerImageName.Contains("/")) { $index = $DockerImageName.LastIndexOf("/") $value = $DockerImageName.Substring(0,$index) if ($value.Contains(".") -or $value.Contains(":")) { return $value } } } function GetFunctionAppServicePlanInfo { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $ServerFarmId, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("ServerFarmId")) { $PSBoundParameters.Remove("ServerFarmId") | Out-Null } $planInfo = $null if ($ServerFarmId.Contains("/")) { $parts = $ServerFarmId -split "/" $planName = $parts[-1] $resourceGroupName = $parts[-5] $planInfo = Az.Functions\Get-AzFunctionAppPlan -Name $planName ` -ResourceGroupName $resourceGroupName ` @PSBoundParameters } if (-not $planInfo) { $errorMessage = "Could not determine the current plan of the functionapp." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "CouldNotDetermineFunctionAppPlan" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $planInfo } function ValidatePlanSwitchCompatibility { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $CurrentServicePlan, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $NewServicePlan ) if (-not (($CurrentServicePlan.SkuTier -eq "ElasticPremium") -or ($CurrentServicePlan.SkuTier -eq "Dynamic") -or ($NewServicePlan.SkuTier -eq "ElasticPremium") -or ($NewServicePlan.SkuTier -eq "Dynamic"))) { $errorMessage = "Currently the switch is only allowed between a Consumption or an Elastic Premium plan." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "InvalidFunctionAppPlanSwitch" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } function NewAppSettingObject { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Hashtable] $CurrentAppSetting ) # Create StringDictionaryProperties (hash table) with the app settings $properties = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20231201.StringDictionaryProperties foreach ($keyName in $currentAppSettings.Keys) { $properties.Add($keyName, $currentAppSettings[$keyName]) } $appSettings = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20231201.StringDictionary $appSettings.Property = $properties return $appSettings } function ContainsReservedFunctionAppSettingName { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String[]] $AppSettingName ) foreach ($name in $AppSettingName) { if ($ReservedFunctionAppSettingNames.Contains($name)) { return $true } } return $false } function GetFunctionAppByName { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $ResourceGroupName, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) $paramsToRemove = @( "Name", "ResourceGroupName" ) foreach ($paramName in $paramsToRemove) { if ($PSBoundParameters.ContainsKey($paramName)) { $PSBoundParameters.Remove($paramName) | Out-Null } } $existingFunctionApp = Az.Functions\Get-AzFunctionApp -ResourceGroupName $ResourceGroupName ` -Name $Name ` -ErrorAction SilentlyContinue ` @PSBoundParameters if (-not $existingFunctionApp) { $errorMessage = "Function app name '$Name' in resource group name '$ResourceGroupName' does not exist." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FunctionAppDoesNotExist" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $existingFunctionApp } function GetAzWebAppConfig { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $Name, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $ResourceGroupName, [Switch] $ErrorIfResultIsNull, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("ErrorIfResultIsNull")) { $PSBoundParameters.Remove("ErrorIfResultIsNull") | Out-Null } $resetDefaultSubscription = $false $webAppConfig = $null $currentSubscription = $null try { $webAppConfig = Az.Functions.internal\Get-AzWebAppConfiguration -ErrorAction SilentlyContinue ` @PSBoundParameters if ($null -eq $webAppConfig) { Write-Warning -Message "Failed to retrieve function app site config. 1st attempt" Write-Warning -Message "Setting session context to subscription id '$($SubscriptionId)'" $resetDefaultSubscription = $true $currentSubscription = (Get-AzContext).Subscription.Id $null = Select-AzSubscription $SubscriptionId $webAppConfig = Az.Functions.internal\Get-AzWebAppConfiguration -ResourceGroupName $ResourceGroupName ` -Name $Name ` -ErrorAction SilentlyContinue ` @PSBoundParameters if ($null -eq $webAppConfig) { Write-Warning -Message "Failed to retrieve function app site config. 2nd attempt." } } } finally { if ($resetDefaultSubscription) { Write-Warning -Message "Resetting session context to subscription id '$currentSubscription'" $null = Select-AzSubscription $currentSubscription } } if ((-not $webAppConfig) -and $ErrorIfResultIsNull) { $errorMessage = "Falied to get config for function app name '$Name' in resource group name '$ResourceGroupName'." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FaliedToGetFunctionAppConfig" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $webAppConfig } function NewIdentityUserAssignedIdentity { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String[]] $IdentityID ) # If creating user assigned identities, only alphanumeric characters (0-9, a-z, A-Z), the underscore (_) and the hyphen (-) are supported. $msiUserAssignedIdentities = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20231201.ManagedServiceIdentityUserAssignedIdentities foreach ($id in $IdentityID) { $functionAppUserAssignedIdentitiesValue = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20231201.ManagedServiceIdentityUserAssignedIdentities $msiUserAssignedIdentities.Add($id, $functionAppUserAssignedIdentitiesValue) } return $msiUserAssignedIdentities } function GetShareSuffix { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Int] $Length = 8 ) # Create char array from 'a' to 'z' $letters = 97..122 | ForEach-Object { [char]$_ } $numbers = 0..9 $alphanumericLowerCase = $letters + $numbers $suffix = [System.Text.StringBuilder]::new() for ($index = 0; $index -lt $Length; $index++) { $value = $alphanumericLowerCase | Get-Random $suffix.Append($value) | Out-Null } $suffix.ToString() } function GetShareName { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionAppName ) $FunctionAppName = $FunctionAppName.ToLower() if ($env:FunctionsTestMode) { # To support the tests' playback mode, we need to have the same values for each function app creation payload. # Adding this test hook will allows us to have a constant share name when creation an app. return $FunctionAppName } <# Share name restrictions: - A share name must be a valid DNS name. - Share names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character. - Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in share names. - All letters in a share name must be lowercase. - Share names must be from 3 through 63 characters long. Docs: https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-shares--directories--files--and-metadata#share-names #> # Share name will be function app name + 8 random char suffix with a max length of 60 $MAXLENGTH = 60 $SUFFIXLENGTH = 8 if (($FunctionAppName.Length + $SUFFIXLENGTH) -lt $MAXLENGTH) { $name = $FunctionAppName } else { $endIndex = $MAXLENGTH - $SUFFIXLENGTH - 1 $name = $FunctionAppName.Substring(0, $endIndex) } $suffix = GetShareSuffix -Length $SUFFIXLENGTH $shareName = $name + $suffix return $shareName } Class Runtime { [string]$Name [string]$FullName [string]$Version [bool]$IsPreview [string[]]$SupportedFunctionsExtensionVersions [hashtable]$AppSettingsDictionary [hashtable]$SiteConfigPropertiesDictionary [bool]$IsHidden [bool]$IsDefault [string]$PreferredOs [hashtable]$AppInsightsSettings } function GetBuiltInFunctionAppStacksDefinition { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$false)] [Switch] $DoNotShowWarning ) if (-not $DoNotShowWarning) { $warmingMessage = "Failed to get Function App Stack definitions from ARM API. " $warmingMessage += "Please open an issue at https://github.com/Azure/azure-powershell/issues with the following title: " $warmingMessage += "[Az.Functions] Failed to get Function App Stack definitions from ARM API." Write-Warning $warmingMessage } $filePath = "$PSScriptRoot/FunctionsStack/functionAppStacks.json" $json = Get-Content -Path $filePath -Raw return $json } # Get the Function App Stack definition from the ARM API using the current Azure session # function GetFunctionAppStackDefinition { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param () if ($env:FunctionsTestMode -or ($null -ne $env:SYSTEM_DEFINITIONID -or $null -ne $env:Release_DefinitionId -or $null -ne $env:AZUREPS_HOST_ENVIRONMENT)) { Write-Debug "$DEBUG_PREFIX Running on test mode. Using built in json file definition." $json = GetBuiltInFunctionAppStacksDefinition -DoNotShowWarning return $json } # Make sure there is an active Azure session $context = Get-AzContext -ErrorAction SilentlyContinue if (-not $context) { $errorMessage = "There is no active Azure PowerShell session. Please run 'Connect-AzAccount'" $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "LoginToAzureViaConnectAzAccount" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } # Get the ResourceManagerUrl $resourceManagerUrl = $context.Environment.ResourceManagerUrl if ([string]::IsNullOrWhiteSpace($resourceManagerUrl)) { Write-Debug "$DEBUG_PREFIX context does not have a ResourceManagerUrl. Using built in json file definition." $json = GetBuiltInFunctionAppStacksDefinition return $json } if (-not $resourceManagerUrl.EndsWith('/')) { $resourceManagerUrl += '/' } Write-Debug "$DEBUG_PREFIX Get AccessToken." $token = . "$PSScriptRoot/../utils/Unprotect-SecureString.ps1" (Get-AzAccessToken -AsSecureString).Token $headers = @{ Authorization="Bearer $token" } $params = @{ stackOsType = 'All' removeDeprecatedStacks = 'true' } $apiEndPoint = $resourceManagerUrl + "providers/Microsoft.Web/functionAppStacks?api-version=2020-10-01" $maxNumberOfTries = 3 $currentCount = 1 Write-Debug "$DEBUG_PREFIX Set TLS 1.2" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 do { $result = $null try { Write-Debug "$DEBUG_PREFIX Pull down Function App Stack definitions from ARM API. Attempt $currentCount of $maxNumberOfTries." $result = Invoke-WebRequest -Uri $apiEndPoint -Method Get -Headers $headers -body $params -ErrorAction Stop } catch { $exception = $_ Write-Debug "$DEBUG_PREFIX Failed to get Function App Stack definitions from ARM API. Attempt $currentCount of $maxNumberOfTries. Error: $($exception.Message)" } if ($result) { # Unauthorized if ($result.StatusCode -eq 401) { # Get a new access token, create new headers and retry $token = . "$PSScriptRoot/../utils/Unprotect-SecureString.ps1" (Get-AzAccessToken -AsSecureString).Token $headers = @{ Authorization = "Bearer $token" } } if ($result.StatusCode -eq 200) { $stackDefinition = $result.Content | ConvertFrom-Json return $stackDefinition.value | ConvertTo-Json -Depth 100 } } $currentCount++ } while ($currentCount -le $maxNumberOfTries) # At this point, we failed to get the stack definition from the ARM API. # Return the built in json file definition $json = GetBuiltInFunctionAppStacksDefinition return $json } function ContainsProperty { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Object] $Object, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $PropertyName ) $result = $Object | Get-Member -MemberType Properties | Where-Object { $_.Name -eq $PropertyName } return ($null -ne $result) } function ParseMinorVersion { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$false)] [System.String] $StackMinorVersion, [Parameter(Mandatory=$false)] [System.String] $PreferredOs, [Parameter(Mandatory=$true)] [PSCustomObject] $RuntimeSettings, [Parameter(Mandatory=$false)] [System.String] $RuntimeFullName, [Parameter(Mandatory=$false)] [Bool] $StackIsLinux ) # If this FunctionsVersion is not supported, skip it if ($RuntimeSettings.supportedFunctionsExtensionVersions -notcontains "~$DefaultFunctionsVersion") { $supportedFunctionsExtensionVersions = $RuntimeSettings.supportedFunctionsExtensionVersions -join ", " Write-Debug "$DEBUG_PREFIX Minimium required Functions version '$DefaultFunctionsVersion' is not supported. Runtime supported Functions versions: $supportedFunctionsExtensionVersions. Skipping..." return } else { Write-Debug "$DEBUG_PREFIX Minimium required Functions version '$DefaultFunctionsVersion' is supported." } $runtimeName = GetRuntimeName -AppSettingsDictionary $RuntimeSettings.AppSettingsDictionary $version = $null if ($RuntimeName -eq "Java" -and $RuntimeSettings.RuntimeVersion -eq "1.8") { # Java 8 is only supported in Windows. The display value is 8; however, the actual SiteConfig.JavaVersion is 1.8 $version = $StackMinorVersion } else { $version = $RuntimeSettings.RuntimeVersion } $runtimeVersion = GetRuntimeVersion -Version $version -StackIsLinux $StackIsLinux # For Java function app, the version from the Stacks API is 8.0, 11.0, and 17.0. However, this is a breaking change which cannot be supported in the current release. # We will convert the version to 8, 11, and 17. This change will be reverted for the May 2024 breaking release. if ($RuntimeName -eq "Java") { $runtimeVersion = [int]$runtimeVersion Write-Debug "$DEBUG_PREFIX Runtime version for Java is modified to be compatible with the current release. Current version '$runtimeVersion'" } # For DotNet function app, the version from the Stacks API is 6.0. 7.0, and 8.0. However, this is a breaking change which cannot be supported in the current release. # We will convert the version to 6, 7, and 8. This change will be reverted for the May 2024 breaking release. if ($RuntimeName -like "DotNet*") { if ($runtimeVersion.EndsWith(".0")) { $runtimeVersion = [int]$runtimeVersion } Write-Debug "$DEBUG_PREFIX Runtime version for $runtimeName is modified to be compatible with the current release. Current version '$runtimeVersion'" } $runtime = [Runtime]::new() $runtime.Name = $runtimeName $runtime.AppSettingsDictionary = GetDictionary -SettingsDictionary $RuntimeSettings.AppSettingsDictionary $runtime.SiteConfigPropertiesDictionary = GetDictionary -SettingsDictionary $RuntimeSettings.SiteConfigPropertiesDictionary $runtime.AppInsightsSettings = GetDictionary -SettingsDictionary $RuntimeSettings.AppInsightsSettings $runtime.SupportedFunctionsExtensionVersions = GetSupportedFunctionsExtensionVersion -SupportedFunctionsExtensionVersions $RuntimeSettings.SupportedFunctionsExtensionVersions foreach ($propertyName in @("isPreview", "isHidden", "isDefault")) { if (ContainsProperty -Object $RuntimeSettings -PropertyName $propertyName) { Write-Debug "$DEBUG_PREFIX Runtime setting contains '$propertyName'" $runtime.$propertyName = $RuntimeSettings.$propertyName } } # When $env:FunctionsDisplayHiddenRuntimes is set to true, we will display all runtimes if ($runtime.IsHidden -and (-not $env:FunctionsDisplayHiddenRuntimes)) { Write-Debug "$DEBUG_PREFIX Runtime $runtimeName is hidden. Skipping..." return } if ($runtimeVersion -and ($runtimeName -ne "custom")) { Write-Debug "$DEBUG_PREFIX Runtime version: $runtimeVersion" $runtime.Version = $runtimeVersion } else { Write-Debug "$DEBUG_PREFIX Runtime $runtimeName does not have a version." $runtime.Version = "" } if ($RuntimeFullName) { $runtime.FullName = $RuntimeFullName } if ($PreferredOs) { $runtime.PreferredOs = $PreferredOs } $targetOs = if ($StackIsLinux) { 'Linux' } else { 'Windows' } Write-Debug "$DEBUG_PREFIX Runtime '$runtimeName' for '$targetOs' parsed successfully." return $runtime } function GetRuntimeVersion { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$false)] [System.String] $Version, [Parameter(Mandatory=$false)] [Bool] $StackIsLinux ) if (-not $Version) { # Some runtimes do not have a version like custom handler return } if ($StackIsLinux) { $Version = $Version.Split('|')[1] } else { $valuesToReplace = @('v', '~') foreach ($value in $valuesToReplace) { if ($Version.Contains($value)) { $Version = $Version.Replace($value, '') } } } $Version = $Version.Trim() return $Version } function GetDictionary { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [PSCustomObject] $SettingsDictionary ) $dictionary = @{} foreach ($property in $SettingsDictionary.PSObject.Properties) { $dictionary.Add($property.Name, $property.Value) } return $dictionary } function GetRuntimeName { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [PSCustomObject] $AppSettingsDictionary ) $settingHashTable = GetDictionary -SettingsDictionary $AppSettingsDictionary $name = $settingHashTable['FUNCTIONS_WORKER_RUNTIME'] if ($RuntimeToFormattedName.ContainsKey($name)) { return $RuntimeToFormattedName[$name] } return $name } function GetSupportedFunctionsExtensionVersion { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String[]] $SupportedFunctionsExtensionVersions ) $supportedExtensionsVersions = @() foreach ($extensionVersion in $SupportedFunctionsExtensionVersions) { if ($extensionVersion -ge "~$DefaultFunctionsVersion") { $supportedExtensionsVersions += $extensionVersion } } return $supportedExtensionsVersions } function AddRuntimeToDictionary { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] $Runtime, [Parameter(Mandatory=$true)] [hashtable] [Ref]$RuntimeToVersionDictionary ) if ($RuntimeToVersionDictionary.ContainsKey($Runtime.Name)) { $list = $RuntimeToVersionDictionary[$Runtime.Name] } else { $list = New-Object System.Collections.Generic.List[[Runtime]] } $list.Add($Runtime) $RuntimeToVersionDictionary[$Runtime.Name] = $list # Add the runtime name and version to the all runtimes list. This is used for the tab completers if ($AllRuntimeVersions.ContainsKey($runtime.Name)) { $allVersionsList = $AllRuntimeVersions[$Runtime.Name] } else { $allVersionsList = @() } if (-not $allVersionsList.Contains($Runtime.Version)) { $allVersionsList += $Runtime.Version $AllRuntimeVersions[$Runtime.name] = $allVersionsList } # Add Functions extension version to AllFunctionsExtensionVersions. This is used for the tab completers foreach ($extensionVersion in $Runtime.SupportedFunctionsExtensionVersions) { $version = $extensionVersion.Replace("~", "") if (-not $AllFunctionsExtensionVersions.Contains($version)) { $AllFunctionsExtensionVersions.Add($version) } } } function SetLinuxandWindowsSupportedRuntimes { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param () Write-Debug "$DEBUG_PREFIX Build function stack definitions." # Get Function App Runtime Definitions $json = GetFunctionAppStackDefinition $functionAppStackDefinition = $json | ConvertFrom-Json # Build a map of runtime -> runtime version -> runtime version properties foreach ($stackDefinition in $functionAppStackDefinition) { $preferredOs = $stackDefinition.properties.preferredOs $stackName = $stackDefinition.properties.value Write-Debug "$DEBUG_PREFIX Parsing stack name: $stackName" foreach ($majorVersion in $stackDefinition.properties.majorVersions) { foreach ($minorVersion in $majorVersion.minorVersions) { $runtimeFullName = $minorVersion.DisplayText Write-Debug "$DEBUG_PREFIX runtime full name: $runtimeFullName" $stackMinorVersion = $minorVersion.value Write-Debug "$DEBUG_PREFIX stack minor version: $stackMinorVersion" $runtime = $null if (ContainsProperty -Object $minorVersion.stackSettings -PropertyName "windowsRuntimeSettings") { $runtime = ParseMinorVersion -RuntimeSettings $minorVersion.stackSettings.windowsRuntimeSettings ` -RuntimeFullName $runtimeFullName ` -PreferredOs $preferredOs ` -StackMinorVersion $stackMinorVersion if ($runtime) { AddRuntimeToDictionary -Runtime $runtime -RuntimeToVersionDictionary ([Ref]$RuntimeToVersionWindows) } } if (ContainsProperty -Object $minorVersion.stackSettings -PropertyName "linuxRuntimeSettings") { $runtime = ParseMinorVersion -RuntimeSettings $minorVersion.stackSettings.linuxRuntimeSettings ` -RuntimeFullName $runtimeFullName ` -PreferredOs $preferredOs ` -StackIsLinux $true if ($runtime) { AddRuntimeToDictionary -Runtime $runtime -RuntimeToVersionDictionary ([Ref]$RuntimeToVersionLinux) } } } } } } # This method pulls down the Functions stack definitions from the ARM API and builds a list of supported runtimes and runtime versions. # This is used to build the tab completers for the New-AzFunctionApp cmdlet. function RegisterFunctionsTabCompleters { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param () if (-not $global:StacksAndTabCompletersInitialized) { SetLinuxandWindowsSupportedRuntimes # New-AzFunction app ArgumentCompleter for the RuntimeVersion parameter # The values of RuntimeVersion depend on the selection of the Runtime parameter $GetRuntimeVersionCompleter = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) if ($fakeBoundParameters.ContainsKey('Runtime')) { # RuntimeVersions is defined in SetLinuxandWindowsSupportedRuntimes $AllRuntimeVersions[$fakeBoundParameters.Runtime] | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } } # New-AzFunction app ArgumentCompleter for the Runtime parameter $GetAllRuntimesCompleter = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $runtimeValues = $AllRuntimeVersions.Keys | Sort-Object | ForEach-Object { $_ } $runtimeValues | Where-Object { $_ -like "$wordToComplete*" } } # New-AzFunction app ArgumentCompleter for the Runtime parameter $GetAllFunctionsVersionsCompleter = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $functionsVersions = $AllFunctionsExtensionVersions | Sort-Object | ForEach-Object { $_ } $functionsVersions | Where-Object { $_ -like "$wordToComplete*" } } # Register tab completers Register-ArgumentCompleter -CommandName New-AzFunctionApp -ParameterName FunctionsVersion -ScriptBlock $GetAllFunctionsVersionsCompleter Register-ArgumentCompleter -CommandName New-AzFunctionApp -ParameterName Runtime -ScriptBlock $GetAllRuntimesCompleter Register-ArgumentCompleter -CommandName New-AzFunctionApp -ParameterName RuntimeVersion -ScriptBlock $GetRuntimeVersionCompleter $global:StacksAndTabCompletersInitialized = $true } } function ValidateCpuAndMemory { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$false)] [Double] $ResourceCpu, [Parameter(Mandatory=$false)] [System.String] $ResourceMemory ) if (-not $ResourceCpu -and -not $ResourceMemory) { return } if ($ResourceCpu -and -not $ResourceMemory) { $errorMessage = "ResourceMemory must be specified when ResourceCpu is specified." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "ResourceMemoryNotSpecified" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } if ($ResourceMemory -and -not $ResourceCpu) { $errorMessage = "ResourceCpu must be specified when ResourceMemory is specified." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "ResourceCpuNotSpecified" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } try { if (-not $ResourceMemory.ToLower().EndsWith("gi")) { throw } # Attempt to parse the numerical part of ResourceMemory to ensure it's a valid format. [double]::Parse($ResourceMemory.Substring(0, $ResourceMemory.Length - 2)) | Out-Null } catch { $errorMessage = "ResourceMemory must be specified in Gi. Please provide a correct value. e.g., 4.0Gi." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "InvalidResourceMemory" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } function FormatFxVersion { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Image ) $fxVersion = $Image # Normalize case and remove HTTP(s) prefixes if present. $normalizedImage = $Image -replace '^(https?://)', '' -replace ' ', '' # Prepend "DOCKER|" if not already prefixed with "docker|" (case-insensitive). if (-not $normalizedImage.StartsWith('docker|', [StringComparison]::OrdinalIgnoreCase)) { $fxVersion = "DOCKER|$Image" } return $fxVersion } function GetManagedEnvironment { [Microsoft.Azure.PowerShell.Cmdlets.Functions.DoNotExportAttribute()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $Environment, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $ResourceGroupName ) $azAppModuleName = "Az.App" if (-not (Get-Module -ListAvailable -Name $azAppModuleName)) { $errorMessage = "The '$azAppModuleName' module is required when creating Function Apps ACA. Please install the module and try again." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "RequiredModuleNotAvailable" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } Import-Module -Name $azAppModuleName -Force -ErrorAction Stop $managedEnv = Get-AzContainerAppManagedEnv -Name $Environment ` -ResourceGroupName $ResourceGroupName ` -ErrorAction SilentlyContinue if (-not $managedEnv) { $errorMessage = "Failed to get the managed environment '$Environment' in resource group name '$ResourceGroupName'." $errorMessage += " Please make sure the managed environment is valid." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FailedToGetEnvironment" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $managedEnv } # SIG # Begin signature block # MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAxovF+GzvG1LX9 # J8FQIzCrdxqHNYqR30a/Ry2qK7JM8qCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINlF24FfqZusT9+X47wkkdkb # r7Bq2XifwZB7kkGkrNirMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAIndSVZzE0Ar56hDB7afy9Ut5HhEEaOUyOtJbFG5gFlHBWll0PTtBRKQg # Z/qLVJ4cK9DhIgpFBj9D9ihd/6wk4cLPvFn0wSTcAqJVBk7IGA0GpC2aOCBcZBiR # lOWCwG+T7W/8SE/J+VVUSZNSpXdeo8UEfnVhdrq1vs+o0hrdlJDjCThbPlWHOAc3 # JKkk6o9XM8HMc9qobgmsUswlVjc63LsFoUFVSqX5UgAYSG1vSNc2iSh8jwb01Kfn # bRBOTp8oPJQQ1kz0Yt91wR0h/kKIiG2kvm9dVXYG2PEn2JkjLMVJeXRQvI28iZ53 # Da2BS4/cnLe6fAynqwirenMztdPdA6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC # F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq # hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCB2uw2bsYBAgMwai/TwLsSTACqQmQ8Ftpzlel3LuhlIegIGZ2LkgITK # GBMyMDI1MDEwOTA2Mzc0OS4yNjVaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # Tjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAACAAvXqn8bKhdWAAEAAAIAMA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0 # MDcyNTE4MzEyMVoXDTI1MTAyMjE4MzEyMVowgdMxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w # ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjUyMUEt # MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr1XaadKkP2TkunoTF573 # /tF7KJM9Doiv3ccv26mqnUhmv2DM59ikET4WnRfo5biFIHc6LqrIeqCgT9fT/Gks # 5VKO90ZQW2avh/PMHnl0kZfX/I5zdVooXHbdUUkPiZfNXszWswmL9UlWo8mzyv9L # p9TAtw/oXOYTAxdYSqOB5Uzz1Q3A8uCpNlumQNDJGDY6cSn0MlYukXklArChq6l+ # KYrl6r/WnOqXSknABpggSsJ33oL3onmDiN9YUApZwjnNh9M6kDaneSz78/YtD/2p # Gpx9/LXELoazEUFxhyg4KdmoWGNYwdR7/id81geOER69l5dJv71S/mH+Lxb6L692 # n8uEmAVw6fVvE+c8wjgYZblZCNPAynCnDduRLdk1jswCqjqNc3X/WIzA7GGs4HUS # 4YIrAUx8H2A94vDNiA8AWa7Z/HSwTCyIgeVbldXYM2BtxMKq3kneRoT27NQ7Y7n8 # ZTaAje7Blfju83spGP/QWYNZ1wYzYVGRyOpdA8Wmxq5V8f5r4HaG9zPcykOyJpRZ # y+V3RGighFmsCJXAcMziO76HinwCIjImnCFKGJ/IbLjH6J7fJXqRPbg+H6rYLZ8X # BpmXBFH4PTakZVYxB/P+EQbL5LNw0ZIM+eufxCljV4O+nHkM+zgSx8+07BVZPBKs # looebsmhIcBO0779kehciYMCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSAJSTavgkj # Kqge5xQOXn35fXd3OjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww # bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF # BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAKPCG9njRtIqQ # +fuECgxzWMsQOI3HvW7sV9PmEWCCOWlTuGCIzNi3ibdLZS0b2IDHg0yLrtdVuBi3 # FxVdesIXuzYyofIe/alTBdV4DhijLTXtB7NgOno7G12iO3t6jy1hPSquzGLry/2m # EZBwIsSoS2D+H+3HCJxPDyhzMFqP+plltPACB/QNwZ7q+HGyZv3v8et+rQYg8sF3 # PTuWeDg3dR/zk1NawJ/dfFCDYlWNeCBCLvNPQBceMYXFRFKhcSUws7mFdIDDhZpx # qyIKD2WDwFyNIGEezn+nd4kXRupeNEx+eSpJXylRD+1d45hb6PzOIF7BkcPtRtFW # 2wXgkjLqtTWWlBkvzl2uNfYJ3CPZVaDyMDaaXgO+H6DirsJ4IG9ikId941+mWDej # kj5aYn9QN6ROfo/HNHg1timwpFoUivqAFu6irWZFw5V+yLr8FLc7nbMa2lFSixzu # 96zdnDsPImz0c6StbYyhKSlM3uDRi9UWydSKqnEbtJ6Mk+YuxvzprkuWQJYWfpPv # ug+wTnioykVwc0yRVcsd4xMznnnRtZDGMSUEl9tMVnebYRshwZIyJTsBgLZmHM7q # 2TFK/X9944SkIqyY22AcuLe0GqoNfASCIcZtzbZ/zP4lT2/N0pDbn2ffAzjZkhI+ # Qrqr983mQZWwZdr3Tk1MYElDThz2D0MwggdxMIIFWaADAgECAhMzAAAAFcXna54C # m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp # Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy # MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51 # yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY # 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9 # cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN # 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua # Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74 # kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2 # K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5 # TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk # i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q # BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri # Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC # BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw # MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp # b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm # ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM # 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW # OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4 # FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw # xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX # fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX # VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC # onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU # 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG # ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # Tjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAjJOfLZb3ivipL3sSLlWFbLrWjmSggYMw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF # AAIFAOspukYwIhgPMjAyNTAxMDkwMzAwMjJaGA8yMDI1MDExMDAzMDAyMlowdzA9 # BgorBgEEAYRZCgQBMS8wLTAKAgUA6ym6RgIBADAKAgEAAgI9JQIB/zAHAgEAAgIT # LDAKAgUA6ysLxgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow # CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQAnEt96NJ54 # lVT057WZD76gmRUosNR+xLJryNUXSrET60YSG8sr4OwE26UByMg3tRkAtM4xSgr/ # OHEvCcdURXey+JVvpN4A41YUj180gzfZJ1FJc/0qsNy5IYiFGIQYmUEuVH0NBcgv # itG9Vn62Ti+esnCUfAMszoFnbscaHbdNp0Z2QnaUg2tLhleVhQSRW7XWTjwz2c1Z # 44n9vVANNTW4ZxQIU8U54rNhSrWUMxppxQNMAp+s/va1B0c0ClM9VGEKpigkGngQ # VmkepmGfAGyQe3e3HNkF5v2dmoakB8xA09E6G8+RLVymtg1rN3LbZKyLV2/Ej3B2 # lOsREYqXk86UMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAIAC9eqfxsqF1YAAQAAAgAwDQYJYIZIAWUDBAIBBQCgggFKMBoG # CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgNqcKz04P # 9nBq+HTIouNHVF/yJLGCJqxJKGkhB3fP50kwgfoGCyqGSIb3DQEJEAIvMYHqMIHn # MIHkMIG9BCDUyO3sNZ3burBNDGUCV4NfM2gH4aWuRudIk/9KAk/ZJzCBmDCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAACAAvXqn8bKhdWAAEA # AAIAMCIEIND+XTKnIYjqk0RlSfUVIwr3rYK4PiW/wPx2N4l88Du4MA0GCSqGSIb3 # DQEBCwUABIICADQfStbUUz8Qd/aM+qPsKoopFafALcGByaerfZOGhltfZc9ICviX # tcZIDIpm0g79o7V5Y6+wjMGARlAEPJ7JypIOAf8Tf8OZHPZg3ek+zS+lLy3MuKFz # k7F8lAZY9jsyp1Xv9loZRBZRex8CTDh/XIyeknx2LfC9KLn5SRFmsEwsbE6HYls7 # +eZHreeROqBBxQJ8SS09a1yhmL6QghvE04vQYB8gM8Musjj8s9Y7MtfI3yqW+2V/ # 6P6qAgzZBUwBVESZkbIF5JBnjRMS3yg0Y8sVSnZK6/x2NIMzXoassrDw76g7cZr2 # U2hd+GKCaxI2U5deWwpDfIV1IUdRqxLA3JSa1zUOqJDnoVj0GtZGPSG5a/dD2jEH # oy1ejVX+1Ux+74ZKzeCh/A/8mgiNAeLOqUJZ085ma4B37T/K3oKQCcxfAAI/GmgT # Ata2ZU7+jp3Bar4/RmGP7fpa5lqEvcPBZ9kKYtjSfn93ix7y4gABAWNcxCdSTWBB # 9NhRMHfywvDLLo3r5YCKfb1l4YLieEnle97FYzVqdjml3lUiyVwZEBpnjvJt0h1b # aR9YEKGjoP+MWskZM0FLozcyOGeBL0c910YaSDLFwOOewM0Jf3p+jqouc0glW3xc # tClGf0cUyHcnpRzMO2UeQjA/SLGQPmO665p483yGLH8w/zbqHJHyAWtB # SIG # End signature block |