netFramework/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 # MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor # 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/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg+AuKfm3U # cYxk0CGe2aRILGVSLP8H4HApMiuchky1gqUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQAsUEwuKbJCWeIFdP7blG4ebJsOqsxGwq+LWUOb5Jbm # /LZd8eWP4uhjrJ3QM4TgbDjMHLymhaJiEFc+ihH7YjFcAxcJ3NCxZQCwLch2qw6x # QFpB+CpiDcXLPhiYaa58BpJzXE86BlTQnCI/es0xbcmACFx8xUAb1/CVWwqumeUb # +ZqpDYQJRlgRkO/Vul/ZaefylZ0serRhL01DCPtQPITz7ZxzkN4Kp/h262o3hTsr # rjPNCyiX7O9hhYGhCjVkxC+2xZCzOeyIMDxvtgaOMOATJLZ3sUNvZzihDgVFb9e6 # ULp7LEOkDVgHottVAqDGqw6Pe8iPPeAF5rFeYVHu6/Q2oYIXADCCFvwGCisGAQQB # gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME # AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIMDPffhi/uKSSq+x+pIRAF41Z1J3LU41kfeJNWyw # 8DoMAgZi1V1ZbBYYEzIwMjIwODEyMTE0MTU2Ljc5NVowBIACAfSggdCkgc0wgcox # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p # Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg # RVNOOjNFN0EtRTM1OS1BMjVEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGg6buMuw6i0XoAAQAAAaAw # DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN # MjExMjAyMTkwNTIzWhcNMjMwMjI4MTkwNTIzWjCByjELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046M0U3QS1FMzU5LUEy # NUQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G # CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/2uIOaHGdAOj2YvhhI6C8iFAq7wrl # /5WpPjj0fEHCi6Ivx/I02Jss/HVhkfGTMGttR5jRhhrJXydWDnOmzRU3B4G525T7 # pwkFNFBXumM/98l5k0U2XiaZ+bulXHe54x6uj/6v5VGFv+0Hh1dyjGUTPaREwS7x # 98Te5tFHEimPa+AsG2mM+n9NwfQRjd1LiECbcCZFkgwbliQ/akiMr1tZmjkDbxtu # 2aQcXjEfDna8JH+wZmfdu0X7k6dJ5WGRFwzZiLOJW4QhAEpeh2c1mmbtAfBnhSPN # +E5yULfpfTT2wX8RbH6XfAg6sZx8896xq0+gUD9mHy8ZtpdEeE1ZA0HgByDW2rJC # bTAJAht71B7Rz2pPQmg5R3+vSCri8BecSB+Z8mwYL3uOS3R6beUBJ7iE4rPS9WC1 # w1fZR7K44ZSme2dI+O9/nhgb3MLYgm6zx3HhtLoGhGVPL+WoDkMnt93IGoO6kNBC # M2X+Cs22ql2tPjkIRyxwxF6RsXh/QHnhKJgBzfO+e84I3TYbI0i29zATL6yHOv5s # Es1zaNMih27IwfWg4Q7+40L7e68uC6yD8EUEpaD2s2T59NhSauTzCEnAp5YrSscc # 9MQVIi7g+5GAdC8pCv+0iRa7QIvalU+9lWgkyABU/niFHWPjyGoB4x3Kzo3tXB6a # C3yZ/dTRXpJnaQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFHK5LlDYKU6RuJFsFC9E # zwthjNDoMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY # MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p # Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF # BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w # a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo # MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI # hvcNAQELBQADggIBADF9xgKr+N+slAmlbcEqQBlpL5PfBMqcLkS6ySeGJjG+LKX3 # Wov5pygrhKftXZ90NYWUftIZpzdYs4ehR5RlaE3eYubWlcNlwsKkcrGSDJKawbbD # GfvO4h/1L13sg66hPib67mG96CAqRVF0c5MA1wiKjjl/5gfrbdNLHgtREQ8zCpbK # 4+66l1Fd0up9mxcOEEphhJr8U3whwFwoK+QJ/kxWogGtfDiaq6RyoFWhP8uKSLVD # V+MTETHZb3p2OwnBWE1W6071XDKdxRkN/pAEZ15E1LJNv9iYo1l1P/RdF+IzpMLG # DAf/PlVvTUw3VrH9uaqbYr+rRxti+bM3ab1wv9v3xRLc+wPoniSxW2p69DN4Wo96 # IDFZIkLR+HcWCiqHVwFXngkCUfdMe3xmvOIXYRkTK0P6wPLfC+Os7oeVReMj2TA1 # QMMkgZ+rhPO07iW7N57zABvMiHJQdHRMeK3FBgR4faEvTjUAdKRQkKFV82uE7w0U # MnseJfX7ELDY9T4aWx2qwEqam9l7GHX4A2Zm0nn1oaa/YxczJ7gIVERSGSOWLwEM # xcFqBGPm9QSQ7ogMBn5WHwkdTTkmanBb/Z2cDpxBxd1vOjyIm4BOFlLjB4pivClO # 2ZksWKH7qBYloYa07U1O3C8jtbzGUdHyLCaVGBV8DfD5h8eOnyjraBG7PNNZMIIH # cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB # iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp # TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw # OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ # Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh # C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx # WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc # UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc # nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo # veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi # YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 # fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH # GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X # KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE # R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ # eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 # FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd # BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE # AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI # MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB # Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud # HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By # b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO # MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy # dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC # AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk # bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng # ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 # lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC # gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 # MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU # BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh # VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ # fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp # NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI # qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 # oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw # IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjozRTdBLUUzNTktQTI1RDElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAEwa4jWjacbOYU++9 # 5ydJ7hSCi5iggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN # BgkqhkiG9w0BAQUFAAIFAOagJqkwIhgPMjAyMjA4MTIwOTA4NTdaGA8yMDIyMDgx # MzA5MDg1N1owdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5qAmqQIBADAKAgEAAgIF # xgIB/zAHAgEAAgITijAKAgUA5qF4KQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor # BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA # A4GBAB7lOduj/mPLYzfWeff6BLgP5qiQC4WBe4ZY0AEtrO5+SjDp2uR4j/ZKrm8S # xTRTavErEUh6Uok0nzTzZDZ0nJ8aHA4zooEYMfmSuHo6mS3uU7dl+0uZiGuE5JPH # O7BHKVBG6VPVGLR1JlAxvvpns3mSGo0JQK/jR62qFoLBvnTuMYIEDTCCBAkCAQEw # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGg6buMuw6i0XoA # AQAAAaAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B # CRABBDAvBgkqhkiG9w0BCQQxIgQgHTMVJ6n33HyI3qQR0KBaT/7KxnBGYckBg+0Z # nLcJhngwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAvR4o8aGUEIhIt3REv # sx0+svnM6Wiaga5SPaK4g6+00zCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAABoOm7jLsOotF6AAEAAAGgMCIEIOnYNDQ0DydHUBumy8Q/ # bQkb4j4RwT09gxSTOmJUmVPCMA0GCSqGSIb3DQEBCwUABIICABgY+fDD5/26dE6S # ai08QtvMofGwvbgvrFw9dXcjW680MtXXRyUhBVlVgZ7QP/Cn2Q5WfDwfFU4QEHmI # aXL9ssdeQ2+G/dYQaSGK0MmT+hrf5yy8DUHu2/IduB8pddesEhP++vpyAU2q67ew # vjMR8RgWHHgdkkMtyb3G6FxsdIbhgE+MxqZaLKa/BAoXsBFFdl6gaPQ2QSDUcNwj # DvtArLDCLdl9Z3n/X3YtUbQnbYqKEa09LJHYbJ++GXKytybCmWIh9+Wn+18rkcXu # c161YfujxpYykbxXbPwmRN5A9JI+3WmxOx0uzZKMQys5899aPD4DPrh3x0p6f3bC # GYB+luCV0/J52JxpOWuZNDl64EvhAfUit1KIXPkcaVZrF9xKANDbVaeY6VYby0RT # WUe8em3RuEnr9j+Hd6TRyOIMtHNf6wYQJgF1hTQ/N1Z6afaXYiyVJntHN9ovZFXG # 85sd+TXi+LxFixBOVRyVxOft/aNmVdPZZIaef++2ePWO8sgUDmRJ6DaRzGI2Gtqo # RMHMJuGsK+Ftx85q+d8eIwXj26vPvgNw7Gdc+LNkp3kghIhrwJU1ep2Tc5oFIlfv # 4NCgAtrusVbFW3hyYy6H6eZ9wW2mQfhoZGOcJXv5GGIjotUkdhnDLF4nHJJw0YGv # skYBJoNTjTT3tPefbP1PYg8+T0QJ # SIG # End signature block |