Public/PrivilegedAccess/Save-EntraOpsPrivilegedEAMJson.ps1

<#
.SYNOPSIS
    Export and save EntraOps Privileged EAM data to JSON files.

.DESCRIPTION
    Get information from EntraOps about classification based on Enterprise Access Model and save them as JSON to folder.

.PARAMETER ExportFolder
    Folder where the JSON files should be stored. Default is ./PrivilegedEAM.

.PARAMETER RbacSystems
    Array of RBAC systems to be processed. Default is Azure, AzureBilling, EntraID, IdentityGovernance, DeviceManagement, ResourceApps.

.PARAMETER UseCache
    Use existing cache instead of clearing it before analysis. Default is $false (cache is cleared for fresh data).
    Set to $true to leverage cached data from previous calls for better performance.

.PARAMETER DefaultCacheTTL
    Cache TTL (in seconds) for regular API responses during this execution. Default is 7200 (2 hours).
    If not specified, uses 2 hours for this execution and restores session value afterwards.

.PARAMETER StaticDataCacheTTL
    Cache TTL (in seconds) for static data like role definitions during this execution. Default is 7200 (2 hours).
    If not specified, uses 2 hours for this execution and restores session value afterwards.

.PARAMETER ExportFailedRequests
    Export details of failed requests (Uri, StatusCode, ErrorMessage, Category) to a local
    FailedRequests_*.json file in the export folder. Category is either "ExhaustedRetries" (a request
    retried the maximum number of times for a transient/throttling condition and still failed) or
    "NonRetryable" (an expected data condition, e.g. a deleted access package catalog or a group not
    enabled for PIM for Groups). Default is $false, so no file is written locally by default.

.PARAMETER IncludeJustification
    Include the Justification property (documenting a manual classification overwrite) on all Classification
    entries of the exported objects. Default is $false, so the property is not present in the export at all.

.PARAMETER IncludeObjectDetails
    Include descriptive object details in console output. Defaults to ConsoleOutput.IncludeObjectDetails from
    EntraOpsConfig.json. Object IDs are always shown.

.EXAMPLE
    Export and save JSON files of EntraOps to default folder
    Save-EntraOpsPrivilegedEAMJson

.EXAMPLE
    Export using cached data for better performance
    Save-EntraOpsPrivilegedEAMJson -UseCache $true

.EXAMPLE
    Run with custom cache TTL of 6 hours
    Save-EntraOpsPrivilegedEAMJson -DefaultCacheTTL 21600 -StaticDataCacheTTL 21600

.EXAMPLE
    Export failed request details to a local FailedRequests_*.json file
    Save-EntraOpsPrivilegedEAMJson -ExportFailedRequests $true
#>


function Save-EntraOpsPrivilegedEAMJson {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [System.String]$ExportFolder = $DefaultFolderClassifiedEam
        ,
        [Parameter(Mandatory = $False)]
        [ValidateSet("Azure", "EntraID", "IdentityGovernance", "DeviceManagement", "ResourceApps", "Defender")]
        [Array]$RbacSystems = ("Azure", "EntraID", "IdentityGovernance", "DeviceManagement", "ResourceApps", "Defender")
        ,
        [Parameter(Mandatory = $false)]
        [System.Boolean]$UseCache = $false
        ,
        [Parameter(Mandatory = $False)]
        [System.Int32]$DefaultCacheTTL = 7200 # Default is 2 hours for this cmdlet
        ,
        [Parameter(Mandatory = $False)]
        [System.Int32]$StaticDataCacheTTL = 7200 # Default is 2 hours for this cmdlet
        ,
        [Parameter(Mandatory = $False)]
        [System.Boolean]$ExportFailedRequests = $false
        ,
        [Parameter(Mandatory = $False)]
        [System.Boolean]$IncludeObjectDetails = [bool]$Global:EntraOpsIncludeObjectDetails
        ,
        [Parameter(Mandatory = $False)]
        [switch]$IncludeJustification
    )

    # Store original TTL values to restore after execution
    $OriginalDefaultTTL = $__EntraOpsSession.DefaultCacheTTL
    $OriginalStaticTTL = $__EntraOpsSession.StaticDataCacheTTL

    # Use provided values (default 2 hours for this cmdlet)
    $EffectiveDefaultTTL = $DefaultCacheTTL
    $EffectiveStaticTTL = $StaticDataCacheTTL

    # Set TTL values for this execution
    $__EntraOpsSession.DefaultCacheTTL = $EffectiveDefaultTTL
    $__EntraOpsSession.StaticDataCacheTTL = $EffectiveStaticTTL

    # Notify user about TTL changes
    if ($EffectiveDefaultTTL -ne $OriginalDefaultTTL -or $EffectiveStaticTTL -ne $OriginalStaticTTL) {
        Write-Host ""
        Write-Host "════════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
        Write-Host " ⏱️ Cache TTL Modified for This Execution" -ForegroundColor Cyan
        Write-Host "════════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
        Write-Host " Default Cache TTL : $([Math]::Round($EffectiveDefaultTTL / 3600, 1)) hours (was $([Math]::Round($OriginalDefaultTTL / 3600, 1)) hours)" -ForegroundColor Yellow
        Write-Host " Static Data Cache TTL: $([Math]::Round($EffectiveStaticTTL / 3600, 1)) hours (was $([Math]::Round($OriginalStaticTTL / 3600, 1)) hours)" -ForegroundColor Yellow
        Write-Host " ℹ️ Cache will be restored to original values after completion" -ForegroundColor Gray
        Write-Host "════════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
        Write-Host ""
    }

    try {
        if (-not $UseCache) {
            Write-Output "Clearing cache before analyzing RBAC and classification data"
            Clear-EntraOpsCache
        } else {
            Write-Output "Using existing cache for analysis (UseCache = $true)"
            Write-Verbose "Current cache contains $($__EntraOpsSession.GraphCache.Count) entries"
        }

        #region Entra ID
        if ($RbacSystems -contains "EntraID") {
            $EamAzureAD = Get-EntraOpsPrivilegedEAMEntraId -IncludeJustification:$IncludeJustification -IncludeObjectDetails $IncludeObjectDetails
            Save-EntraOpsEAMRbacSystemJson -ExportFolder "$($ExportFolder)/EntraID" -RbacSystemName "EntraID" -EamData $EamAzureAD -AggregateFileName "EntraID.json"
        }
        #endregion

        #region Entra Resource Apps
        if ($RbacSystems -contains "ResourceApps") {
            $EamAzureAdResourceApps = Get-EntraOpsPrivilegedEAMResourceApps -IncludeJustification:$IncludeJustification -IncludeObjectDetails $IncludeObjectDetails
            Save-EntraOpsEAMRbacSystemJson -ExportFolder "$($ExportFolder)/ResourceApps" -RbacSystemName "ResourceApps" -EamData $EamAzureAdResourceApps -AggregateFileName "ResourceApps.json"
        }
        #endregion

        #region Device Management
        if ($RbacSystems -contains "DeviceManagement") {
            $EamDeviceMgmt = Get-EntraOpsPrivilegedEAMIntune -IncludeJustification:$IncludeJustification -IncludeObjectDetails $IncludeObjectDetails
            Save-EntraOpsEAMRbacSystemJson -ExportFolder "$($ExportFolder)/DeviceManagement" -RbacSystemName "DeviceManagement" -EamData $EamDeviceMgmt -AggregateFileName "DeviceManagement.json"
        }
        #endregion

        #region Identity Governance
        if ($RbacSystems -contains "IdentityGovernance") {
            $EamIdGov = Get-EntraOpsPrivilegedEAMIdGov -IncludeJustification:$IncludeJustification -IncludeObjectDetails $IncludeObjectDetails
            Save-EntraOpsEAMRbacSystemJson -ExportFolder "$($ExportFolder)/IdentityGovernance" -RbacSystemName "IdentityGovernance" -EamData $EamIdGov -AggregateFileName "IdentityGovernance.json"
        }
        #endregion
        #region Defender
        if ($RbacSystems -contains "Defender") {
            $EamDefender = Get-EntraOpsPrivilegedEAMDefender -IncludeJustification:$IncludeJustification -IncludeObjectDetails $IncludeObjectDetails
            Save-EntraOpsEAMRbacSystemJson -ExportFolder "$($ExportFolder)/Defender" -RbacSystemName "Defender" -EamData $EamDefender -AggregateFileName "Defender.json"
        }
        #endregion

        #region Azure
        # Azure RBAC is collected last so classification of managed identities (resolved from the other RBAC
        # systems) is available beforehand. Get-EntraOpsPrivilegedEAMAzure uses the Azure (Get-AzContext) tenant.
        if ($RbacSystems -contains "Azure") {
            $EamAzure = Get-EntraOpsPrivilegedEAMAzure -IncludeJustification:$IncludeJustification -IncludeObjectDetails $IncludeObjectDetails
            Save-EntraOpsEAMRbacSystemJson -ExportFolder "$($ExportFolder)/Azure" -RbacSystemName "Azure" -EamData $EamAzure -AggregateFileName "Azure.json"
        }
        #endregion

        # Display Throttle Statistics Summary
        if ($__EntraOpsSession.ContainsKey('RetryStatistics') -and ($__EntraOpsSession.RetryStatistics.TotalRetries -gt 0 -or $__EntraOpsSession.RetryStatistics.FailedRequests -gt 0 -or $__EntraOpsSession.RetryStatistics.NonRetryableRequests -gt 0)) {
            Write-Host ""
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Yellow
            Write-Host " ⚠ API Throttling Summary" -ForegroundColor Yellow
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Yellow

            $Stats = $__EntraOpsSession.RetryStatistics
            Write-Host " Total Retries : $($Stats.TotalRetries)" -ForegroundColor Yellow
            Write-Host " Throttled Requests : $($Stats.ThrottledRequests)" -ForegroundColor Yellow

            # Requests that failed immediately on a non-retryable status (404 for a deleted access
            # package catalog/directory object, 400 for a group not enabled for PIM for Groups, etc.).
            # These are expected data conditions the caller already accounts for (falls back or logs a
            # dedicated warning), so they are reported for visibility only and never fail the run.
            if ($Stats.NonRetryableRequests -gt 0) {
                Write-Host " ℹ Non-Retryable Requests (expected, e.g. deleted/unsupported resources): $($Stats.NonRetryableRequests)" -ForegroundColor Yellow
            }

            $FailedRequestDetails = @($Stats.FailedRequestDetails)
            $NonRetryableRequestDetails = @($Stats.NonRetryableRequestDetails)
            $AllRequestDetails = @($FailedRequestDetails | ForEach-Object { $_ | Add-Member -NotePropertyName Category -NotePropertyValue "ExhaustedRetries" -Force -PassThru }) +
                @($NonRetryableRequestDetails | ForEach-Object { $_ | Add-Member -NotePropertyName Category -NotePropertyValue "NonRetryable" -Force -PassThru })

            $ExportedLogPath = $null
            if ($AllRequestDetails.Count -gt 0 -and $ExportFailedRequests) {
                $ExportedLogPath = Join-Path -Path $ExportFolder -ChildPath "FailedRequests_$(Get-Date -Format 'yyyyMMdd_HHmmss').json"
                $AllRequestDetails | Sort-Object Timestamp | ConvertTo-Json -Depth 5 | Out-File -FilePath $ExportedLogPath -Force
                Write-Host " Details exported to : $ExportedLogPath" -ForegroundColor Yellow
                Write-Host ""
            }

            if ($NonRetryableRequestDetails.Count -gt 0) {
                Write-Host " Non-retryable request(s):" -ForegroundColor Yellow
                # Grouped by (StatusCode, GUID-normalized Uri) so e.g. 30 deleted Access Package Catalogs
                # collapse into a single summarized line instead of one line per catalog ID.
                foreach ($Group in (Group-EntraOpsRequestDetailsByUriTemplate -RequestDetails $NonRetryableRequestDetails | Sort-Object StatusCode, NormalizedUri)) {
                    Write-EntraOpsGroupedRequestDetail -Group $Group -Color "Yellow" -DetailColor "DarkYellow" -IncludeObjectDetails $IncludeObjectDetails
                }
            }

            if ($Stats.FailedRequests -gt 0) {
                Write-Host " ❌ Failed Requests : $($Stats.FailedRequests)" -ForegroundColor Red

                if ($FailedRequestDetails.Count -gt 0) {
                    Write-Host " Failed request(s):" -ForegroundColor Red
                    foreach ($Group in (Group-EntraOpsRequestDetailsByUriTemplate -RequestDetails $FailedRequestDetails | Sort-Object StatusCode, NormalizedUri)) {
                        Write-EntraOpsGroupedRequestDetail -Group $Group -Color "Red" -DetailColor "DarkRed" -IncludeObjectDetails $IncludeObjectDetails
                    }
                }
                # Intended non-terminating post-export summary (exports above already completed);
                # -ErrorAction Continue keeps these Write-Error calls non-terminating despite the
                # module-wide $ErrorActionPreference = "Stop"
                if ($ExportedLogPath) {
                    Write-Error "Some requests failed completely after exhausting retries. See '$ExportedLogPath' for details." -ErrorAction Continue
                } else {
                    Write-Error "Some requests failed completely after exhausting retries. Re-run with -ExportFailedRequests `$true to export details to a local JSON file." -ErrorAction Continue
                }
            } elseif ($Stats.NonRetryableRequests -eq 0) {
                Write-Host " ✓ All requests succeeded after retries" -ForegroundColor Green
            }
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Yellow
            Write-Host ""
        }
    } finally {
        # Restore original TTL values
        $__EntraOpsSession.DefaultCacheTTL = $OriginalDefaultTTL
        $__EntraOpsSession.StaticDataCacheTTL = $OriginalStaticTTL
    
        Write-Verbose "Cache TTL restored to original values: Default=$([Math]::Round($OriginalDefaultTTL / 3600, 1))h, Static=$([Math]::Round($OriginalStaticTTL / 3600, 1))h"
    }
}
# SIG # Begin signature block
# MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD9pazr0ALD1pHN
# 8FEHniufs63+c7oD5R29KTKpzFv6QqCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j
# 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
# ARUwLwYJKoZIhvcNAQkEMSIEIBzuwQfkwZ9skgN9uWfiZJZQieUCNNrhxZGsyX2C
# tCQ8MA0GCSqGSIb3DQEBAQUABIIBgFOuZqOaHp2KZ53Hrt6rgOkIhh06PQ5QnrR+
# ooxnM2TcayvtMQYKDr13JaulOo1KddWo2N3qs1lvJfi6z3sv8wMSsicvvhe4iVas
# 4Pln6oSlVi8Jimg3ubcE/Y4dOl9bp9soYhJm4W6v7iia65W4VyDFZVJ82uOUEyPT
# BcJWn9XtKm55sJ7i/e6B6EodpmBMKo2y/OAgoaNKTjKXAZ3qVGgTLxutq/F/bIp+
# ZSWjWEq8TlrOJuj1IxSynEXWpPgCt88xvQoqB/4HmlGIEdvNAZcv4F8tJ/lfqraj
# JMaWTwZNIW7nT5Ev7q/R5PCNvo5MCXjQlqJEvvRNFKv5kwzAUdbysh3xWLzockuv
# tfZKKUpPJ0ev1rF0eD8rEeUfeRZDqe5fFSb9Y5LKYokfIv/24bbWoDNtN3m3BgLe
# S27aD8s1mtnVDKAOA3JllkxIK6tJPxmaUMEDQs97k5nwMbXIx3zzJsTMr+NL7pt3
# IpmSCptFwViuTnTkVDIvxvdas3gcUqGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID
# 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0
# ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ
# KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTQxMzdaMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY
# 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDCqLNY7duf0mwANsdMuRQsdBay91G5Z33X9
# wq9JKjeL5stZetr/XrfauXFruIvaq+UwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ
# MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh
# MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0
# dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI
# hvcNAQEBBQAEggIAW6IOEchGvg3s1GHYM5mNOGrO/oWdB29j0AwpiHwnG7oxqMtj
# j4TtaSNMFhycOR4A2hVJ16iudLkPNGXLP46uP3X5eAnF7WoJf0wtnxnxevxLJL8R
# 5HhSTrwz5Oh2X2fPKbZfCXyPOjsEcG5GxhIUXrbSmlFL+n8aUaXVHyjqDYfriPuz
# K+visRKzWIqWGrd60G9XF/xW7IrK/kQpq1pfUPmwqSCeqs8oXEjfHINPuBwxES5u
# LX7AKJCMJ9ifBRFAGlJtFwElaToobNcEMlIx6fQTOSv0b0l2RNbd2LHke7GA0t99
# BqIXwcht5DBO3Umjz7vDXUS+D7QIIFPc5HNQjhsAi766jPL7nqn++RWISU8vo0/A
# XGD9jo8OdPVV42X9avZchh46nlJAQQCceqLDD2OA/2tMmZf6yUFsqxh+qDVbFzK1
# DMhjZsKjGDfaMJpFeSGGI03RM19fs7G8QimsKI4IiE2SDPovaRojckqbQDf+R5j9
# KF+8nAdtLMktszmQ3bKckGqQMqEfMkKpBDahEgGaeRpHiaolwkFSlZmOjMbwA6jQ
# HdNVIHcaDNjFoSx7veDyqtmHfcfuJMXdCanHdOYsvlEf0tCERNGXcVvw5wB/HR9V
# bmWYV3uwx3F0pjU0v3cfVOtPXZIZBaY1XeFrXUd9pCjnXnqv61HY/uTXy4M=
# SIG # End signature block