Private/log-analytics.ps1

function GetLogAnalyticsWorkspace ($ResourceGroup, $WorkspaceId) {
    # Try to find by workspace id
    if($null -ne $WorkspaceId) {
        $workspaces = Invoke-Az @("graph", "query", "-q", "Resources | where type == 'microsoft.operationalinsights/workspaces' and properties.customerId == '$WorkspaceId' | project name, workspaceId = properties.customerId, location") | Convert-LinesToObject
    }

    # Try to find by resource group
    if($null -eq $workspaces -or $workspaces.count -eq 0) {
        $workspaces = Invoke-Az @("graph", "query", "-q", "Resources | where type == 'microsoft.operationalinsights/workspaces' and resourceGroup == '$ResourceGroup' | project name, workspaceId = properties.customerId, location") | Convert-LinesToObject
    }

    if($workspaces.count -eq 1) {
        Write-Information "Found log analytics workspace $($workspaces.data[0].name)"
        return $workspaces.data[0]
    } elseif($workspaces.count -gt 1) {
        Write-Information "Found log analytics workspaces:"
        $workspaces.data | ForEach-Object { Write-Information $_.name }
        $potentialWorkspaceName = Read-Host "We have found more than one existing log analytics workspace in the resource group $ResourceGroup. Please hit enter now if you still want to create a workspace or enter the workspace you would like to use, and then hit enter"
        if(!$potentialWorkspaceName) {
            Write-Information "User selected to create a log analytics workspace"
            return $null
        } else {
            $potentialWorkspace = $workspaces.data | Where-Object { $_.name -eq $potentialWorkspaceName }
            if($null -eq $potentialWorkspace) {
                Write-Error "We couldn't find a log analytics workspace with name $potentialWorkspaceName in resource group $ResourceGroup. Please try to re-run the script"
                throw "We couldn't find a log analytics workspace with name $potentialWorkspaceName in resource group $ResourceGroup. Please try to re-run the script"
            } else {
                return $potentialWorkspace
            }
        }
    }
    else {
        Write-Warning "Unable to determine the log analytics workspace"
        return $null
    }
}

function RemoveDataCollectorAPISettings ($ResourceGroup, $AppServiceName) {
    # Keep AzureOfferingDomain because it is used by the Log Ingestion API target as well
    $isAppServiceLinux = IsAppServiceLinux -AppServiceName $AppServiceName -ResourceGroup $ResourceGroup
    if($isAppServiceLinux) {
        $WorkspaceIdVariable = "AppConfig__LoggingConfig__WorkspaceId"
        $SharedKeyVariable = "AppConfig__LoggingConfig__SharedKey"
    } else {
        $WorkspaceIdVariable = "AppConfig:LoggingConfig:WorkspaceID"
        $SharedKeyVariable = "AppConfig:LoggingConfig:SharedKey"
    }
    $null = Invoke-Az @("webapp", "config", "appsettings", "delete", "--name", $AppServiceName, "--resource-group", $ResourceGroup, "--setting-names", $WorkspaceIdVariable, $SharedKeyVariable)
}

function CreateLogAnalyticsWorkspace($ResourceGroup, $WorkspaceId) {
    $workspaceAccount = GetLogAnalyticsWorkspace -ResourceGroup $ResourceGroup -WorkspaceId $WorkspaceId
        if($null -eq $workspaceAccount) {
            #Create a new workspace
            Write-Information 'Log analytics workspace not found. We will create one now'
            $workspaceName = $ResourceGroup.ToLower() -replace '[^a-z0-9]',''

            # Length between 4-63, Alphanumerics and hyphens, Start and end with alphanumeric.
            if($workspaceName.Length -gt 56) {
                $workspaceName = $workspaceName.Substring(0,56)
            }
            $workspaceName = "log-$($workspaceName)-sc"
            $potentialWorkspaceName = Read-Host "Please hit enter now if you want to create the log analytics workspace with name $workspaceName or enter the name of your choice, and then hit enter"
            if($potentialWorkspaceName) {
                $workspaceName = $potentialWorkspaceName
            }
            $workspaceAccount = Invoke-Az @("monitor", "log-analytics", "workspace", "create", "--resource-group", $ResourceGroup, "--name", $workspaceName, "--only-show-errors") | Convert-LinesToObject
            if($null -eq $workspaceAccount) {
                Write-Error 'Log analytics workspace not found and we are unable to create one. Please check logs for more details before re-running the script'
                throw 'Log analytics workspace not found and we are unable to create one. Please check logs for more details before re-running the script'
            }
            Write-Information "Log analytics workspace $workspaceName created"
            $workspaceAccount = GetLogAnalyticsWorkspace -ResourceGroup $ResourceGroup

            if($null -eq $workspaceAccount) {
                Write-Error 'Log analytics workspace not found after creation'
                throw 'Log analytics workspace not found after creation'
            }
        }
    return $workspaceAccount
}

function GetLogAnalyticsTable($ResourceGroup, $WorkspaceAccount, $SubscriptionId, $tableName) {
    $table = Invoke-Az -MaxRetries 0 -azCommand @("monitor", "log-analytics", "workspace", "table", "show", "--resource-group", $ResourceGroup, "--workspace-name", $($WorkspaceAccount.name), "--name", $tableName) | Convert-LinesToObject
    return $table
}

function ValidateLogAnalyticsTable($ResourceGroup, $WorkspaceAccount, $SubscriptionId) {
    $LogsTableColumnDefinitions = @(
        @{ name="TimeGenerated"; type="datetime" },
        @{ name="Timestamp"; type="string" },
        @{ name="Level"; type="string" },
        @{ name="Message"; type="string" },
        @{ name="Exception"; type="string" },
        @{ name="TenantIdentifier"; type="string" },
        @{ name="RequestUrl"; type="string" },
        @{ name="UserAgent"; type="string" },
        @{ name="LogCategory"; type="string" },
        @{ name="EventId"; type="string" },
        @{ name="Hostname"; type="string" },
        @{ name="WebsiteHostname"; type="string" },
        @{ name="WebsiteSiteName"; type="string" },
        @{ name="WebsiteSlotName"; type="string" },
        @{ name="BaseUrl"; type="string" },
        @{ name="TraceIdentifier"; type="string" }
    )
    # Generate column definition strings for the az command
    $LogsTableColumns = $LogsTableColumnDefinitions | ForEach-Object { "$($_.name)=$($_.type)" }

    # Try to find table
    $tableDetails = GetLogAnalyticsTable -ResourceGroup $ResourceGroup -WorkspaceAccount $WorkspaceAccount -SubscriptionId $SubscriptionId -tableName $LogsTableName
    if ($null -eq $tableDetails) {
        Write-Verbose "Table $LogsTableName does not exist in the workspace $($WorkspaceAccount.name). Creating it now."

        $azCommandToCreateWorkspaceTable = @("monitor", "log-analytics", "workspace", "table", "create", "--resource-group", $ResourceGroup, "--workspace-name", $($WorkspaceAccount.name), "--name", $LogsTableName)
        # We add the columns separately as they would end up as a single string in the command otherwise which would fail
        $azCommandToCreateWorkspaceTable += "--columns"
        $azCommandToCreateWorkspaceTable += $LogsTableColumns
        $null = Invoke-Az $azCommandToCreateWorkspaceTable

        Write-Information "Table $LogsTableName successfully created in the workspace $($WorkspaceAccount.name)"
    } else {
        # We have a table already, check if it is of the correct type
        if ($tableDetails.schema.tableSubType -ne 'DataCollectionRuleBased') {
            Write-Information "Table $LogsTableName exists but is not of type DataCollectionRuleBased. Found subType: $($tableDetails.schema.tableSubType)"
            Write-Information "Migrating to DataCollectionRuleBased table."

            $azCommandToMigrateWorkspaceTable = @("monitor", "log-analytics", "workspace", "table", "migrate", "--resource-group", $ResourceGroup, "--workspace-name", $($WorkspaceAccount.name), "--table-name", $LogsTableName)
            $null = Invoke-Az $azCommandToMigrateWorkspaceTable
        } else {
            Write-Verbose "Table $LogsTableName already exists in the workspace $($WorkspaceAccount.name) with the correct subtype"
        }

        # We have a table of the correct type, check if all columns are present
        $existingColumnNames = ($tableDetails.schema.columns | ForEach-Object { $_.name }) + ($tableDetails.schema.standardColumns | ForEach-Object { $_.name })
        $missingColumns = @()
        foreach ($columnDefinition in $LogsTableColumnDefinitions) {
            if (-not ($existingColumnNames -contains $columnDefinition.name)) {
                $missingColumns += $columnDefinition
            }
        }

        if ($missingColumns.Count -gt 0) {
            Write-Verbose "Could not find $($missingColumns.Count) columns in table $LogsTableName"
            Write-Verbose "Missing columns: $($missingColumns | ForEach-Object { $_.name } | Join-String -Separator ', ')"
            Write-Information "Updating table schema."
            $azCommandToUpdateWorkspaceTableSchema = @("monitor", "log-analytics", "workspace", "table", "update", "--resource-group", $ResourceGroup, "--workspace-name", $($WorkspaceAccount.name), "--name", $LogsTableName)
            # We add the columns separately as they would end up as a single string in the command otherwise which would fail
            $azCommandToUpdateWorkspaceTableSchema += "--columns"
            $azCommandToUpdateWorkspaceTableSchema += $LogsTableColumns
            $null = Invoke-Az $azCommandToUpdateWorkspaceTableSchema
        }
    }
}

function ValidateDCR($ResourceGroup, $WorkspaceAccount, $WorkspaceResourceId) {
    # Define the destinations JSON in a variable
    $destinations = @{
        "logAnalytics" = @(
            @{
                "WorkspaceResourceId" = "$WorkspaceResourceId";
                "name" = "$LogsDestinationName"
            }
        )
    }

    # Define the streamDeclarations JSON in a variable
    $streamDeclarations = @{
        "Custom-SCEPmanLogs" = @{
            "columns" = @(
                @{ "name" = "Timestamp"; "type" = "string" },
                @{ "name" = "Level"; "type" = "string" },
                @{ "name" = "Message"; "type" = "string" },
                @{ "name" = "Exception"; "type" = "string" },
                @{ "name" = "TenantIdentifier"; "type" = "string" },
                @{ "name" = "RequestUrl"; "type" = "string" },
                @{ "name" = "UserAgent"; "type" = "string" },
                @{ "name" = "LogCategory"; "type" = "string" },
                @{ "name" = "EventId"; "type" = "string" },
                @{ "name" = "Hostname"; "type" = "string" },
                @{ "name" = "WebsiteHostname"; "type" = "string" },
                @{ "name" = "WebsiteSiteName"; "type" = "string" },
                @{ "name" = "WebsiteSlotName"; "type" = "string" },
                @{ "name" = "BaseUrl"; "type" = "string" },
                @{ "name" = "TraceIdentifier"; "type" = "string" },
                @{ "name" = "TimeGenerated"; "type" = "Datetime" }
            )
        }
    }

    # Define the dataFlows JSON in a variable
    $dataFlows = @(
        @{
            "streams" = @("Custom-SCEPmanLogs");
            "destinations" = @("$LogsDestinationName");
            "outputStream" = "Custom-$LogsTableName"
        }
    )

    $destinationsJson = HashTable2AzJson -psHashTable $destinations
    $streamDeclarationsJson = HashTable2AzJson -psHashTable $streamDeclarations
    $dataFlowsJson = HashTable2AzJson -psHashTable $dataFlows

    $existingDcrDetails = Invoke-Az -MaxRetries 0 -azCommand @("monitor", "data-collection", "rule", "show", "--resource-group", $ResourceGroup, "--name", $DCRName) | Convert-LinesToObject

    # Check if we need to create the DCR
    if($null -eq $existingDcrDetails) {
        Write-Verbose "Data Collection Rule $DCRName does not exist in the resource group $ResourceGroup. Creating it now."

        # Create DCR
        $newDcrDetails = Invoke-Az @("monitor", "data-collection", "rule", "create", "--resource-group", $ResourceGroup, "--name", $DCRName, "--description", "Data Collection Rule for SCEPman logs", "--stream-declarations", $streamDeclarationsJson, "--destinations", $destinationsJson, "--data-flows", $dataFlowsJson, "--kind", "Direct", "--location", $($WorkspaceAccount.location), "--only-show-errors") | Convert-LinesToObject
        Write-Information "Data Collection Rule $DCRName successfully created"
        return $newDcrDetails
    }

    # Verify existing DCR configuration
    $DCRNeedsUpdate = $false

    # Verify destinations
    if($existingDcrDetails.destinations.logAnalytics.count -eq 0 -or $existingDcrDetails.destinations.logAnalytics[0].name -ne $LogsDestinationName -or $existingDcrDetails.destinations.logAnalytics[0].workspaceResourceId -ne $WorkspaceResourceId) {
        Write-Information "Data Collection Rule $DCRName exists but does not have the correct Log Analytics destination configured. Updating it now."
        $DCRNeedsUpdate = $true
    }

    # Verify streamDeclaration
    if($null -eq $existingDcrDetails.streamDeclarations.'Custom-SCEPmanLogs') {
        Write-Information "Data Collection Rule $DCRName exists but does not have the correct stream declarations configured. Updating it now."
        $DCRNeedsUpdate = $true
    }

    # Verify streamDeclarations columns
    if($null -ne $existingDcrDetails.streamDeclarations.'Custom-SCEPmanLogs') {
        $missingColumns = 0
        $streamDeclarations.'Custom-SCEPmanLogs'.columns | ForEach-Object {
            $columnName = $_.name
            $existingColumn = $existingDcrDetails.streamDeclarations.'Custom-SCEPmanLogs'.columns | Where-Object { $_.name -eq $columnName }
            if($null -eq $existingColumn) {
                $missingColumns++
            }
        }

        if($missingColumns -gt 0) {
            Write-Verbose "Data Collection Rule $DCRName is missing $missingColumns columns in the stream declaration Custom-SCEPmanLogs. Updating it now."
            $DCRNeedsUpdate = $true
        }
    }

    # Verify dataFlows
    if($existingDcrDetails.dataFlows.count -eq 0 -or $existingDcrDetails.dataFlows[0].outputStream -ne $dataFlows[0].outputStream) {
        Write-Verbose "Data Collection Rule $DCRName exists but does not have the correct data flows configured. Updating it now."
        $DCRNeedsUpdate = $true
    }

    if($existingDcrDetails.dataFlows[0].streams -ne $dataFlows[0].streams) {
        Write-Verbose "Data Collection Rule $DCRName exists but does not have the correct data flows streams configured. Updating it now."
        $DCRNeedsUpdate = $true
    }

    if($DCRNeedsUpdate) {
        # Update DCR
        $updatedDcrDetails = Invoke-Az @("monitor", "data-collection", "rule", "update", "--resource-group", $ResourceGroup, "--name", $DCRName, "--description", "Data Collection Rule for SCEPman logs", "--stream-declarations", $streamDeclarationsJson, "--destinations", $destinationsJson, "--data-flows-raw", $dataFlowsJson, "--kind", "Direct", "--only-show-errors") | Convert-LinesToObject
        Write-Information "Data Collection Rule $DCRName successfully updated"
        return $updatedDcrDetails
    } else {
        Write-Information "Data Collection Rule $DCRName already exists with the correct configuration. Skipping the creation/update of the DCR"
        return $existingDcrDetails
    }
}


function GetRuleIdName($SubscriptionId, $ResourceGroup) {
    $ruleIdName = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Insights/dataCollectionRules/$DCRName"
    return $ruleIdName
}

function ConfigureLogIngestionAPIResources($ResourceGroup, $WorkspaceAccount, $SubscriptionId) {
    Write-Information "Installing az monitor control service extension"
    Invoke-Az @("extension", "add", "--name", "monitor-control-service", "--only-show-errors")

    # Create the new table
    ValidateLogAnalyticsTable -ResourceGroup $ResourceGroup -WorkspaceAccount $WorkspaceAccount -SubscriptionId $SubscriptionId

     # Create the DCR
    $workspaceResourceId = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/microsoft.operationalinsights/workspaces/$($WorkspaceAccount.name)"
    $dcrDetails = ValidateDCR -ResourceGroup $ResourceGroup -WorkspaceAccount $WorkspaceAccount -WorkspaceResourceId $workspaceResourceId

    return $dcrDetails
}

function ShouldConfigureLogIngestionAPIInAppService($ExistingConfig, $dcrDetails, $ResourceGroup, $AppServiceName, $WorkspaceAccount) {

    if(!$ResourceGroup -or !$AppServiceName) {
        return $false
    }

    if($null -eq $ExistingConfig -or $null -eq $ExistingConfig.settings) {
        throw "No existing configuration found in the App Service $AppServiceName. Skipping the configuration of Log ingestion API settings"
    }

    $shouldConfigure = $true

    #Check if the Log ingestion API settings(DataCollectionEndpointUri, RuleId) exist; If they do, delete the data collector API settings else configure the Log ingestion API settings and then delete the data collector API settings
    $dataCollectionEndpointUri = $ExistingConfig.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:DataCollectionEndpointUri" }
    $ruleId = $ExistingConfig.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:RuleId" }
    $workspaceId = $ExistingConfig.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:WorkspaceId" }

    $intendedDCEUri = $dcrDetails.endpoints.logsIngestion
    $intendedDCRId = $dcrDetails.immutableId
    $intendedWorkspaceId = $WorkspaceAccount.workspaceId

    if(($dataCollectionEndpointUri.value -ne $intendedDCEUri) -or ($ruleId.value -ne $intendedDCRId) -or ($workspaceId.value -ne $intendedWorkspaceId)) {
        Write-Information "Log ingestion API settings not configured correctly in the App Service $AppServiceName. They will be configured"
        Write-Verbose "Existing DataCollectionEndpointUri: $($dataCollectionEndpointUri.value), Intended DataCollectionEndpointUri: $intendedDCEUri"
        Write-Verbose "Existing RuleId: $($ruleId.value), Intended RuleId: $intendedDCRId"
        Write-Verbose "Existing WorkspaceId: $($workspaceId.value), Intended WorkspaceId: $intendedWorkspaceId"
        $shouldConfigure = $true;
    } elseif(($dataCollectionEndpointUri.value -eq $intendedDCEUri) -and ($ruleId.value -eq $intendedDCRId) -and ($workspaceId.value -eq $intendedWorkspaceId)) {
        Write-Information "Log ingestion API settings already configured correctly in the App Service $AppServiceName. Skipping the configuration and ensure data collector API settings are removed"
        RemoveDataCollectorAPISettings -ResourceGroup $ResourceGroup -AppServiceName $AppServiceName
        $shouldConfigure = $false;
    }

    return $shouldConfigure;
}

function GetExistingWorkspaceId($ExistingConfigSc, $ExistingConfigCm, $SCEPmanAppServiceName, $CertMasterAppServiceName, $SCEPmanResourceGroup,  $SubscriptionId) {
    $workspaceIdSc = $null;
    $workspaceIdCm = $null;

    $workspaceId = $ExistingConfigSc.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:WorkspaceId" }

    if($null -ne $workspaceId) {
        Write-Information "Found workspace ID $workspaceId in the App Service $SCEPmanAppServiceName"
        $workspaceIdSc = $workspaceId.value
    }

    if($null -ne $ExistingConfigCm -and $null -ne $ExistingConfigCm.settings) {
        $workspaceId = $ExistingConfigCm.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:WorkspaceId" }

        if($null -ne $workspaceId) {
            Write-Information "Found workspace ID $workspaceId in the App Service $CertMasterAppServiceName"
            $workspaceIdCm = $workspaceId.value
        }
    }

    if($null -ne $workspaceIdCm -and $null -ne $workspaceIdSc -and $workspaceIdSc -ne $workspaceIdCm) {
        throw "Inconsistency: SCEPman($SCEPmanAppServiceName) and CertMaster($CertMasterAppServiceName) have different log analytics workspaces configured"
    }

    # If workspace id is still null; Check if DataCollectionEndpointUri and RuleId are present in the SCEPman app service settings. If they are, fetch the workspace ID from the DCR
    if($null -eq $workspaceIdSc -and $null -eq $workspaceIdCm) {
        $dataCollectionEndpointUri = $ExistingConfigSc.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:DataCollectionEndpointUri" }
        $ruleId = $ExistingConfigSc.settings | Where-Object { $_.name -eq "AppConfig:LoggingConfig:RuleId" }

        if($null -ne $dataCollectionEndpointUri -and $null -ne $ruleId -and $dataCollectionEndpointUri.value -and $ruleId.value) {
            $ruleIdName = GetRuleIdName -SubscriptionId $SubscriptionId -ResourceGroup $SCEPmanResourceGroup
            $configuredDCRDetails = Invoke-Az @("monitor", "data-collection", "rule", "show", "--ids", $ruleIdName) | Convert-LinesToObject
            if($null -ne $configuredDCRDetails) {
                [array]$logAnalyticsDestinations = $configuredDCRDetails.destinations.logAnalytics
                if($logAnalyticsDestinations.count -gt 0) {
                    $potentialWorkspaceId = $logAnalyticsDestinations | Where-Object { $_.name -eq "$LogsDestinationName" } | Select-Object -ExpandProperty workspaceId
                    if($null -ne $potentialWorkspaceId) {
                        Write-Information "Fetched workspace ID $potentialWorkspaceId from the Data Collection Rule in the App Service $SCEPmanAppServiceName"
                        $workspaceIdSc = $potentialWorkspaceId
                    }
                }
            }
        }
    }

    if ($null -ne $workspaceIdSc) {
        return $workspaceIdSc
    } elseif ($null -ne $workspaceIdCm) {
        return $workspaceIdCm
    } else {
        return $null
    }
}

function AddLogIngestionAPISettings($ResourceGroup, $AppServiceName, $DcrDetails, $Slot, $WorkspaceAccount) {
    $settings = @(
        @{ name='AppConfig:LoggingConfig:DataCollectionEndpointUri'; value=$($DcrDetails.endpoints.logsIngestion) },
        @{ name='AppConfig:LoggingConfig:RuleId'; value=$($DcrDetails.immutableId) }
        @{ name='AppConfig:LoggingConfig:WorkspaceId'; value=$($WorkspaceAccount.workspaceId) }
    )
    SetAppSettings -AppServiceName $AppServiceName -ResourceGroup $ResourceGroup -Settings $settings -Slot $Slot
    Write-Information "Log ingestion API settings configured in the App Service $AppServiceName"
}

function AddAppRoleAssignmentsForLogIngestionAPI($ResourceGroup, $AppServiceName, $DcrDetails, $SkipAppRoleAssignments = $false) {
    $servicePrincipal = GetServicePrincipal -appServiceNameParam $AppServiceName -resourceGroupParam $ResourceGroup
    if($null -ne $servicePrincipal.principalId) {
        $azCommandToAssignRole = "az role assignment create --role 'Monitoring Metrics Publisher' --assignee-object-id $($servicePrincipal.principalId) --assignee-principal-type ServicePrincipal --scope $($DcrDetails.id)"
        if($SkipAppRoleAssignments) {
            Write-Warning "Skipping app role assignment (please execute manually): $azCommandToAssignRole"
            return
        }
        $null = ExecuteAzCommandRobustly -azCommand $azCommandToAssignRole
        Write-Information "Role 'Monitoring Metrics Publisher' assigned to the App Service $AppServiceName service principal"
    } else {
        Write-Information "$AppServiceName does not have a System-assigned Managed Identity turned on"
    }
}


function Set-LoggingConfigInScAndCmAppSettings {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Mandatory=$true)]        [string]$SubscriptionId,
        [Parameter(Mandatory=$true)]        [string]$SCEPmanResourceGroup,
        [Parameter(Mandatory=$true)]        [string]$SCEPmanAppServiceName,
        [Parameter(Mandatory=$false)]        [string]$CertMasterResourceGroup,
        [Parameter(Mandatory=$false)]        [string]$CertMasterAppServiceName,
        [Parameter(Mandatory=$false)]        [string]$DeploymentSlotName = $null,
        [Parameter(Mandatory=$false)]        [System.Collections.IList]$DeploymentSlots,
        [switch]$SkipAppRoleAssignments
    )

    $existingConfigSc = ReadAppSettings -ResourceGroup $SCEPmanResourceGroup -AppServiceName $SCEPmanAppServiceName -Slot $DeploymentSlotName
    $existingConfigCm = $null

    if($CertMasterResourceGroup -and $CertMasterAppServiceName) {
        $existingConfigCm = ReadAppSettings -ResourceGroup $CertMasterResourceGroup -AppServiceName $CertMasterAppServiceName -Slot $DeploymentSlotName
    }

    # Ensure resources exist
    $existingWorkspaceId = GetExistingWorkspaceId -ExistingConfigSc $existingConfigSc -ExistingConfigCm $existingConfigCm -SCEPmanAppServiceName $SCEPmanAppServiceName -CertMasterAppServiceName $CertMasterAppServiceName -SCEPmanResourceGroup $SCEPmanResourceGroup -SubscriptionId $SubscriptionId
    $workspaceAccount = CreateLogAnalyticsWorkspace -ResourceGroup $SCEPmanResourceGroup -WorkspaceId $existingWorkspaceId
    $dcrDetails = ConfigureLogIngestionAPIResources -ResourceGroup $SCEPmanResourceGroup -WorkspaceAccount $workspaceAccount -SubscriptionId $SubscriptionId

    # Check if we need to configure Log Ingestion API settings in the App Services
    $shouldConfigureLoggingInSc = ShouldConfigureLogIngestionAPIInAppService -ExistingConfig $existingConfigSc -ResourceGroup $SCEPmanResourceGroup -AppServiceName $SCEPmanAppServiceName -dcrDetails $dcrDetails -WorkspaceAccount $workspaceAccount
    $shouldConfigureLoggingConfigInCm = ShouldConfigureLogIngestionAPIInAppService -ExistingConfig $existingConfigCm -ResourceGroup $CertMasterResourceGroup -AppServiceName $CertMasterAppServiceName -dcrDetails $dcrDetails -WorkspaceAccount $workspaceAccount

    if($shouldConfigureLoggingInSc) {
        AddAppRoleAssignmentsForLogIngestionAPI -ResourceGroup $SCEPmanResourceGroup -AppServiceName $SCEPmanAppServiceName -DcrDetails $dcrDetails -SkipAppRoleAssignments $SkipAppRoleAssignments
        AddLogIngestionAPISettings -ResourceGroup $SCEPmanResourceGroup -AppServiceName $SCEPmanAppServiceName -DcrDetails $dcrDetails -Slot $DeploymentSlotName -WorkspaceAccount $workspaceAccount
        RemoveDataCollectorAPISettings -ResourceGroup $SCEPmanResourceGroup -AppServiceName $SCEPmanAppServiceName
    }
    if($shouldConfigureLoggingConfigInCm) {
        AddAppRoleAssignmentsForLogIngestionAPI -ResourceGroup $CertMasterResourceGroup -AppServiceName $CertMasterAppServiceName -DcrDetails $dcrDetails -SkipAppRoleAssignments $SkipAppRoleAssignments
        AddLogIngestionAPISettings -ResourceGroup $CertMasterResourceGroup -AppServiceName $CertMasterAppServiceName -DcrDetails $dcrDetails -Slot $DeploymentSlotName -WorkspaceAccount $workspaceAccount
        RemoveDataCollectorAPISettings -ResourceGroup $CertMasterResourceGroup -AppServiceName $CertMasterAppServiceName
    }
}
# SIG # Begin signature block
# MIIsiwYJKoZIhvcNAQcCoIIsfDCCLHgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCWDF8aZv3ev5qC
# Sgb+NWUuJcawXXB2WmXZoLTs9onE5aCCFA4wggVyMIIDWqADAgECAhB2U/6sdUZI
# k/Xl10pIOk74MA0GCSqGSIb3DQEBDAUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQK
# ExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENvZGUgU2ln
# bmluZyBSb290IFI0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFMx
# CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQD
# EyBHbG9iYWxTaWduIENvZGUgU2lnbmluZyBSb290IFI0NTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBALYtxTDdeuirkD0DcrA6S5kWYbLl/6VnHTcc5X7s
# k4OqhPWjQ5uYRYq4Y1ddmwCIBCXp+GiSS4LYS8lKA/Oof2qPimEnvaFE0P31PyLC
# o0+RjbMFsiiCkV37WYgFC5cGwpj4LKczJO5QOkHM8KCwex1N0qhYOJbp3/kbkbuL
# ECzSx0Mdogl0oYCve+YzCgxZa4689Ktal3t/rlX7hPCA/oRM1+K6vcR1oW+9YRB0
# RLKYB+J0q/9o3GwmPukf5eAEh60w0wyNA3xVuBZwXCR4ICXrZ2eIq7pONJhrcBHe
# OMrUvqHAnOHfHgIB2DvhZ0OEts/8dLcvhKO/ugk3PWdssUVcGWGrQYP1rB3rdw1G
# R3POv72Vle2dK4gQ/vpY6KdX4bPPqFrpByWbEsSegHI9k9yMlN87ROYmgPzSwwPw
# jAzSRdYu54+YnuYE7kJuZ35CFnFi5wT5YMZkobacgSFOK8ZtaJSGxpl0c2cxepHy
# 1Ix5bnymu35Gb03FhRIrz5oiRAiohTfOB2FXBhcSJMDEMXOhmDVXR34QOkXZLaRR
# kJipoAc3xGUaqhxrFnf3p5fsPxkwmW8x++pAsufSxPrJ0PBQdnRZ+o1tFzK++Ol+
# A/Tnh3Wa1EqRLIUDEwIrQoDyiWo2z8hMoM6e+MuNrRan097VmxinxpI68YJj8S4O
# JGTfAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBQfAL9GgAr8eDm3pbRD2VZQu86WOzANBgkqhkiG9w0BAQwFAAOCAgEA
# Xiu6dJc0RF92SChAhJPuAW7pobPWgCXme+S8CZE9D/x2rdfUMCC7j2DQkdYc8pzv
# eBorlDICwSSWUlIC0PPR/PKbOW6Z4R+OQ0F9mh5byV2ahPwm5ofzdHImraQb2T07
# alKgPAkeLx57szO0Rcf3rLGvk2Ctdq64shV464Nq6//bRqsk5e4C+pAfWcAvXda3
# XaRcELdyU/hBTsz6eBolSsr+hWJDYcO0N6qB0vTWOg+9jVl+MEfeK2vnIVAzX9Rn
# m9S4Z588J5kD/4VDjnMSyiDN6GHVsWbcF9Y5bQ/bzyM3oYKJThxrP9agzaoHnT5C
# JqrXDO76R78aUn7RdYHTyYpiF21PiKAhoCY+r23ZYjAf6Zgorm6N1Y5McmaTgI0q
# 41XHYGeQQlZcIlEPs9xOOe5N3dkdeBBUO27Ql28DtR6yI3PGErKaZND8lYUkqP/f
# obDckUCu3wkzq7ndkrfxzJF0O2nrZ5cbkL/nx6BvcbtXv7ePWu16QGoWzYCELS/h
# AtQklEOzFfwMKxv9cW/8y7x1Fzpeg9LJsy8b1ZyNf1T+fn7kVqOHp53hWVKUQY9t
# W76GlZr/GnbdQNJRSnC0HzNjI3c/7CceWeQIh+00gkoPP/6gHcH1Z3NFhnj0qinp
# J4fGGdvGExTDOUmHTaCX4GUT9Z13Vunas1jHOvLAzYIwggboMIIE0KADAgECAhB3
# vQ4Ft1kLth1HYVMeP3XtMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAkJFMRkw
# FwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENv
# ZGUgU2lnbmluZyBSb290IFI0NTAeFw0yMDA3MjgwMDAwMDBaFw0zMDA3MjgwMDAw
# MDBaMFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIw
# MAYDVQQDEylHbG9iYWxTaWduIEdDQyBSNDUgRVYgQ29kZVNpZ25pbmcgQ0EgMjAy
# MDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMsg75ceuQEyQ6BbqYoj
# /SBerjgSi8os1P9B2BpV1BlTt/2jF+d6OVzA984Ro/ml7QH6tbqT76+T3PjisxlM
# g7BKRFAEeIQQaqTWlpCOgfh8qy+1o1cz0lh7lA5tD6WRJiqzg09ysYp7ZJLQ8LRV
# X5YLEeWatSyyEc8lG31RK5gfSaNf+BOeNbgDAtqkEy+FSu/EL3AOwdTMMxLsvUCV
# 0xHK5s2zBZzIU+tS13hMUQGSgt4T8weOdLqEgJ/SpBUO6K/r94n233Hw0b6nskEz
# IHXMsdXtHQcZxOsmd/KrbReTSam35sOQnMa47MzJe5pexcUkk2NvfhCLYc+YVaMk
# oog28vmfvpMusgafJsAMAVYS4bKKnw4e3JiLLs/a4ok0ph8moKiueG3soYgVPMLq
# 7rfYrWGlr3A2onmO3A1zwPHkLKuU7FgGOTZI1jta6CLOdA6vLPEV2tG0leis1Ult
# 5a/dm2tjIF2OfjuyQ9hiOpTlzbSYszcZJBJyc6sEsAnchebUIgTvQCodLm3HadNu
# twFsDeCXpxbmJouI9wNEhl9iZ0y1pzeoVdwDNoxuz202JvEOj7A9ccDhMqeC5LYy
# AjIwfLWTyCH9PIjmaWP47nXJi8Kr77o6/elev7YR8b7wPcoyPm593g9+m5XEEofn
# GrhO7izB36Fl6CSDySrC/blTAgMBAAGjggGtMIIBqTAOBgNVHQ8BAf8EBAMCAYYw
# EwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUJZ3Q/FkJhmPF7POxEztXHAOSNhEwHwYDVR0jBBgwFoAUHwC/RoAK/Hg5t6W0
# Q9lWULvOljswgZMGCCsGAQUFBwEBBIGGMIGDMDkGCCsGAQUFBzABhi1odHRwOi8v
# b2NzcC5nbG9iYWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUwRgYIKwYBBQUH
# MAKGOmh0dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2NvZGVzaWdu
# aW5ncm9vdHI0NS5jcnQwQQYDVR0fBDowODA2oDSgMoYwaHR0cDovL2NybC5nbG9i
# YWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3JsMFUGA1UdIAROMEwwQQYJ
# KwYBBAGgMgECMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24u
# Y29tL3JlcG9zaXRvcnkvMAcGBWeBDAEDMA0GCSqGSIb3DQEBCwUAA4ICAQAldaAJ
# yTm6t6E5iS8Yn6vW6x1L6JR8DQdomxyd73G2F2prAk+zP4ZFh8xlm0zjWAYCImbV
# YQLFY4/UovG2XiULd5bpzXFAM4gp7O7zom28TbU+BkvJczPKCBQtPUzosLp1pnQt
# pFg6bBNJ+KUVChSWhbFqaDQlQq+WVvQQ+iR98StywRbha+vmqZjHPlr00Bid/XSX
# hndGKj0jfShziq7vKxuav2xTpxSePIdxwF6OyPvTKpIz6ldNXgdeysEYrIEtGiH6
# bs+XYXvfcXo6ymP31TBENzL+u0OF3Lr8psozGSt3bdvLBfB+X3Uuora/Nao2Y8nO
# ZNm9/Lws80lWAMgSK8YnuzevV+/Ezx4pxPTiLc4qYc9X7fUKQOL1GNYe6ZAvytOH
# X5OKSBoRHeU3hZ8uZmKaXoFOlaxVV0PcU4slfjxhD4oLuvU/pteO9wRWXiG7n9dq
# cYC/lt5yA9jYIivzJxZPOOhRQAyuku++PX33gMZMNleElaeEFUgwDlInCI2Oor0i
# xxnJpsoOqHo222q6YV8RJJWk4o5o7hmpSZle0LQ0vdb5QMcQlzFSOTUpEYck08T7
# qWPLd0jV+mL8JOAEek7Q5G7ezp44UCb0IXFl1wkl1MkHAHq4x/N36MXU4lXQ0x72
# f1LiSY25EXIMiEQmM2YBRN/kMw4h3mKJSAfa9TCCB6gwggWQoAMCAQICDF3VjaKN
# us83AvC1UTANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQ
# R2xvYmFsU2lnbiBudi1zYTEyMDAGA1UEAxMpR2xvYmFsU2lnbiBHQ0MgUjQ1IEVW
# IENvZGVTaWduaW5nIENBIDIwMjAwHhcNMjUxMTI3MTcwNDI2WhcNMjcxMTI4MTcw
# NDI2WjCCAQwxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRIwEAYDVQQF
# EwlIUkIgMTIzODExEzARBgsrBgEEAYI3PAIBAxMCREUxFzAVBgsrBgEEAYI3PAIB
# AhMGSGVzc2VuMSIwIAYLKwYBBAGCNzwCAQETEU9mZmVuYmFjaCBhbSBNYWluMQsw
# CQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMRowGAYDVQQHExFPZmZlbmJhY2gg
# YW0gTWFpbjEZMBcGA1UECQwQS2Fpc2Vyc3RyYcOfZSAzOTEXMBUGA1UEChMOZ2x1
# ZWNra2FuamEgQUcxFzAVBgNVBAMTDmdsdWVja2thbmphIEFHMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAkQoXi0dUFVttodx+Ydj1O6EZZqgDdlSGDA/6
# x1UCkMrWNVEW+LdbUGU8KW7fUcKPCAcDJNrXfXxZeBht2G4pPvhaMz/kBdSK6bI1
# sqo1WSN//beapdUefQpq/wgnUneq13tEJQAke6EWdLyidObcogBSp9wCXBbMWsTO
# utgCONjyu8AilmzRY+94lO7VwUA2LGGPX8FRAEt5AMzifsEo2lIEKiDou2H8HUUC
# PibiChiuT3oGIDYYnCA/RzS44E0cAuAzlD3NQNCeIDzfoFiUD8mAC1gYU6i8yIej
# jUGl8+kpbpBYjgzwbsiCBn0rDhrlpJ3MHkZCrp82kzWK0l7c3ukNvdlGcU4tKdXk
# AHgpJecdYUDvz9iaYFvYEivF+Jg+Tc8ZnzsP5/q3KKw4g0QiJ+MXgvwJx8OSvAKW
# tkwkLxgE9oxufs3Y8xsmwyWqxWDBcyzzvs6yISnUaeTtGmyB8BsEbahDFrxHhV6U
# nwxNpJ+iM+j08J1tNIW0AXjY6ojGOIC8IIL+EiK34MXJ6Jxy22mntMnc6ztK6c7H
# IKiRHIPX4jXtg7IYRS/k5muuIt/xKzN7qtF9xJbaZi8jRE6fgWDwszLJUMHSLthh
# yKTsUEvuqZ79WnSHErg26EPQYirAY/IFt7Z7+3SDW2WI8uG2qY6hkpE0hm+/F3uS
# M+s98jUCAwEAAaOCAbYwggGyMA4GA1UdDwEB/wQEAwIHgDCBnwYIKwYBBQUHAQEE
# gZIwgY8wTAYIKwYBBQUHMAKGQGh0dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20v
# Y2FjZXJ0L2dzZ2NjcjQ1ZXZjb2Rlc2lnbmNhMjAyMC5jcnQwPwYIKwYBBQUHMAGG
# M2h0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1ZXZjb2Rlc2lnbmNh
# MjAyMDBVBgNVHSAETjBMMEEGCSsGAQQBoDIBAjA0MDIGCCsGAQUFBwIBFiZodHRw
# czovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAHBgVngQwBAzAJBgNV
# HRMEAjAAMEcGA1UdHwRAMD4wPKA6oDiGNmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5j
# b20vZ3NnY2NyNDVldmNvZGVzaWduY2EyMDIwLmNybDATBgNVHSUEDDAKBggrBgEF
# BQcDAzAfBgNVHSMEGDAWgBQlndD8WQmGY8Xs87ETO1ccA5I2ETAdBgNVHQ4EFgQU
# q/cn5ijjtp0mG1yoiF02hg4dx4IwDQYJKoZIhvcNAQELBQADggIBAJ1TZv/rvy2w
# jANcL/kb6rTk+/6L7l49UghLghUKFVfrdEEc+21iexA7zlkvhM0TrhdiFU7TjDky
# InPctzsDlqwUhawEx4PT8ZkZkZzm25YWaqtZH44st/Fz59KiG+85NUdRd+0cL3Y8
# NR66z3xfI6K3W/nrIcE6RHm/opOM+L02Hd2MBligLnoFYcTvR3NPCA21A6+IOaYM
# n5YZzNKFXWry8ZHpWjnE4u9mxHYpS1zu2aIkwL8mfYM5moYoh0PAcp9XA5Sm4KrV
# LeIzZ3HIy4EzLCbFBP+OGFpkqq8pTtmYItG+g1rYEg5a8egrY83zJMHazaTFBgRI
# MNXCgeMZhC8O6NsAtbj3FSbiYKg1hNwZzHYL+uL3jcPZjuUoOpmvXu67xWs4ZfdT
# Mluy5E7FyWwtnOjr/04EXWyKATYMDIkd47Wqam/ZB7umF5T5YPnmTlv18ArEXuVQ
# EEpS/cN90DtRz2OGruu+V9bg3fk6NKDJLve8detDOTTBN0C/bFGxI5YLHmwVAdaq
# pz3t14ShRjVcxP7aN0bEL3YOuQvjnjQGe29H6n/MPf8UG4WYMd+a8qIP4HROLJq0
# YJylzYBglqoQeQC/OG+PtWTvL9oByPVYNc+llAuap/xmWSLZgAqPbi+PAfow69Lg
# bppHUCnJhNkXD/mJ4qB0KvPG+bzL19dEMYIX0zCCF88CAQEwbDBcMQswCQYDVQQG
# EwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEyMDAGA1UEAxMpR2xvYmFs
# U2lnbiBHQ0MgUjQ1IEVWIENvZGVTaWduaW5nIENBIDIwMjACDF3VjaKNus83AvC1
# UTANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkG
# CSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEE
# AYI3AgEVMC8GCSqGSIb3DQEJBDEiBCC+dZLnufYKzyV1z2j1yq/ri2ZFci9tjNSK
# r0++1Xo4AzANBgkqhkiG9w0BAQEFAASCAgA47FhGLSWqU3n5EnB/IHSHCvkd5DVh
# 46KkbBz7uj+1pFa251VCAZ3AcBoDrlbNZDk5JRO+bR8Xyssxq1NNRSmV9fOEH4cC
# H6ahdiHerGeOel2jWra+ugRUqtlcRbqBI4ScBi73hUJ8BBbxemYtDzfEO14nV0lz
# Hw0bIqYyKi5fyir5rdWplzNYUzuaJgk+1IkzBpUoyWQIUA4hX/vV6NeYa1i1NjvK
# Kwc7cSepHGfQ1S7KgvPKRIhthfnAdZOjiAk5UkoEUjVaefPrD8EQI6wsO8SNjG14
# PJC2heBI+QW6hec2gm5WIdOIsWrpUbo9GyuDR0lct9So5vSW2EFAwbMpHLkIEBPC
# v0ztEPzTIkhEVIt+mUFbNo6Ry2bzinPtUBT/NJq6BbdvYIH7Nb+bJ3f0cHJ5HO33
# a2QKphxWHKUig9fuCfS++yZakvPrt7QoO58lIa/SbrsYI1pDmHbH6VWKW6t3ApXP
# s5eROs8BhF5IN+yQbFjT4XEdry9v7Ra6gb2QQjjDW36c0RqYay3jkkz/NPSa++53
# rbNiENrJUC/onJkzfV+ENAuo+w73PBALMzDuC46LX7g5jf7A5UyMk14cghmgdQnW
# hVONMNMeMbbb9SBDhW5owINM1UHqJfe3UHOxhTpG4AYi7vi9wN3FqBOeFrWc0FZX
# oQ7dV9RMBRwwK6GCFLEwghStBgorBgEEAYI3AwMBMYIUnTCCFJkGCSqGSIb3DQEH
# AqCCFIowghSGAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFpBgsqhkiG9w0BCRABBKCC
# AVgEggFUMIIBUAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCBxJ+lu
# fFxDqB/+1IWbuqdjeUo4xZNY7JhoI55pNnETIwIGaTxKXaZEGBIyMDI1MTIxNzA5
# NTA0OS4zNFowBIACAfSggemkgeYwgeMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlv
# bnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjQ5MUEtMDVFMC1E
# OTQ3MTUwMwYDVQQDEyxNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lIFN0YW1waW5n
# IEF1dGhvcml0eaCCDykwggeCMIIFaqADAgECAhMzAAAABeXPD/9mLsmHAAAAAAAF
# MA0GCSqGSIb3DQEBDAUAMHcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jvc29mdCBJZGVudGl0eSBWZXJp
# ZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMDAeFw0yMDEx
# MTkyMDMyMzFaFw0zNTExMTkyMDQyMzFaMGExCzAJBgNVBAYTAlVTMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJs
# aWMgUlNBIFRpbWVzdGFtcGluZyBDQSAyMDIwMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEAnnznUmP94MWfBX1jtQYioxwe1+eXM9ETBb1lRkd3kcFdcG9/
# sqtDlwxKoVIcaqDb+omFio5DHC4RBcbyQHjXCwMk/l3TOYtgoBjxnG/eViS4sOx8
# y4gSq8Zg49REAf5huXhIkQRKe3Qxs8Sgp02KHAznEa/Ssah8nWo5hJM1xznkRsFP
# u6rfDHeZeG1Wa1wISvlkpOQooTULFm809Z0ZYlQ8Lp7i5F9YciFlyAKwn6yjN/kR
# 4fkquUWfGmMopNq/B8U/pdoZkZZQbxNlqJOiBGgCWpx69uKqKhTPVi3gVErnc/qi
# +dR8A2MiAz0kN0nh7SqINGbmw5OIRC0EsZ31WF3Uxp3GgZwetEKxLms73KG/Z+Mk
# euaVDQQheangOEMGJ4pQZH55ngI0Tdy1bi69INBV5Kn2HVJo9XxRYR/JPGAaM6xG
# l57Ei95HUw9NV/uC3yFjrhc087qLJQawSC3xzY/EXzsT4I7sDbxOmM2rl4uKK6eE
# purRduOQ2hTkmG1hSuWYBunFGNv21Kt4N20AKmbeuSnGnsBCd2cjRKG79+TX+sTe
# hawOoxfeOO/jR7wo3liwkGdzPJYHgnJ54UxbckF914AqHOiEV7xTnD1a69w/UTxw
# jEugpIPMIIE67SFZ2PMo27xjlLAHWW3l1CEAFjLNHd3EQ79PUr8FUXetXr0CAwEA
# AaOCAhswggIXMA4GA1UdDwEB/wQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADAdBgNV
# HQ4EFgQUa2koOjUvSGNAz3vYr0npPtk92yEwVAYDVR0gBE0wSzBJBgRVHSAAMEEw
# PwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9j
# cy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3
# FAIEDB4KAFMAdQBiAEMAQTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFMh+
# 0mqFKhvKGZgEByfPUBBPaKiiMIGEBgNVHR8EfTB7MHmgd6B1hnNodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBJZGVudGl0eSUy
# MFZlcmlmaWNhdGlvbiUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUy
# MDIwMjAuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDCBgQYIKwYBBQUHMAKGdWh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwSWRl
# bnRpdHklMjBWZXJpZmljYXRpb24lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRo
# b3JpdHklMjAyMDIwLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAX4h2x35ttVoVdedM
# eGj6TuHYRJklFaW4sTQ5r+k77iB79cSLNe+GzRjv4pVjJviceW6AF6ycWoEYR0LY
# haa0ozJLU5Yi+LCmcrdovkl53DNt4EXs87KDogYb9eGEndSpZ5ZM74LNvVzY0/nP
# ISHz0Xva71QjD4h+8z2XMOZzY7YQ0Psw+etyNZ1CesufU211rLslLKsO8F2aBs2c
# Io1k+aHOhrw9xw6JCWONNboZ497mwYW5EfN0W3zL5s3ad4Xtm7yFM7Ujrhc0aqy3
# xL7D5FR2J7x9cLWMq7eb0oYioXhqV2tgFqbKHeDick+P8tHYIFovIP7YG4ZkJWag
# 1H91KlELGWi3SLv10o4KGag42pswjybTi4toQcC/irAodDW8HNtX+cbz0sMptFJK
# +KObAnDFHEsukxD+7jFfEV9Hh/+CSxKRsmnuiovCWIOb+H7DRon9TlxydiFhvu88
# o0w35JkNbJxTk4MhF/KgaXn0GxdH8elEa2Imq45gaa8D+mTm8LWVydt4ytxYP/bq
# jN49D9NZ81coE6aQWm88TwIf4R4YZbOpMKN0CyejaPNN41LGXHeCUMYmBx3PkP8A
# DHD1J2Cr/6tjuOOCztfp+o9Nc+ZoIAkpUcA/X2gSMkgHAPUvIdtoSAHEUKiBhI6J
# QivRepyvWcl+JYbYbBh7pmgAXVswggefMIIFh6ADAgECAhMzAAAATqPGDj4xw3Qn
# AAAAAABOMA0GCSqGSIb3DQEBDAUAMGExCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBQdWJsaWMg
# UlNBIFRpbWVzdGFtcGluZyBDQSAyMDIwMB4XDTI1MDIyNzE5NDAxN1oXDTI2MDIy
# NjE5NDAxN1owgeMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjQ5MUEtMDVFMC1EOTQ3MTUwMwYDVQQD
# EyxNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lIFN0YW1waW5nIEF1dGhvcml0eTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIbflIu6bAltld7nRX0T6SbF
# 4bEMjoEmU7dL7ZHBsOQtg5hiGs8GQlrZVE1yAWzGArehop47rm2Q0Widteu8M0H/
# c7caoehVD1so8GY0Vo12kfImQp1qt5A1kcTcYXWmyQbeLx9w8KHBnIHpesP+sk2S
# TglYsFu3CtHtIFXjrLAF7+NjA0Urws3ny5bPd+tjxO6vFIY3V6yXb3GIbcHbfmle
# Nfra4ZEAA/hFxDDdm2ReLt/6ij7iVM7Q6EbDQrguRMQydF8HEyLP98iGKHEH36mc
# z+eJ9Xl/bva+Pk/9Yj1aic2MBrA7YTbY/hdw3HSskxvUUgNIcKFQVsz36FSMXQOz
# VXW1cFXL4UiGqw+ylClJcZ0l3H0Aiwsnpvo0t9v4zD5jwJrmeNIlKBeH5EGbfXPe
# lbVEZ2ntMBCgPegB5qelqo+bMfSz9lRTO2c7LByYfQs6UOJL2JhgrZoT+g7WNSEZ
# KXQ+o6DXujpif5XTMdMzWCOOiJnMcevpZdD2aYaOEGFXUm51QE2JLKni/71ecZjI
# 6Df4C6vBXRV7WT76BYUgcEa08kYbW5kN0jjnBPGFASr9SSnZNGFKQ4J8MyRxEBPZ
# TN33MX9Pz+3ZfZF4mS8oyXMCcOmE406M9RSQP9bTVWVuOR0MHo56EpUAK1hVLKfu
# SD0dEwbGiMawHrelOKNBAgMBAAGjggHLMIIBxzAdBgNVHQ4EFgQU8Me6g3SqStL0
# tyd5iw4rvw1NamIwHwYDVR0jBBgwFoAUa2koOjUvSGNAz3vYr0npPtk92yEwbAYD
# VR0fBGUwYzBhoF+gXYZbaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwUHVibGljJTIwUlNBJTIwVGltZXN0YW1waW5nJTIwQ0El
# MjAyMDIwLmNybDB5BggrBgEFBQcBAQRtMGswaQYIKwYBBQUHMAKGXWh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwUHVibGlj
# JTIwUlNBJTIwVGltZXN0YW1waW5nJTIwQ0ElMjAyMDIwLmNydDAMBgNVHRMBAf8E
# AjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDBmBgNV
# HSAEXzBdMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wCAYGZ4EM
# AQQCMA0GCSqGSIb3DQEBDAUAA4ICAQATJnMrpCGuWq9ZOgEKcKyPZj71n/JpX9SY
# aTK/qOrPsIxzf/qvq//uj0dTBnfx7KW0aI1Yz2C6Q78g1b80AU8ARNyoIhmT2SWN
# I8k7FLo7qeWSzN4bcgDgTRSaKGPiWWbJtEjbCgbIkNJ3ZTP9iBJCsxZwv6a45an9
# ApG1NV/wP8niV0RBCH9SIHmD6sv34lxlzHTgGGf1n289fg/LoSMsLFPZ4+G3p0KY
# u7A5fz616IBk9ZWpXQxHFNcSMg/rlwbO65k0k0sRrUlIWkk+71nt2NgpsFaWi2JY
# q0msX0uzV3LbLaWfKzg1B3ugoSXLypZg3pPypkdXh1wra9h222RuzjyOmwyWi7jT
# QUBOPZenyapbJhAZXlCxOBaN00bs1V+zUg2miNte9E8CWHagq+Rts/1iSiPCwWmM
# KfqilSSdSMtYSXMyciCKWexeRjAX0QovSsGv0pMqkYfPa5ubnI03ab/A6Kod2TEF
# 8ufShV9sQSqbDscMW12TQOboyTUhc8wPp8p2WWejvrH+9AUO6hToaYeM4jMmmOcA
# AlpHm2AY+GAk+Y54d6DYA6NBED+CSEFSakUVRqNbkN4mN1SOklodZhvRphmF9Ot0
# DuzLu/KByWIfHbaY/wTusrEVGH4W4n39FmcMIvVbMpeOENZ59+xGiFwt5izuabZi
# HN/EFR4leTGCA9QwggPQAgEBMHgwYTELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFB1YmxpYyBS
# U0EgVGltZXN0YW1waW5nIENBIDIwMjACEzMAAABOo8YOPjHDdCcAAAAAAE4wDQYJ
# YIZIAWUDBAIBBQCgggEtMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkq
# hkiG9w0BCQQxIgQgfajEz+MRz3UBEmPsm2c5GDE+nQd/ud21zQ2LE3K3tccwgd0G
# CyqGSIb3DQEJEAIvMYHNMIHKMIHHMIGgBCBvsqfT7ygFHbDe/Tj3QCn25JHDuUhg
# ytNPRb67/5ZKFzB8MGWkYzBhMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUHVibGljIFJTQSBU
# aW1lc3RhbXBpbmcgQ0EgMjAyMAITMwAAAE6jxg4+McN0JwAAAAAATjAiBCCImB4/
# l6Ts6VHGom/r5JRr9BO/mE3xSuSLmbeNe1qetzANBgkqhkiG9w0BAQsFAASCAgAy
# 5UBMf7nnJTFtXDopGTtZWRHFup4MXX5/jjY664Gjqc8ZkdqBjA35U5ch2c4hTsy6
# ZazbFhCC/rVLmufb0Oea5psdHk1x6T/loaqBeczOIcEAvQrHhzhet6ox0olykX4c
# FihOEDstPgewDRhGS5SPwHjC8ApTZaqiWWXMBKRRecsmImqYF68Mf3BgGAmXN/DK
# 9bsdhYM152I7+10jRTVXUJtjOfpaBFYW5FAAbnntzU/I1As3r+4yCYFRp2+FJeTw
# HDnJXthzt4tb+yWAiPEyrReI3VfbQz3UlJwvs5SapAsK/mb2NaZXx51BvmJpZQPa
# LG5vef5zY9p1RZLWwVDCEFJdVxQpncume2Ak6ATgmwoOoytLTSAs/uhwr67AtzJt
# xZXqayX46vNL6UvhZAxZFN9lw81I42Sxi4VgfsoxXCgLuI2M0GU1y2fHNUBLeKf+
# q9H+fyoOBA0X0lek+/delRVutsPlNnYuvKiQTOOhpltEU2SGmLWIC92v3EbQaI3j
# m/8RQiXe1BBR5EATYmzDgvBa1XOAdAzR5YfZ7+xSNewBrHdQd8auQDANlBc5d7Fg
# I/FvqbGxhuoX9QioW4Edi/Lr2/Qnwpm8OqqFLQnl4sRFejLoB+5rg5ehz0WhuAoG
# 7yXd+I4bkDhRiEoLxQmDcqWF2B2irm91yLIFzidAnA==
# SIG # End signature block