Public/PrivilegedAccess/Update-EntraOpsPrivilegedUnprotectedElmCatalog.ps1

<#
.SYNOPSIS
    Sync privilege level of Entitlement Management catalogs based on EntraOps classification: Catalogs with Control Plane scope will be protected as "privileged" catalog, catalogs without privileged scope will be reverted to "standard".

.DESCRIPTION
    Identifies Entitlement Management (Access Package) catalogs which have been classified as Control Plane scope in EntraOps
    (RBAC system "IdentityGovernance") and syncs their privilege level (Preview) by using Microsoft Graph API:

    - Catalog with Control Plane scope and privilege level "standard" -> updated to "privileged" (PROTECT)
    - Catalog with Control Plane scope and privilege level "privileged" -> no change (KEEP)
    - Catalog without Control Plane scope and privilege level "privileged" -> reverted to "standard" (REVERT)
    - Catalog without Control Plane scope and privilege level "standard" -> no change (ignored)

    Privileged catalogs apply stricter controls:
    - Applications must have directory role management permissions to write to a privileged catalog.
    - Only Global Administrators, or Privileged Role Administrators who also have the Identity Governance Administrator role,
      can perform create, update, or delete actions.
    - No new auto-assignment policies can be created for privileged catalogs.

    CAUTION: This cmdlet requires the elevated Microsoft Graph permission "EntitlementManagement.ReadWrite.All" and should be
    used with caution. The permission is only assigned by New-EntraOpsWorkloadIdentity when the EntraOpsConfig.json setting
    AutomatedElmCatalogProtection.ApplyPrivilegedElmCatalogProtection is enabled. Reference for required permissions:
    https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-update?view=graph-rest-beta

    CAUTION: EntraOps cannot distinguish whether a catalog has been set to "privileged" by EntraOps or manually by an administrator.
    Catalogs without Control Plane scope in EntraOps will be reverted to "standard", even if they have been protected manually.

    NOTE: Updating the privilege level protects the catalog from delegated modification, but the existing role assignments
    on the catalog will still be classified as Control Plane by EntraOps and should be reviewed and removed as soon as possible.

.PARAMETER ApplyToAccessTierLevel
    Array of Access Tier Levels which will be protected as privileged catalog. Default is ControlPlane.

.PARAMETER RemovalSafetyThreshold
    Fraction of currently privileged catalogs that may be reverted in a single run before catalog reverts abort. Default is 0.5 (50%).

.PARAMETER ForceRemovalBeyondSafetyThreshold
    Apply a reviewed catalog-revert plan even when it exceeds RemovalSafetyThreshold.

.EXAMPLE
    Sync privilege level of all Entitlement Management catalogs based on Control Plane scope classification
    Update-EntraOpsPrivilegedUnprotectedElmCatalog
#>


function Update-EntraOpsPrivilegedUnprotectedElmCatalog {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $False)]
        [ValidateSet("ControlPlane", "ManagementPlane")]
        [Array]$ApplyToAccessTierLevel = ("ControlPlane")
        ,
        # Maximum fraction of privileged catalogs reversible per synchronization.
        [Parameter(Mandatory = $False)]
        [ValidateRange(0, 1)]
        [double]$RemovalSafetyThreshold = 0.5
        ,
        # Permits catalog reverts that exceed RemovalSafetyThreshold.
        [Parameter(Mandatory = $False)]
        [switch]$ForceRemovalBeyondSafetyThreshold
        ,
        [Parameter(Mandatory = $False)]
        [boolean]$ApplyPrivilegedElmCatalogProtection = $false
    )

    Write-Warning "This cmdlet updates the privilege level of Entitlement Management catalogs and requires the elevated Microsoft Graph permission 'EntitlementManagement.ReadWrite.All'. Use with caution."

    # Get all privileged EAM objects from IdentityGovernance
    $PrivilegedEamFilePath = "$DefaultFolderClassifiedEam/IdentityGovernance/IdentityGovernance.json"
    if (-not (Test-Path -Path $PrivilegedEamFilePath)) {
        Write-Error "No classified EAM data found at $PrivilegedEamFilePath. Run Save-EntraOpsPrivilegedEAMJson for IdentityGovernance first."
        return
    }
    $PrivilegedEamObjects = Get-Content -Path $PrivilegedEamFilePath | ConvertFrom-Json

    # Identify unique catalogs with role assignments classified at the selected tier levels (desired privileged catalogs)
    $DesiredPrivilegedCatalogIds = [System.Collections.Generic.List[string]]::new()
    foreach ($PrivilegedEamObject in $PrivilegedEamObjects) {
        foreach ($RoleAssignment in @($PrivilegedEamObject.RoleAssignments)) {
            if ("$($RoleAssignment.RoleAssignmentScopeId)" -notlike "/AccessPackageCatalog/*") { continue }
            $TierLevelMatch = @($RoleAssignment.Classification | Where-Object { $_.AdminTierLevelName -in $ApplyToAccessTierLevel })
            if ($TierLevelMatch.Count -eq 0) { continue }
            $CatalogId = "$($RoleAssignment.RoleAssignmentScopeId)".Replace("/AccessPackageCatalog/", "")
            if (-not $DesiredPrivilegedCatalogIds.Contains($CatalogId)) { $DesiredPrivilegedCatalogIds.Add($CatalogId) }
        }
    }

    # Get all Entitlement Management catalogs with their current privilege level
    try {
        $AllCatalogs = Invoke-EntraOpsMsGraphQuery -Method "GET" -Uri "/beta/identityGovernance/catalogs?`$select=id,displayName,privilegeLevel" -OutputType PSObject -DisableCache
    } catch {
        Write-Error "Could not get Entitlement Management catalogs: $_"
        return
    }

    $CurrentPrivilegedCatalogs = @($AllCatalogs | Where-Object { $_.privilegeLevel -eq "privileged" })

    # Evaluate catalog reverts before applying catalog protection updates.
    $PlannedReverts = @($CurrentPrivilegedCatalogs | Where-Object { -not $DesiredPrivilegedCatalogIds.Contains("$($_.id)") })
    $RevertSafetyCheck = Test-EntraOpsRemovalSafetyThreshold -CurrentCount $CurrentPrivilegedCatalogs.Count -RemovalCount $PlannedReverts.Count -RemovalSafetyThreshold $RemovalSafetyThreshold
    $SkipReverts = $false
    if ($RevertSafetyCheck.Exceeds -and -not $ForceRemovalBeyondSafetyThreshold) {
        $SkipReverts = $true
        Write-Warning "[ABORT] $($PlannedReverts.Count) catalog revert(s) exceed the $($RevertSafetyCheck.ThresholdPercent)% safety threshold ($($RevertSafetyCheck.RemovalThreshold) of $($CurrentPrivilegedCatalogs.Count) currently privileged). This may indicate an upstream data issue. Reverts are skipped (protect operations still apply) - review the summary, then re-run with -ForceRemovalBeyondSafetyThreshold to apply."
    } elseif ($RevertSafetyCheck.Exceeds) {
        Write-Warning "[FORCED] Applying $($PlannedReverts.Count) catalog revert(s) despite exceeding the $($RevertSafetyCheck.ThresholdPercent)% safety threshold - requested via -ForceRemovalBeyondSafetyThreshold."
    }

    # Summary tracking
    $SyncSummary = [System.Collections.Generic.List[psobject]]::new()
    $WarningMessages = New-Object -TypeName "System.Collections.Generic.List[psobject]"
    $TotalProtected = 0
    $TotalReverted = 0
    $TotalKept = 0
    $TotalFailed = 0
    $TotalAborted = 0
    if ($SkipReverts) {
        $WarningMessages.Add([PSCustomObject]@{ Type = "SafetyAbort"; Message = "$($PlannedReverts.Count) catalog revert(s) exceed the $($RevertSafetyCheck.ThresholdPercent)% removal safety threshold - reverts skipped (re-run with -ForceRemovalBeyondSafetyThreshold to apply)" })
    }

    Write-Host ""
    Write-Host "=========================================================" -ForegroundColor Cyan
    Write-Host " EntraOps - Privileged ELM Catalog Sync" -ForegroundColor Cyan
    Write-Host " Access Tiers : $($ApplyToAccessTierLevel -join ', ')" -ForegroundColor Cyan
    Write-Host " Catalogs : $(@($AllCatalogs).Count) total | $($DesiredPrivilegedCatalogIds.Count) with privileged scope | $($CurrentPrivilegedCatalogs.Count) currently privileged" -ForegroundColor Cyan
    Write-Host "=========================================================" -ForegroundColor Cyan
    Write-Host ""

    foreach ($Catalog in $AllCatalogs) {

        $IsDesiredPrivileged = $DesiredPrivilegedCatalogIds.Contains("$($Catalog.id)")
        $IsCurrentlyPrivileged = $Catalog.privilegeLevel -eq "privileged"

        # Case 1: Catalog with privileged scope is already protected - no change
        if ($IsDesiredPrivileged -and $IsCurrentlyPrivileged) {
            Write-Host " [~] KEEP $($Catalog.displayName) - already privileged" -ForegroundColor DarkGreen
            $SyncSummary.Add([PSCustomObject]@{ Catalog = $Catalog.displayName; Before = "privileged"; After = "privileged"; Action = "Keep"; Status = "OK" })
            $TotalKept++
            continue
        }

        # Case 2: Catalog with privileged scope is not protected yet - update to privileged
        if ($IsDesiredPrivileged -and -not $IsCurrentlyPrivileged) {
            try {
                $Body = @{ privilegeLevel = "privileged" } | ConvertTo-Json
                Invoke-EntraOpsMsGraphQuery -Method "PATCH" -Uri "/beta/identityGovernance/entitlementManagement/catalogs/$($Catalog.id)" -Body $Body -OutputType PSObject -ThrowOnFailure | Out-Null
                Write-Host " [+] PROTECT $($Catalog.displayName) - privilege level: $($Catalog.privilegeLevel) -> privileged" -ForegroundColor Green
                Write-Warning " Role assignments to catalog '$($Catalog.displayName)' will still be classified as Control Plane by EntraOps and should be removed as soon as possible."
                $SyncSummary.Add([PSCustomObject]@{ Catalog = $Catalog.displayName; Before = $Catalog.privilegeLevel; After = "privileged"; Action = "Protect"; Status = "OK" })
                $TotalProtected++
            } catch {
                Write-Warning " [!] FAIL PROTECT $($Catalog.displayName) ($($Catalog.id)): $_"
                $WarningMessages.Add([PSCustomObject]@{ Type = "ApiError"; Message = "FAIL PROTECT catalog $($Catalog.displayName) ($($Catalog.id)): $_" })
                $SyncSummary.Add([PSCustomObject]@{ Catalog = $Catalog.displayName; Before = $Catalog.privilegeLevel; After = $Catalog.privilegeLevel; Action = "Protect"; Status = "FAILED" })
                $TotalFailed++
            }
            continue
        }

        # Case 3: Catalog without privileged scope is still protected - revert to standard
        if (-not $IsDesiredPrivileged -and $IsCurrentlyPrivileged) {
            if ($SkipReverts) {
                Write-Host " [!] ABORT $($Catalog.displayName) - revert skipped by removal safety threshold" -ForegroundColor Yellow
                $SyncSummary.Add([PSCustomObject]@{ Catalog = $Catalog.displayName; Before = "privileged"; After = "privileged"; Action = "Revert"; Status = "ABORTED" })
                $TotalAborted++
                continue
            }
            try {
                $Body = @{ privilegeLevel = "standard" } | ConvertTo-Json
                Invoke-EntraOpsMsGraphQuery -Method "PATCH" -Uri "/beta/identityGovernance/entitlementManagement/catalogs/$($Catalog.id)" -Body $Body -OutputType PSObject -ThrowOnFailure | Out-Null
                Write-Host " [-] REVERT $($Catalog.displayName) - no longer in scope of $($ApplyToAccessTierLevel -join ', ') - privilege level: privileged -> standard" -ForegroundColor Yellow
                $SyncSummary.Add([PSCustomObject]@{ Catalog = $Catalog.displayName; Before = "privileged"; After = "standard"; Action = "Revert"; Status = "OK" })
                $TotalReverted++
            } catch {
                Write-Warning " [!] FAIL REVERT $($Catalog.displayName) ($($Catalog.id)): $_"
                $WarningMessages.Add([PSCustomObject]@{ Type = "ApiError"; Message = "FAIL REVERT catalog $($Catalog.displayName) ($($Catalog.id)): $_" })
                $SyncSummary.Add([PSCustomObject]@{ Catalog = $Catalog.displayName; Before = "privileged"; After = "privileged"; Action = "Revert"; Status = "FAILED" })
                $TotalFailed++
            }
            continue
        }

        # Case 4: Catalog without privileged scope and standard privilege level - nothing to do (not shown in summary)
    }

    # Warn about catalogs with privileged scope that could not be found in the tenant (e.g., deleted catalogs)
    foreach ($DesiredCatalogId in $DesiredPrivilegedCatalogIds) {
        if ("$DesiredCatalogId" -notin @($AllCatalogs.id)) {
            Write-Warning " [!] Catalog $DesiredCatalogId with privileged scope not found in tenant (likely deleted)."
            $WarningMessages.Add([PSCustomObject]@{ Type = "CatalogNotFound"; Message = "Catalog $DesiredCatalogId with privileged scope not found in tenant (likely deleted)." })
            $SyncSummary.Add([PSCustomObject]@{ Catalog = $DesiredCatalogId; Before = "N/A"; After = "N/A"; Action = "Protect"; Status = "NOT FOUND" })
        }
    }

    # Final summary
    Write-Host ""
    Write-Host "=========================================================" -ForegroundColor Cyan
    Write-Host " Privileged ELM Catalog Sync Complete" -ForegroundColor Cyan
    Write-Host " Protected: $TotalProtected | Reverted: $TotalReverted | Kept: $TotalKept | Failed: $TotalFailed" -ForegroundColor Cyan
    Write-Host "=========================================================" -ForegroundColor Cyan
    Show-EntraOpsWarningSummary -WarningMessages $WarningMessages
    if ($SyncSummary.Count -gt 0) {
        $SyncSummary | Format-Table -AutoSize -Property Catalog, Before, After, Action, Status
    } else {
        Write-Host " No catalog changes required - all catalogs are already in sync." -ForegroundColor Gray
    }

    if ($TotalFailed -gt 0 -or $TotalAborted -gt 0) {
        $Reasons = [System.Collections.Generic.List[string]]::new()
        if ($TotalFailed -gt 0) { $Reasons.Add("$TotalFailed privilege level operation(s) failed") | Out-Null }
        if ($TotalAborted -gt 0) { $Reasons.Add("$TotalAborted catalog revert(s) aborted by the removal safety threshold") | Out-Null }
        throw "Entitlement Management catalog sync did not complete: $($Reasons -join '; '). Review the warning summary above."
    }
}

# SIG # Begin signature block
# MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCDHRg2bRth9O/u
# rKTFBBlg3U2V4BhgRWxSgun8FRU7LqCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j
# AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
# ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
# dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l
# dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG
# A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl
# BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb
# Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC
# 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va
# qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ
# 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet
# /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe
# GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV
# WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl
# s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb
# MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc
# noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl
# jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA
# AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8
# +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud
# DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w
# bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v
# c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np
# dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG
# CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM
# BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1
# BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR
# mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl
# lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs
# NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv
# n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZrMIIEU6ADAgECAhBX5gfP8LabvnxC
# XoItEBa/MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB
# c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp
# Z25pbmcgMjAyMSBDQTAeFw0yNjEwMDYwODI4MjNaFw0yNzEwMDYwODI4MjJaMIGQ
# MQswCQYDVQQGEwJERTEdMBsGA1UECAwUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQ
# BgNVBAcMCUxhaG5zdGVpbjEeMBwGA1UECgwVT3BlbiBTb3VyY2UgRGV2ZWxvcGVy
# MS4wLAYDVQQDDCVPcGVuIFNvdXJjZSBEZXZlbG9wZXIgVGhvbWFzIE5hdW5oZWlt
# MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAphp2ljSmVsRB6gvkEq7A
# DUMENpKZiDFoAfF1ktD1nCwaVyI+bJ0ROjHwAn4iJgS+vlBDPOUIw2GLHkiqUhOg
# HSwzg6gj45GNmt6zxsKP0zPlhMB/fb0zmz7/F7e3yfKCXLfhbHuk0Es9gJ+8Wmet
# 9StVwwSE/K0MpW06GKA9nPWyE4N/ADwigDIj9rsTKJR4cve9w8G1b3zohpUc/qMr
# onab+HVw5r9b3sOBnnaXC6zvrX4fovRz1ztwfWjRjILLoFG6mW/TuK5F2ICD3Rhl
# 9R2rJWWWhzTDMsQlzvX+/SaM74q3OFpIVNdhvtmWU9pKUBRAyYMijcDqKEFGSzrc
# 8JFmGMpP5TuroWF+N21d3px2eARvW1mV0DCfFAH+Og20SH/gVW6/ltQQEkH/z/Zt
# 49ARyJrgESLOQtlMCIYSoKISdkk4ml4VUS1E/PcbzXEYj3a7m6DOxcd3WuY03UT9
# cnk3p0LMN6LeLYAZgVY+HRloZlH+kW2SkOHNuWgtXvZhAgMBAAGjggF4MIIBdDAM
# BgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jY3NjYTIwMjEu
# Y3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUFBwEBBGcwZTAsBggr
# BgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1bS5jb20wNQYIKwYB
# BQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jY3NjYTIwMjEuY2Vy
# MB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0GA1UdDgQWBBTBvGyH
# MXj1dMbVnxuof9oP4PY7cDBLBgNVHSAERDBCMAgGBmeBDAEEATA2BgsqhGgBhvZ3
# AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0dW0ucGwvQ1BTMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
# AAOCAgEAkC165c7/r6y3VeEEpNXas9bQs6ZpKPdJffHkf9xoPi4kWvAIWGc4ocG1
# DJLb18RXiDYShDmT5sb/6I08FeipKi2DNJkuOq6yOTJ3S7+hNFIZCyd6i7WKO8ea
# P5I+FAbyJP4+5coZoMg45bUa9JoocaJCm+5n7WiCfN7/EQpClW5Trm4+ZMbp7EMN
# F1uLJgGCMnN0SsoUarp7PHlqupMxMC4Uhhe+lAP2ytcSIU81L97/qjrADbLWqICk
# 9il5iGM+aZndguq/cmfH9rJj4GRZHWnG6WwnzYD9+KgYlRI6em+/9dq+IRiXe2Bs
# thUuIaFW7U2gWw8tBTEYm1UBLHdJx+L1gjJCLs5KT/wy5wU32wZxNDqPUQejUC1x
# GvU3cKqmLgRq+yNf3gFVfzqIoggyM3U9wQvnk7nBtCkpjXS2hdx4yxzcmrlWsDHr
# z6+6/jeyQySzbYUlFmKnc4n9Mw2soHOajEua5poFARVRS5KgUBuiiQ21YFNYzWBo
# qGuECaaAI/C5rGxapMJ3dAOBnHw6NLqBet+ZXBJEccX5y89MLlOQu+jDmJP8HieR
# qDLlwfhVZnw4RY8Rus43NZBwhNlVuOUXqocHKmJhKNlekda3qsJj93eQBQOxKJcc
# w5O08q9jukpH2+yzFAj+7ll33DuNUgWptogc4Awtbx3glzcEvJQwggaCMIIEaqAD
# AgECAhAo8HfBHDa9/l90MkdwJy4DMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYT
# AlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMT
# G0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQTAeFw0yNjAzMTEwNzM0NTRaFw0z
# NjAyMjcwNzM0NTRaMFAxCzAJBgNVBAYTAlBMMSEwHwYDVQQKDBhBc3NlY28gRGF0
# YSBTeXN0ZW1zIFMuQS4xHjAcBgNVBAMMFUNlcnR1bSBUaW1lc3RhbXAgMjAyNjCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi9zfBw+xI12hi2F9uNQBNO
# STkTIi13fuh3DiWybI5392bBfJw6tO7zDs7UqP/rejVf4xFTU3XNOPsuOFkP5SPF
# snWwn3VJL+NhNhsuujvYsQtaQnvNo1wk46reTgws1L8OFEdKaxgyqT4zOQx81mLw
# NZSqXNU9Rrb9oAJWJF4Mydz+C1ebu0D0+vgM3tqAQ8TU203fqgKCPqIdHloaFqH9
# eyR+8tnpmy2hnnN59ZOtmsuHeRwf5iTOhlhf0d/qqquLOSIGPgGVL1Hj5XBUox2O
# E7tQRr+l/xxNbL6OuwBO4Aa2EbuxYg37cpXv5i2/lTL4NASB/duf9W4fjA3Ro+GP
# +bC54n2OYiM3Cq8z92ejx8F5HWO2e4/75eC95B9tMw/eq7109GDyW2+2ut5Bn0o8
# MXJTERkyystWbqcnUkomA97qfiDCwvpWuOMdfTOksXe9S+xDxOJaCbiyTivgPulx
# cT+tfQTRXBr5UwtPvc+INnafOEilzVHA11tv+pHcylryxBc08cUihf/ttqkZS0bm
# x3VxQay8uohaTp0//ECA9vYGW51SKdJMJhXvKyIdBLy8WYNchTt8yRITv9Adxg9J
# qGujh+TnP1bW3aQjFtlEl7SzgNeI8OVA/CJAWWUuRT3Au9ToOpHy/flrrknhN4he
# Ew7/6ILMv4HSsj0YZ4otAgMBAAGjggFQMIIBTDB1BggrBgEFBQcBAQRpMGcwOwYI
# KwYBBQUHMAKGL2h0dHA6Ly9zdWJjYS5yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdHNj
# YTIwMjEuY2VyMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u
# Y29tMB8GA1UdIwQYMBaAFL5UAi+/QGxzQ86sCSVOnkNEGu7gMAwGA1UdEwEB/wQC
# MAAwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3N1YmNhLmNybC5jZXJ0dW0ucGwv
# Y3RzY2EyMDIxLmNybDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E
# BAMCB4AwIgYDVR0gBBswGTAIBgZngQwBBAIwDQYLKoRoAYb2dwIFAQswHQYDVR0O
# BBYEFCM5aiirmhKnpKfyH/lFmr+N/oIDMA0GCSqGSIb3DQEBDAUAA4ICAQBm/pOb
# rAveTqYFPCSthxNVwBHTW4TfuBQY2KGCdMlH6ALjVPYf2XARNGMTXScR0JQ2/c2L
# xLpoHHhRwxFf3d933DhFS165rYj6iPSQKm41rc9AMpTCjMdmai1aK9o870pOzTzL
# RhWFVIxm+5Qf+t4V5z14tPoUsAWdfRKD1Zzvzfe5AHlE9rxOZcGROiULxs3Edf7Z
# J8RZI0jzD9juj2oEX2XS7BVKuNCK1W0h1mjapv028yqo8UtZh5/1Ib48Yyp88BDY
# Shh5PSGUUuS5tYXVfcduIPBe3PdNTYRYdT5VNxkwLgjMzIcr/6SH2fejFJDjnCxj
# vXLuarErJg/IdI+hXdGIBDVOGv1zrkF4ktoQvFZIp5DIAYh0GwyUhc87qqn5UaYT
# j2+iyiBDSNh7aGCrkuAnqg7Qh7frhX+QhMGeJ9SpLpnG76qN85hLyjvYR6gRYiLT
# j8G4fxjqV400/buPw0rBo9yJZyq/8cGdCOLPOYf9qpefTcrZKeO9pg+UJHx8UABb
# aTqizRxm8sUSEycccCCFhIit3AI/3wGuSOADeiA676NcQ7+dsP04YvzvdN7pTZNy
# g672WpBDJdg0eItHl4yomvNeIOWh1qs4QqOW6TI8Ocsi4uSDTLorxLUDnBTwKQzz
# pxd1YUctEgRh32R0J9FkFkeEruJU94RljFr1uDCCBrkwggShoAMCAQICEQCZo4AK
# JlU7ZavcboSms+o5MA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAG
# A1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVt
# IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3Rl
# ZCBOZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjE4WhcNMzYwNTE4MDUzMjE4WjBW
# MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
# MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQCdI88EMCM7wUYs5zNzPmNdenW6vlxNur3r
# Lfi+5OZ+U3iZIB+AspO+CC/bj+taJUbMbFP1gQBJUzDUCPx7BNLgid1TyztVLn52
# NKgxxu8gpyTr6EjWyGzKU/gnIu+bHAse1LCitX3CaOE13rbuHbtrxF2tPU8f253Q
# gX6eO8yTbGps1Mg+yda3DcTsOYOhSYNCJiL+5wnjZ9weoGRtvFgMHtJg6i671OPX
# IciiHO4Lwo2p9xh/tnj+JmCQEn5QU0NxzrOiRna4kjFaA9ZcwSaG7WAxeC/xoZSx
# F1oK1UPZtKVt+yrsGKqWONoK6f5EmBOAVEK2y4ATDSkb34UD7JA32f+Rm0wsr5aj
# zftDhA5mBipVZDjHpwzv8bTKzCDUSUuUmPo1govD0RwFcTtMXcfJtm1i+P2UNXad
# PyYVKRxKQATHN3imsfBiNRdN5kiVVeqP55piqgxOkyt+HkwIA4gbmSc3hD8ke66t
# 9MjlcNg73rZZlrLHsAIV/nJ0mmgSjBI/TthoGJDydekOQ2tQD2Dup/+sKQptalDl
# ui59SerVSJg8gAeV7N/ia4mrGoiez+SqV3olVfxyLFt3o/OQOnBmjhKUANoKLYlK
# mUpKEFI0PfoT8Q1W/y6s9LTI6ekbi0igEbFUIBE8KDUGfIwnisEkBw5KcBZ3XwnH
# mfznwlKo8QIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# 3XRdTADbe5+gdMqxbvc8wDLAcM0wHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61Pgc
# ptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDAGA1Ud
# HwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYI
# KwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0
# dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv
# Y3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0
# cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQB1iFgP5Y9Q
# KJpTnxDsQ/z0O23JmoZifZdEOEmQvo/79PQg9nLF/GJe6ZiUBEyDBHMtFRK0mXj3
# Qv3gL0sYXe+PPMfwmreJHvgFGWQ7XwnfMh2YIpBrkvJnjwh8gIlNlUl4KENTK5DL
# qsYPEtRQCw7R6p4s2EtWyDDr/M58iY2UBEqfUU/ujR9NuPyKk0bEcEi62JGxauFY
# zZ/yld13fHaZskIoq2XazjaD0pQkcQiIueL0HKiohS6XgZuUtCKA7S6CHttZEsOb
# QJ1j2s0urIDdqF7xaXFVaTHKtAuMfwi0jXtF3JJphrJfc+FFILgCbX/uYBPBlbBI
# P4Ht4xxk2GmfzMn7oxPITpigQFJFWuzTMUUgdRHTxaTSKRJ/6Uh7ki/pFjf9sUAS
# WgxT69QF9Ki4JF5nBIujxZ2sOU9e1HSCJwOfK07t5nnzbs1LbHuAIGJsRJiQ6HX/
# DW1XFOlXY1rc9HufFhWU+7Uk+hFkJsfzqBz3pRO+5aI6u5abI4Qws4YaeJH7H7M8
# X/YNoaArZbV4Ql+jarKsE0+8XvC4DJB+IVcvC9Ydqahi09mjQse4fxfef0L7E3hh
# o2O3bLDM6v60rIRUCi2fJT2/IRU5ohgyTch4GuYWefSBsp5NPJh4QRTP9DC3gc5Q
# EKtbrTY0Ka87Web7/zScvLmvQBm8JDFpDjCCBrkwggShoAMCAQICEQDn/2nHOzXO
# S5Em2HR8aKWHMA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UE
# ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
# cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBO
# ZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjA3WhcNMzYwNTE4MDUzMjA3WjBWMQsw
# CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQw
# IgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0EwggIiMA0GCSqGSIb3
# DQEBAQUAA4ICDwAwggIKAoICAQDpEh8ENe25XXrFppVBvoplf0530W0lddNmjtv4
# YSh/f7eDQKFaIqc7tHj7ox+u8vIsJZlroakUeMS3i3T8aJRC+eQs4FF0GqvkM6+W
# ZO8kmzZfxmZaBYmMLs8FktgFYCzywmXeQ1fEExflee2OpbHVk665eXRHjH7MYZIz
# Nnjl2m8Hy8ulB9mR8wL/W0v0pjKNT6G0sfrx1kk+3OGosFUb7yWNnVkWKU4qSxLv
# 16kJ6oVJ4BSbZ4xMak6JLeB8szrK9vwGDpvGDnKCUMYL3NuviwH1x4gZG0JAXU3x
# 2pOAz91JWKJSAmRy/l0s0l5bEYKolg+DMqVhlOANd8Yh5mkQWaMEvBRE/kAGzIqg
# WhwzN2OsKIVtO8mf5sPWSrvyplSABAYa13rMYnzwfg08nljZHghquCJYCa/xHK9a
# cev9UD7Y+usr15d7mrszzxhF1JOr1Mpup2chNSBlyOObhlSO16rwrffVrg/SzaKf
# SndS5swRhr8bnDqNJY9TNyEYvBYpgF95K7p0g4LguR4A++Z1nFIHWVY5v0fNVZmg
# zxD9uVo/gta3onGOQj3JCxgYx0KrCXu4yc9QiVwTFLWbNdHFSjBCt5/8Q9pLuRhV
# ocdCunhcHudMS1CGQ/Rn0+7P+fzMgWdRKfEOh/hjLrnQ8BdJiYrZNxvIOhM2aa3z
# EDHNwwIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvlQC
# L79AbHNDzqwJJU6eQ0Qa7uAwHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61PgcptE6
# Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMDAGA1UdHwQp
# MCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYIKwYB
# BQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u
# Y29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3Ru
# Y2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov
# L3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC4k1l3yUwV/ZQH
# CKCneqAs8EGTnwEUJLdDpokN/dMhKjK0rR5qX8nIIHzxpQR3TAw2IRw1Uxsr2Pli
# G3bCFqSdQTUbfaTq6V3vBzEebDru9QFjqlKnxCF2h1jhLNFFplbPJiW+JSnJTh1f
# KEqEdKdxgl9rVTvlxfEJ7exOn25MGbd/wGPwuSmMxRJVO0wnqgS7kmoJjNF9zqee
# hFSDDP8ZVkWg4EZ2tIS0M3uZmByRr+1Lkwjjt8AtW83mVnZTyTsOb+FNfwJY7DS4
# FmWhkRbgcHRetreoTirPOr/ozyDKhT8MTSTf6Lttg6s6T/u08mDWw6HK04ZRDfQ9
# sb77QV8mKgO44WGP31vXnVKoWVJpFBjPvjL8/Zck/5wXX2iqjOaLStFOR/IQki+E
# hn4zlcgVm22ZVCBPF+l8nAwUUShCtKuSU7GmZLKCmmxQMkSiWILTm8EtVD6AxnJh
# oq8EnhjEEyUoflkeRF2WhFiVQOmWTwZRr44IxWGkNJC6tTorW5rl2Zl+2e9JLPYf
# 3pStAPMDoPKIjVXd6NW2+fZrNUBeDo2eOa5Fn7Brs/HLQff5Xgris5MeUbdVgDrF
# 8uxO6cLPvZPo63j62SsNg55pTWk9fUIF9iPoRbb4QurjoY/woI1RAOKtYtTic6aA
# Jq3u83RIPpGXBSJKwx4KJAOZnCDCtTGCBqIwggaeAgEBMGowVjELMAkGA1UEBhMC
# UEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMb
# Q2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBX5gfP8LabvnxCXoItEBa/MA0G
# CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI
# hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC
# ARUwLwYJKoZIhvcNAQkEMSIEIBUuYxlE0R5p21mQh8ss8Sgefqm3Me7OjLkaKm6e
# aFRBMA0GCSqGSIb3DQEBAQUABIIBgD0qNYuVQ05Liu5Yjmxc7Pa5dal09sASeUm3
# EJQk/+w5YLYx99548jAEujRZqSxa1Y7PDi9ukuHuDnEyi2u5R20eEu6hYkqCmrKZ
# 2EZgTQ0ZLmTs0IOzierqgZrMfpyTT5AE6W3mvkDjfbtfhLSEK1WdvqC5B8QL4XHm
# +GHYbmFVTtOb6S59aKnXJ9Uxtd1Qt2Q2sbx1bs276Geb+Dx20SgQhvicb7h0Q7KB
# 6+qNKD54FWftVRRvAsgAAcFgb/yUPfWUlLkGk5zRiNtqaVEWYY+e0hzro3z2M1WM
# BZ9A6HxitHRIoOkDQk2TfkndMzl3SQ9EqWlZlgcZyJulGm15YouF97REJjqX8qkQ
# vQ92juWnytaQh4vQuTBdgcnfeoWQWcqU+foHiG7qjWYqLgyJAW6ltgwksEsgesnw
# Yk149EKAc0aQ6W4NGoW6kZfznkb3drwvYqaGaOF4hPNA8F0bipFrimW8jp3TBFke
# 4Rsvbrw8CcGQP58YJthELE4hAyULQaGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID
# 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0
# ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ
# KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQxNDVaMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY
# 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDAcoEz3bFKDFvdFcQ53W08FCCuciQ5fwLbN
# jLGBTXIDMzAagdwlp/2W0DCzqjjaytkwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ
# MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh
# MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0
# dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI
# hvcNAQEBBQAEggIAtDD5QbzrHdZSnpxYfQ7cwP6Fuwm2SANxv8JhaIW8j7tC23fm
# unhaYhPc+6PuJXXCSnouwK8jQuIS1tRIGYy4laENOuSL9qKobEFwiwrrB7/7+2VK
# qneR1oWaeEK4xWJsQuTwNtZ04k1q4c6sDmmK7QAO4R8VLbR1O9Kr4maTTCxB49f7
# tjSSagrbqCHzwHbWVwkLkqZWJW/sRsBiVRx9YjK/9G+QOcTLBYvA17kkZElyMljt
# C1qX5N/CcrMD51+ltvOXjBUXYmJqf+6MBkZjblIx0ycFHoD1S+Uy2ka/OALBluv9
# L1ikvkTp0bnusPLktF/K9zICQsrLeyxZPeU+F4DBYT5YHuMS9TVIA+4qkBblN9+8
# +riP3Z53cEc6GDPC23paWK+8C1ROqUUC7OStkGUlAl1QevjsrKoQKfRFULiKIVE3
# lhRqMsMSLG1o7GB1e2igY8piRXn9ijoMyG6pWkvt10/65BECrQKjRDvcSDQkn8Fg
# lHhjjgf3gcOl1lqOTnkT2mK9xOYdtJID0cTTkQexGM2ZaclbnO7fYxb7ffgpspAv
# tvIigaY0nAeeEGaaWDsYpE/gd7nJTtcaf16AJgnZpgPMrAd2Q7FIsHPIAz7WDJmy
# KxUJF7sTYO27Wr34eYvbH2emXJnhwVdQX/XU4ijXdalljinGFXes1rn0Hfs=
# SIG # End signature block