Public/Core/Connect-EntraOps.ps1

<#
.SYNOPSIS
    Establishes connections to required PowerShell modules and requests EntraOps access tokens.
 
.DESCRIPTION
    Connection to Azure Resource Management and Microsoft Graph API by using Connect-AzAccount and Connect-MgGraph.
 
.PARAMETER AuthenticationType
    Type of authentication to be used for Azure and Microsoft Graph. Default is "AlreadyAuthenticated".
 
.PARAMETER UseInvokeRestMethodOnly
    Use Invoke-RestMethod instead of the Microsoft Graph SDK (Invoke-MgGraphRequest) for all
    Invoke-EntraOps*Query cmdlets in this session. The Microsoft Graph SDK is neither required nor
    installed in this mode; tokens are provided via -MsGraphAccessToken or acquired with
    Get-AzAccessToken from the Az PowerShell context. Can also be set through the config file
    (top-level setting "UseInvokeRestMethodOnly"); an explicit parameter wins over the config file.
    Note: parallel object resolution requires the Graph SDK and falls back to sequential processing
    in this mode. Connect-EntraOps itself also skips Connect-MgGraph for service-principal/managed-
    identity AuthenticationType values (SystemAssignedMSI, UserAssignedMSI, FederatedCredentials,
    AlreadyAuthenticated) in this mode - their Graph tokens come from Get-AzAccessToken instead, since
    application permissions are fully contained in a client-credentials token without needing
    per-request scopes. UserInteractive and DeviceAuthentication still call Connect-MgGraph (and thus
    still require Microsoft.Graph.Authentication) even with this switch enabled, because only
    Connect-MgGraph -Scopes can trigger the interactive/incremental consent prompt that delegated
    Graph scopes require; a warning is emitted when this fallback applies.
 
.PARAMETER Scope
    Delegated Microsoft Graph scopes requested for interactive sign-in. "Report" (default) requests read scopes,
    "ServiceEM" the ServiceEM write scopes and "ApplyProtection" the write scopes of the protection cmdlets of
    Push-EntraOpsPrivilegedEAM (administrative units, Conditional Access groups, catalog protection). Catalog
    protection (EntitlementManagement.ReadWrite.All) and ServiceEM administrative units (AdministrativeUnit.ReadWrite.All)
    are only requested when enabled in EntraOpsConfig.json.
 
.PARAMETER GraphApiVersion
    Microsoft Graph API version ("beta" or "v1.0"). Default is "beta". Reserved; currently not used by
    Connect-EntraOps.
 
.PARAMETER AccountId
    Application (client) ID of the user-assigned managed identity for AuthenticationType "UserAssignedMSI".
    For "AlreadyAuthenticated", the account ID used with -AzArmAccessToken and -MsGraphAccessToken.
 
.PARAMETER TenantName
    Name of the tenant to connect to (e.g. contoso.onmicrosoft.com). Mandatory.
 
.PARAMETER TenantId
    Tenant ID of the tenant to connect to. Taken from the TenantId of the loaded configuration file
    (see -ConfigFilePath) when not specified.
 
.PARAMETER ManagingTenantId
    Tenant ID of the governing (managing) tenant of a Tenant Governance relationship. Connect-EntraOps
    pre-authenticates to this tenant for cross-tenant classification. Taken from the loaded configuration
    file (see -ConfigFilePath) when not specified.
 
.PARAMETER ManagingTenantName
    Name of the governing (managing) tenant of a Tenant Governance relationship. Taken from the loaded configuration
    file (see -ConfigFilePath) when not specified.
 
.PARAMETER MultiTenantRepo
    Stores the Privileged EAM export in a tenant-specific subfolder (PrivilegedEAM/<TenantName>/) for
    repositories that contain data of multiple tenants. Default is false.
 
.PARAMETER AzArmAccessToken
    Azure Resource Manager access token for AuthenticationType "AlreadyAuthenticated". Used together with
    -AccountId and -MsGraphAccessToken to connect Az PowerShell with existing tokens.
 
.PARAMETER MsGraphAccessToken
    Microsoft Graph access token to use for this session, e.g. from a pipeline secret or federated token.
    With -UseInvokeRestMethodOnly, the token is used for the REST requests instead of a token from Get-AzAccessToken.
 
.PARAMETER ConfigFilePath
    Path to the EntraOpsConfig.json to load into the session ($EntraOpsConfig). Without this parameter,
    EntraOpsConfig.json in the current folder, otherwise in the EntraOps working folder, is loaded
    automatically unless its TenantName differs from -TenantName. The connection summary shows which
    file is used.
 
.PARAMETER NoWelcome
    Suppresses the welcome banner and the connection summary.
 
.EXAMPLE
    Using Interactive Sign-In of User with double authentication to Az PowerShell (Connect-AzAccount) and Microsoft Graph SDK (Connect-MgGraph)
    Connect-EntraOps -AuthenticationType "UserInteractive" -TenantName "contoso.onmicrosoft.com"
 
.EXAMPLE
    Using authenticated session to Az PowerShell (Connect-AzAccount) in GitHub workflow or any other workload identity environment to request access token for Microsoft Graph SDK (by Get-AzAccessToken) without any further initial authentication.
    Connect-EntraOps -AuthenticationType "AlreadyAuthenticated" -TenantName "contoso.onmicrosoft.com"
 
.EXAMPLE
    Using Managed Identity (User Assigned) to sign-in to Azure and Microsoft Graph
    Connect-EntraOps -AuthenticationType "UserAssignedMSI" -AccountId "00000000-0000-0000-0000-000000000000" -TenantName "contoso.onmicrosoft.com"
#>

function Connect-EntraOps {
    # -MsGraphAccessToken is a plaintext [string] by public contract (populated from workflow
    # secrets/federated tokens in CI pipelines); Connect-MgGraph -AccessToken requires a
    # SecureString, so the one-time in-memory conversion below is unavoidable without a breaking
    # change to the parameter type.
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'MsGraphAccessToken is an existing public string parameter fed by external pipeline secrets; converting it in memory for Connect-MgGraph -AccessToken does not expose a new plaintext secret, and changing the parameter to SecureString would break the public API.')]
    [cmdletbinding()]
    param (
        [Parameter(Mandatory = $False)]
        [ValidateSet('UserInteractive', 'SystemAssignedMSI', 'UserAssignedMSI', 'FederatedCredentials', 'AlreadyAuthenticated', 'DeviceAuthentication')]
        [System.String]$AuthenticationType = "AlreadyAuthenticated"
        ,
        [Parameter(Mandatory = $False)]
        [ValidateSet("Report", "ServiceEM", "ApplyProtection")]
        [System.String]$Scope = "Report"
        ,        
        [Parameter(Mandatory = $False)]
        [ValidateSet("beta", "v1.0")]
        [System.String]$GraphApiVersion = "beta"
        ,
        [Parameter(Mandatory = $False)]
        [System.String]$AccountId
        ,
        [Parameter(Mandatory = $True)]
        [ValidatePattern('^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$')]
        [System.String]$TenantName
        ,
        [Parameter(Mandatory = $False)]
        [ValidatePattern('^$|^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')]
        [System.String]$TenantId
        ,
        [Parameter(Mandatory = $False)]
        [ValidatePattern('^$|^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$')]
        [System.String]$ManagingTenantId
        ,
        [Parameter(Mandatory = $False)]
        [System.String]$ManagingTenantName
        ,
        [Parameter(Mandatory = $False)]
        [boolean]$MultiTenantRepo = $false
        ,
        [Parameter(Mandatory = $False)]
        [System.String]$AzArmAccessToken
        ,
        [Parameter(Mandatory = $False)]
        [System.String]$MsGraphAccessToken
        ,
        [Parameter(Mandatory = $False)]
        [ValidateScript({ Test-Path $_ })]
        [System.String]$ConfigFilePath
        ,
        [Parameter(Mandatory = $False)]
        [switch]$NoWelcome
        ,
        [Parameter(Mandatory = $False)]
        [switch]$UseInvokeRestMethodOnly       
    )

    Process {

        $ErrorActionPreference = "Stop"

        # Display welcome banner unless suppressed
        if (-not $NoWelcome) {
            # Robustly resolve module manifest path
            $ManifestPath = if ($MyInvocation.MyCommand.Module.ModuleBase) {
                Join-Path $MyInvocation.MyCommand.Module.ModuleBase "EntraOps.psd1"
            } else {
                # Fallback to relative path if module context is missing (e.g. running script directly)
                "$PSScriptRoot/../../EntraOps.psd1"
            }
            
            if (Test-Path $ManifestPath) {
                $ModuleManifest = Import-PowerShellDataFile $ManifestPath
                $ModuleVersion = $($ModuleManifest.ModuleVersion)
            } else {
                $ModuleVersion = "Unknown"
            }

            $PSEnvironment = "PowerShell " + ($PSVersionTable.PSEdition) + " " + ($PSVersionTable.PSVersion.ToString())

            # Splash art and a color map of the same shape: each map character sets the color of the art character at that position
            $SplashArt = @'
         . . . ______ __ ____
         |\/ \/| / ____/___ / /__________ _/ __ \____ _____
        _'-----'_ / __/ / __ \/ __/ ___/ __ `/ / / / __ \/ ___/
       /_________/| / /___/ / / / /_/ / / /_/ / /_/ / /_/ (__ )
     __|_________|/_ /_____/_/ /_/\__/_/ \__,_/\____/ .___/____/
    /_______________/| /_/
  __|_______________|/_
 /_____________________/|
 |_____________________|/
'@
 -split '\r?\n'

            $SplashColorMap = @'
         C C C WWWWWW WW WWWW
         CCC CCC W WWWWWWWW W WWWWWWWWWWW WW WW WWWWW WWWWW
        0CCCCCCC0 W WWW W WW WW WWW WWWW WW WW W W W WW WW WWWW
       000000000000 W WWWWW W W W WWW W W WWW W WWW W WWW WWW W
     110000000000001 WWWWWWWWW WWWWWWWWW WWWWWWWWWWWW WWWWWWWWWW
    111111111111111111 WWW
  221111111111111111112
 222222222222222222222222
 222222222222222222222222
'@
 -split '\r?\n'

            # 24-bit colors on terminals with virtual terminal support, console colors as fallback
            $SplashUseRgb = $Host.UI.SupportsVirtualTerminal -and -not [Console]::IsOutputRedirected -and $PSStyle.OutputRendering -ne 'PlainText'
            $SplashColors = @{
                'C' = @{ Rgb = 0xF2C14E; Console = 'Yellow' }    # Crown (gold)
                '0' = @{ Rgb = 0xE5534B; Console = 'Red' }       # Top level (red)
                '1' = @{ Rgb = 0x4DA3E8; Console = 'Blue' }      # Middle level (blue)
                '2' = @{ Rgb = 0x57C26B; Console = 'Green' }     # Lowest level (green)
                'W' = @{ Console = 'Blue' }                      # EntraOps wordmark
            }
            $WriteSplashSegment = {
                param($Text, $ColorKey)
                $Color = $SplashColors[$ColorKey]
                if ($SplashUseRgb -and $Color.Rgb) {
                    Write-Host "$($PSStyle.Foreground.FromRgb($Color.Rgb))$Text$($PSStyle.Reset)" -NoNewline
                } else {
                    Write-Host $Text -ForegroundColor $Color.Console -NoNewline
                }
            }

            # Info lines are printed to the right of the pyramid base
            $SplashInfo = @{
                 6 = @("📦 Version $($ModuleVersion) on $($PSEnvironment)", 'Gray')
                 7 = @("👤 Community Project by Thomas Naunheim", 'Gray')
                 8 = @("🌐 www.entraops.com", 'Cyan')
            }

            for ($Row = 0; $Row -lt $SplashArt.Count; $Row++) {
                $Line = $SplashArt[$Row]
                $Map = $SplashColorMap[$Row]
                if ($SplashInfo.ContainsKey($Row)) { $Line = $Line.PadRight(31) }

                # Group characters of the same color into segments to keep the number of Write-Host calls low
                $Segment = ''
                $SegmentKey = 'W'
                for ($Col = 0; $Col -lt $Line.Length; $Col++) {
                    $ColorKey = if ($Col -lt $Map.Length -and $Map[$Col] -ne ' ') { [string]$Map[$Col] } else { $SegmentKey }
                    if ($ColorKey -ne $SegmentKey -and $Segment) {
                        & $WriteSplashSegment $Segment $SegmentKey
                        $Segment = ''
                    }
                    $SegmentKey = $ColorKey
                    $Segment += $Line[$Col]
                }
                & $WriteSplashSegment $Segment $SegmentKey

                if ($SplashInfo.ContainsKey($Row)) {
                    Write-Host $SplashInfo[$Row][0] -ForegroundColor $SplashInfo[$Row][1] -NoNewline
                }
                Write-Host ''
            }
        }

        #region Resolve EntraOpsConfig.json when -ConfigFilePath is not passed
        $ConfigFileStatus = if ($ConfigFilePath) { 'Parameter' } else { 'NotFound' }
        $ConfigFileSkipReason = $null
        if (-not $ConfigFilePath) {
            $ConfigCandidates = @(
                Join-Path (Get-Location -PSProvider FileSystem).ProviderPath 'EntraOpsConfig.json'
                if (-not [string]::IsNullOrWhiteSpace($EntraOpsBaseFolder)) { Join-Path $EntraOpsBaseFolder 'EntraOpsConfig.json' }
            ) | Select-Object -Unique
            $DetectedConfigPath = $ConfigCandidates | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
            if ($DetectedConfigPath) {
                try {
                    $DetectedConfig = Get-Content -LiteralPath $DetectedConfigPath -Raw | ConvertFrom-Json
                    $DetectedTenantName = if ($DetectedConfig.PSObject.Properties.Name -contains 'TenantName') { [string]$DetectedConfig.TenantName }
                    if (-not [string]::IsNullOrWhiteSpace($DetectedTenantName) -and $DetectedTenantName -ne $TenantName) {
                        $ConfigFileSkipReason = "TenantName '$DetectedTenantName' in $DetectedConfigPath does not match -TenantName '$TenantName'"
                    } else {
                        $ConfigFilePath = $DetectedConfigPath
                        $ConfigFileStatus = 'AutoDetected'
                    }
                } catch {
                    $ConfigFileSkipReason = "$DetectedConfigPath is not valid JSON: $($_.Exception.Message)"
                }
                if ($ConfigFileSkipReason) {
                    $ConfigFileStatus = 'Skipped'
                    Write-Warning "EntraOpsConfig.json is not used: $ConfigFileSkipReason. Pass -ConfigFilePath to load a configuration explicitly."
                }
            }
        }
        #endregion

        #region Load tenant and managing tenant settings from config file if not provided as parameters
        if ($ConfigFilePath -and (Test-Path $ConfigFilePath)) {
            $EarlyConfig = Get-Content -Path $ConfigFilePath | ConvertFrom-Json
            if ([string]::IsNullOrEmpty($TenantId) -and -not [string]::IsNullOrEmpty($EarlyConfig.TenantId)) {
                $TenantId = $EarlyConfig.TenantId
            }
            if ([string]::IsNullOrEmpty($ManagingTenantId) -and -not [string]::IsNullOrEmpty($EarlyConfig.ManagingTenantId)) {
                $ManagingTenantId = $EarlyConfig.ManagingTenantId
            }
            if ([string]::IsNullOrEmpty($ManagingTenantName) -and -not [string]::IsNullOrEmpty($EarlyConfig.ManagingTenantName)) {
                $ManagingTenantName = $EarlyConfig.ManagingTenantName
            }
        }
        #endregion

        #region Switch between Microsoft Graph SDK (Invoke-MgGraphRequest) and Azure PowerShell only in combination with Invoke-RestMethod
        # Resolve the effective mode: explicit parameter > config file setting > default ($false)
        if (-not $PSBoundParameters.ContainsKey('UseInvokeRestMethodOnly') -and $null -ne $EarlyConfig.UseInvokeRestMethodOnly) {
            $UseInvokeRestMethodOnly = [bool]$EarlyConfig.UseInvokeRestMethodOnly
            Write-Verbose "UseInvokeRestMethodOnly set from config file: $UseInvokeRestMethodOnly"
        }

        # The module-private session store is the authoritative home of the mode - all
        # Invoke-EntraOps*Query cmdlets read it from there (a user-set global variable is
        # only honored as legacy fallback when the session store has no value)
        $__EntraOpsSession['UseInvokeRestMethodOnly'] = [bool]$UseInvokeRestMethodOnly

        # Clean up the flag persisted as global variable by previous module versions
        Remove-Variable -Name UseInvokeRestMethodOnly -Scope Global -Force -ErrorAction SilentlyContinue

        # Start each connection with fresh token state: cached tokens from a previous connection may
        # belong to another tenant and would otherwise be re-used until they expire. Tenant-keyed ARM
        # token entries stay valid; only the context-dependent 'default' entry must go.
        $__EntraOpsSession.MsGraphTokenCache.Clear()
        $__EntraOpsSession.ArmTokenCache.Remove('default')

        # Persist an explicitly provided Microsoft Graph access token in the module-private session
        # store so the Invoke-RestMethod path of Invoke-EntraOpsMsGraphQuery can use it (required for
        # token-based authentication where the Az context cannot mint new Graph tokens itself).
        # Deliberately not a global variable: module scope keeps the token out of the user-visible
        # session state (Get-Variable, transcripts, diagnostic dumps).
        if (-not [string]::IsNullOrEmpty($MsGraphAccessToken)) {
            # Read the real expiry from the JWT exp claim; fall back to a conservative default
            $ProvidedTokenExpiry = [DateTime]::UtcNow.AddMinutes(50)
            try {
                $PayloadPart = ($MsGraphAccessToken -split '\.')[1].Replace('-', '+').Replace('_', '/')
                switch ($PayloadPart.Length % 4) { 2 { $PayloadPart += '==' } 3 { $PayloadPart += '=' } }
                $TokenPayload = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($PayloadPart)) | ConvertFrom-Json
                if ($TokenPayload.exp) { $ProvidedTokenExpiry = [DateTimeOffset]::FromUnixTimeSeconds($TokenPayload.exp).UtcDateTime }
            } catch {
                Write-Verbose "Could not parse expiry from provided Microsoft Graph access token: $($_.Exception.Message)"
            }

            $__EntraOpsSession.MsGraphTokenCache['provided'] = @{ Token = $MsGraphAccessToken; Expiry = $ProvidedTokenExpiry }

            # Clean up a token persisted as global variable by previous module versions
            Remove-Variable -Name MsGraphAccessToken -Scope Global -Force -ErrorAction SilentlyContinue
        }

        # Azure authentication and ARM access are required in every connection mode.
        @(
            @{ ModuleName = 'Az.Accounts'; ModuleVersion = '2.19.0' }
            @{ ModuleName = 'Az.Resources'; ModuleVersion = '6.16.2' }
        ) | ForEach-Object {
            Install-EntraOpsRequiredModule -ModuleName $_.ModuleName -MinimalVersion $_.ModuleVersion
        }

        # The Microsoft Graph SDK is only required (and installed) when it is actually used
        if (-not $__EntraOpsSession['UseInvokeRestMethodOnly']) {
            $RequiredCoreModules = @{
                ModuleName    = 'Microsoft.Graph.Authentication'
                ModuleVersion = '2.0.0'
            }
            # Recommendation 1: Validate module availability before installation check
            $RequiredCoreModules | ForEach-Object {
                if (-not (Get-Module -Name $_.ModuleName)) {
                    Install-EntraOpsRequiredModule -ModuleName $_.ModuleName -MinimalVersion $_.ModuleVersion
                }
            }
        }
        # Read before sign-in: optional write scopes depend on the features enabled in EntraOpsConfig.json
        $Scopes = Get-EntraOpsMsGraphDelegatedScope -Scope $Scope -Config $EarlyConfig
        #endregion

        # In UseInvokeRestMethodOnly mode, Invoke-EntraOps*Query cmdlets acquire and cache their own
        # Microsoft Graph token via Get-AzAccessToken, so Connect-MgGraph is unnecessary for the
        # service-principal/managed-identity authentication types below: their Graph permissions are
        # Application permissions already admin-consented to the app, and a client-credentials token
        # always contains every granted app role (no per-request scope negotiation needed).
        # UserInteractive/DeviceAuthentication are the exception: Get-AzAccessToken cannot trigger the
        # interactive/incremental consent prompt that delegated Graph scopes require, so those two
        # authentication types still fall back to Connect-MgGraph (and therefore still require
        # Microsoft.Graph.Authentication to be installed) even when UseInvokeRestMethodOnly is enabled.
        $SkipGraphSdkAuth = [bool]$__EntraOpsSession['UseInvokeRestMethodOnly'] -and ($AuthenticationType -notin @('UserInteractive', 'DeviceAuthentication'))
        if ($__EntraOpsSession['UseInvokeRestMethodOnly'] -and $AuthenticationType -in @('UserInteractive', 'DeviceAuthentication')) {
            Write-Warning "UseInvokeRestMethodOnly is enabled, but AuthenticationType '$AuthenticationType' still requires Connect-MgGraph (Microsoft.Graph.Authentication module) as a fallback: Get-AzAccessToken cannot trigger the interactive/incremental admin-consent prompt needed for delegated Microsoft Graph scopes, only Connect-MgGraph -Scopes can. Use a service-principal-based AuthenticationType (SystemAssignedMSI, UserAssignedMSI, FederatedCredentials or AlreadyAuthenticated) to avoid this Graph SDK dependency, or provide an already-consented -MsGraphAccessToken."
        }

        #region Switch to choose authentication method for Azure and Microsoft Graph
        switch ( $AuthenticationType ) {
            UserInteractive {
                try {
                    # Pre-authenticate to managing tenant for cross-tenant access
                    if (-not [string]::IsNullOrEmpty($ManagingTenantId)) {
                        Write-Output "Pre-authenticating to managing tenant (Azure)..."
                        Connect-AzAccount -Tenant $ManagingTenantId -ErrorAction Stop | Out-Null
                        Write-Output "Pre-authenticating to managing tenant (Microsoft Graph) using Az token..."
                        $SecureManagingAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -TenantId $ManagingTenantId -AsSecureString -ErrorAction Stop).Token
                        Connect-MgGraph -AccessToken $SecureManagingAccessToken -NoWelcome -ErrorAction Stop
                        Write-Output "Successfully pre-authenticated to managing tenant $ManagingTenantId"
                    }

                    Write-Output "Logging in to Azure..."
                    Connect-AzAccount -Tenant $TenantName -ErrorAction Stop | Out-Null
                    if ($TenantId -ne (Get-AzContext).Tenant.Id -or $null -eq $TenantId) {
                        $TenantId = (Get-AzContext).Tenant.Id
                    }
                    Write-Output "Succesfully logged in to Azure"
                    Write-Output "Logging in to Microsoft Graph..."
                    Connect-MgGraph -TenantId $TenantId -NoWelcome -ErrorAction Stop -Scopes $Scopes
                    Write-Output "Succesfully logged in to Microsoft Graph"
                } catch {
                    Write-Error -Message $_.Exception
                    throw $_.Exception
                }
            }
            DeviceAuthentication {
                try {
                    # Pre-authenticate to managing tenant for cross-tenant access
                    if (-not [string]::IsNullOrEmpty($ManagingTenantId)) {
                        Write-Output "Pre-authenticating to managing tenant (Azure)..."
                        Connect-AzAccount -Tenant $ManagingTenantId -ErrorAction Stop -UseDeviceAuthentication | Out-Null
                        Write-Output "Pre-authenticating to managing tenant (Microsoft Graph) using Az token..."
                        $SecureManagingAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -TenantId $ManagingTenantId -AsSecureString -ErrorAction Stop).Token
                        Connect-MgGraph -AccessToken $SecureManagingAccessToken -NoWelcome -ErrorAction Stop
                        Write-Output "Successfully pre-authenticated to managing tenant $ManagingTenantId"
                    }

                    Write-Output "Logging in to Azure..."
                    Connect-AzAccount -Tenant $TenantName -ErrorAction Stop -UseDeviceAuthentication | Out-Null
                    if ($TenantId -ne (Get-AzContext).Tenant.Id -or $null -eq $TenantId) {
                        $TenantId = (Get-AzContext).Tenant.Id
                    }
                    Write-Output "Succesfully logged in to Azure"
                    Write-Output "Logging in to Microsoft Graph..."
                    Connect-MgGraph -TenantId $TenantId -NoWelcome -ErrorAction Stop -Scopes $Scopes -UseDeviceAuthentication
                    Write-Output "Succesfully logged in to Microsoft Graph"
                } catch {
                    Write-Error -Message $_.Exception
                    throw $_.Exception
                }
            }
            SystemAssignedMSI {
                try {
                    Write-Output "Logging in to Azure..."
                    Connect-AzAccount -Identity -ErrorAction Stop
                    Write-Output "Succesfully logged in to Azure"
                    if (-not $SkipGraphSdkAuth) {
                        Write-Output "Logging in to Microsoft Graph..."
                        Connect-MgGraph -Identity -ErrorAction Stop -NoWelcome
                        Write-Output "Succesfully logged in to Microsoft Graph"
                    } else {
                        Write-Verbose "UseInvokeRestMethodOnly is enabled: skipping Connect-MgGraph, Microsoft Graph tokens will be acquired via Get-AzAccessToken when needed."
                    }
                } catch {
                    Write-Error -Message $_.Exception
                    throw $_.Exception
                }
            }
            UserAssignedMSI {
                try {
                    Write-Output "Logging in to Azure..."
                    Connect-AzAccount -Identity -AccountId $AccountId -ErrorAction Stop
                    Write-Output "Succesfully logged in to Azure"
                    if (-not $SkipGraphSdkAuth) {
                        Write-Output "Logging in to Microsoft Graph..."
                        Connect-MgGraph -Identity -ClientId $AccountId -NoWelcome -ErrorAction Stop
                        Write-Output "Succesfully logged in to Microsoft Graph"
                    } else {
                        Write-Verbose "UseInvokeRestMethodOnly is enabled: skipping Connect-MgGraph, Microsoft Graph tokens will be acquired via Get-AzAccessToken when needed."
                    }
                } catch {
                    Write-Error -Message $_.Exception
                    throw $_.Exception
                }
            }
            FederatedCredentials {
                if ($Null -eq (Get-AzContext).Tenant.Id) {
                    throw "Federated environment is not already authenticated"
                }
                try {
                    # Pre-authenticate to managing tenant for cross-tenant access
                    if (-not [string]::IsNullOrEmpty($ManagingTenantId)) {
                        $SecureManagingAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -TenantId $ManagingTenantId -AsSecureString).Token
                        if (-not $SkipGraphSdkAuth) {
                            Write-Output "Pre-authenticating to managing tenant (Microsoft Graph)..."
                            Connect-MgGraph -AccessToken $SecureManagingAccessToken -ErrorAction Stop -NoWelcome
                            Write-Output "Successfully pre-authenticated to managing tenant $ManagingTenantId"
                        }
                    }

                    # Connect to target tenant
                    if (-not [string]::IsNullOrEmpty($TenantId)) {
                        $SecureAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -TenantId $TenantId -AsSecureString).Token
                    } else {
                        $SecureAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -AsSecureString).Token
                    }
                    if (-not $SkipGraphSdkAuth) {
                        Connect-MgGraph -AccessToken $SecureAccessToken -ErrorAction Stop -NoWelcome
                    } else {
                        Write-Verbose "UseInvokeRestMethodOnly is enabled: skipping Connect-MgGraph, Microsoft Graph tokens will be acquired via Get-AzAccessToken when needed."
                    }

                    # Pre-warm an ARM access token too (same pattern as above), while the GitHub OIDC
                    # federated assertion behind this Az context is still fresh (it's only valid for a
                    # few minutes). Az.Accounts caches acquired tokens per resource/tenant across the
                    # job's remaining steps, so a later step's first-ever ARM request (e.g. Azure RBAC
                    # collection) is served from this cached token instead of needing a new assertion
                    # exchange once it has expired. Non-fatal: callers that never touch Azure RBAC
                    # should not fail Connect-EntraOps over this optimization.
                    try {
                        if (-not [string]::IsNullOrEmpty($TenantId)) {
                            $SecureArmAccessToken = (Get-AzAccessToken -ResourceTypeName Arm -TenantId $TenantId -AsSecureString).Token
                        } else {
                            $SecureArmAccessToken = (Get-AzAccessToken -ResourceTypeName Arm -AsSecureString).Token
                        }
                    } catch {
                        Write-Verbose "Could not pre-warm an Azure Resource Manager access token: $($_.Exception.Message)"
                    }
                } catch {
                    throw $_.Exception
                }
            }
            AlreadyAuthenticated {
                # Recommendation 2: Optimize context retrieval to avoid redundant cmdlet calls
                $CurrentAzContext = Get-AzContext
                $CurrentMgContext = if (-not $SkipGraphSdkAuth) { Get-MgContext } else { $null }

                # Pre-authenticate to managing tenant for cross-tenant access
                if (-not [string]::IsNullOrEmpty($ManagingTenantId) -and $Null -ne $CurrentAzContext.Tenant.Id) {
                    try {
                        $SecureManagingAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -TenantId $ManagingTenantId -AsSecureString).Token
                        if (-not $SkipGraphSdkAuth) {
                            Write-Output "Pre-authenticating to managing tenant (Microsoft Graph)..."
                            Connect-MgGraph -AccessToken $SecureManagingAccessToken -ErrorAction Stop -NoWelcome
                            Write-Output "Successfully pre-authenticated to managing tenant $ManagingTenantId"
                        }
                    } catch {
                        Write-Warning "Failed to pre-authenticate to managing tenant: $($_.Exception.Message)"
                    }
                }

                if ($AccountId -and $MsGraphAccessToken -and $AzArmAccessToken) {
                    Connect-AzAccount -AccountId $AccountId -AccessToken $AzArmAccessToken -Tenant $TenantName

                    if (-not $SkipGraphSdkAuth) {
                        $SecureMsGraphAccessToken = $MsGraphAccessToken | ConvertTo-SecureString -AsPlainText -Force
                        Connect-MgGraph -AccessToken $SecureMsGraphAccessToken -NoWelcome
                    }

                } elseif ($Null -ne $CurrentAzContext.Tenant.Id -and $Null -ne $CurrentMgContext.TenantId) {
                    try {
                        $SecureAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -AsSecureString).Token
                        Connect-MgGraph -AccessToken $SecureAccessToken -ErrorAction Stop -NoWelcome
                    } catch {
                        $ErrorMessage = if ($null -ne $_.Exception.Message) { $_.Exception.Message } else { $_.ToString() }
                        throw "Failed to connect to Microsoft Graph using Azure access token: $ErrorMessage"
                    }                    
                } elseif ($Null -ne $CurrentAzContext.Tenant.Id) {
                    if ($SkipGraphSdkAuth) {
                        Write-Verbose "UseInvokeRestMethodOnly is enabled: skipping Connect-MgGraph, Microsoft Graph tokens will be acquired via Get-AzAccessToken when needed."
                    } else {
                        try {
                            $SecureAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -AsSecureString).Token
                            Connect-MgGraph -AccessToken $SecureAccessToken -ErrorAction Stop -NoWelcome
                        } catch {
                            $ErrorMessage = if ($null -ne $_.Exception.Message) { $_.Exception.Message } else { $_.ToString() }
                            throw "Failed to connect to Microsoft Graph using Azure access token: $ErrorMessage"
                        }
                    }
                } else {
                    Write-Error -Message 'User or workload is not already authenticated. This authentication method is the default for EntraOps. Check "Get-Help Connect-EntraOps" to review the various options. Authenticated Azure PowerShell session is required for using "AlreadyAuthenticated" mode.'
                }
            }
        }
        #endregion

        #region Verify connection to target tenant
        if (-not [string]::IsNullOrEmpty($TenantId)) {
            $VerifyAzContext = Get-AzContext
            if ($VerifyAzContext -and $VerifyAzContext.Tenant.Id -ne $TenantId) {
                Write-Output "Azure context is on tenant $($VerifyAzContext.Tenant.Id), switching to target tenant $TenantId..."
                Set-AzContext -TenantId $TenantId -ErrorAction Stop | Out-Null
                Write-Output "Successfully switched Azure context to target tenant"
            }

            $VerifyMgContext = if (-not $SkipGraphSdkAuth) { Get-MgContext } else { $null }
            if ($VerifyMgContext -and $VerifyMgContext.TenantId -ne $TenantId) {
                Write-Output "Microsoft Graph is connected to $($VerifyMgContext.TenantId), reconnecting to target tenant $TenantId..."
                try {
                    $SecureTargetAccessToken = (Get-AzAccessToken -ResourceTypeName "MSGraph" -TenantId $TenantId -AsSecureString).Token
                    Connect-MgGraph -AccessToken $SecureTargetAccessToken -ErrorAction Stop -NoWelcome
                    Write-Output "Successfully reconnected Microsoft Graph to target tenant"
                } catch {
                    Write-Warning "Could not reconnect Microsoft Graph to target tenant ${TenantId}: $($_.Exception.Message)"
                }
            }
        }
        #endregion

        #region Summary of established connection to ARM and Microsoft Graph API
        # Recommendation 3: Optimize context retrieval and verbose output generation
        Write-Verbose -Message "Connected to Azure Management"
        $AzContextRaw = Get-AzContext
        # Account and tenant only - the token cache has no diagnostic value and doesn't belong in logs
        $AzContext = $AzContextRaw | Select-Object Account, Tenant
        Write-Verbose ($AzContext | Out-String)

        # Retrieve MG context once (skipped when Connect-MgGraph was itself skipped in REST-only mode)
        $MgContextRaw = if (-not $SkipGraphSdkAuth) { Get-MgContext } else { $null }

        if ($MgContextRaw) {
            Write-Verbose -Message "Connected to Microsoft Graph"
        } else {
            Write-Verbose -Message "Not connected via Microsoft Graph SDK (UseInvokeRestMethodOnly mode: tokens are acquired via Get-AzAccessToken as needed)"
        }
        $MgContext = $MgContextRaw | Select-Object ClientId, TenantId, AppName, ContextScope
        Write-Verbose -Message ($MgContext | Out-String)

        Write-Verbose "Scoped permissions in Microsoft Graph"
        $MgScopes = $MgContextRaw.Scopes
        Write-Verbose -Message ($MgScopes | Out-String)
        #endregion

        #region Validate baseline Graph scopes for already-authenticated user sessions
        if ($AuthenticationType -eq 'AlreadyAuthenticated' -and $AzContextRaw.Account.Type -eq 'User' -and $MgContextRaw) {
            $MissingScopes = @($Scopes | Where-Object { $_ -notin $MgContextRaw.Scopes })
            $TenantGovernanceRelationshipScope = 'TenantGovernance-Relationship.Read.All'
            $MissingBaselineScopes = @($MissingScopes | Where-Object { $_ -ne $TenantGovernanceRelationshipScope })
            if ($MissingBaselineScopes.Count -gt 0) {
                Write-Warning "The already-authenticated user session is missing $($MissingBaselineScopes.Count) baseline EntraOps delegated scope(s): $($MissingBaselineScopes -join ', '). Dependent collection steps may fail with 403 Forbidden. Reconnect with the missing scopes consented."
            }
            if ($MissingScopes -contains $TenantGovernanceRelationshipScope) {
                $HasManagingTenantConfiguration = -not [string]::IsNullOrWhiteSpace($ManagingTenantId) -or -not [string]::IsNullOrWhiteSpace($ManagingTenantName)
                if ($HasManagingTenantConfiguration) {
                    Write-Warning "The already-authenticated user session is missing '$TenantGovernanceRelationshipScope'. Tenant Governance delegated-administration relationships may not be discovered or analyzed. Reconnect with this delegated scope consented."
                } else {
                    Write-Warning "The already-authenticated user session is missing '$TenantGovernanceRelationshipScope'. No ManagingTenantId or ManagingTenantName is configured, so this may be acceptable only if you have verified that no Tenant Governance delegated-administration relationships exist. Without this scope, unknown relationships cannot be discovered or analyzed."
                }
            }
        }
        #endregion

        #region Import Environment variables if exists
        $IncludeObjectDetails = $false
        if ($ConfigFilePath) {
            try {
                $EntraOpsConfig = Get-Content -Path $ConfigFilePath | ConvertFrom-Json -Depth 10 -AsHashtable
            } catch {
                Write-Error "Issue to import config file $($ConfigFilePath)! Check if the file exists and is in JSON format."
            }
            # Remove Ingest or Apply* parameters from config file and show configuration
            if ($EntraOpsConfig.AutomatedClassificationUpdate) { $EntraOpsConfig.AutomatedClassificationUpdate.Remove("ApplyAutomatedClassificationUpdate") }
            if ($EntraOpsConfig.AutomatedControlPlaneScopeUpdate) { $EntraOpsConfig.AutomatedControlPlaneScopeUpdate.Remove("ApplyAutomatedControlPlaneScopeUpdate") }
            if ($EntraOpsConfig.LogAnalytics) { $EntraOpsConfig.LogAnalytics.Remove("IngestToLogAnalytics") }
            if ($EntraOpsConfig.SentinelWatchLists) { $EntraOpsConfig.SentinelWatchLists.Remove("IngestToWatchLists") }
            if ($EntraOpsConfig.AutomatedAdministrativeUnitManagement) { $EntraOpsConfig.AutomatedAdministrativeUnitManagement.Remove("ApplyAdministrativeUnitAssignments") }
            if ($EntraOpsConfig.AutomatedConditionalAccessTargetGroups) { $EntraOpsConfig.AutomatedConditionalAccessTargetGroups.Remove("ApplyConditionalAccessTargetGroups") }
            if ($EntraOpsConfig.AutomatedRmauAssignmentsForUnprotectedObjects) { $EntraOpsConfig.AutomatedRmauAssignmentsForUnprotectedObjects.Remove("ApplyRmauAssignmentsForUnprotectedObjects") }
            if ($EntraOpsConfig.AutomatedElmCatalogProtection) { $EntraOpsConfig.AutomatedElmCatalogProtection.Remove("ApplyPrivilegedElmCatalogProtection") }
            if ($EntraOpsConfig.ConsoleOutput -and $null -ne $EntraOpsConfig.ConsoleOutput.IncludeObjectDetails) {
                $IncludeObjectDetails = [bool]$EntraOpsConfig.ConsoleOutput.IncludeObjectDetails
            }

            New-Variable -Name EntraOpsConfig -Value $EntraOpsConfig -Scope Global -Force
            $__EntraOpsSession['ConfigFilePath'] = (Resolve-Path -LiteralPath $ConfigFilePath -ErrorAction Stop).ProviderPath
            Write-Verbose -Message "Config file $($ConfigFilePath) imported"
        } else {
            # A configuration from a previous connection may belong to another tenant
            Remove-Variable -Name EntraOpsConfig -Scope Global -Force -ErrorAction SilentlyContinue
            $__EntraOpsSession.Remove('ConfigFilePath')
        }
        $ConfigFileSummary = switch ($ConfigFileStatus) {
            'Parameter' { "$ConfigFilePath" }
            'AutoDetected' { "$ConfigFilePath (found automatically)" }
            'Skipped' { "Not used - $ConfigFileSkipReason" }
            default { 'Not used - no EntraOpsConfig.json found, defaults apply' }
        }
        Write-Verbose -Message "EntraOpsConfig.json: $ConfigFileSummary"
        #endregion

        #region Set global variables
        New-Variable -Name TenantIdContext -Value $TenantId -Scope Global -Force
        New-Variable -Name TenantNameContext -Value $TenantName -Scope Global -Force
        New-Variable -Name ManagingTenantIdContext -Value $ManagingTenantId -Scope Global -Force
        New-Variable -Name ManagingTenantNameContext -Value $ManagingTenantName -Scope Global -Force
        New-Variable -Name EntraOpsIncludeObjectDetails -Value $IncludeObjectDetails -Scope Global -Force
        New-Variable -Name XdrAvdHuntingAccess -Value (-not $SkipGraphSdkAuth -and (Get-MgContext).Scopes -contains "ThreatHunting.Read.All") -Scope Global -Force
        $__EntraOpsSession['AuthenticationType'] = $AuthenticationType
        # $DefaultFolderClassification is always the Classification/ root; consumers append
        # $TenantNameContext or Templates/ themselves (Resolve-EntraOpsClassificationPath,
        # Import-EntraOpsClassificationOverwrites, Update-EntraOpsClassificationControlPlaneScope, ...).
        New-Variable -Name DefaultFolderClassification -Value "$EntraOpsBaseFolder/Classification/" -Scope Global -Force
        if ($MultiTenantRepo -eq $true) {
            New-Variable -Name DefaultFolderClassifiedEam -Value "$EntraOpsBaseFolder/PrivilegedEAM/$($TenantName)/" -Scope Global -Force
            Write-Verbose -Message "Multi Tenant in Repository"
        } else {
            New-Variable -Name DefaultFolderClassifiedEam -Value "$EntraOpsBaseFolder/PrivilegedEAM/" -Scope Global -Force
            Write-Verbose -Message "Single Tenant in Repository"
        }
        #endregion

        if (-not $NoWelcome) {
            #region Display connection summary
            Write-Host ""
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
            Write-Host " 🔐 Connection Summary" -ForegroundColor Cyan
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
            
            Write-Host " Authentication Type : $AuthenticationType" -ForegroundColor White
            Write-Host " EntraOps Scope : $Scope" -ForegroundColor White
            Write-Host " Config File : $ConfigFileSummary" -ForegroundColor $(switch ($ConfigFileStatus) { 'Skipped' { 'Yellow' } 'NotFound' { 'Gray' } default { 'Green' } })
            $WorkingFolderSource = if ($EntraOpsBaseFolderResolution -and $EntraOpsBaseFolderResolution.Path -eq $Global:EntraOpsBaseFolder) {
                switch ($EntraOpsBaseFolderResolution.Source) {
                    'EnvironmentVariable' { 'ENTRAOPS_ROOT' }
                    'RepositoryCheckout' { 'repository clone' }
                    'CurrentFolder' { 'current folder with EntraOpsConfig.json' }
                    'UserFolder' { 'default, set ENTRAOPS_ROOT to change' }
                }
            } else { 'set in this session' }
            Write-Host " Working Folder : $Global:EntraOpsBaseFolder ($WorkingFolderSource)" -ForegroundColor White

            # Display managing tenant info if configured
            if (-not [string]::IsNullOrEmpty($ManagingTenantId)) {
                Write-Host " Managing Tenant ID : $ManagingTenantId" -ForegroundColor White
                Write-Host " Managing Tenant Name: $ManagingTenantName" -ForegroundColor White
            }

            # Get Azure context
            $AzContext = Get-AzContext -ErrorAction SilentlyContinue
            if ($AzContext) {
                Write-Host " Azure Account : $($AzContext.Account.Id)" -ForegroundColor Green
                Write-Host " Azure Tenant : $($AzContext.Tenant.Id)" -ForegroundColor White
                Write-Host " Azure Subscription : $($AzContext.Subscription.Name)" -ForegroundColor White
            } else {
                Write-Host " Azure Account : Not connected" -ForegroundColor Gray
            }
            
            # Get Microsoft Graph context
            $MgContext = if (-not $SkipGraphSdkAuth) { Get-MgContext } else { $null }
            if ($MgContext) {
                Write-Host " Graph Account : $($MgContext.Account)" -ForegroundColor Green
                Write-Host " Graph Tenant : $($MgContext.TenantId)" -ForegroundColor White
                Write-Host " Graph Auth Type : $($MgContext.AuthType)" -ForegroundColor White
                
                # Display scopes (truncate if too many)
                $Scopes = $MgContext.Scopes
                if ($Scopes.Count -gt 0) {
                    $ScopeDisplay = if ($Scopes.Count -le 5) {
                        $Scopes -join ", "
                    } else {
                        (($Scopes | Select-Object -First 5) -join ", ") + " (+$($Scopes.Count - 5) more)"
                    }
                    Write-Host " Graph Scopes : $ScopeDisplay" -ForegroundColor White
                }
            } elseif ($SkipGraphSdkAuth) {
                Write-Host " Graph Account : Not connected (UseInvokeRestMethodOnly - tokens acquired via Get-AzAccessToken as needed)" -ForegroundColor Gray
            } else {
                Write-Host " Graph Account : Not connected" -ForegroundColor Gray
            }
            
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
            Write-Host ""
            #endregion
            
            #region Display cache information
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
            Write-Host " 📦 Cache Configuration" -ForegroundColor Cyan
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
            
            # Cache location
            Write-Host " Cache Location : $($__EntraOpsSession.PersistentCachePath)" -ForegroundColor White
            
            # Memory cache
            $MemoryCacheCount = $__EntraOpsSession.GraphCache.Count
            Write-Host " Memory Cache : $MemoryCacheCount entries" -ForegroundColor $(if ($MemoryCacheCount -gt 0) { "Green" } else { "Gray" })
            
            # Persistent cache statistics
            if (Test-Path $__EntraOpsSession.PersistentCachePath) {
                $PersistentFiles = Get-ChildItem -Path $__EntraOpsSession.PersistentCachePath -Filter "*.json" -ErrorAction SilentlyContinue
                $PersistentCount = $PersistentFiles.Count
                
                if ($PersistentCount -gt 0) {
                    $PersistentSizeMB = [Math]::Round(($PersistentFiles | Measure-Object -Property Length -Sum).Sum / 1MB, 2)
                    Write-Host " Persistent Cache : $PersistentCount files ($PersistentSizeMB MB)" -ForegroundColor Green
                    
                    # Show newest cache file
                    $NewestCache = $PersistentFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1
                    if ($NewestCache) {
                        $CacheAge = [Math]::Round(((Get-Date) - $NewestCache.LastWriteTime).TotalHours, 1)
                        Write-Host " Latest Cache Update : $CacheAge hours ago" -ForegroundColor $(if ($CacheAge -lt 1) { "Green" } elseif ($CacheAge -lt 24) { "Yellow" } else { "Gray" })
                    }
                } else {
                    Write-Host " Persistent Cache : Empty (will populate on first API call)" -ForegroundColor Gray
                }
            } else {
                Write-Host " Persistent Cache : Directory will be created on first use" -ForegroundColor Gray
            }
            
            # Cache TTL settings
            Write-Host " Default TTL : $([Math]::Round($__EntraOpsSession.DefaultCacheTTL / 3600, 1)) hours" -ForegroundColor White
            Write-Host " Static Data TTL : $([Math]::Round($__EntraOpsSession.StaticDataCacheTTL / 3600, 1)) hours" -ForegroundColor White
            
            Write-Host "═══════════════════════════════════════════════════════════════════════════════" -ForegroundColor Cyan
            Write-Host ""
            
            # Warning to disconnect when finished
            Write-Host "⚠️ REMINDER: Run 'Disconnect-EntraOps' when finished to:" -ForegroundColor Yellow
            Write-Host " • Clear persistent cache on disk with results from your session and free memory" -ForegroundColor Yellow
            Write-Host " • Disconnect from Azure Resource Management" -ForegroundColor Yellow
            Write-Host " • Disconnect from Microsoft Graph API" -ForegroundColor Yellow
            Write-Host ""
            #endregion
        }
    }
}
# SIG # Begin signature block
# MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBIpjRHd6rbycaf
# /9yvKKuwKoIt0qZvdvgUJ+uAXpwGoaCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j
# 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
# ARUwLwYJKoZIhvcNAQkEMSIEIDr10i0nWs1o9zUw/ASPscjzBvfl/nD/gAbGtELy
# +k9VMA0GCSqGSIb3DQEBAQUABIIBgBz6Q08R22JF0nOoesFMGnak4HEh594d3DM1
# F22rceaBfIWVRT+aR3pJo0EBykl3LARg4zrVNDvNRnTunlo3txcTPxayYJI1JgLy
# /m0I0BX6OAwhriUn4NADzoWd/HwIi6vV042m6tx1FV6OLlFp4Pb8zF3SH7aTUhwW
# HCDjRjGmMWhYVH74NVqdEUPehI2pFrGqc0tROFZEIJXBEwUMg28e09TsqMjHgKC3
# ap38wxRfuCbnXg0d2Txw544dqNl/nHZ1vEXFKpaFZHqAcGgX4LPhfxz0A2qK5wp1
# i92+z3Yun8XxT6o0mCJ4OJpHV4f5UQ4mRQjmnmU143Fc1p+U7sD53v7UwVnHdNAt
# HkPuTVPvg8jIfKodz3nbQghcmHlONPAh4MG0426qWXJY1sg1lISIc2k+FRi3A6H1
# JevMszp5k6oyw6Lmnbnplc0tYqZbjH8NEDy1aiUqKtWtOcEFWWUZBdb6C98A6o8o
# j7MWnBpax2OEMALQ2O4OPYP5777fp6GCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID
# 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0
# ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ
# KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMjIxMTM5MDNaMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY
# 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDCcmyaEiXSmpEIiAzXAJEyLF9X0pDu7K2Pc
# YA/52YeYvZtV6/Dy1NAPToNkgJR9VrIwgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ
# MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh
# MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0
# dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI
# hvcNAQEBBQAEggIAJV2jjEdOEw1PgZLWzUnCRFCRnDHWAqtqg3dU6uIEpY1On+6o
# 9tLk+y6LQk7J1vjSNmiBFN7Zbg7bZTj0m1beH47hO5RrAMc073Z6X/Ll2mQsTvlE
# HpQq0ZGAJYXF7Tf2Cf7iDzyqVJv4NOj3NLaiIgEy+/Mqbovi4Eaz/p7W7NKZO74D
# rqJc8LxBhggXfOQhy9JqONHpQoGDJ1iORrGrG+W3X+Ov0jAebUTh86CleNGjYvj0
# 6Nm0MeMRLzPyuOdMMF40F3pfNZvyXGpLE0KO+s8hi3HyBOuIc1BBiC+lt2y924/g
# lOwtyKlhXKRnZcRyOkg2NIp+nSQKWZ8KWeGbpYyG3jkPzKT9pGxl+vL4D8gQNIYi
# 8faMPvZ2CWdbwbREOaVJJDtqST8TN5aXIHQ1l3oeKDws8tXD8ziWvslOahryLp/K
# jaWI0vUUCeujNmIH/UeQzoQDeu2z/bMpaYvtCYLmwedzQju7qCevenbOpQsHmx7o
# ma3iT3u3P0BcZtWszbEAF6JMjwaF1Y433n5OJy3O1k9oEwh4/y+IqgP0ex/+3aH8
# bga19pqcz0GfiRSA39uyLjLlKmGLxOU57YyKSrJl2lzveztPG0wOFhY1iLYfvvqv
# d54RWAifnXXpSqT4fSgAaJ94UnEipJ6HK2olj0YbfY+/DdR4y8Q6F3JNVr8=
# SIG # End signature block