netCore/ExchangeOnlineManagementBeta.psm1
# Import the REST module so that the EXO* cmdlets are present before Connect-ExchangeOnline in the powershell instance. $RestModule = "Microsoft.Exchange.Management.RestApiClient.dll" $RestModulePath = [System.IO.Path]::Combine($PSScriptRoot, $RestModule) Import-Module $RestModulePath $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellGalleryModule.dll" $ExoPowershellModulePath = [System.IO.Path]::Combine($PSScriptRoot, $ExoPowershellModule) Import-Module $ExoPowershellModulePath ############# Helper Functions Begin ############# <# Get the ExchangeOnlineManagement module version. Same function is present in the autogen module. Both the codes should be kept in sync. #> function Get-ModuleVersion { try { # Return the already computed version info if available. if ($script:ModuleVersion -ne $null -and $script:ModuleVersion -ne '') { Write-Verbose "Returning precomputed version info: $script:ModuleVersion" return $script:ModuleVersion; } $exoModule = Get-Module ExchangeOnlineManagement # Check for ExchangeOnlineManagementBeta in case the psm1 is loaded directly if ($exoModule -eq $null) { $exoModule = (Get-Command -Name Connect-ExchangeOnline).Module } # Get the module version from the loaded module info. $script:ModuleVersion = $exoModule.Version.ToString() # Look for prerelease information from the corresponding module manifest. $exoModuleRoot = (Get-Item $exoModule.Path).Directory.Parent.FullName $exoModuleManifestPath = Join-Path -Path $exoModuleRoot -ChildPath ExchangeOnlineManagement.psd1 $isExoModuleManifestPathValid = Test-Path -Path $exoModuleManifestPath if ($isExoModuleManifestPathValid -ne $true) { # Could be a local debug build import for testing. Skip extracting prerelease info for those. Write-Verbose "Module manifest path invalid, path: $exoModuleManifestPath, skipping extracting prerelease info" return $script:ModuleVersion } $exoModuleManifestContent = Get-Content -Path $exoModuleManifestPath $preReleaseInfo = $exoModuleManifestContent -match "Prerelease = '(.*)'" if ($preReleaseInfo -ne $null) { $script:ModuleVersion = "{0}-{1}" -f $exoModule.Version.ToString(),$preReleaseInfo[0].Split('=')[1].Trim().Trim("'") } Write-Verbose "Computed version info: $script:ModuleVersion" return $script:ModuleVersion } catch { return [string]::Empty } } <# .Synopsis Validates a given Uri #> function Test-Uri { [CmdletBinding()] [OutputType([bool])] Param ( # Uri to be validated [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [string] $UriString ) [Uri]$uri = $UriString -as [Uri] $uri.AbsoluteUri -ne $null -and $uri.Scheme -eq 'https' } <# .Synopsis Is Cloud Shell Environment #> function global:IsCloudShellEnvironment() { return [Microsoft.Exchange.Management.AdminApiProvider.Utility]::IsCloudShellEnvironment(); } <# .Synopsis Override Get-PSImplicitRemotingSession function for reconnection #> function global:UpdateImplicitRemotingHandler() { # Remote Powershell Sessions created by the ExchangeOnlineManagement module are given a name that starts with "ExchangeOnlineInternalSession". # Only modules from such sessions should be modified here, to prevent modfification of RPS tmp_* modules created by running the New-PSSession cmdlet directly, or when connecting to exchange on-prem tenants. $existingPSSession = Get-PSSession | Where-Object {$_.ConfigurationName -like "Microsoft.Exchange" -and $_.Name -like "ExchangeOnlineInternalSession*"} if ($existingPSSession.count -gt 0) { foreach ($session in $existingPSSession) { $module = Get-Module $session.CurrentModuleName if ($module -eq $null) { continue } [bool]$moduleProcessed = $false [string] $moduleUrl = $module.Description [int] $queryStringIndex = $moduleUrl.IndexOf("?") if ($queryStringIndex -gt 0) { $moduleUrl = $moduleUrl.SubString(0,$queryStringIndex) } if ($moduleUrl.EndsWith("/PowerShell-LiveId", [StringComparison]::OrdinalIgnoreCase) -or $moduleUrl.EndsWith("/PowerShell", [StringComparison]::OrdinalIgnoreCase)) { & $module { ${function:Get-PSImplicitRemotingSession} = ` { param( [Parameter(Mandatory = $true, Position = 0)] [string] $commandName ) $shouldRemoveCurrentSession = $false; # Clear any left over PS tmp modules if (($script:PSSession -ne $null) -and ($script:PSSession.PreviousModuleName -ne $null) -and ($script:PSSession.PreviousModuleName -ne $script:MyModule.Name)) { $null = Remove-Module -Name $script:PSSession.PreviousModuleName -ErrorAction SilentlyContinue $script:PSSession.PreviousModuleName = $null } if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened')) { Set-PSImplicitRemotingSession ` (& $script:GetPSSession ` -InstanceId $script:PSSession.InstanceId.Guid ` -ErrorAction SilentlyContinue ) } if ($script:PSSession -ne $null) { if ($script:PSSession.Runspace.RunspaceStateInfo.State -eq 'Disconnected') { # If we are handed a disconnected session, try re-connecting it before creating a new session. Set-PSImplicitRemotingSession ` (& $script:ConnectPSSession ` -Session $script:PSSession ` -ErrorAction SilentlyContinue) } else { # Import the module once more to ensure that Test-ActiveToken is present Import-Module $global:_EXO_ModulePath -Cmdlet Test-ActiveToken; # If there is no active token run the new session flow $hasActiveToken = Test-ActiveToken -TokenExpiryTime $script:PSSession.TokenExpiryTime $sessionIsOpened = $script:PSSession.Runspace.RunspaceStateInfo.State -eq 'Opened' if (($hasActiveToken -eq $false) -or ($sessionIsOpened -ne $true)) { #If there is no active user token or opened session then ensure that we remove the old session $shouldRemoveCurrentSession = $true; } } } if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened') -or ($shouldRemoveCurrentSession -eq $true)) { # Import the module once more to ensure that New-ExoPSSession is present Import-Module $global:_EXO_ModulePath -Cmdlet New-ExoPSSession; Write-PSImplicitRemotingMessage ('Creating a new Remote PowerShell session using Modern Authentication for implicit remoting of "{0}" command ...' -f $commandName) $session = New-ExoPSSession -PreviousSession $script:PSSession if ($session -ne $null) { if ($shouldRemoveCurrentSession -eq $true) { Remove-PSSession $script:PSSession } # Import the latest session to ensure that the next cmdlet call would occur on the new PSSession instance. if ([string]::IsNullOrEmpty($script:MyModule.ModulePrefix)) { $PSSessionModuleInfo = Import-PSSession $session -AllowClobber -DisableNameChecking -CommandName $script:MyModule.CommandName -FormatTypeName $script:MyModule.FormatTypeName } else { $PSSessionModuleInfo = Import-PSSession $session -AllowClobber -DisableNameChecking -CommandName $script:MyModule.CommandName -FormatTypeName $script:MyModule.FormatTypeName -Prefix $script:MyModule.ModulePrefix } # Add the name of the module to clean up in case of removing the broken session $session | Add-Member -NotePropertyName "CurrentModuleName" -NotePropertyValue $PSSessionModuleInfo.Name $CurrentModule = Import-Module $PSSessionModuleInfo.Path -Global -DisableNameChecking -Prefix $script:MyModule.ModulePrefix -PassThru $CurrentModule | Add-Member -NotePropertyName "ModulePrefix" -NotePropertyValue $script:MyModule.ModulePrefix $CurrentModule | Add-Member -NotePropertyName "CommandName" -NotePropertyValue $script:MyModule.CommandName $CurrentModule | Add-Member -NotePropertyName "FormatTypeName" -NotePropertyValue $script:MyModule.FormatTypeName $session | Add-Member -NotePropertyName "PreviousModuleName" -NotePropertyValue $script:MyModule.Name UpdateImplicitRemotingHandler $script:PSSession = $session } } if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened')) { throw 'No session has been associated with this implicit remoting module' } return [Management.Automation.Runspaces.PSSession]$script:PSSession }} } } } } <# .SYNOPSIS Extract organization name from UserPrincipalName #> function Get-OrgNameFromUPN { param([string] $UPN) $fields = $UPN -split '@' return $fields[-1] } <# .SYNOPSIS Get the command from the given module #> function global:Get-WrappedCommand { param( [string] $CommandName, [string] $ModuleName, [string] $CommandType) $cmd = (Get-Module $moduleName).ExportedFunctions[$CommandName] return $cmd } <# .Synopsis Writes a message to the console in Yellow. Call this method with caution since it uses Write-Host internally which cannot be suppressed by the user. #> function Write-Message { param([string] $message) Write-Host $message -ForegroundColor Yellow } ############# Helper Functions End ############# ###### Begin Main ###### $EOPConnectionInProgress = $false function Connect-ExchangeOnline { [CmdletBinding()] param( # Connection Uri for the Remote PowerShell endpoint [string] $ConnectionUri = '', # Azure AD Authorization endpoint Uri that can issue the OAuth2 access tokens [string] $AzureADAuthorizationEndpointUri = '', # Exchange Environment name [Microsoft.Exchange.Management.RestApiClient.ExchangeEnvironment] $ExchangeEnvironmentName = 'O365Default', # PowerShell session options to be used when opening the Remote PowerShell session [System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption = $null, # Switch to bypass use of mailbox anchoring hint. [switch] $BypassMailboxAnchoring = $false, # Delegated Organization Name [string] $DelegatedOrganization = '', # Prefix [string] $Prefix = '', # Show Banner of Exchange cmdlets Mapping and recent updates [switch] $ShowBanner = $true, #Cmdlets to Import for rps cmdlets , by default it would bring all [string[]] $CommandName = @("*"), #The way the output objects would be printed on the console [string[]] $FormatTypeName = @("*"), # Use Remote PowerShell Session based connection [switch] $UseRPSSession = $false ) DynamicParam { if (($isCloudShell = IsCloudShellEnvironment) -eq $false) { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # User Principal Name or email address of the user $UserPrincipalName = New-Object System.Management.Automation.RuntimeDefinedParameter('UserPrincipalName', [string], $attributeCollection) $UserPrincipalName.Value = '' # User Credential to Logon $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection) $Credential.Value = $null # Certificate $Certificate = New-Object System.Management.Automation.RuntimeDefinedParameter('Certificate', [System.Security.Cryptography.X509Certificates.X509Certificate2], $attributeCollection) $Certificate.Value = $null # Certificate Path $CertificateFilePath = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificateFilePath', [string], $attributeCollection) $CertificateFilePath.Value = '' # Certificate Password $CertificatePassword = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificatePassword', [System.Security.SecureString], $attributeCollection) $CertificatePassword.Value = $null # Certificate Thumbprint $CertificateThumbprint = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificateThumbprint', [string], $attributeCollection) $CertificateThumbprint.Value = '' # Application Id $AppId = New-Object System.Management.Automation.RuntimeDefinedParameter('AppId', [string], $attributeCollection) $AppId.Value = '' # Organization $Organization = New-Object System.Management.Automation.RuntimeDefinedParameter('Organization', [string], $attributeCollection) $Organization.Value = '' # Switch to collect telemetry on command execution. $EnableErrorReporting = New-Object System.Management.Automation.RuntimeDefinedParameter('EnableErrorReporting', [switch], $attributeCollection) $EnableErrorReporting.Value = $false # Where to store EXO command telemetry data. By default telemetry is stored in the directory "%TEMP%/EXOTelemetry" in the file : EXOCmdletTelemetry-yyyymmdd-hhmmss.csv. $LogDirectoryPath = New-Object System.Management.Automation.RuntimeDefinedParameter('LogDirectoryPath', [string], $attributeCollection) $LogDirectoryPath.Value = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "EXOCmdletTelemetry") # Create a new attribute and valiate set against the LogLevel $LogLevelAttribute = New-Object System.Management.Automation.ParameterAttribute $LogLevelAttribute.Mandatory = $false $LogLevelAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $LogLevelAttributeCollection.Add($LogLevelAttribute) $LogLevelList = @([Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::Default, [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::All) $ValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogLevelList) $LogLevel = New-Object System.Management.Automation.RuntimeDefinedParameter('LogLevel', [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel], $LogLevelAttributeCollection) $LogLevel.Attributes.Add($ValidateSet) # Switch to use Managed Identity flow. $ManagedIdentity = New-Object System.Management.Automation.RuntimeDefinedParameter('ManagedIdentity', [switch], $attributeCollection) $ManagedIdentity.Value = $false # ManagedIdentityAccountId to be used in case of User Assigned Managed Identity flow $ManagedIdentityAccountId = New-Object System.Management.Automation.RuntimeDefinedParameter('ManagedIdentityAccountId', [string], $attributeCollection) $ManagedIdentityAccountId.Value = '' # EXO params start # Switch to track perfomance $TrackPerformance = New-Object System.Management.Automation.RuntimeDefinedParameter('TrackPerformance', [bool], $attributeCollection) $TrackPerformance.Value = $false # Flag to enable or disable showing the number of objects written $ShowProgress = New-Object System.Management.Automation.RuntimeDefinedParameter('ShowProgress', [bool], $attributeCollection) $ShowProgress.Value = $false # Switch to enable/disable Multi-threading in the EXO cmdlets $UseMultithreading = New-Object System.Management.Automation.RuntimeDefinedParameter('UseMultithreading', [bool], $attributeCollection) $UseMultithreading.Value = $true # Pagesize Param $PageSize = New-Object System.Management.Automation.RuntimeDefinedParameter('PageSize', [uint32], $attributeCollection) $PageSize.Value = 1000 # Switch to MSI auth $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection) $Device.Value = $false # Switch to CmdInline parameters $InlineCredential = New-Object System.Management.Automation.RuntimeDefinedParameter('InlineCredential', [switch], $attributeCollection) $InlineCredential.Value = $false # EXO params end $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('UserPrincipalName', $UserPrincipalName) $paramDictionary.Add('Credential', $Credential) $paramDictionary.Add('Certificate', $Certificate) $paramDictionary.Add('CertificateFilePath', $CertificateFilePath) $paramDictionary.Add('CertificatePassword', $CertificatePassword) $paramDictionary.Add('AppId', $AppId) $paramDictionary.Add('Organization', $Organization) $paramDictionary.Add('EnableErrorReporting', $EnableErrorReporting) $paramDictionary.Add('LogDirectoryPath', $LogDirectoryPath) $paramDictionary.Add('LogLevel', $LogLevel) $paramDictionary.Add('TrackPerformance', $TrackPerformance) $paramDictionary.Add('ShowProgress', $ShowProgress) $paramDictionary.Add('UseMultithreading', $UseMultithreading) $paramDictionary.Add('PageSize', $PageSize) $paramDictionary.Add('ManagedIdentity', $ManagedIdentity) $paramDictionary.Add('ManagedIdentityAccountId', $ManagedIdentityAccountId) if($PSEdition -eq 'Core') { $paramDictionary.Add('Device', $Device) $paramDictionary.Add('InlineCredential', $InlineCredential); # We do not want to expose certificate thumprint in Linux as it is not feasible there. if($IsWindows) { $paramDictionary.Add('CertificateThumbprint', $CertificateThumbprint); } } else { $paramDictionary.Add('CertificateThumbprint', $CertificateThumbprint); } return $paramDictionary } else { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # Switch to MSI auth $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection) $Device.Value = $false # Switch to collect telemetry on command execution. $EnableErrorReporting = New-Object System.Management.Automation.RuntimeDefinedParameter('EnableErrorReporting', [switch], $attributeCollection) $EnableErrorReporting.Value = $false # Where to store EXO command telemetry data. By default telemetry is stored in the directory "%TEMP%/EXOTelemetry" in the file : EXOCmdletTelemetry-yyyymmdd-hhmmss.csv. $LogDirectoryPath = New-Object System.Management.Automation.RuntimeDefinedParameter('LogDirectoryPath', [string], $attributeCollection) $LogDirectoryPath.Value = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "EXOCmdletTelemetry") # Create a new attribute and validate set against the LogLevel $LogLevelAttribute = New-Object System.Management.Automation.ParameterAttribute $LogLevelAttribute.Mandatory = $false $LogLevelAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $LogLevelAttributeCollection.Add($LogLevelAttribute) $LogLevelList = @([Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::Default, [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::All) $ValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogLevelList) $LogLevel = New-Object System.Management.Automation.RuntimeDefinedParameter('LogLevel', [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel], $LogLevelAttributeCollection) $LogLevel.Attributes.Add($ValidateSet) # Switch to CmdInline parameters $InlineCredential = New-Object System.Management.Automation.RuntimeDefinedParameter('InlineCredential', [switch], $attributeCollection) $InlineCredential.Value = $false # User Credential to Logon $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection) $Credential.Value = $null $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('Device', $Device) $paramDictionary.Add('EnableErrorReporting', $EnableErrorReporting) $paramDictionary.Add('LogDirectoryPath', $LogDirectoryPath) $paramDictionary.Add('LogLevel', $LogLevel) $paramDictionary.Add('Credential', $Credential) $paramDictionary.Add('InlineCredential', $InlineCredential) return $paramDictionary } } process { $startTime = Get-Date # Validate parameters if (($ConnectionUri -ne '') -and (-not (Test-Uri $ConnectionUri))) { throw "Invalid ConnectionUri parameter '$ConnectionUri'" } if (($AzureADAuthorizationEndpointUri -ne '') -and (-not (Test-Uri $AzureADAuthorizationEndpointUri))) { throw "Invalid AzureADAuthorizationEndpointUri parameter '$AzureADAuthorizationEndpointUri'" } if (($Prefix -ne '')) { if ($Prefix -notmatch '^[a-z0-9]+$') { throw "Use of any special characters in the Prefix string is not supported." } if ($Prefix -eq 'EXO') { throw "Prefix 'EXO' is a reserved Prefix, please use a different prefix." } } # Keep track of error count at beginning. $errorCountAtStart = $global:Error.Count; try { $moduleVersion = Get-ModuleVersion Write-Verbose "ModuleVersion: $moduleVersion" # Generate a ConnectionId to use in all logs and to send in all server calls. $connectionContextID = [System.Guid]::NewGuid() $cmdletLogger = New-CmdletLogger -ExoModuleVersion $moduleVersion -LogDirectoryPath $LogDirectoryPath.Value -EnableErrorReporting:$EnableErrorReporting.Value -ConnectionId $connectionContextID -IsRpsSession:$UseRPSSession.IsPresent $logFilePath = $cmdletLogger.GetCurrentLogFilePath() $cmdletLogger.InitLog($connectionContextID) $cmdletLogger.LogStartTime($connectionContextID, $startTime) $cmdletLogger.LogCmdletName($connectionContextID, "Connect-ExchangeOnline"); $cmdletLogger.LogCmdletParameters($connectionContextID, $PSBoundParameters); if ($isCloudShell -eq $false) { $ConnectionContext = Get-ConnectionContext -ExchangeEnvironmentName $ExchangeEnvironmentName -ConnectionUri $ConnectionUri ` -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -UserPrincipalName $UserPrincipalName.Value ` -PSSessionOption $PSSessionOption -Credential $Credential.Value -BypassMailboxAnchoring:$BypassMailboxAnchoring ` -DelegatedOrg $DelegatedOrganization -Certificate $Certificate.Value -CertificateFilePath $CertificateFilePath.Value ` -CertificatePassword $CertificatePassword.Value -CertificateThumbprint $CertificateThumbprint.Value -AppId $AppId.Value ` -Organization $Organization.Value -Device:$Device.Value -InlineCredential:$InlineCredential.Value -CommandName $CommandName ` -FormatTypeName $FormatTypeName -Prefix $Prefix -PageSize $PageSize.Value -ExoModuleVersion:$moduleVersion -Logger $cmdletLogger ` -ConnectionId $connectionContextID -IsRpsSession $UseRPSSession.IsPresent -EnableErrorReporting:$EnableErrorReporting.Value ` -ManagedIdentity:$ManagedIdentity.Value -ManagedIdentityAccountId $ManagedIdentityAccountId.Value } else { $ConnectionContext = Get-ConnectionContext -ExchangeEnvironmentName $ExchangeEnvironmentName -ConnectionUri $ConnectionUri ` -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -Credential $Credential.Value -PSSessionOption $PSSessionOption ` -BypassMailboxAnchoring:$BypassMailboxAnchoring -Device:$Device.Value -InlineCredential:$InlineCredential.Value ` -DelegatedOrg $DelegatedOrganization -CommandName $CommandName -FormatTypeName $FormatTypeName -Prefix $prefix -ExoModuleVersion:$moduleVersion ` -Logger $cmdletLogger -ConnectionId $connectionContextID -IsRpsSession $UseRPSSession.IsPresent -EnableErrorReporting:$EnableErrorReporting.Value } if ($isCloudShell -eq $false) { $global:_EXO_EnableErrorReporting = $EnableErrorReporting.Value; } if ($ShowBanner -eq $true) { try { $BannerContent = Get-EXOBanner -ConnectionContext:$ConnectionContext -IsRPSSession:$UseRPSSession.IsPresent Write-Host -ForegroundColor Yellow $BannerContent } catch { Write-Verbose "Failed to fetch banner content from server. Reason: $_" $cmdletLogger.LogGenericError($connectionContextID, $_); } } if (($ConnectionUri -ne '') -and ($AzureADAuthorizationEndpointUri -eq '')) { Write-Information "Using ConnectionUri:'$ConnectionUri', in the environment:'$ExchangeEnvironmentName'." } if (($AzureADAuthorizationEndpointUri -ne '') -and ($ConnectionUri -eq '')) { Write-Information "Using AzureADAuthorizationEndpointUri:'$AzureADAuthorizationEndpointUri', in the environment:'$ExchangeEnvironmentName'." } if ($EnableErrorReporting.Value -eq $true -and $UseRPSSession -eq $false) { Write-Message ("Writing cmdlet logs to " + $logFilePath) } $ImportedModuleName = ''; $LogModuleDirectoryPath = [System.IO.Path]::GetTempPath(); if ($UseRPSSession -eq $true) { $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellGalleryModule.dll"; $ModulePath = [System.IO.Path]::Combine($PSScriptRoot, $ExoPowershellModule); Import-Module $ModulePath; $global:_EXO_ModulePath = $ModulePath; $PSSession = New-ExoPSSession -ConnectionContext $ConnectionContext if ($PSSession -ne $null) { if ([string]::IsNullOrEmpty($Prefix)) { $PSSessionModuleInfo = Import-PSSession $PSSession -AllowClobber -DisableNameChecking -CommandName $CommandName -FormatTypeName $FormatTypeName } else { $PSSessionModuleInfo = Import-PSSession $PSSession -AllowClobber -DisableNameChecking -CommandName $CommandName -FormatTypeName $FormatTypeName -Prefix $Prefix } # Add the name of the module to clean up in case of removing the broken session $PSSession | Add-Member -NotePropertyName "CurrentModuleName" -NotePropertyValue $PSSessionModuleInfo.Name # Import the above module globally. This is needed as with using psm1 files, # any module which is dynamically loaded in the nested module does not reflect globally. $CurrentModule = Import-Module $PSSessionModuleInfo.Path -Global -DisableNameChecking -Prefix $Prefix -PassThru $CurrentModule | Add-Member -NotePropertyName "ModulePrefix" -NotePropertyValue $Prefix $CurrentModule | Add-Member -NotePropertyName "CommandName" -NotePropertyValue $CommandName $CurrentModule | Add-Member -NotePropertyName "FormatTypeName" -NotePropertyValue $FormatTypeName UpdateImplicitRemotingHandler # Import the REST module $RestPowershellModule = "Microsoft.Exchange.Management.RestApiClient.dll"; $RestModulePath = [System.IO.Path]::Combine($PSScriptRoot, $RestPowershellModule); Import-Module $RestModulePath -Cmdlet Set-ExoAppSettings; $ImportedModuleName = $PSSessionModuleInfo.Name; } } else { # Download the new web based EXOModule $ImportedModule = New-EXOModule -ConnectionContext $ConnectionContext; if ($null -ne $ImportedModule) { $ImportedModuleName = $ImportedModule.Name; $LogModuleDirectoryPath = $ImportedModule.ModuleBase Write-Verbose "AutoGen EXOModule created at $($ImportedModule.ModuleBase)" if ($null -ne $HelpFileNames -and $HelpFileNames -is [array] -and $HelpFileNames.Count -gt 0) { Get-HelpFiles -HelpFileNames $HelpFileNames -ConnectionContext $ConnectionContext -ImportedModule $ImportedModule -EnableErrorReporting:$EnableErrorReporting.Value } else { Write-Warning "Get-help cmdlet might not work. Please reconnect if you want to access that functionality." } } else { throw "Module could not be correctly formed. Please run Connect-ExchangeOnline again." } } # If we are configured to collect telemetry, add telemetry wrappers in case of an RPS connection if ($EnableErrorReporting.Value -eq $true) { if ($UseRPSSession -eq $true) { $FilePath = Add-EXOClientTelemetryWrapper -Organization (Get-OrgNameFromUPN -UPN $UserPrincipalName.Value) -PSSessionModuleName $ImportedModuleName -LogDirectoryPath $LogDirectoryPath.Value -LogModuleDirectoryPath $LogModuleDirectoryPath Write-Message("Writing telemetry records for this session to " + $FilePath[0] ); $global:_EXO_TelemetryFilePath = $FilePath[0] Import-Module $FilePath[1] -DisableNameChecking -Global Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Connect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid } $endTime = Get-Date $cmdletLogger.LogEndTime($connectionContextID, $endTime); $cmdletLogger.CommitLog($connectionContextID); if ($EOPConnectionInProgress -eq $false) { # Set the AppSettings Set-ExoAppSettings -ShowProgress $ShowProgress.Value -PageSize $PageSize.Value -UseMultithreading $UseMultithreading.Value -TrackPerformance $TrackPerformance.Value -EnableErrorReporting $true -LogDirectoryPath $LogDirectoryPath.Value -LogLevel $LogLevel.Value } } else { if ($EOPConnectionInProgress -eq $false) { # Set the AppSettings disabling the logging Set-ExoAppSettings -ShowProgress $ShowProgress.Value -PageSize $PageSize.Value -UseMultithreading $UseMultithreading.Value -TrackPerformance $TrackPerformance.Value -EnableErrorReporting $false } } } catch { # If telemetry is enabled, log errors generated from this cmdlet also. # If telemetry is not enabled, calls to cmdletLogger will be a no-op. $errorCountAtProcessEnd = $global:Error.Count $numErrorRecordsToConsider = $errorCountAtProcessEnd - $errorCountAtStart for ($i = 0 ; $i -lt $numErrorRecordsToConsider ; $i++) { $cmdletLogger.LogGenericError($connectionContextID, $global:Error[$i]); } $cmdletLogger.CommitLog($connectionContextID); if ($EnableErrorReporting.Value -eq $true -and $UseRPSSession -eq $true) { if ($global:_EXO_TelemetryFilePath -eq $null) { $global:_EXO_TelemetryFilePath = New-EXOClientTelemetryFilePath -LogDirectoryPath $LogDirectoryPath.Value # Import the REST module $RestPowershellModule = "Microsoft.Exchange.Management.RestApiClient.dll"; $RestModulePath = [System.IO.Path]::Combine($PSScriptRoot, $RestPowershellModule); Import-Module $RestModulePath -Cmdlet Set-ExoAppSettings; # Set the AppSettings Set-ExoAppSettings -ShowProgress $ShowProgress.Value -PageSize $PageSize.Value -UseMultithreading $UseMultithreading.Value -TrackPerformance $TrackPerformance.Value -ConnectionUri $ConnectionUri -EnableErrorReporting $true -LogDirectoryPath $LogDirectoryPath.Value -LogLevel $LogLevel.Value } # Log errors which are encountered during Connect-ExchangeOnline execution. Write-Message ("Writing Connect-ExchangeOnline error log to " + $global:_EXO_TelemetryFilePath) Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Connect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid -ErrorObject $global:Error -ErrorRecordsToConsider ($errorCountAtProcessEnd - $errorCountAtStart) } if ($_.Exception -ne $null) { # Connect-ExchangeOnline Failed, Remove ConnectionContext from Map. if ([Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionContextFactory]::RemoveConnectionContextUsingConnectionId($connectionContextID)) { Write-Verbose "ConnectionContext Removed" } if ($_.Exception.InnerException -ne $null) { throw $_.Exception.InnerException; } else { throw $_.Exception; } } else { throw $_; } } } } function Connect-IPPSSession { [CmdletBinding()] param( # Connection Uri for the Remote PowerShell endpoint [string] $ConnectionUri = 'https://ps.compliance.protection.outlook.com/PowerShell-LiveId', # Azure AD Authorization endpoint Uri that can issue the OAuth2 access tokens [string] $AzureADAuthorizationEndpointUri = 'https://login.microsoftonline.com/organizations', # Delegated Organization Name [string] $DelegatedOrganization = '', # PowerShell session options to be used when opening the Remote PowerShell session [System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption = $null, # Switch to bypass use of mailbox anchoring hint. [switch] $BypassMailboxAnchoring = $false, # Prefix [string] $Prefix = '', #Cmdlets to Import, by default it would bring all [string[]] $CommandName = @("*"), #The way the output objects would be printed on the console [string[]] $FormatTypeName = @("*") ) DynamicParam { if (($isCloudShell = IsCloudShellEnvironment) -eq $false) { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # User Principal Name or email address of the user $UserPrincipalName = New-Object System.Management.Automation.RuntimeDefinedParameter('UserPrincipalName', [string], $attributeCollection) $UserPrincipalName.Value = '' # User Credential to Logon $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection) $Credential.Value = $null # Certificate $Certificate = New-Object System.Management.Automation.RuntimeDefinedParameter('Certificate', [System.Security.Cryptography.X509Certificates.X509Certificate2], $attributeCollection) $Certificate.Value = $null # Certificate Path $CertificateFilePath = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificateFilePath', [string], $attributeCollection) $CertificateFilePath.Value = '' # Certificate Password $CertificatePassword = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificatePassword', [System.Security.SecureString], $attributeCollection) $CertificatePassword.Value = $null # Certificate Thumbprint $CertificateThumbprint = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificateThumbprint', [string], $attributeCollection) $CertificateThumbprint.Value = '' # Application Id $AppId = New-Object System.Management.Automation.RuntimeDefinedParameter('AppId', [string], $attributeCollection) $AppId.Value = '' # Organization $Organization = New-Object System.Management.Automation.RuntimeDefinedParameter('Organization', [string], $attributeCollection) $Organization.Value = '' $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('UserPrincipalName', $UserPrincipalName) $paramDictionary.Add('Credential', $Credential) $paramDictionary.Add('Certificate', $Certificate) $paramDictionary.Add('CertificateFilePath', $CertificateFilePath) $paramDictionary.Add('CertificatePassword', $CertificatePassword) $paramDictionary.Add('AppId', $AppId) $paramDictionary.Add('Organization', $Organization) if($PSEdition -eq 'Core') { # We do not want to expose certificate thumprint in Linux as it is not feasible there. if($IsWindows) { $paramDictionary.Add('CertificateThumbprint', $CertificateThumbprint); } } else { $paramDictionary.Add('CertificateThumbprint', $CertificateThumbprint); } return $paramDictionary } else { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # Switch to MSI auth $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection) $Device.Value = $false $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('Device', $Device) return $paramDictionary } } process { try { $EOPConnectionInProgress = $true if ($isCloudShell -eq $false) { Connect-ExchangeOnline -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -UserPrincipalName $UserPrincipalName.Value -PSSessionOption $PSSessionOption -Credential $Credential.Value -BypassMailboxAnchoring:$BypassMailboxAnchoring -ShowBanner:$false -DelegatedOrganization $DelegatedOrganization -Certificate $Certificate.Value -CertificateFilePath $CertificateFilePath.Value -CertificatePassword $CertificatePassword.Value -CertificateThumbprint $CertificateThumbprint.Value -AppId $AppId.Value -Organization $Organization.Value -Prefix $Prefix -CommandName $CommandName -FormatTypeName $FormatTypeName -UseRPSSession:$true } else { Connect-ExchangeOnline -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -PSSessionOption $PSSessionOption -BypassMailboxAnchoring:$BypassMailboxAnchoring -Device:$Device.Value -ShowBanner:$false -DelegatedOrganization $DelegatedOrganization -Prefix $Prefix -CommandName $CommandName -FormatTypeName $FormatTypeName -UseRPSSession:$true } } finally { $EOPConnectionInProgress = $false } } } function Disconnect-ExchangeOnline { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')] param() process { if ($PSCmdlet.ShouldProcess( "Running this cmdlet clears all active sessions created using Connect-ExchangeOnline or Connect-IPPSSession.", "Press(Y/y/A/a) if you want to continue.", "Running this cmdlet clears all active sessions created using Connect-ExchangeOnline or Connect-IPPSSession. ")) { # Keep track of error count at beginning. $errorCountAtStart = $global:Error.Count; $startTime = Get-Date try { # Get all the connection contexts so that the logger can be initialized. $connectionContexts = [Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionContextFactory]::GetAllConnectionContexts() $disconnectCmdletId = [System.Guid]::NewGuid().ToString() # denotes if any of the connections is an RPS session. # This is used to Push-EXOTelemetryRecord in case any RPS connections are present. $rpsConnectionWithErrorReportingExists = $false foreach ($context in $connectionContexts) { $context.Logger.InitLog($disconnectCmdletId); $context.Logger.LogStartTime($disconnectCmdletId, $startTime); $context.Logger.LogCmdletName($disconnectCmdletId, "Disconnect-ExchangeOnline"); if ($context.IsRpsSession -and $context.ErrorReportingEnabled) { $rpsConnectionWithErrorReportingExists = $true } } # Import the module once more to ensure that Test-ActiveToken is present $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellGalleryModule.dll"; $ModulePath = [System.IO.Path]::Combine($PSScriptRoot, $ExoPowershellModule); Import-Module $ModulePath -Cmdlet Clear-ActiveToken; $existingPSSession = Get-PSSession | Where-Object {$_.ConfigurationName -like "Microsoft.Exchange" -and $_.Name -like "ExchangeOnlineInternalSession*"} if ($existingPSSession.count -gt 0) { for ($index = 0; $index -lt $existingPSSession.count; $index++) { $session = $existingPSSession[$index] Remove-PSSession -session $session Write-Information "Removed the PSSession $($session.Name) connected to $($session.ComputerName)" # Remove any active access token from the cache Clear-ActiveToken -TokenProvider $session.TokenProvider # Remove any previous modules loaded because of the current PSSession if ($session.PreviousModuleName -ne $null) { if ((Get-Module $session.PreviousModuleName).Count -ne 0) { $null = Remove-Module -Name $session.PreviousModuleName -ErrorAction SilentlyContinue } $session.PreviousModuleName = $null } # Remove any leaked module in case of removal of broken session object if ($session.CurrentModuleName -ne $null) { if ((Get-Module $session.CurrentModuleName).Count -ne 0) { $null = Remove-Module -Name $session.CurrentModuleName -ErrorAction SilentlyContinue } } } } # Remove all the AutoREST modules from this instance of powershell if created $existingAutoRESTModules = Get-Module "tmpEXO_*" foreach ($module in $existingAutoRESTModules) { $null = Remove-Module -Name $module -ErrorAction SilentlyContinue } Write-Information "Disconnected successfully !" # Remove all the Wrapped modules from this instance of powershell if created $existingWrappedModules = Get-Module "EXOCmdletWrapper-*" foreach ($module in $existingWrappedModules) { $null = Remove-Module -Name $module -ErrorAction SilentlyContinue } # Remove all ConnectionContexts # this internally clears all the active tokens in ConnectionContexts [Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionContextFactory]::RemoveAllConnectionContexts() if ($rpsConnectionWithErrorReportingExists) { if ($global:_EXO_TelemetryFilePath -eq $null) { $global:_EXO_TelemetryFilePath = New-EXOClientTelemetryFilePath } Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Disconnect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid } } catch { # If telemetry is enabled for any of the connections, log errors generated from this cmdlet also. $errorCountAtProcessEnd = $global:Error.Count $endTime = Get-Date foreach ($context in $connectionContexts) { $numErrorRecordsToConsider = $errorCountAtProcessEnd - $errorCountAtStart for ($i = 0 ; $i -lt $numErrorRecordsToConsider ; $i++) { $context.Logger.LogGenericError($disconnectCmdletId, $global:Error[$i]); } $context.Logger.LogEndTime($disconnectCmdletId, $endTime); $context.Logger.CommitLog($disconnectCmdletId); $logFilePath = $context.Logger.GetCurrentLogFilePath(); } if ($rpsConnectionWithErrorReportingExists) { if ($global:_EXO_TelemetryFilePath -eq $null) { $global:_EXO_TelemetryFilePath = New-EXOClientTelemetryFilePath } # Log errors which are encountered during Disconnect-ExchangeOnline execution. Write-Message ("Writing Disconnect-ExchangeOnline errors to " + $global:_EXO_TelemetryFilePath) Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Disconnect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid -ErrorObject $global:Error -ErrorRecordsToConsider ($errorCountAtProcessEnd - $errorCountAtStart) } throw $_ } $endTime = Get-Date foreach ($context in $connectionContexts) { if ($context.Logger -ne $null) { $context.Logger.LogEndTime($disconnectCmdletId, $endTime); $context.Logger.CommitLog($disconnectCmdletId); } } } } } # SIG # Begin signature block # MIInrAYJKoZIhvcNAQcCoIInnTCCJ5kCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCQm8Vble/zE0jq # BbbdXJ5C7y67PjCT7bmrgpvH4AIwpaCCDYEwggX/MIID56ADAgECAhMzAAACzI61 # lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK # No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH # UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9 # DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0 # RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz # xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D # sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs # J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13 # vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB # d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/ # 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG # AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG # dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0 # GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc # J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM # j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z # 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZgTCCGX0CAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg+AuKfm3U # cYxk0CGe2aRILGVSLP8H4HApMiuchky1gqUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQAsUEwuKbJCWeIFdP7blG4ebJsOqsxGwq+LWUOb5Jbm # /LZd8eWP4uhjrJ3QM4TgbDjMHLymhaJiEFc+ihH7YjFcAxcJ3NCxZQCwLch2qw6x # QFpB+CpiDcXLPhiYaa58BpJzXE86BlTQnCI/es0xbcmACFx8xUAb1/CVWwqumeUb # +ZqpDYQJRlgRkO/Vul/ZaefylZ0serRhL01DCPtQPITz7ZxzkN4Kp/h262o3hTsr # rjPNCyiX7O9hhYGhCjVkxC+2xZCzOeyIMDxvtgaOMOATJLZ3sUNvZzihDgVFb9e6 # ULp7LEOkDVgHottVAqDGqw6Pe8iPPeAF5rFeYVHu6/Q2oYIXCzCCFwcGCisGAQQB # gjcDAwExghb3MIIW8wYJKoZIhvcNAQcCoIIW5DCCFuACAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIMDPffhi/uKSSq+x+pIRAF41Z1J3LU41kfeJNWyw # 8DoMAgZi2tXSgU4YEzIwMjIwODEyMTE0NDA0LjIzNlowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjpGN0E2LUUyNTEtMTUwQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCEV4wggcQMIIE+KADAgECAhMzAAABpQDeCMRAB3FOAAEA # AAGlMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIyMDMwMjE4NTExOVoXDTIzMDUxMTE4NTExOVowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGN0E2 # LUUyNTEtMTUwQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALqxhuggaki8wuoOMMd7 # rsEQnmAhtV8iU1Y0itsHq30TdCXJDmvZjaZ8yvOHYFVhTyD1b5JGJtwZjWz1fglC # qsx5qBxP1Wx1CZnsQ1tiRsRWQc12VkETmkY8x46MgHsGyAmhPPpsgRnklGai7HqQ # FB31x/Qjkx7rbAlr6PblB4tOmaR1nKxl4VIgstDwfneKaoEEw4iN/xTdztZjwyGi # Y5hNp6beetkcizgJFO3/yRHYh0gtk+bREhrmIgbarrrgbz7MsnA7tlKvGcO9iHc6 # +2symrAVy3CzQ4IMNPFcTTx8wTZ+kpv6lFs1eG8xlfsu2NDWKshrMlKH2JpYzWAW # 1fCOD5irXsE4LOvixZQvbneQE6+iGfIQwabj+fRdouAU2AiE+iaNsIDapdKab8WL # xz6VPRbEL+M6MFkcsoiuKHHoshCp7JhmZ9iM0yrEx2XebOha/XQ342KsRGs2h02g # pX6wByyT8eD3MJVIxSRm4MLIilvWcpd9N3rooawbLU6gdk7goKWS69+w2jtouXCE # Yt6IPfZq8ldi0L/CwYbtv7mbHmIZ9Oc0JEJc6b9gcVDfoPiemMKcz15BLepyx7np # Q2MiDKIscOqKhXuZI+PZerNOHhi/vsy2/Fj9lB6kJrMYSfV0F2frvBSBXMB7xjv8 # pgqX5QXUe8nTxb4UfJ0cDAvBAgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUX6aPAwCX # rq6tcO773FkXS2ipGt8wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIw # XwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3Js # MGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB # JTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcD # CDANBgkqhkiG9w0BAQsFAAOCAgEAlpsLF+UwMKKER2p0WJno4G6GGGnfg3qjDdaH # c5uvXYtG6KmHrqAf/YqHkmNotSr6ZEEnlGCJYR7W3uJ+5bpvj03wFqGefvQsKIR2 # +q6TrzozvP4NsodWTT5SVp/C6TEDGuLC9mOQKA4tyL40HTW7txb0cAdfgnyHFoI/ # BsZo/FaXezQ8hO4xUjhDpyNNeJ6WYvX5NC+Hv9nmTyzjqyEg/L2cXAOmxEWvfPAQ # 1lfxvrtUwG75jGeUaewkhwtzanCnP3l6YjwJFKB6n7/TXtrfik1xY1kgev1JwQ5a # UdPxwSdDmGE4XTN2s6pPOi8IO199Of6AEvh41eDxRz+11VUcpuGn7tJUeSTUSHsv # zQ8ECOj5w77Mv55/F8hWu07egnG8SrWj5+TFxNPCpx/AFNvzz+odTRTZd4LWuomc # MHUmLFiUGOAdetF6SofHG5EcFn0DTD1apBZzCP8xsGQcZgwVqo7ov23/uIJlMCLA # yTYZV9ITCP09ciUJbKBVCQNrGEnQ/XLFO9mysyyDRrvHhU5uGPdXz4Jt2/ZN7JQY # RuVNSuCpNwoK0Jr1s6ciDvHEeLyiczxoIe9GH3SyfbHx6v/phI+iE3DWo1TCK75E # L6pt6k5i36/kn2uSVXdTH44ZVkh3/ihV3vEws78uGlvsiMcrKBgpo3HdcjDHiHoW # sUf4GIwwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC0TCC # AjoCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNv # MSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGN0E2LUUyNTEtMTUwQTElMCMGA1UE # AxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA # s8lw20WzmxDKiN1Lhh7mZWXutKiggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt # cCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOagWnUwIhgPMjAyMjA4MTIwODQ5 # NTdaGA8yMDIyMDgxMzA4NDk1N1owdjA8BgorBgEEAYRZCgQBMS4wLDAKAgUA5qBa # dQIBADAJAgEAAgFjAgH/MAcCAQACAhEqMAoCBQDmoav1AgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQEFBQADgYEAi5tp7rTzxCX7dCztEHEuHQcAez4x8/EwLuBNKRjD6T+Q # WJyvLm6k81y7O9U7HUiD/866f6x7Hrg8eqVOap8YILail/POYgK2f8N/zRvlvzry # MRCzryleiUgd7X2FeqTtybdc8jKrw8Rlgh/Zg26/oExVrl2A84h6ZPZK8ko702Qx # ggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA # AaUA3gjEQAdxTgABAAABpTANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDMTndXiSIforQ2P3nLOyQc # /MnGKQfEgtc3GRmX87loQzCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILgK # OHF+ZgSxoK3YBTzcqGH7okeXKTcHvS98wcyUEtxcMIGYMIGApH4wfDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGlAN4IxEAHcU4AAQAAAaUwIgQg/d5z # /j8/VGyyV0yXaAceWSI99GWZXxtOpfzRELntv5swDQYJKoZIhvcNAQELBQAEggIA # o3BRnIoPruDcZsZcIlISeqGNgV+ReupBIJMmGtJHy2nu/aASICv8Az6oXTI84YvR # hmUgH4a/lBh4o3pkm6v3OghjUPmo+hlDbYHA7kRsIP8EXSBKJfjrIQmblEaDDoak # NL+PWe+NCHFs+jcQSIFf0T/JoEmIoMFg8oHxA6On+U7WwBSWKkZAU0eus+j+QhQU # Qk7sqgaE7UlAmg3isVBpibrLWV0qE2yIGfYMR0EWBo3MV9Lykt1AQIAUJNPfftaw # V/cliRsdpxxOAwLkMeovIt5y72EuxSaZUPikdGtmj/5BX/hoa59slf6rkX9vxKmQ # RBtWG8luyT/yLoioo8japY4lOHWhizy8GJp220iITN+HBBDf07OPRfq5gaPQIMkY # iaO8+R8KdLgxnpAW/RmXPt2H8+Snd+kMGHKSgxoHe7pVmCGrDg07m5RMZ70QmRH4 # uGbh8cbEbsiMCwRrHwDquFql/wOX2JQwj/iQc3LR9PcYY6i1983+70F+f6QMFcQs # Tt25mKxVjWIx8xV0N2HhG1+ioaELI+6ogZZ96B0E3LCAPZO1bEeyLgdkG+5D1qOj # 4qaJUF2XuVGhj2LPa2Y5va1pNtTlVTIkr7QyhUwVMggGv5phi1EWqEtGQX+U49mw # 2q1+PYPUIHQWiVX5Qs3+jYTr0nz4nzMLrop0CWFSviE= # SIG # End signature block |