ConnectedKubernetes.Autorest/custom/New-AzConnectedKubernetes.ps1


# ----------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Code generated by Microsoft (R) AutoRest Code Generator.Changes may cause incorrect behavior and will be lost if the code
# is regenerated.
# ----------------------------------------------------------------------------------

[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '',
    Justification = 'Kubernetes is a recognised term', Scope = 'Function', Target = 'New-AzConnectedKubernetes')]
[CmdletBinding()]
param()

<#
.Synopsis
API to register a new Kubernetes cluster and create a tracked resource in Azure Resource Manager (ARM).
.Description
API to register a new Kubernetes cluster and create a tracked resource in Azure Resource Manager (ARM).
.Example
New-AzConnectedKubernetes -ClusterName azps_test_cluster -ResourceGroupName azps_test_group -Location eastus
.Example
New-AzConnectedKubernetes -ClusterName azps_test_cluster1 -ResourceGroupName azps_test_group -Location eastus -KubeConfig $HOME\.kube\config -KubeContext azps_aks_t01
 
.Outputs
Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Models.Api20240715Preview.IConnectedCluster
.Link
https://learn.microsoft.com/powershell/module/az.connectedkubernetes/new-azconnectedkubernetes
#>

function New-AzConnectedKubernetes {
    [OutputType([Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Models.Api20240715Preview.IConnectedCluster])]
    [CmdletBinding(DefaultParameterSetName = 'CreateExpanded', SupportsShouldProcess, PositionalBinding = $false, ConfirmImpact = 'Medium')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '',
        Justification = 'Code published before this issue was identified')]
    param(
        [Parameter(Mandatory)]
        [Alias('Name')]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.String]
        # The name of the Kubernetes cluster on which get is called.
        ${ClusterName},

        [Parameter(Mandatory)]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.String]
        # The name of the resource group.
        # The name is case insensitive.
        ${ResourceGroupName},

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

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.Uri]
        # The http URI of the proxy server for the kubernetes cluster to use
        ${HttpProxy},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.Uri]
        # The https URI of the proxy server for the kubernetes cluster to use
        ${HttpsProxy},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.String]
        # The comma-separated list of hostnames that should be excluded from the proxy server for the kubernetes cluster to use
        ${NoProxy},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.String]
        # The path to the certificate file for proxy or custom Certificate Authority.
        ${ProxyCert},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [ValidateRange(0, 3600)]
        [Int]
        # The time required (in seconds) for the arc-agent pods to be installed on the kubernetes cluster.
        ${OnboardingTimeout} = 600,

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.Management.Automation.SwitchParameter]
        # Flag to disable auto upgrade of arc agents.
        ${DisableAutoUpgrade},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Path')]
        [System.String]
        # Override the default container log path to enable fluent-bit logging.
        ${ContainerLogPath},

        [Parameter(HelpMessage = "Path to the kube config file")]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # Path to the kube config file
        ${KubeConfig},

        [Parameter(HelpMessage = "Kubconfig context from current machine")]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # Kubconfig context from current machine
        ${KubeContext},

        [Parameter(Mandatory)]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # The geo-location where the resource lives
        ${Location},

        [Parameter()]
        [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.AzureHybridBenefit])]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.AzureHybridBenefit]
        # Indicates whether Azure Hybrid Benefit is opted in
        ${AzureHybridBenefit},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # The Kubernetes distribution running on this connected cluster.
        ${Distribution},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # The Kubernetes distribution version on this connected cluster.
        ${DistributionVersion},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # The infrastructure on which the Kubernetes cluster represented by this connected cluster is running on.
        ${Infrastructure},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # The resource id of the private link scope this connected cluster is assigned to, if any.
        ${PrivateLinkScopeResourceId},

        [Parameter()]
        [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.PrivateLinkState])]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.PrivateLinkState]
        # Property which describes the state of private link on a connected cluster resource.
        ${PrivateLinkState},

        [Parameter()]
        [ArgumentCompleter([Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.ProvisioningState])]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.ProvisioningState]
        # Provisioning state of the connected cluster resource.
        ${ProvisioningState},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Runtime.Info(PossibleTypes = ([Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Models.Api20.ITrackedResourceTags]))]
        [System.Collections.Hashtable]
        # Resource tags.
        ${Tag},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # OID of 'custom-locations' app.
        ${CustomLocationsOid},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.Management.Automation.SwitchParameter]
        # Whether to enable oidc issuer for workload identity integration.
        ${OidcIssuerProfileEnabled},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.String]
        # The issuer url for public cloud clusters - AKS, EKS, GKE - used for the workload identity feature.
        ${OidcIssuerProfileSelfHostedIssuerUrl},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Body')]
        [System.Management.Automation.SwitchParameter]
        # Whether to enable or disable the workload identity Webhook
        ${WorkloadIdentityEnabled},

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        # Accept EULA of ConnectedKubernetes, legal term will pop up without this parameter provided
        ${AcceptEULA},

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

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

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

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

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

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

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

        [Parameter(DontShow)]
        [ValidateNotNull()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Runtime')]
        [System.Management.Automation.PSCredential]
        # The credential of the proxy server for host os to use
        ${ProxyCredential},

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

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Runtime')]
        [System.Collections.Hashtable]
        # Arc Agentry System Configuration (hash table of hash tables).
        ${ConfigurationSetting},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Runtime')]
        [System.Collections.Hashtable]
        # Arc Agentry System Protected Configuration (hash table of hash tables).
        ${ConfigurationProtectedSetting},

        [Parameter()]
        [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Category('Runtime')]
        [System.String]
        # Arc Gateway resource Id
        ${GatewayResourceId}
    )

    process {
        $ProtectedSettingsPlaceholderValue = "redacted"

        Write-Debug "Checking if Azure Hybrid Benefit is opted in and processing the EULA."
        . "$PSScriptRoot/helpers/HelmHelper.ps1"
        . "$PSScriptRoot/helpers/ConfigDPHelper.ps1"
        . "$PSScriptRoot/helpers/AzCloudMetadataHelper.ps1"
        . "$PSScriptRoot/helpers/UtilsHelper.ps1"

        # Configuration is structured as a hashtable of hashtables where the final
        # values must be strings. Check this!
        Test-ConfigurationSyntax -name 'ConfigurationSetting'
        Test-ConfigurationSyntax -configuration 'ConfigurationProtectedSetting'

        if ($AzureHybridBenefit) {
            if (!$AcceptEULA) {
                $legalTermPath = Join-Path $PSScriptRoot -ChildPath "LegalTerm.txt"
                try {
                    $legalTerm = (Get-Content -Path $legalTermPath) -join "`r`n"
                }
                catch {
                    Write-Error "Get legal term failed."
                    throw
                }
                $confirmation = Read-Host $legalTerm"`n[Y] Yes [N] No (default is `"N`")"
                if ($confirmation -ine "Y") {
                    Return
                }
            }
        }
        Write-Debug "Removed the AcceptEULA parameter after processing."
        $null = $PSBoundParameters.Remove('AcceptEULA')
        Write-Debug "Determining the kube config file path."

        if ($PSBoundParameters.ContainsKey("KubeConfig")) {
            $Null = $PSBoundParameters.Remove('KubeConfig')
        }
        elseif (Test-Path Env:KUBECONFIG) {
            $KubeConfig = Get-ChildItem -Path $Env:KUBECONFIG
        }
        elseif (Test-Path Env:Home) {
            $KubeConfig = Join-Path -Path $Env:Home -ChildPath '.kube' | Join-Path -ChildPath 'config'
        }
        else {
            $KubeConfig = Join-Path -Path $Home -ChildPath '.kube' | Join-Path -ChildPath 'config'
        }
        if (-not (Test-Path $KubeConfig)) {
            Write-Error 'Cannot find the kube-config. Please make sure that you have the kube-config on your machine.'
            return
        }
        Write-Debug "Setting the kube context."
        if ($PSBoundParameters.ContainsKey("KubeContext")) {
            $Null = $PSBoundParameters.Remove('KubeContext')
        }
        if (($null -eq $KubeContext) -or ($KubeContext -eq '')) {
            $KubeContext = kubectl config current-context
        }
        Write-Debug "Checking whether Gateway is enabled"

        # If GatewayResourceId is provided then set the gateway as enabled.
        $PSBoundParameters.Add('GatewayEnabled', -not [String]::IsNullOrEmpty($GatewayResourceId))

        $CommonPSBoundParameters = @{}
        if ($PSBoundParameters.ContainsKey('HttpPipelineAppend')) {
            $CommonPSBoundParameters['HttpPipelineAppend'] = $HttpPipelineAppend
        }
        if ($PSBoundParameters.ContainsKey('HttpPipelinePrepend')) {
            $CommonPSBoundParameters['HttpPipelinePrepend'] = $HttpPipelinePrepend
        }
        if ($PSBoundParameters.ContainsKey('SubscriptionId')) {
            $CommonPSBoundParameters['SubscriptionId'] = $SubscriptionId
        }
        if ($PSBoundParameters.ContainsKey('PrivateLinkState') -and ($null -ne $CustomLocationsOid) -and ($CustomLocationsOid -ne '')) {
            Write-Warning "The features 'cluster-connect' and 'custom-locations' cannot be enabled for a private link enabled connected cluster."
            $CustomLocationsOid = $null
        }
        if ($PSBoundParameters.ContainsKey('CustomLocationsOid')) {
            $Null = $PSBoundParameters.Remove('CustomLocationsOid')
        }
        $IdentityType = [Microsoft.Azure.PowerShell.Cmdlets.ConnectedKubernetes.Support.ResourceIdentityType]::SystemAssigned
        $PSBoundParameters.Add('IdentityType', $IdentityType)

        #Region check helm install
        Confirm-HelmVersion `
            -KubeConfig $KubeConfig `
            -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
            -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)


        #EndRegion
        $helmClientLocation = 'helm'

        #Region get release namespace
        $ReleaseNamespaces = Get-HelmReleaseNamespaces -KubeConfig $KubeConfig -KubeContext $KubeContext
        $ReleaseNamespace = $ReleaseNamespaces['ReleaseNamespace']
        $ReleaseInstallNamespace = $ReleaseNamespaces['ReleaseInstallNamespace']

        #Endregion

        if (-not ([string]::IsNullOrEmpty($ReleaseNamespace))) {
            $Configmap = kubectl get configmap --namespace azure-arc azure-clusterconfig -o json --kubeconfig $KubeConfig | ConvertFrom-Json
            $ConfigmapRgName = $Configmap.data.AZURE_RESOURCE_GROUP
            $ConfigmapClusterName = $Configmap.data.AZURE_RESOURCE_NAME
            try {
                $ExistConnectedKubernetes = Get-AzConnectedKubernetes `
                    -ResourceGroupName $ConfigmapRgName `
                    -ClusterName $ConfigmapClusterName `
                    @CommonPSBoundParameters `
                    -ErrorAction 'silentlycontinue'

                if (($ResourceGroupName -eq $ConfigmapRgName) -and ($ClusterName -eq $ConfigmapClusterName)) {
                    # This performs a re-PUT of an existing connected cluster which should really be done using
                    # a Set-AzConnectedKubernetes cmdlet!

                    if ($PSCmdlet.ShouldProcess($ClusterName, "Updating existing ConnectedKubernetes cluster")) {
                        $PSBoundParameters.Add('AgentPublicKeyCertificate', $ExistConnectedKubernetes.AgentPublicKeyCertificate)
                        return Az.ConnectedKubernetes.internal\New-AzConnectedKubernetes @PSBoundParameters
                    }
                    else {
                        # We are done here if doing a What-if.
                        return
                    }
                }
                else {
                    # We have a cluster with the same Kubernetes settings but already associated via a different RG - error!
                    Write-Error "The kubernetes cluster you are trying to onboard is already onboarded to the resource group '${ConfigmapRgName}' with resource name '${ConfigmapClusterName}'."
                }
                return
            }
            catch {
                # This is attempting to delete Azure Arc resources that are orphaned.
                # We are catching and ignoring any messages here.
                $null = helm delete azure-arc --ignore-not-found --namespace $ReleaseNamespace --kubeconfig $KubeConfig --kube-context $KubeContext
            }
        }

        $RegistryPath = Set-HelmRepositoryAndModules -KubeConfig $KubeConfig -KubeContext $KubeContext -Location $Location -ProxyCert $ProxyCert -DisableAutoUpgrade $DisableAutoUpgrade -ContainerLogPath $ContainerLogPath -CustomLocationsOid $CustomLocationsOid

        # Region create RSA keys
        Write-Debug "Generating RSA keys for secure communication."
        $RSA = [System.Security.Cryptography.RSA]::Create(4096)
        if ($PSVersionTable.PSVersion.Major -eq 5) {
            try {
                . "$PSScriptRoot/helpers/RSAHelper.ps1"
                $AgentPublicKey = ExportRSAPublicKeyBase64($RSA) `
                    -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
                    -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)
                $AgentPrivateKey = ExportRSAPrivateKeyBase64($RSA) `
                    -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
                    -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

                $AgentPrivateKey = "-----BEGIN RSA PRIVATE KEY-----`n" + $AgentPrivateKey + "`n-----END RSA PRIVATE KEY-----"
            }
            catch {
                throw "Unable to generate RSA keys"
            }
        }
        else {
            $AgentPublicKey = [System.Convert]::ToBase64String($RSA.ExportRSAPublicKey())
            $AgentPrivateKey = "-----BEGIN RSA PRIVATE KEY-----`n" + [System.Convert]::ToBase64String($RSA.ExportRSAPrivateKey()) + "`n-----END RSA PRIVATE KEY-----"
        }
        #Endregion
        Write-Debug "Processing Helm chart installation options."

        $options = ""

        if ($DisableAutoUpgrade) {
            # $options += " --set systemDefaultValues.azureArcAgents.autoUpdate=false"
            $Null = $PSBoundParameters.Remove('DisableAutoUpgrade')
            $PSBoundParameters.Add('ArcAgentProfileAgentAutoUpgrade', 'Disabled')
        }
        if (-not ([string]::IsNullOrEmpty($ContainerLogPath))) {
            $options += " --set systemDefaultValues.fluent-bit.containerLogPath=$ContainerLogPath"
            $Null = $PSBoundParameters.Remove('ContainerLogPath')
        }
        if (-not ([string]::IsNullOrEmpty($KubeConfig))) {
            $options += " --kubeconfig $KubeConfig"
        }
        if (-not ([string]::IsNullOrEmpty($KubeContext))) {
            $options += " --kube-context $KubeContext"
        }
        if (-not ([string]::IsNullOrEmpty($CustomLocationsOid))) {
            $options += " --set systemDefaultValues.customLocations.oid=$CustomLocationsOid"
            $options += " --set systemDefaultValues.customLocations.enabled=true"
        }

        if (!$NoWait) {
            $options += " --wait --timeout $OnboardingTimeout"
            $options += "s"
        }

        if ($PSBoundParameters.ContainsKey('OnboardingTimeout')) {
            $PSBoundParameters.Remove('OnboardingTimeout')
        }

        if ((-not ([string]::IsNullOrEmpty($Proxy))) -and (-not $PSBoundParameters.ContainsKey('ProxyCredential'))) {
            if (-not ([string]::IsNullOrEmpty($Proxy.UserInfo))) {
                try {
                    $userInfo = $Proxy.UserInfo -Split ':'
                    $pass = ConvertTo-SecureString $userInfo[1] -AsPlainText -Force
                    $ProxyCredential = New-Object System.Management.Automation.PSCredential ($userInfo[0] , $pass)
                    $PSBoundParameters.Add('ProxyCredential', $ProxyCredential)
                }
                catch {
                    throw "Please set ProxyCredential or provide username and password in the Proxy parameter"
                }
            }
            else {
                Write-Warning "If the proxy is a private proxy, pass ProxyCredential parameter or provide username and password in the Proxy parameter"
            }
        }

        #Region Deal with configuration settings and protected settings

        if (-not $ConfigurationSetting) {
            $ConfigurationSetting = @{}
        }
        if (-not $ConfigurationProtectedSetting) {
            $ConfigurationProtectedSetting = @{}
        }
        if (-not $ConfigurationProtectedSetting.ContainsKey('proxy')) {
            $ConfigurationProtectedSetting['proxy'] = @{}
        }

        if (-not ([string]::IsNullOrEmpty($HttpProxy))) {
            $HttpProxyStr = $HttpProxy.ToString()
            $HttpProxyStr = $HttpProxyStr -replace ',', '\,'
            $HttpProxyStr = $HttpProxyStr -replace '/', '\/'
            $ConfigurationProtectedSetting["proxy"]["http_proxy"] = $HttpProxyStr
            $Null = $PSBoundParameters.Remove('HttpProxy')
        }
        if (-not ([string]::IsNullOrEmpty($HttpsProxy))) {
            $HttpsProxyStr = $HttpsProxy.ToString()
            $HttpsProxyStr = $HttpsProxyStr -replace ',', '\,'
            $HttpsProxyStr = $HttpsProxyStr -replace '/', '\/'
            $ConfigurationProtectedSetting["proxy"]["https_proxy"] = $HttpsProxyStr
            $Null = $PSBoundParameters.Remove('HttpsProxy')
        }
        if (-not ([string]::IsNullOrEmpty($NoProxy))) {
            $NoProxy = $NoProxy -replace ',', '\,'
            $NoProxy = $NoProxy -replace '/', '\/'
            $ConfigurationProtectedSetting["proxy"]["no_proxy"] = $NoProxy
            $Null = $PSBoundParameters.Remove('NoProxy')
        }

        try {
            if ((-not ([string]::IsNullOrEmpty($ProxyCert))) -and (Test-Path $ProxyCert)) {
                $ConfigurationProtectedSetting["proxy"]["proxy_cert"] = $ProxyCert
            }
        }
        catch {
            throw "Unable to find ProxyCert from file path"
        }

        $RedactedProtectedConfiguration = @{}
        # Duplicate the protected settings into the settings.
        foreach ($feature in $ConfigurationProtectedSetting.Keys) {
            if (-not $RedactedProtectedConfiguration.ContainsKey($feature)) {
                $RedactedProtectedConfiguration[$feature] = @{}
            }
            foreach ($setting in $ConfigurationProtectedSetting[$feature].Keys) {
                $RedactedProtectedConfiguration[$feature][$setting] = "${ProtectedSettingsPlaceholderValue}:${feature}:${setting}"
            }
        }

        #Endregion

        # A lot of what follows relies on knowing the cloud we are using and the
        # various endpoints so get that information now.
        $cloudMetadata = Get-AzCloudMetadata `
            -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
            -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

        # Perform DP health check
        $configDpinfo = Get-ConfigDPEndpoint `
            -location $Location `
            -Cloud $cloudMetadata `
            -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
            -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

        $configDPEndpoint = $configDpInfo.configDPEndpoint

        # If the health check fails (not 200 response), an exception is thrown
        # so we can ignore the output.
        $null = Invoke-ConfigDPHealthCheck `
            -configDPEndpoint $configDPEndpoint `
            -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
            -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

        # This call does the "pure ARM" update of the ARM objects.
        Write-Debug "Writing Connected Kubernetes ARM objects."

        # We sometimes see the AgentPublicKeyCertificate present with value $null.
        # If this is the case, update rather than adding.
        if ($PSBoundParameters.ContainsKey('AgentPublicKeyCertificate')) {
            $PSBoundParameters['AgentPublicKeyCertificate'] = $AgentPublicKey
        }
        else {
            $PSBoundParameters.Add('AgentPublicKeyCertificate', $AgentPublicKey)
        }

        # Process the Arc agentry settings and protected settings
        # Create any empty array of IArcAgentryConfigurations.
        # Shortened name to avoid class with type name.
        #
        # Arc Configuration "Name" Mapping
        # ================================
        # The Swagger naming of Arc configuration does NOT match the names that
        # will be used in the final helm values file. Instead there needs to be
        # an explicit mapping which is done by the ConfigDP.
        #
        # We do not trust the ConfigDP's security though so we do not pass
        # protected configuration values to the ConfigDP. Instead we hold them
        # in a local hashtable and pass the hash-tabe indexing to the ConfigDP
        # as the Arc configuration protected setting value.
        #
        # One return, the ConfigDP gives us the correct "helm" name for the
        # setting, with the indexing value, and we then replace this index value
        # with the real value.
        #
        # This ensures that when a new feature is implemented, only the ConfigDP
        # needs to change and not the Powershell script (or az CLI).
        #
        # Do not send protected settings to CCRP
        $arcAgentryConfigs = ConvertTo-ArcAgentryConfiguration `
            -ConfigurationSetting $ConfigurationSetting `
            -RedactedProtectedConfiguration @{} `
            -CCRP $true `
            -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
            -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

        # It is possible to set an empty value for these parameters and then
        # the code above gets skipped but we still need to remove the empty
        # values from $PSBoundParameters.
        if ($PSBoundParameters.ContainsKey('ConfigurationSetting')) {
            $PSBoundParameters.Remove('ConfigurationSetting')
        }
        if ($PSBoundParameters.ContainsKey('ConfigurationProtectedSetting')) {
            $PSBoundParameters.Remove('ConfigurationProtectedSetting')
        }

        $PSBoundParameters.Add('ArcAgentryConfiguration', $arcAgentryConfigs)

        Write-Output "Creating 'Kubernetes - Azure Arc' object in Azure"
        Write-Debug "PSBoundParameters: $PSBoundParameters"
        $Response = Az.ConnectedKubernetes.internal\New-AzConnectedKubernetes @PSBoundParameters

        if ((-not $WhatIfPreference) -and (-not $Response)) {
            Write-Error "Failed to create the 'Kubernetes - Azure Arc' resource."
            return
        }

        $arcAgentryConfigs = ConvertTo-ArcAgentryConfiguration `
            -ConfigurationSetting $ConfigurationSetting `
            -RedactedProtectedConfiguration $RedactedProtectedConfiguration `
            -CCRP $false `
            -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
            -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

        # Convert the $Response object into a nested hashtable.

        Write-Debug "PUT response: $Response"
        $Response = ConvertFrom-Json "$Response" -AsHashTable -Depth 10

        # What-If processing does not create a full response so we might have
        # to create a minimal one.
        if (-not $Response) {
            $Response = @{}
        }
        if (-not $Response.ContainsKey('properties')) {
            $Response['properties'] = @{}
        }
        $Response['properties']['arcAgentryConfigurations'] = $arcAgentryConfigs

        # Retrieving Helm chart OCI (Open Container Initiative) Artifact location
        Write-Debug "Retrieving Helm chart OCI (Open Container Initiative) Artifact location."
        Write-Debug "PUT response: $Response"
        $ResponseStr = $Response | ConvertTo-Json -Depth 10
        Write-Debug "PUT response: $ResponseStr"
        
        if ($PSCmdlet.ShouldProcess("configDP", "request Helm values")) {
            $helmValuesDp = Get-HelmValuesFromConfigDP `
                -configDPEndpoint $configDPEndpoint `
                -releaseTrain $ReleaseTrain `
                -requestBody $ResponseStr `
                -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
                -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)

            Write-Debug "helmValuesDp: $helmValuesDp"
            Write-Debug "OCI Artifact location: ${helmValuesDp.repositoryPath}."

            $registryPath = if ($env:HELMREGISTRY) { $env:HELMREGISTRY } else { $helmValuesDp.repositoryPath }
            Write-Debug "RegistryPath: ${registryPath}"

            $helmValuesContent = $helmValuesDp.helmValuesContent
            Write-Debug "Helm values: ${helmValuesContent}."

            # We now need to process the helm values, add all the settings and
            # protected settings to the helm options and replace any placeholders
            # with the values that we have stored in the protected settings
            # hashtable.
            $optionsFromDp = ""
            foreach ($field in $helmValuesContent.PSObject.Properties) {
                if ($field.Value.StartsWith($ProtectedSettingsPlaceholderValue)) {
                    $parsedValue = $field.Value.Split(":")
                    # "${ProtectedSettingsPlaceholderValue}:${feature}:${setting}"
                    $field.Value = $ConfigurationProtectedSetting[$parsedValue[1]][$parsedValue[2]]
                }
                if ($field.Name -eq "global.proxyCert") {
                    $optionsFromDp += " --set-file $($field.Name)=$($field.Value)"
                }
                $optionsFromDp += " --set $($field.Name)=$($field.Value)"
            }
            # In helm, priority is given to new values, so we append $options contains user input last
            $options = $optionsFromDp + $options
        }

        # Get helm chart path (within the OCI registry).
        if ($PSCmdlet.ShouldProcess("configDP", "request Helm chart")) {
            $chartPath = Get-HelmChartPath `
                -registryPath $registryPath `
                -kubeConfig $KubeConfig `
                -kubeContext $KubeContext `
                -helmClientLocation $HelmClientLocation `
                -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) `
                -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent -eq $true)
            if (Test-Path Env:HELMCHART) {
                $ChartPath = Get-ChildItem -Path $Env:HELMCHART
            }
        }

        $TenantId = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext.Tenant.Id
        Write-Debug $options -ErrorAction Continue
        if ($DebugPreference -eq "Continue") {
            $options += " --debug"
        }
        if ($PSCmdlet.ShouldProcess($ClusterName, "Update Kubernetes cluster with Azure Arc")) {
            Write-Output "Executing helm upgrade command, this can take a few minutes...."
            try {
                helm upgrade `
                    --install azure-arc `
                    $ChartPath `
                    --namespace $ReleaseInstallNamespace `
                    --create-namespace `
                    --set global.subscriptionId=$SubscriptionId `
                    --set global.resourceGroupName=$ResourceGroupName `
                    --set global.resourceName=$ClusterName `
                    --set global.tenantId=$TenantId `
                    --set global.location=$Location `
                    --set global.onboardingPrivateKey=$AgentPrivateKey `
                    --set systemDefaultValues.spnOnboarding=false `
                    --set global.azureEnvironment=AZUREPUBLICCLOUD `
                    --set systemDefaultValues.clusterconnect-agent.enabled=true `
                    --set global.kubernetesDistro=$Distribution `
                    --set global.kubernetesInfra=$Infrastructure (-split $options)

                # $helmDebug
            }
            catch {
                throw "Unable to install helm chart at $ChartPath"
            }
        }

        if ($PSCmdlet.ShouldProcess($ClusterName, "Check agent state of the connected cluster")) {
            if ($PSBoundParameters.ContainsKey('OidcIssuerProfileEnabled') -or $PSBoundParameters.ContainsKey('WorkloadIdentityEnabled') ) {
                $ExistConnectedKubernetes = Get-AzConnectedKubernetes -ResourceGroupName $ResourceGroupName -ClusterName $ClusterName @CommonPSBoundParameters
    
                Write-Output "Cluster configuration is in progress..."
                $timeout = [datetime]::Now.AddMinutes(60)
    
                while (($ExistConnectedKubernetes.ArcAgentProfileAgentState -ne "Succeeded") -and ($ExistConnectedKubernetes.ArcAgentProfileAgentState -ne "Failed") -and ([datetime]::Now -lt $timeout)) {
                    Start-Sleep -Seconds 30
                    $ExistConnectedKubernetes = Get-AzConnectedKubernetes -ResourceGroupName $ResourceGroupName -ClusterName $ClusterName @CommonPSBoundParameters
                }
    
                if ($ExistConnectedKubernetes.ArcAgentProfileAgentState -eq "Succeeded") {
                    Write-Output "Cluster configuration succeeded."
                }
                elseif ($ExistConnectedKubernetes.ArcAgentProfileAgentState -eq "Failed") {
                    Write-Error "Cluster configuration failed."
                }
                else {
                    Write-Error "Cluster configuration timed out after 60 minutes."
                }      
            }
        }
        Return $Response
    }
}

# SIG # Begin signature block
# MIIoPAYJKoZIhvcNAQcCoIIoLTCCKCkCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAy4vovt1wxfiXQ
# fJg9DFzlWgni3CHhRRVb6Te+3bKKNKCCDYUwggYDMIID66ADAgECAhMzAAAEA73V
# lV0POxitAAAAAAQDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTEzWhcNMjUwOTExMjAxMTEzWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCfdGddwIOnbRYUyg03O3iz19XXZPmuhEmW/5uyEN+8mgxl+HJGeLGBR8YButGV
# LVK38RxcVcPYyFGQXcKcxgih4w4y4zJi3GvawLYHlsNExQwz+v0jgY/aejBS2EJY
# oUhLVE+UzRihV8ooxoftsmKLb2xb7BoFS6UAo3Zz4afnOdqI7FGoi7g4vx/0MIdi
# kwTn5N56TdIv3mwfkZCFmrsKpN0zR8HD8WYsvH3xKkG7u/xdqmhPPqMmnI2jOFw/
# /n2aL8W7i1Pasja8PnRXH/QaVH0M1nanL+LI9TsMb/enWfXOW65Gne5cqMN9Uofv
# ENtdwwEmJ3bZrcI9u4LZAkujAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU6m4qAkpz4641iK2irF8eWsSBcBkw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMjkyNjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AFFo/6E4LX51IqFuoKvUsi80QytGI5ASQ9zsPpBa0z78hutiJd6w154JkcIx/f7r
# EBK4NhD4DIFNfRiVdI7EacEs7OAS6QHF7Nt+eFRNOTtgHb9PExRy4EI/jnMwzQJV
# NokTxu2WgHr/fBsWs6G9AcIgvHjWNN3qRSrhsgEdqHc0bRDUf8UILAdEZOMBvKLC
# rmf+kJPEvPldgK7hFO/L9kmcVe67BnKejDKO73Sa56AJOhM7CkeATrJFxO9GLXos
# oKvrwBvynxAg18W+pagTAkJefzneuWSmniTurPCUE2JnvW7DalvONDOtG01sIVAB
# +ahO2wcUPa2Zm9AiDVBWTMz9XUoKMcvngi2oqbsDLhbK+pYrRUgRpNt0y1sxZsXO
# raGRF8lM2cWvtEkV5UL+TQM1ppv5unDHkW8JS+QnfPbB8dZVRyRmMQ4aY/tx5x5+
# sX6semJ//FbiclSMxSI+zINu1jYerdUwuCi+P6p7SmQmClhDM+6Q+btE2FtpsU0W
# +r6RdYFf/P+nK6j2otl9Nvr3tWLu+WXmz8MGM+18ynJ+lYbSmFWcAj7SYziAfT0s
# IwlQRFkyC71tsIZUhBHtxPliGUu362lIO0Lpe0DOrg8lspnEWOkHnCT5JEnWCbzu
# iVt8RX1IV07uIveNZuOBWLVCzWJjEGa+HhaEtavjy6i7MIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAQDvdWVXQ87GK0AAAAA
# BAMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEICqZ
# 61JNoIKZYx0a2cGLHqUVf1kY46IbMt/7ipfZwyibMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAZPS53M06iAtRNCiEdngK/IKEV+nq8uEuuEe1
# lMA6A9hfD9CsfCWSeAfwzmaVJ0BkAlOU8LbkPGI650GClF9ooywWNJ9MGGVyyrBb
# ieyMUMTzn4PWYPbGuYzNKcrCQFbv8AuBoZjhVmiky5Q3/2zma4naz8FlXZ5Oa/9D
# CEcLsQRivZPcQ9HnEtlPS/kluqCRgZxKooT0Y5/GnzSBqQj3TE81v8kZF0yt7mnt
# V6lt11kc2mEke7K7Lkek2QYQNB9zApq4rvQG6I6qwJKzMbPdNRp07Evhg1gPJy6q
# Tlhj2zCnWVdtXm1LAIbf3clK7MEVox3iFPhJW/8EeNLgEvJ296GCF5cwgheTBgor
# BgEEAYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCCVez94CXevdzCd7ABGzLSf1iDt+JF60E6P
# 7E0oClNemQIGZwf/s/oCGBMyMDI0MTAxNzA3NTQxOC4yNDNaMASAAgH0oIHRpIHO
# MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk
# IFRTUyBFU046OTYwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAe+JP1ahWMyo2gAB
# AAAB7zANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDAeFw0yMzEyMDYxODQ1NDhaFw0yNTAzMDUxODQ1NDhaMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjC1jinwzgHwhOakZqy17o
# E4BIBKsm5kX4DUmCBWI0lFVpEiK5mZ2Kh59soL4ns52phFMQYGG5kypCipungwP9
# Nob4VGVE6aoMo5hZ9NytXR5ZRgb9Z8NR6EmLKICRhD4sojPMg/RnGRTcdf7/TYvy
# M10jLjmLyKEegMHfvIwPmM+AP7hzQLfExDdqCJ2u64Gd5XlnrFOku5U9jLOKk1y7
# 0c+Twt04/RLqruv1fGP8LmYmtHvrB4TcBsADXSmcFjh0VgQkX4zXFwqnIG8rgY+z
# DqJYQNZP8O1Yo4kSckHT43XC0oM40ye2+9l/rTYiDFM3nlZe2jhtOkGCO6GqiTp5
# 0xI9ITpJXi0vEek8AejT4PKMEO2bPxU63p63uZbjdN5L+lgIcCNMCNI0SIopS4ga
# VR4Sy/IoDv1vDWpe+I28/Ky8jWTeed0O3HxPJMZqX4QB3I6DnwZrHiKn6oE38tgB
# TCCAKvEoYOTg7r2lF0Iubt/3+VPvKtTCUbZPFOG8jZt9q6AFodlvQntiolYIYtqS
# rLyXAQIlXGhZ4gNcv4dv1YAilnbWA9CsnYh+OKEFr/4w4M69lI+yaoZ3L/t/UfXp
# T/+yc7hS/FolcmrGFJTBYlS4nE1cuKblwZ/UOG26SLhDONWXGZDKMJKN53oOLSSk
# 4ldR0HlsbT4heLlWlOElJQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFO1MWqKFwrCb
# trw9P8A63bAVSJzLMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G
# A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs
# BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
# AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQAYGZa3aCDudbk9
# EVdkP8xcQGZuIAIPRx9K1CA7uRzBt80fC0aWkuYYhQMvHHJRHUobSM4Uw3zN7fHE
# N8hhaBDb9NRaGnFWdtHxmJ9eMz6Jpn6KiIyi9U5Og7QCTZMl17n2w4eddq5vtk4r
# RWOVvpiDBGJARKiXWB9u2ix0WH2EMFGHqjIhjWUXhPgR4C6NKFNXHvWvXecJ2WXr
# JnvvQGXAfNJGETJZGpR41nUN3ijfiCSjFDxamGPsy5iYu904Hv9uuSXYd5m0Jxf2
# WNJSXkPGlNhrO27pPxgT111myAR61S3S2hc572zN9yoJEObE98Vy5KEM3ZX53cLe
# fN81F1C9p/cAKkE6u9V6ryyl/qSgxu1UqeOZCtG/iaHSKMoxM7Mq4SMFsPT/8ieO
# dwClYpcw0CjZe5KBx2xLa4B1neFib8J8/gSosjMdF3nHiyHx1YedZDtxSSgegeJs
# i0fbUgdzsVMJYvqVw52WqQNu0GRC79ZuVreUVKdCJmUMBHBpTp6VFopL0Jf4Srgg
# +zRD9iwbc9uZrn+89odpInbznYrnPKHiO26qe1ekNwl/d7ro2ItP/lghz0DoD7kE
# GeikKJWHdto7eVJoJhkrUcanTuUH08g+NYwG6S+PjBSB/NyNF6bHa/xR+ceAYhcj
# x0iBiv90Mn0JiGfnA2/hLj5evhTcAjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb
# SZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj
# YXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy
# NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXI
# yjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjo
# YH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1y
# aa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v
# 3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pG
# ve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viS
# kR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYr
# bqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM
# jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSL
# W6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AF
# emzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIu
# rQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIE
# FgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWn
# G1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEW
# M2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5
# Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi
# AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV
# 9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx
# MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2
# LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv
# 6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZn
# OlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1
# bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4
# rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU
# 6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDF
# NLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/
# HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdU
# CbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi
# excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTm
# dHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZq
# ELQdVTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp
# Y2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjk2MDAtMDVF
# MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMK
# AQEwBwYFKw4DAhoDFQBLcI81gxbea1Ex2mFbXx7ck+0g/6CBgzCBgKR+MHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6rsPXDAi
# GA8yMDI0MTAxNzA0MjE0OFoYDzIwMjQxMDE4MDQyMTQ4WjB3MD0GCisGAQQBhFkK
# BAExLzAtMAoCBQDquw9cAgEAMAoCAQACAhKDAgH/MAcCAQACAhPCMAoCBQDqvGDc
# AgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSCh
# CjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBADAyIv2U6Cq/fHOtH+gvaeHD
# hYbMR0DDp2iZvcwgqx1WxUU/aAS4reQHpqUw0cCVtl2cc35R2MrdKTaZLtpo4/oT
# GJlRBtpa6jbr4cI5FneTHHGs9gPR+ujHI7EGwfGZH8hbHABoe7rsegXr+rfz4BDU
# FKHl1mzh988npLXwf2iC7456dct2AJyY7QeG5rzWJyPy7j6WJy5lMxDxA8u2h5iA
# G92L34YbZnRHHgWnOsfKzj1DhwNkJssFZvVhNWzKaBkEarh5YpaqS4JOUWxMZt2M
# 0vfY2OtuSmcDqF5NBvbt9W2iH3m9zy2JwbMXUZW2dkpnFI/VVCXsoKYaNQfnKdwx
# ggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# Ae+JP1ahWMyo2gABAAAB7zANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCC8T7tycX2KXeJOYOFgCB1R
# 2pGDLzKZCNm3WRYzy7JvWjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIPBh
# KEW4Fo3wUz09NQx2a0DbcdsX8jovM5LizHmnyX+jMIGYMIGApH4wfDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHviT9WoVjMqNoAAQAAAe8wIgQgqx6Z
# yCJ8iMdOeGoIiC7NRUduAqCUNrkDstfgGmaOIBwwDQYJKoZIhvcNAQELBQAEggIA
# TVcxMsOXDxw2R7k6jvmAkkoj1K9fdmPYympdDSLufz9taexloBN1Uat4eQS7CdAe
# ffIPAA1oi/S3iedDMfEP6sH+AIe/oY4eTdf1MD8eX+BqeIKQwYJx8eyh6//qTT+n
# 8FIW/nhj9mgLgDnuQury+wmFCm0GB33yJesYUXEFi/FiOD5dKkrm5npuucmMeiRG
# aBtS8t3V8VvE2NflnFKCUDq56UnW5OGIeC/l3vsJS94/RGc8iRm25voo7QEhtiTU
# VIEibZvWD5ME9X0eMd4kpoanIAf0HfF2uhyE6NE2sKYV8nheUbu6q5CMdZrkakMD
# iXgKiLjQpNP4kw7b78p2Afh1KzKUFPAE0fxXBIlrE4rVDFo8794OXGiUlUCnl4Lt
# ymXu2B/P0Bkqk/t2rH1k99ancyucfWXkEAxaLKYS/0x7+bWLvPczuMGTmG2zCuy9
# kJ5cOhp0wr18LQx88NKMKH4f3XkgB3yGIeofymM4bVCt3x2xBhb/XmqY8dldvPF7
# BO5FxlD0npbg3pdWYPNp2boiPCN3Ex6UhBs2kt4azaYSE2kQtpgcPIfHvj1z/xTF
# GVs4KNy44b0MH/oh/Etf1TUjl+h254H5In35+EJw/zv5IcY/qRL2Jkh4DHFgd1dI
# v6UmwDaHk6LQYAutMOJ3NZGTAf9TsikTufhcftJfSJI=
# SIG # End signature block