custom/HelperFunctions.ps1
# Load Az.Functions module constants $constants = @{} $constants["AllowedStorageTypes"] = @('Standard_GRS', 'Standard_RAGRS', 'Standard_LRS', 'Standard_ZRS', 'Premium_LRS') $constants["RequiredStorageEndpoints"] = @('PrimaryEndpointFile', 'PrimaryEndpointQueue', 'PrimaryEndpointTable') $constants["DefaultFunctionsVersion"] = '3' $constants["NodeDefaultVersion"] = @{ '2' = '~10' '3' = '~12' } $constants["RuntimeToDefaultVersion"] = @{ 'Linux' = @{ '2'= @{ 'Node' = '10' 'DotNet'= '2' 'Python' = '3.7' } '3' = @{ 'Node' = '10' 'DotNet' = '3' 'Python' = '3.7' 'Java' = '8' } } 'Windows' = @{ '2'= @{ 'Node' = '10' 'DotNet'= '2' 'PowerShell' = '6.2' 'Java' = '8' } '3' = @{ 'Node' = '10' 'DotNet' = '3' 'PowerShell' = '6.2' 'Java' = '8' } } } $constants["RuntimeToFormattedName"] = @{ 'node' = 'Node' 'dotnet' = 'DotNet' 'python' = 'Python' 'java' = 'Java' 'powershell' = 'PowerShell' } $constants["RuntimeToDefaultOSType"] = @{ 'DotNet'= 'Windows' 'Node' = 'Windows' 'Java' = 'Windows' 'PowerShell' = 'Windows' 'Python' = 'Linux' } # These are used for tab completion for the RuntimeVersion parameter in New-AzFunctionApp. $constants["RuntimeVersions"] = @{ 'DotNet'= @('2', '3') 'Node' = @('8', '10', '12') 'Java' = @('8', '11') 'PowerShell' = @('6.2', '7.0') 'Python' = @('3.6', '3.7', '3.8') } $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["DotNetRuntimeVersionToDotNetLinuxFxVersion"] = @{ '2' = '2.2' '3' = '3.1' } foreach ($variableName in $constants.Keys) { if (-not (Get-Variable $variableName -ErrorAction SilentlyContinue)) { Set-Variable $variableName -value $constants[$variableName] } } function GetDefaultRuntimeVersion { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionsVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType ) if ($Runtime -eq "DotNet") { return $FunctionsVersion } $defaultVersion = $RuntimeToDefaultVersion[$OSType][$FunctionsVersion][$Runtime] if (-not $defaultVersion) { $errorMessage = "$Runtime is not supported in Functions version $FunctionsVersion for $OSType." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "RuntimeNotSuported" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $defaultVersion } function GetDefaultOSType { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime ) $defaultOSType = $RuntimeToDefaultOSType[$Runtime] if (-not $defaultOSType) { $errorMessage = "Failed to get default OS type for $Runtime." $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId "FailedToGetDefaultOSType" ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $defaultOSType } function GetConnectionString { 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 } $accountKey = $keys[0].Value $connectionString = "DefaultEndpointsProtocol=https;AccountName=$StorageAccountName;AccountKey=$accountKey" return $connectionString } function NewAppSetting { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Value ) $setting = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20190801.NameValuePair $setting.Name = $Name $setting.Value = $Value return $setting } function GetServicePlan { 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 { 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 { 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 { 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 { param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Object] $ApplicationSetting ) # Create a key value pair to hold the function app settings $applicationSettings = @{} foreach ($keyName in $ApplicationSetting.Property.Keys) { $applicationSettings[$keyName] = $ApplicationSetting.Property[$keyName] } return $applicationSettings } function AddFunctionAppSettings { param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [Object] $App, $SubscriptionId, $HttpPipelineAppend, $HttpPipelinePrepend ) if ($PSBoundParameters.ContainsKey("App")) { $PSBoundParameters.Remove("App") | Out-Null } $App.AppServicePlan = ($App.ServerFarmId -split "/")[-1] $App.OSType = if ($App.kind.ToLower().Contains("linux")){ "Linux" } else { "Windows" } $currentSubscription = $null $resetDefaultSubscription = $false try { $settings = Az.Functions.internal\Get-AzWebAppApplicationSetting -Name $App.Name ` -ResourceGroupName $App.ResourceGroup ` -ErrorAction SilentlyContinue ` @PSBoundParameters if ($null -eq $settings) { $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 return $App } } } finally { if ($resetDefaultSubscription) { $null = Select-AzSubscription $currentSubscription } } # Add application settings $App.ApplicationSettings = ConvertWebAppApplicationSettingToHashtable -ApplicationSetting $settings $runtimeName = $App.ApplicationSettings["FUNCTIONS_WORKER_RUNTIME"] $App.Runtime = if (($null -ne $runtimeName) -and ($RuntimeToFormattedName.ContainsKey($runtimeName))) { $RuntimeToFormattedName[$runtimeName] } elseif ($App.ApplicationSettings.ContainsKey("DOCKER_CUSTOM_IMAGE_NAME")) { "Custom Image" } else {""} # 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 { 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.ToLower().Contains("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 { 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 { 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 { 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 { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Sku ) if ($Sku -eq "SHARED") { return "D1" } return $Sku } function CreateFunctionsIdentity { 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 { 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://docs.microsoft.com/en-us/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 { 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 GetFunctionAppDefaultNodeVersion { param ( [System.String] $FunctionsVersion, [System.String] $Runtime, [System.String] $RuntimeVersion ) if ((-not $Runtime) -or ($Runtime -ne "node")) { return $NodeDefaultVersion[$FunctionsVersion] } if ($RuntimeVersion) { return "~$RuntimeVersion" } return $NodeDefaultVersion[$FunctionsVersion] } function GetLinuxFxVersion { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionsVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType, [System.String] $RuntimeVersion ) if (-not $RuntimeVersion) { $RuntimeVersion = $RuntimeToDefaultVersion[$OSType][$FunctionsVersion][$Runtime] } if ($Runtime -eq "DotNet") { $RuntimeVersion = $DotNetRuntimeVersionToDotNetLinuxFxVersion[$FunctionsVersion] } $runtimeName = $Runtime.ToUpper() return "$runtimeName|$RuntimeVersion" } function GetErrorMessage { 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 { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType ) if ($OSType -eq "Linux") { return $LinuxRuntimes } elseif ($OSType -eq "Windows") { return $WindowsRuntimes } throw "Unknown OS type '$OSType'" } function ValidateRuntimeAndRuntimeVersion { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionsVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $RuntimeVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType ) if ($Runtime -eq "DotNet") { return } $runtimeVersionIsSupported = $false $supportedRuntimes = GetSupportedRuntimes -OSType $OSType foreach ($majorVersion in $supportedRuntimes[$Runtime].MajorVersions) { if ($majorVersion.DisplayVersion -eq $RuntimeVersion) { # SupportedFunctionsExtensionVersions[0] is of the form '~int' # Add '~' to the begining of FunctionsVersion if ($majorVersion.SupportedFunctionsExtensionVersions -contains "~$FunctionsVersion") { $runtimeVersionIsSupported = $true break } } } if (-not $runtimeVersionIsSupported) { $errorMessage = "$Runtime version $RuntimeVersion in Functions version $FunctionsVersion for $OSType is not supported." $errorMessage += " For supported languages, please visit 'https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions#languages'." $errorId = "InvalidRuntimeVersionFor" + $Runtime + "In" + $OSType $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId $errorId ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } } function GetWorkerVersion { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $FunctionsVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $Runtime, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $RuntimeVersion, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.String] $OSType ) $workerRuntimeVersion = $null $supportedRuntimes = GetSupportedRuntimes -OSType $OSType foreach ($majorVersion in $supportedRuntimes[$Runtime].MajorVersions) { if ($majorVersion.DisplayVersion -eq $RuntimeVersion) { $workerRuntimeVersion = $majorVersion.runtimeVersion break } } if (-not $workerRuntimeVersion) { $errorMessage = "Falied to get runtime version for $Runtime $RuntimeVersion in Functions version $FunctionsVersion for $OSType." $errorId = "InvalidWorkerRuntimeVersionFor" + $Runtime + "In" + $OSType $exception = [System.InvalidOperationException]::New($errorMessage) ThrowTerminatingError -ErrorId $errorId ` -ErrorMessage $errorMessage ` -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) ` -Exception $exception } return $workerRuntimeVersion } function ValidatePlanLocation { 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 { 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 { 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 { 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 { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [hashtable] $Tag ) $resourceTag = [Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20190801.ResourceTags]::new() foreach ($tagName in $Tag.Keys) { $resourceTag.Add($tagName, $Tag[$tagName]) } return $resourceTag } function ParseDockerImage { 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 { 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 { 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 { 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.Api20190801.StringDictionaryProperties foreach ($keyName in $currentAppSettings.Keys) { $properties.Add($keyName, $currentAppSettings[$keyName]) } $appSettings = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20190801.StringDictionary $appSettings.Property = $properties return $appSettings } function ContainsReservedFunctionAppSettingName { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String[]] $AppSettingName ) foreach ($name in $AppSettingName) { if ($ReservedFunctionAppSettingNames.Contains($name)) { return $true } } return $false } function GetFunctionAppByName { 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 { 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 try { $webAppConfig = Az.Functions.internal\Get-AzWebAppConfiguration -ErrorAction SilentlyContinue ` @PSBoundParameters if ($null -eq $webAppConfig) { $resetDefaultSubscription = $true $currentSubscription = (Get-AzContext).Subscription.Id $null = Select-AzSubscription $App.SubscriptionId $webAppConfig = Az.Functions.internal\Get-AzWebAppConfiguration -ResourceGroupName $ResourceGroupName ` -Name $Name ` -ErrorAction SilentlyContinue ` @PSBoundParameters } } finally { if ($resetDefaultSubscription) { $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 { 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.Api20190801.ManagedServiceIdentityUserAssignedIdentities foreach ($id in $IdentityID) { $functionAppUserAssignedIdentitiesValue = New-Object -TypeName Microsoft.Azure.PowerShell.Cmdlets.Functions.Models.Api20190801.Components1Jq1T4ISchemasManagedserviceidentityPropertiesUserassignedidentitiesAdditionalproperties $msiUserAssignedIdentities.Add($IdentityID, $functionAppUserAssignedIdentitiesValue) } return $msiUserAssignedIdentities } # Set Linux and Windows supported runtimes Class Runtime { [string]$Name [MajorVersion[]]$MajorVersions } Class MajorVersion { [string]$DisplayVersion [string]$RuntimeVersion [string[]]$SupportedFunctionsExtensionVersions [hashtable]$AppSettingsDictionary [hashtable]$SiteConfigPropertiesDictionary } $LinuxRuntimes = @{} $WindowsRuntimes = @{} function SetLinuxandWindowsSupportedRuntimes { foreach ($fileName in @("LinuxFunctionsStacks.json", "WindowsFunctionsStacks.json")) { $filePath = Join-Path "$PSScriptRoot/FunctionsStack" $fileName if (-not (Test-Path $filePath)) { throw "Unable to create list of supported runtimes. File path '$filePath' does not exist." } $functionsStack = Get-Content -Path $filePath -Raw | ConvertFrom-Json foreach ($stack in $functionsStack.value) { $runtime = [Runtime]::new() $runtime.name = $stack.name foreach ($version in $stack.properties.majorVersions) { $majorVersion = [MajorVersion]::new() $majorVersion.RuntimeVersion = $version.RuntimeVersion $majorVersion.SupportedFunctionsExtensionVersions = $version.supportedFunctionsExtensionVersions if ($version.displayVersion) { $majorVersion.DisplayVersion = $version.displayVersion } if ($version.appSettingsDictionary) { $appSettings = @{} foreach ($property in $version.appSettingsDictionary.PSObject.Properties) { $appSettings.Add($property.Name, $property.Value) } $majorVersion.appSettingsDictionary = $appSettings } if ($version.appSettingsDictionary) { $siteConfigProperties = @{} foreach ($property in $version.siteConfigPropertiesDictionary.PSObject.Properties) { $siteConfigProperties.Add($property.Name, $property.Value) } $majorVersion.SiteConfigPropertiesDictionary = $siteConfigProperties } $runtime.MajorVersions += $majorVersion } if ($stack.type -like "*LinuxFunctions") { if (-not $LinuxRuntimes.ContainsKey($runtime.name)) { $LinuxRuntimes[$runtime.name] = $runtime } } elseif ($stack.type -like "*WindowsFunctions") { if (-not $WindowsRuntimes.ContainsKey($runtime.name)) { $WindowsRuntimes[$runtime.name] = $runtime } } else { throw "Unknown stack type '$($stack.type)'" } } } } 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 at the top of this file $RuntimeVersions[$fakeBoundParameters.Runtime] | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } else { $RuntimeVersions.RuntimeVersion | ForEach-Object {$_} } } Register-ArgumentCompleter -CommandName New-AzFunctionApp -ParameterName RuntimeVersion -ScriptBlock $GetRuntimeVersionCompleter # SIG # Begin signature block # MIIjhQYJKoZIhvcNAQcCoIIjdjCCI3ICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyxZ4ROK4KPYXG # MaBEj1Fy/CXFpzqP6+SXSQqPXmgU6qCCDYEwggX/MIID56ADAgECAhMzAAABh3IX # chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB # znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH # sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d # weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ # itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV # Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy # S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K # NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV # BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr # qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx # zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe # yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g # yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf # AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI # 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 # GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea # jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWjCCFVYCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgQ06otYQM # u62GAyk1sH01/4Z+75RB1Dw5ER6ZDH0J96AwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQDBnXFfcRn4AwwezQeL2jyzcOl5lR4Tpz0WD4lSaNFW # d2X/u2k3sp6P/t5PiW1CWEIMSSLZRqBjo2RJLGM2rZKRixWIw4FIMTZKnf1OwI0G # ilIoibm1u+83nsJjL+TIqF9pmrzB29HL4lEWEKQNQiQph9YkNXeNDTIBtcO1q7zd # 2HotYY+gPpEVECkcR94v6KK42TpZ9yF3VABX3qdy4KxHXBI2UwQhaRAUse2Sl81x # exRmsNYhBYwr2723m3VVArhfG8DYEQvNqBETYfR+vU64cEbwQ/g9UE05E1eDy2Dv # Xu9UXR0N1calIOAugLenV/8jfPwGL4OnOhS12EsU5QVgoYIS5DCCEuAGCisGAQQB # gjcDAwExghLQMIISzAYJKoZIhvcNAQcCoIISvTCCErkCAQMxDzANBglghkgBZQME # AgEFADCCAVAGCyqGSIb3DQEJEAEEoIIBPwSCATswggE3AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIFmdZuzzHRIDw4IUwS7Pn+gzsMjcJFmxy7KIoWC7 # 7DisAgZezof45MwYEjIwMjAwNjE3MTUyMDM2LjQ4WjAEgAIB9KCB0KSBzTCByjEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWlj # cm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBF # U046NDlCQy1FMzdBLTIzM0MxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2Wggg48MIIE8TCCA9mgAwIBAgITMwAAARcxYH4HdjGeCQAAAAABFzAN # BgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0x # OTExMTMyMTQwMzRaFw0yMTAyMTEyMTQwMzRaMIHKMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP # cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0OUJDLUUzN0EtMjMz # QzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJ # KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMahUp8trc0Dysh7EVb2E05G4q3LREkd # Wn1yblL1PPBXWku3ZvQiG5Lfib/p1Pdna0hPGEK4OCmLgwf9lEV/KgFYt+J9gBkA # 6fLiB88nvFhK55JjPmRXl/cFvSJS1OE7yIpnMPSoO9OzsKT8Jv2mtTo5LJtKxhdu # dN3XneWtFc6/8teDORTWP1qLw5Wd+L8MLxmx2EADkJVWp0G0sNRKDIKDKPoabrUQ # 4Hp6/5MZ5Pz4vY8WZbKDcjI5phTiYIX8ofXspqbrwOkOZCGStS+nxwoSH4tvYXNQ # feB8BjIqwT9f5P2f2snqF2MlFYT6hWG4/oEU9mJPKMrUYFLcGv6S7SsCAwEAAaOC # ARswggEXMB0GA1UdDgQWBBSuVNtW34mLD9pOtOS7+Dhk6K3DBTAfBgNVHSMEGDAW # gBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8v # Y3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0Ff # MjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEw # LTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G # CSqGSIb3DQEBCwUAA4IBAQBf14UZpXmKXfjfNoPSILBfijNMQdsFTRmU8F91CFMD # hN5H6M4ss0FWWY6UjmeF0ZEnxegOtgKULhgFLZbIYe8HlB1TY5sqgcX0qbtvm4bB # fIovSnEbrtY5AIEW7meMaye/luvXQyucieAHTte3AbBT+q53vik7qWhAxfDwZcrh # fwt/JmRDum5d4UAZuHfszEQ+07L+hjN7gUZMyg7unQFk5LFo09hvOe08lX3DbIhx # T9qk9wgkSISL1f+rWfwRUm8gGu1LHGoIjs4Zo8lA5kGnbYQoGIEx/fVc8V3L9UUQ # KNmWxeqDERmyHWH+lHFYu5TTfq916/4TT/B/ixCzcmeMMIIGcTCCBFmgAwIBAgIK # YQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAxMjE0 # NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoAgoX7 # 7XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiEVEMM # 1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+BVLHP # k0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3wV3Ws # vYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXoeByw # 6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYwggHi # MBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNoWoVt # VTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0T # AQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNV # HR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w # cm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEE # TjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2Nl # cnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGVMIGS # MIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4y # IB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAu # IB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM9TG+ # zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0YBKK # dsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgPF/Uv # eYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/625Y4z # u2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZqkHim # bdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96LjlX # dqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5vvfHh # AN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiFAR6A # +xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduWsqdC # osnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV42ne # V8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto229Nf # j950iEkSoYICzjCCAjcCAQEwgfihgdCkgc0wgcoxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9w # ZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjQ5QkMtRTM3QS0yMzND # MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYF # Kw4DAhoDFQCd9GUqHhFR83q4mVyGXNpEYzBB0qCBgzCBgKR+MHwxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU # aW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA4pQMfDAiGA8yMDIw # MDYxNzExMjkwMFoYDzIwMjAwNjE4MTEyOTAwWjB3MD0GCisGAQQBhFkKBAExLzAt # MAoCBQDilAx8AgEAMAoCAQACAhHUAgH/MAcCAQACAhGuMAoCBQDilV38AgEAMDYG # CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEA # AgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAVK8qi8esyxhzXCKolDLJMlkQB/1EZNlG # mR2Uaj5ybDzFvyAfqMoIv2REBquOVg/ZL2tudBVp3oQ74I/Ftwa9ua7gEGRGhxGU # Y/SoxiLovjtep/ZlglWcIG133GNVYE4Ntq4pO4fo88e1sZxxBnqbuaiujjekTX6A # FzzFCvyaruYxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg # MjAxMAITMwAAARcxYH4HdjGeCQAAAAABFzANBglghkgBZQMEAgEFAKCCAUowGgYJ # KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCC0IXBfT3BY # fWxnak9p8w4Abb+N7Fi6k1/BW4tjaHcyATCB+gYLKoZIhvcNAQkQAi8xgeowgecw # geQwgb0EIGxpZsjislnMX7qYH49qZTnSzYRECYnf6u44H8ja2F8kMIGYMIGApH4w # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEXMWB+B3YxngkAAAAA # ARcwIgQgoc6E7vW9SWq3Sh+DhBwZYA4GomVR78SpQH7+kUa6FV4wDQYJKoZIhvcN # AQELBQAEggEASlmQuGxtsCiD4ZKwuWTqdCNmncqF+et18TaRfs9bhMYCAnXMfr0d # wqL+K7flmOCELbpIN9YBxZVnTNviCzLXkuLoSP+USxCO2Y1/cJ7DHf35/q4HG/LJ # eczyfITbVbbRKw5zSK0qeQOCEr5o/w+rE5mECVElsQOot1NKy3POA+GstbNwNGkG # lujeW0l8haerbastTpM5WbNuc7MDg4jjoo/NWHqi1nVDDSjb9LPhSI9j7YjvTCcN # GFk8lA9YuEIRNaK3zatdTJhlFbuNcO0ZsAbcZd0iS478SAXcBJyqS7gXO+tncfQf # LAfjciRXORw3q+ZVSR+L2BmbFPH00jWx0Q== # SIG # End signature block |