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 |