Public/Common.ps1
<#
.SYNOPSIS Detects the SEPPmail.Cloud deployment status, based in M365 Tenant information .DESCRIPTION Checks deployment Status of the SEPPmail.Cloud based on M365 tenant information from outside, based on DNS information - Queries routing mode based on available MX records in the SEPPmail.cloud - Queries region based on IP addresses and mail hosts - Tests if MX record is set correctly in Inline Mode - Checks if the SEPPmail.cloud is prepared for Certificate-based-Connectors Creates a PSObject with the following values: Routing = inline/parallel # Routing Mode Region = ch/de/prv # Cloud Region (data center location) SEPPmailCloudDomain = 'contoso.de','contoso.ch' # Mail domains which will be routed via SEPPmail. Is basis for naming the mail routing hosts (gate/relay/mail) CBCenabled = $true/$false # Certificate Based Connectors setup available CBCConnectorHost = '<tenantid>.<rg>.seppmail.cloud' # Hostname of TLS host for CBC InlineMXMatch = $true/$false # (Inline Mod only) MX record points to the correct (SEPPmail) host RelayHost = domain-tls.relay.seppmail.cloud # Name of relay host GateHost = domain-tls.gate.seppmail.cloud # Name of gate host MailHost = domain-tls.mail.seppmail.cloud # Name of mail host .NOTES Emits parameters for New-SC365Setup .LINK https://github.com/seppmail/SEPPmail365cloud/blob/main/README.md .EXAMPLE Get-SC365DeploymentInfo DeployMentStatus : True SEPPmailCloudDomain : contoso.com Region : ch Routing : inline InBoundOnly : False CBCDeployed : True CBCConnectorHost : 271dd771-832d-4913-80d7-9c21616accd4.ch.seppmail.cloud CBCDnsEntry : c60abc9d247a2bf21cbc3344eef199eb738876b2.cbc.seppmail.cloud InlineMXMatch : True MailHost : RelayHost : contoso-com.relay.seppmail.cloud GateHost : contoso-com.gate.seppmail.cloud .EXAMPLE Get-SC365DeploymentInfo -SEPPmailCLoudDomain contoso.eu DeployMentStatus : True SEPPmailCloudDomain : contoso.eu Region : de Routing : inline InBoundOnly : False CBCDeployed : True CBCConnectorHost : 271dd771-832d-4913-80d7-9c21616accd4.de.seppmail.cloud CBCDnsEntry : c60abc9d247a2bf21cbc3344eef199eb738876b2.cbc.seppmail.cloud InlineMXMatch : True MailHost : RelayHost : contoso-eu.relay.seppmail.cloud GateHost : contoso-eu.gate.seppmail.cloud #> function Get-SC365DeploymentInfo { [CmdletBinding()] param ( [Parameter( Mandatory = $false, HelpMessage = "Domain name you selected for Tenant-onboarding" )] [string[]]$SEPPmailCloudDomain ) begin { if (!(Test-SC365ConnectionStatus)){ throw [System.Exception] "You're not connected to Exchange Online - please connect to the designated tenant prior to using this CmdLet" } else { Write-Information "Connected to Exchange Organization `"$Script:ExODefaultDomain`"" -InformationAction Continue } # Reset the Stage $DeployMentStatus = $null $region = $null $routing = $null $inBoundOnly = $null $DeploymentInfo = [PSCustomObject]@{ DeploymentStatus = $null SEPPmailCloudDomain = $Null Region = $null Routing = $null InboundOnly = $null CBCDeployed = $Null CBCConnectorHost = $null CBCDnsEntry = $null InlineMXMatch = $null MailHost = $null GateHost = $null RelayHost = $null SwissSignCheckTXT = $null spfTXT = $null DnsSecurEmailCNAME = $null DnsLetsEncryptCNAME = $null DnsDKIMTXT = $null DnsWildCardActive = $null } } process { #region Select DefaultDomain if (!($SEPPmailCloudDomain)) { [String]$DNSHostDomain = $tenantAcceptedDomains |Where-Object 'Default' -eq $true |select-Object -ExpandProperty DomainName Write-Verbose "Extracted Default-Domain with name $DNSHostDomain from TenantDefaultDomains" } else { foreach ($dom in $SEPPmailCloudDomain) { if (!($tenantAcceptedDomains.DomainName.Contains($Dom))) { throw [System.Exception] "Domain $Dom is not member of this tenant! Check for typos or connect to different tenant" } else { Write-Verbose "Domain $Dom is member of the tenant domains" if ($dom -eq ($tenantAcceptedDomains |Where-Object 'Default' -eq $true |select-Object -ExpandProperty DomainName)) { $DnsHostDomain = $dom } else { Write-Verbose "TenantDefaultDomain not selected, using $dom and DNSHost domain" $DnsHostDomain = $dom } } } } #endregion Select DefaultDomain #region Query SEPPmail routing-Hosts DNS records and detect routing mode and in/outbound [string]$relayHost = $DnsHostDomain.Replace('.','-') + '.relay.seppmail.cloud' [string]$mailHost = $DnsHostDomain.Replace('.','-') + '.mail.seppmail.cloud' [string]$gateHost = $DnsHostDomain.Replace('.','-') + '.gate.seppmail.cloud' $defEAPref = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' $DeploymentStatus = 'unknown' if (((Resolve-Dns -Query $GateHost).Answers)) { $routing = 'inline' if ((Resolve-Dns -Query $RelayHost).Answers) { $inBoundOnly = $false Write-Verbose "$GateHost and $relayHost alive ==> InLine-bidirectional" $deploymentStatus = $true } else { if (!((Resolve-Dns -Query $RelayHost).Answers)) { $inBoundOnly = $true $relayHost = $null Write-Verbose "$GateHost alive,$relayHost missing ==> InLine-InBound only" $deploymentStatus = $true } } } else { if ((Resolve-Dns -Query $MailHost ).Answers) { Write-verbose "$mailHost alive ==> parallel" $routing = 'parallel' $deploymentStatus = $true } else { $mailHost = $null $deploymentStatus = $false } } #endregion mail host queries #region DoubleCheck if MX Record is set correctly $mxFull = get-mxrecordreport -Domain $DnsHostDomain if ($mxFull.Count -eq 0) { $DeployMentStatus = $false } else { if ($mxFull.Count -eq 1) { $mx = $mxFull | Select-Object -ExpandProperty highestpriorityMailHost -Unique } if ($mxFull.Count -gt 1) { $mx = $mxFull[0] | Select-Object -ExpandProperty highestpriorityMailHost -Unique } if (($mx.Split($DnsHostDomain.Replace('.', '-'))) -eq '.gate.seppmail.cloud') { Write-Verbose "MX = SEPPmail" } if (($mx.Split($DnsHostDomain.Replace('.', '-'))) -eq '.mail.protection.outlook.com') { Write-Verbose "MX = Microsoft" } if ($routing -eq 'inline') { if ($mx -eq $gateHost) { Write-Verbose "MX record $mx in M365 Config matches $gateHost" $mxMatch = $true } else { Write-Warning "MX Record $mx configured in M365 does not fit to SEPPmail GateHost $gateHost in DNS - Check your provisioning Status in SEPPmail.cloud Portal" $mxMatch = $false $DeployMentStatus = $false } } } #endRegion MX Check #region Identify region based on Cloud-IPAddresses $region = $null $ch = Get-SC365CloudConfig -region 'ch' $de = Get-SC365CloudConfig -region 'de' $prv = Get-SC365CloudConfig -region 'prv' $dev = Get-SC365CloudConfig -region 'dev' if ($routing -eq 'inline') { [String[]]$GateIP = ((Resolve-Dns -Query $GateHost).Answers)|Select-Object -expand Address| Select-Object -expand IPAddressToString Foreach ($IP in $GateIP) {if ($ch.IPv4GateIPs.Contains($Ip)) {$region = 'ch';break}} Foreach ($IP in $GateIP) {if ($de.IPv4GateIPs.Contains($Ip)) {$region = 'de';break}} Foreach ($IP in $GateIP) {if ($prv.IPv4GateIPs.Contains($Ip)) {$region = 'prv';break}} Foreach ($IP in $GateIP) {if ($dev.IPv4GateIPs.Contains($Ip)) {$region = 'dev';break}} } if ($routing -eq 'parallel') { [string[]]$MailIP = ((Resolve-Dns -Query $MailHost).Answers)|Select-Object -expand Address| Select-Object -expand IPAddressToString Foreach ($ip in $mailIp) {if ($ch.IPv4MailIPs.Contains($Ip)) { $region = 'ch';break}} Foreach ($ip in $mailIp) {if ($de.IPv4MailIPs.Contains($Ip)) { $region = 'de';break}} Foreach ($ip in $mailIp) {if ($prv.IPv4MailIPs.Contains($IP)) { $region = 'prv';break}} Foreach ($ip in $mailIp) {if ($dev.IPv4MailIPs.Contains($IP)) { $region = 'dev';break}} } #endregion Cloud-IP-Addresses #region Check CBC Availability [String]$TenantID = Get-SC365TenantID -MailDomain $DnsHostDomain -OutVariable "TenantID" $TenantIDHash = Get-SC365StringHash -String $TenantID [string]$hashedDomain = $TenantIDHash + '.cbc.seppmail.cloud' if (((resolve-dns -query $hashedDomain -QueryType TXT).Answers)) { $CBCDeployed = $true Write-Verbose "$HashedDomain of TenantID $tenantId has a CBC entry" } else { $CBCDeployed = $false Write-Warning "Could not find TXT Entry for TenantID $TenantID of domain $DNSHostCloudDomain. Setup will most likely fail! Go to the SEPPmail.cloud-portal and check the deployment status." } #endregion CBC #region Advanced DNS Queries # TXT records (Swisssign check and SPF) $txtRecords = (Resolve-dns -querytype TXT $DNSHostdomain).Answers if ($txtRecords) { $swisssignTXTRecord = $txtRecords|Where-Object EscapedText -like 'swisssign-check*' if ($swissSignTXTRecord) { $swissSignCheck = $swissSignTXTRecord.EscapedText.Trim('{','}') } else { Write-Warning "Swisssign TXT (swissSign-check) record is missing - SC-CERT deployment will fail!" } $spfTXTrecord = $txtRecords|Where-Object EscapedText -like 'v=spf*' if ($spfTXTrecord) { $spf = $spfTXTrecord.EscapedText.Trim('{','}') } else { Write-Warning "SPF TXT (v=spf*) record is missing" } } ## WebService hosts [String]$SecurEmailCNAME = (Resolve-dns -querytype CNAME -Query ('securemail.' + $DNSHostdomain)).Answers.CanonicalName.Value ## Letsencrypt [String]$LetsEncryptCNAME = (Resolve-dns -querytype CNAME -Query ('_acme-challenge.securemail.' + $DNSHostdomain)).Answers.CanonicalName.Value ## DKIM (default._domainkey) [String]$dkimRecord = (resolve-dns -QueryType TXT -query ('default._domainkey.' + $DNSHostDomain)).Answers.EscapedText ## Wildcard records if ((resolve-dns -query ('jioak84-nlkjec.' + $DNSHostDomain)).Answers.EscapedText) { $WildcardRecord = $true } else { $WildcardRecord = $false } $ErrorActionPreference = $defEAPref #endregion } end { $DeploymentInfo.DeploymentStatus = $DeploymentStatus $DeploymentInfo.Region = $region $DeploymentInfo.Routing = $routing $DeploymentInfo.InboundOnly = $inBoundOnly $DeploymentInfo.SEPPmailCloudDomain = $DNSHostDomain $DeploymentInfo.CBCDeployed = $CBCDeployed if ($region) {$DeploymentInfo.CBCConnectorHost = ($tenantId + ((Get-Variable $region).Value.TlsCertificate).Replace('*',''))} if ($CBCDeployed -eq $true) {$DeploymentInfo.CBCDnsEntry = ($TenantIDHash + '.cbc.seppmail.cloud')} if ($routing -eq 'inline') {$DeploymentInfo.InlineMXMatch = $MxMatch} if (($routing -eq 'inline') -and (!($inBoundOnly))) {$DeploymentInfo.RelayHost = $relayHost} if ($routing -eq 'inline') {$DeploymentInfo.GateHost = $gateHost} if ($routing -eq 'parallel') {$DeploymentInfo.MailHost = $MailHost} # DNS Records $DeploymentInfo.swisssignCheckTXT = $swisssignCheck $DeploymentInfo.spfTXT = $spf $DeploymentInfo.DnsSecurEmailCNAME = $SecurEmailCNAME $DeploymentInfo.DnsLetsEncryptCNAME = $LetsEncryptCNAME $DeploymentInfo.DnsDKIMTXT = $DKIMRecord $DeploymentInfo.DnsWildCardActive = $wildcardRecord return $DeploymentInfo } } <# .SYNOPSIS Removes all Rules and Connectors .DESCRIPTION Based on autodiscovery, or forced values through parameters, Remove-SC365Setup removes all connectors and rules from an Exo-Tenant .NOTES - none - .LINK https://github.com/seppmail/seppmail365cloud .EXAMPLE Remove-SC365Setup # Without any parameters, it runs discovery mode and removes rules and connectors .EXAMPLE Remove-SC365Setup -parallel # Forces to remove parallel setup config .EXAMPLE Remove-SC365Setup -inline # Forces to remove inline setup config .EXAMPLE Remove-SC365Setup -inline -inBoundOnly # Forces to remove inline in "InboundOnly" mode setup config #> function Remove-SC365Setup { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName='parallel' )] param( [Parameter( ParameterSetName = 'parallel', Mandatory=$false, HelpMessage="Inline routing via SEPPmail (MX ==> SEPPmail), or routing via Microsoft (MX ==> Microsoft)" )] [ValidateNotNullOrEmpty()] [ValidateSet('parallel','inline','p','i')] [Parameter( ParameterSetName = 'inline', Mandatory=$false, HelpMessage="Inline routing via SEPPmail (MX ==> SEPPmail), or routing via Microsoft (MX ==> Microsoft)" )] [ValidateNotNullOrEmpty()] [ValidateSet('parallel','inline','p','i')] [String]$routing, [Parameter( ParameterSetName = 'inline', Mandatory=$false, HelpMessage="No routing of outbound traffic via SEPPmail.cloud" )] [switch]$InBoundOnly ) Begin { if(!(Test-SC365ConnectionStatus)) { throw [System.Exception] "You're not connected to Exchange Online - please connect prior to using this CmdLet" } else { if ((!($InboundOnly)) -or (!($routing)) ) { try { $deploymentInfo = Get-SC365DeploymentInfo } catch { Throw [System.Exception] "Could not autodetect SEPPmail.cloud Deployment Status, use manual parameters" } if ($DeploymentInfo.DeployMentStatus -eq $false) { Write-Error "SEPPmail.cloud setup not (fully) deployed. Use Cloud-Portal and fix deployment." break } else { if ($DeploymentInfo) { if ($deploymentInfo.Routing) {$Routing = $deploymentInfo.Routing} else {Write-Error "Cloud not autodetect routing info, use manual parameters"; break} if ($DeploymentInfo.inBoundOnly -eq $true) {$inboundOnly = $true} if ($DeploymentInfo.inBoundOnly -eq $false) {$inboundOnly = $false} if ($null -eq $DeploymentInfo.inBoundOnly) {$inboundOnly = $false} } } } else { if ($deploymentInfo.routing -eq 'p') {$routing = 'parallel'} if ($deploymentInfo.routing -eq 'i') {$routing = 'inline'} } } } Process { Write-Verbose "Creating Progress Bar" $objectCount = $null # Count Rules foreach ($file in (Get-ChildItem -Path "$psscriptroot\..\ExOConfig\Rules\")) { $objectCount += if ((Get-SC365TransportRuleSettings -routing $routing -file $file).count -gt 0) {1} } # Count Connectors #if ((Get-SC365InboundConnectorSettings -routing $routing -file $file).count -gt 0) {1} $objectCount += 2 try { if ($InBoundOnly) { #Write-Progress -Activity "Removing SEPPmail.Cloud Setup" -Status "Removing Rules" -PercentComplete (0) Write-Information '--- Remove connector(s) ---' -InformationAction Continue Remove-SC365Connectors -routing $routing -InboundOnly:$inboundOnly } else { #Write-Progress -Activity "Removing SEPPmail.Cloud Setup" -Status "Removing Rules" -PercentComplete (0) Write-Information '--- Removing transport rules ---' -InformationAction Continue Remove-SC365Rules Write-Information '--- Remove connector(s) ---' -InformationAction Continue Remove-SC365Connectors -routing $routing -InboundOnly:$inboundOnly } } catch { throw [System.Exception] "Error: $($_.Exception.Message)" break } } End{ Write-Information "--- Successfully removed SEPPmail.cloud Setup in $routing mode ---" -InformationAction Continue } } <# .SYNOPSIS Creates all Rules and Connectors for SEPPmail.cloud .DESCRIPTION Based on autodiscovery, or forced values through parameters, New-SC365Setup creates all connectors and rules for an Exo-Tenant .NOTES - none - .LINK https://github.com/seppmail/seppmail365cloud .EXAMPLE New-SC365Setup # Without any parameters, it runs discovery mode and created rules and connectors .EXAMPLE New-SC365Setup -force # The force parameter will force the removal of an existing setup and recreate connectors and rules .EXAMPLE New-SC365Setup -SEPPmailCloudDomain contoso.com -routing parallel -region ch # Creates a setup for one domain in parallel mode and in region Switzerland .EXAMPLE New-SC365Setup -SEPPmailCloudDomain contoso.de -routing inline -region de # Creates a setup for one domain in inline mode and in region Germany/EU .EXAMPLE New-SC365Setup -SEPPmailCloudDomain contoso.de -routing inline -region de -inboundOnly # Creates a setup for one domain in inline mode and in region Germany/EU inbound only. #> function New-SC365Setup { [CmdLetBinding( SupportsShouldProcess=$true, ConfirmImpact = 'Medium', HelpURI = 'https://github.com/seppmail/SEPPmail365cloud/blob/main/README.md' )] # Specifies a path to one or more locations. param( [Parameter( Mandatory=$false, HelpMessage="The primary domain, booked in the SEPPmail.cloud" )] [Alias('domain')] [ValidateNotNullOrEmpty()] [String[]]$SEPPmailCloudDomain, [Parameter( Mandatory=$false, HelpMessage="Inline routing via SEPPmail (MX ==> SEPPmail), or routing via Microsoft (MX ==> Microsoft)" )] [ValidateNotNullOrEmpty()] [ValidateSet('parallel','inline','p','i')] [String]$routing, [Parameter( Mandatory=$false, HelpMessage="Physical location of your data" )] [ValidateSet('dev','prv','de','ch')] [String]$region, [Parameter( Mandatory=$false, HelpMessage="No routing of outbound traffic via SEPPmail.cloud" )] [switch]$InBoundOnly, [Parameter( Mandatory=$false, HelpMessage="Removes existing setup" )] [switch]$force ) Begin { if(!(Test-SC365ConnectionStatus)) { throw [System.Exception] "You're not connected to Exchange Online - please connect prior to using this CmdLet" } else { Write-Verbose "Connected to Exchange Organization `"$Script:ExODefaultDomain`"" -InformationAction Continue } # If user tries to use *.onmicrosoft.com domain ==> BREAK if ($SEPPmailCloudDomain -like '*.onmicrosoft.com') { Write-Error "Domain $SEPPmailCloudDomain is not intended for E-Mail sending and cannot be booked for the SEPPmail-cloud Service. Specify a custom domain of your tenant and retry." break } Write-Verbose "Detecting deployment status from SEPPmail.cloud setup" try { $deploymentInfo = Get-SC365DeploymentInfo } catch { Throw [System.Exception] "Could not autodetect SEPPmail.cloud deployment status, check SEPPmail.cloud portal deployment status" } Write-Verbose "Not enough parameters given, reading from Tenant, otherwise use data from console" if ((!($SEPPmailCloudDomain)) -or (!($region)) -or (!($routing)) ) { # Customers where TDAD is set to *.onmicrosoft.com ==> BREAK if ($DeploymentInfo.SEPPmailCloudDomain -like '*.onmicrosoft.com') { Write-Error "Domain $($DeploymentInfo.SEPPmailCloudDomain) is set as the tenant default accepted domain. $($DeploymentInfo.SEPPmailCloudDomain) is not intended for E-Mail sending and cannot be booked for the SEPPmail-cloud Service. Specify a custom domain of your tenant and retry or change the Default accepted domain in your Exchange Online tenant." break } if ($DeploymentInfo.DeployMentStatus -eq $false) { Write-Error "SEPPmail.cloud setup for domain $deploymentInfo.SEPPmailCloudDomain is not (fully) deployed. Use Cloud-Portal and fix deployment." break } else { if ($Deploymentinfo) { if ($deploymentInfo.Routing) {$Routing = $deploymentInfo.Routing} else {Write-Error "Cloud not autodetect routing info, use manual parameters"; break} if ($deploymentInfo.Routing -ne $routing) {Write-Error "SEPPmail.cloud is deployed with routing $deploymentInfo.Routing but the routing parameter is set to $routing, this will NOT WORK, exiting ..."; break} if ($deploymentInfo.Region) {$Region = $deploymentInfo.Region} else {Write-Error "Could not autodetect region. Use manual parameters"; break} if ($deploymentInfo.Region -ne $region) {Write-Error "SEPPmail.cloud is deployed in region $deploymentInfo.Region but the region parameter is set to $region, this will NOT WORK, exiting ..."; break} if ($DeploymentInfo.SEPPmailCloudDomain) {$SEPPmailCloudDomain = $DeploymentInfo.SEPPmailCloudDomain} else {Write-Error "Could not autodetect SEPPmailCloudDomain. Use manual parameters"; break} if ($DeploymentInfo.inBoundOnly -eq $true) {$inboundOnly = $true} if ($DeploymentInfo.inBoundOnly -eq $false) {$inboundOnly = $false} if ($null -eq $DeploymentInfo.inBoundOnly) {$inboundOnly = $false} } } } else { if ($deploymentInfo.routing -eq 'p') {$routing = 'parallel'} if ($deploymentInfo.routing -eq 'i') {$routing = 'inline'} Write-Verbose "Checking if console parameter fit to deployment Info" if ($SEPPmailCloudDomain -ne $deploymentInfo.SEPPmailCloudDomain) {Write-Warning "Domain `"$SEPPmailCloudDomain`" does not fit to collected deployment info, just detected domain `"$($deploymentInfo.SEPPmailcloudDomain)`""} if ($routing -ne $deploymentInfo.routing) {Write-Error "Routing mode `"$routing`" does not fit to deployment info, just detected routing mode `"$($DeploymentInfo.routing)`" STOPPING because deployment will FAIL";break} if ($region -ne $deploymentInfo.region) {Write-Error "Region `"$region`" does not fit to deployment info, just detected region `"$($deploymentInfo.region)`" STOPPING because deployment will FAIL";break} Write-Verbose "Confirming if $SEPPmailCloudDomain is part or the tenant" $TenantDefaultDomain = $null foreach ($validationDomain in $SEPPmailCloudDomain) { if ((Confirm-SC365TenantDefaultDomain -ValidationDomain $validationDomain) -eq $true) { Write-verbose "Domain is part of the tenant and the Default Domain" $TenantDefaultDomain = $ValidationDomain } else { if ((Confirm-SC365TenantDefaultDomain -ValidationDomain $validationDomain) -eq $false) { Write-verbose "Domain is part of the tenant" } else { Write-Error "Domain is NOT Part of the tenant" break } } } } } Process { try { if ($force) { Remove-SC365Setup } } catch { throw [System.Exception] "Error: $($_.Exception.Message)" Write-Error "Setup removal failed. Try removing SEPPmail.cloud Rules and Connectors from the Microsoft portal admin.microsoft.com or with native Exchange Online PowerShell Module CmdLets." break } # For Connectors - use Tenant Default Domain # For TransportRules, use all domains in the array if ($SEPPmailCloudDomain.count -le 1) { $ConnectorDomain = $SEPPmailCloudDomain[0] } else { $ConnectorDomain = $TenantDefaultDomain } try { if ($InBoundOnly -eq $true) { Write-Information '--- Creating inbound connector ---' -InformationAction Continue New-SC365Connectors -SEPPmailCloudDomain $ConnectorDomain -routing $routing -region $region -inboundOnly:$true } else { Write-Information '--- Creating in and outbound connectors ---' -InformationAction Continue New-SC365Connectors -SEPPmailCloudDomain $ConnectorDomain -routing $routing -region $region } } catch { throw [System.Exception] "Error: $($_.Exception.Message)" break } try { if ($inboundonly -eq $false) { Write-Information '--- Creating transport rules ---' -InformationAction Continue New-SC365Rules -SEPPmailCloudDomain $SEPPmailCloudDomain -routing $routing } } catch { throw [System.Exception] "Error: $($_.Exception.Message)" break } } End{ Write-Information "--- Successfully created SEPPmail.cloud Setup for $SEPPmailCloudDomain in region $region in $routing mode ---" -InformationAction Continue Write-Information "--- Wait a few minutes until changes are applied in the Microsoft cloud ---" -InformationAction Continue Write-Information "--- Afterwards, start testing E-Mails in and out ---" -InformationAction Continue } } <# .SYNOPSIS Reads all Rules and Connectors for SEPPmail.cloud in an Exo-Tenant .DESCRIPTION Based on autodiscovery, or forced values through parameters, Get-SC365Setup reads all connectors and rules from an Exo-Tenant .NOTES - none - .LINK https://github.com/seppmail/seppmail365cloud .EXAMPLE Get-SC365Setup # Without any parameters, it runs discovery mode and reads rules and connectors .EXAMPLE Get-SC365Setup -parallel # Reads parallel setup config .EXAMPLE Get-SC365Setup -inline # Reads inline setup config .EXAMPLE Get-SC365Setup -inline -inBoundOnly # Reads inline in "inboundOnly" mode setup config #> function Get-SC365Setup { [CmdletBinding()] param() Begin { if(!(Test-SC365ConnectionStatus)) { throw [System.Exception] "You're not connected to Exchange Online - please connect prior to using this CmdLet" } else { Write-Verbose "Connected to Exchange Organization `"$Script:ExODefaultDomain`"" -InformationAction Continue } Write-Verbose "Detecting SEPPmail.cloud deployment scenario to read Exo Deployment" try { $deploymentInfo = Get-SC365DeploymentInfo } catch { Throw [System.Exception] "Could not autodetect SEPPmail.cloud Deployment Status, use manual parameters" } if ($deploymentInfo.DeployMentStatus -eq $false) { Write-Error "SEPPmail.cloud setup not (fully) deployed. Use Cloud-Portal and fix deployment." break } else { if ($deploymentInfo) { if ($deploymentInfo.Routing) {$Routing = $deploymentInfo.Routing} else {Write-Error "Cloud not autodetect routing info, use manual parameters"; break} if ($deploymentInfo.InBoundOnly -eq $true) {$InBoundOnly = $deploymentInfo.InBoundOnly} else {$InBoundOnly = $false} } } } Process { try { if ($InBoundOnly -eq $true) { Write-Verbose "Get SEPPmail.cloud Connectors in inbound-only mode" $smcConn = Get-SC365Connectors -Routing $routing -inboundOnly:$true } else { Write-Verbose "Get SEPPmail.cloud Connectors" $smcConn = Get-SC365Connectors -Routing $routing -inboundOnly:$false } if ($InBoundOnly -eq $false) { Write-Verbose "Get SEPPmail.cloud Transport Rules" $smcTRules = Get-SC365Rules } } catch { Write-Warning "Found no or incomplete setup, please check manually in EAC." break } } End{ Out-Host -InputObject $smcConn -Paging if ($InBoundOnly -eq $false) { Out-Host -InputObject $smcTRules -Paging } } } <# .SYNOPSIS Updates the SEPPmail.cloud setup in Exchange Online by performing a controlled upgrade of transport rules and connectors. .DESCRIPTION The `Update-SC365Setup` function helps update the SEPPmail.cloud integration with Exchange Online while minimizing disruptions. It follows a structured approach to renaming and replacing existing transport rules and connectors with updated versions. The update process includes: 1. Checking for existing backup objects. 2. Renaming existing SEPPmail.cloud transport rules to a backup name. 3. Creating temporary connectors with a prefixed name. 4. Reconfiguring outbound transport rules to use the new connectors. 5. Renaming existing SEPPmail.cloud connectors to backup names. 6. Reattaching old transport rules to the renamed backup connectors. 7. Renaming the new connectors to their final names. 8. Creating new transport rules in a disabled state for review. This function is designed for standard SEPPmail.cloud setups. If you have customizations in your Exchange Online environment, additional manual adjustments may be necessary. .PARAMETER BackupName Specifies a custom prefix for the backup mail flow objects created during the update process. The default value is 'SC-BKP'. .PARAMETER TempPrefix Specifies the prefix used for temporary connectors during the update process. The default value is 'temp'. .EXAMPLE Update-SC365Setup Runs the update process using the default backup name ('SC-BKP') and temporary prefix ('temp'). .EXAMPLE Update-SC365Setup -BackupName "SEPPmail-Backup" -TempPrefix "TempConn" Runs the update process with custom naming for backup and temporary objects. .NOTES - This script should only be used for standard SEPPmail.cloud setups. - After execution, manual verification and testing are recommended. - The old setup remains active until the administrator enables the new configuration. .LINK https://github.com/seppmail/SEPPmail365cloud #> function Update-SC365Setup { [CmdletBinding( SupportsShouldProcess = $true ) ] param( # Specifies the name for the backup to be created during the update process. [Parameter( Mandatory = $false, HelpMessage = "Provide a custom name for the backup mail flow object during the update process." )] [string]$BackupName = 'SC-BKP', # Specifies the name for the backup to be created during the update process. [Parameter( Mandatory = $false, HelpMessage = "Prefix for connectors for temporary swapping rules traffic" )] [string]$TempPrefix = 'temp' ) begin { $existEAValue = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' } process { #region info block Write-Host "+---------------------------------------------------------------------+" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| This script is a helper and provides basic steps to upgrade your |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| SEPPmail.cloud/Exchange integration. It covers only STANDARD Setups!|" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| The Script will: |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 1.) Check if there are any orphaned rule or connector objects |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 2.) Rename SEPPmail.cloud Transport rules to `$backupName |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 3.) Create Connectors with Temp Name |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 4.) Set (200) outbound transport rule to New-Connector |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 5.) Rename SEPPmail.cloud Connectors to `$backupName |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 6.) Attach old Transport rules to old Connector with BackupNam |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 7.) Rename NEW Connectors to original names |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| 8.) Create new transport rules -PlacementPriority BOTTOM |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| ----------------- OLD SETUP STILL RUNNING ------------------ |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| Check any: |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| - customizations to SEPPmail.cloud rules |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| - other corporate transport rules |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| - disclaimer Services integrated via rules |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| - or other special scenarios in your Exo-Tenant |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| you need to adapt/change/post-configure the outcome of this script, |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| and ENABLE the new setup in Exchange Online. |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| DO NOT JUST FIRE IT UP AND HOPE THINGS ARE GOING TO WORK !!!!!! |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "+---------------------------------------------------------------------+" -ForegroundColor Magenta -BackgroundColor Black #endregion $response = Read-Host "I have read and understood the above warning (Type MURPHY if you agree)!" if ($response -eq 'MURPHY') { Write-Verbose '1 - Checking if there are existing Backup objects in the Exchange Tenant' # Define total steps for progress calculation $totalSteps = 8 $step = 0 $backupWildCard = '[' + $backupName + '*' Write-Verbose "1a - Checking if there are any orphaned backup objects" if (!((Get-InboundConnector -Identity $BackupWildcard -ea SilentlyContinue) -or (Get-OutboundConnector -Identity $backupWildCard -ea SilentlyContinue) -or (Get-TransportRule -Identity $backupWildCard -ea SilentlyContinue))) { # Step 1: Get Deployment Info $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Retrieving Deployment Info ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "1b - Getting DeploymentInfo" $DeplInfo = Get-SC365DeploymentInfo # Step 2: Rename Existing Rules $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Renaming Transport Rules ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "2 - Rename existing SEPPmail.cloud rules" # Get all transport rules that match SEPPmail.cloud pattern $oldTrpRls = Get-TransportRule -Identity '[SEPPmail.cloud]*' $totalRules = $oldTrpRls.Count $ruleCounter = 0 # Initialize sub-progress counter foreach ($rule in $oldTrpRls) { $ruleCounter++ # Sub-Progress Bar: Shows progress for each rule being renamed Write-Progress -Activity "Renaming Transport Rules" ` -Status "Renaming ($ruleCounter of $totalRules): $($rule.Name)" ` -PercentComplete (($ruleCounter / $totalRules) * 100) ` -Id 2 # Unique ID for the sub-progress bar # Rename the rule Set-TransportRule -Identity $rule.Name -Name ($rule.Name -replace 'SEPPmail.Cloud', $BackupName) @PSBoundParameters } # Clear the sub-progress bar once renaming is done Write-Progress -Id 2 -Activity "Renaming Transport Rules" -Completed # Step 3: Create New Connectors $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Creating New Connectors ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "3 - Creating new connectors with temp name" if ($PSCmdlet.ShouldProcess('New connectors', 'create')) { $newConnectors = New-SC365connectors -SEPPmailCloudDomain $DeplInfo.SEPPmailCloudDomain -region $DeplInfo.region -routing $DeplInfo.routing -NamePrefix $tempPrefix @psBoundParameters } # Step 4: Update Transport Rules to Use New Connectors $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Updating Transport Rules ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "4 - Set outbound rules to new connector" # Get the current outbound connector $oldObc = Get-OutboundConnector -Identity '[SEPPmail.cloud] Outbound-*' [string]$tempObcName = $TempPrefix + $($OldObc.Identity) # Get transport rules that need updating $rulesToChange = Get-TransportRule -Identity '[*' | Where-Object {($_.RouteMessageOutboundConnector) -and ($_.Name -like "*$backupName*")} $totalRules = $rulesToChange.Count $ruleCounter = 0 # Initialize sub-progress counter foreach ($rule in $rulesToChange) { $ruleCounter++ # Sub-Progress Bar: Show progress for each rule being updated Write-Progress -Activity "Updating Transport Rules" ` -Status "Updating ($ruleCounter of $totalRules): $($rule.Name)" ` -PercentComplete (($ruleCounter / $totalRules) * 100) ` -Id 3 # Unique ID for the sub-progress bar # Update the transport rule to use the new connector Set-TransportRule -Identity $($rule.Identity) -RouteMessageOutboundConnector $tempObcName @psBoundParameters } # Clear the sub-progress bar once the update is complete Write-Progress -Id 3 -Activity "Updating Transport Rules" -Completed # Step 5: Rename Old Connectors $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Renaming Old Connectors ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "5 - Rename existing SEPPmail.cloud Connectors to $backupName" $oldIbc = Get-InboundConnector -Identity '[SEPPmail.cloud] Inbound-*' Set-InboundConnector -Identity $($OldIbc.Identity) -Name ($($OldIbc.Identity) -replace 'SEPPmail.Cloud', $backupName) @PSBoundParameters $oldObc = Get-OutBoundConnector -Identity "[SEPPmail.cloud] OutBound-*" Set-OutBoundConnector -Identity $($oldObc.Identity) -Name ($oldObc.Identity -replace 'SEPPmail.Cloud', $backupName) @PSBoundParameters # Step 6: Attach Old Transport Rules to Renamed Backup Connectors $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Reattaching Old Rules ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "6 - Set outbound rule to old backup connector again" $bkpConnWildcard = "[" + $backupName + "]*" $bkpObc = Get-OutboundConnector -Identity $bkpConnWildcard foreach ($rule in $rulesToChange) { Set-TransportRule -Identity $($rule.Identity) -RouteMessageOutboundConnector $bkpObc @PSBoundParameters } # Step 7: Rename New Connectors to Their Final Names $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Renaming New Connectors ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "7 - Rename existing $tempPrefix connectors to final name" $finalObCName = ($newConnectors | Where-Object Identity -like '*OutBound*').Identity -replace "^$([regex]::Escape($tempPrefix))", "" Set-OutboundConnector -Identity ($newConnectors | Where-Object Identity -like '*OutBound*').Identity -Name $finalObcName @PSBoundParameters $finalIbcName = ($newConnectors | Where-Object Identity -like '*Inbound*').Identity -replace "^$([regex]::Escape($tempPrefix))", "" Set-InBoundConnector -Identity ($newConnectors | Where-Object Identity -like '*InBound*').Identity -Name $finalIbcName @PSBoundParameters # Step 8: Create New Transport Rules in Disabled State for Review $step++ Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Creating New Transport Rules ($step/$totalSteps)" -PercentComplete (($step / $totalSteps) * 100) Write-Verbose "8 - Creating new Transport Rules" New-SC365Rules -SEPPmailCloudDomain $DeplInfo.SEPPmailCloudDomain -routing $DeplInfo.routing -PlacementPriority Top @PSBoundParameters -Disabled # Complete Progress Bar Write-Progress -Activity "Updating SEPPmail.cloud Setup" -Status "Completed" -Completed $switchFinalConfig = @' # To enable the new configuration, use something like the command below. Get-TransportRule | Where-Object {$_.Name -like "*[SEPPmail.cloud]*"} | Enable-TransportRule # To remove the old configuration, use something like the code below as an example. Get-TransportRule | Where-Object {$_.Name -like "*BKP*"} | Remove-TransportRule -Confirm:$false Get-InboundConnector |? {$_.Identity -like '*BKP*'}|Remove-InboundConnector -Confirm:$false Get-OutboundConnector |? {$_.Identity -like '*BKP*'}|Remove-OutboundConnector -Confirm:$false '@ # Inform User About Next Steps Write-Host "+---------------------------------------------------------------------+" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| The CmdLet has finished and the OLD SETUP $backupName IS STILL ACTIVE |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| CUSTOMIZE your setup and ENABLE the new setup when done. |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| After final tests you can DELETE the $backupName objects |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "| Find some example commands below: |" -ForegroundColor Magenta -BackgroundColor Black Write-Host "+---------------------------------------------------------------------+" -ForegroundColor Magenta -BackgroundColor Black $switchFinalConfig.Replace('*BKP*','*$backupName*') #> } else { Write-Error "STOPPING - Found Existing Backup Objects - clean up the environment from $BackupName objects (rules and connectors) and TRY again" break } } else { Write-Host "Wise decision! Analyze your integration with New-SC365ExoReport and come back again if you are more familiar with the environment." -ForegroundColor Green -BackgroundColor DarkGray } } end { $ErrorActionPreference = $existEAValue } } Register-ArgumentCompleter -CommandName New-SC365Setup -ParameterName SEPPmailCloudDomain -ScriptBlock $paramDomSB # SIG # Begin signature block # MIIVzAYJKoZIhvcNAQcCoIIVvTCCFbkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB2DwTD3khVlnjR # BnhJqJ0LJ7aqyrvMY5/4LdFJi71N7aCCEggwggVvMIIEV6ADAgECAhBI/JO0YFWU # jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI # DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM # EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy # dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG # EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv # IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s # hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD # J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7 # P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme # me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz # T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q # RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz # mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc # QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T # OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/ # AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID # AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD # VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV # HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE # VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v # ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE # KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI # hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF # OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC # J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ # pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl # d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH # +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYaMIIEAqADAgECAhBiHW0M # UgGeO5B5FSCJIRwKMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD # VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv # ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5 # NTlaMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp # BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0G # CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjI # ztNsfvxYB5UXeWUzCxEeAEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NV # DgFigOMYzB2OKhdqfWGVoYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/3 # 6F09fy1tsB8je/RV0mIk8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05Zw # mRmTnAO5/arnY83jeNzhP06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm # +qxp4VqpB3MV/h53yl41aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUe # dyz8rNyfQJy/aOs5b4s+ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz4 # 4MPZ1f9+YEQIQty/NQd/2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBM # dlyh2n5HirY4jKnFH/9gRvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQY # MBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritU # pimqF6TNDDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNV # HSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsG # A1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1 # YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsG # AQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2Rl # U2lnbmluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0 # aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURh # w1aVcdGRP4Wh60BAscjW4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0Zd # OaWTsyNyBBsMLHqafvIhrCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajj # cw5+w/KeFvPYfLF/ldYpmlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNc # WbWDRF/3sBp6fWXhz7DcML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalO # hOfCipnx8CaLZeVme5yELg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJs # zkyeiaerlphwoKx1uHRzNyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z7 # 6mKnzAfZxCl/3dq3dUNw4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5J # KdGvspbOrTfOXyXvmPL6E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHH # j95Ejza63zdrEcxWLDX6xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2 # Bev6SivBBOHY+uqiirZtg0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/ # L9Uo2bC5a4CH2RwwggZzMIIE26ADAgECAhAMcJlHeeRMvJV4PjhvyrrbMA0GCSqG # SIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0 # ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYw # HhcNMjMwMzIwMDAwMDAwWhcNMjYwMzE5MjM1OTU5WjBqMQswCQYDVQQGEwJERTEP # MA0GA1UECAwGQmF5ZXJuMSQwIgYDVQQKDBtTRVBQbWFpbCAtIERldXRzY2hsYW5k # IEdtYkgxJDAiBgNVBAMMG1NFUFBtYWlsIC0gRGV1dHNjaGxhbmQgR21iSDCCAiIw # DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOapobQkNYCMP+Y33JcGo90Soe9Y # /WWojr4bKHbLNBzKqZ6cku2uCxhMF1Ln6xuI4ATdZvm4O7GqvplG9nF1ad5t2Lus # 5SLs45AYnODP4aqPbPU/2NGDRpfnceF+XhKeiYBwoIwrPZ04b8bfTpckj/tvenB9 # P8/9hAjWK97xv7+qsIz4lMMaCuWZgi8RlP6XVxsb+jYrHGA1UdHZEpunEFLaO9Ss # OPqatPAL2LNGs/JVuGdq9p47GKzn+vl+ANd5zZ/TIP1ifX76vorqZ9l9a5mzi/HG # vq43v2Cj3jrzIQ7uTbxtiLlPQUqkRzPRtiwTV80JdtRE+M+gTf7bT1CTvG2L3scf # YKFk7S80M7NydxV/qL+l8blGGageCzJ8svju2Mo4BB+ALWr+gBmCGqrM8YKy/wXR # tbvdEvBOLsATcHX0maw9xRCDRle2jO+ndYkTKZ92AMH6a/WdDfL0HrAWloWWSg62 # TxmJ/QiX54ILQv2Tlh1Al+pjGHN2evxS8i+XoWcUdHPIOoQd37yjnMjCN593wDzj # XCEuDABYw9BbvfSp29G/uiDGtjttDXzeMRdVCJFgULV9suBVP7yFh9pK/mVpz+aC # L2PvqiGYR41xRBKqwrfJEdoluRsqDy6KD985EdXkTvdIFKv0B7MfbcBCiGUBcm1r # fLAbs8Q2lqvqM4bxAgMBAAGjggGpMIIBpTAfBgNVHSMEGDAWgBQPKssghyi47G9I # ritUpimqF6TNDDAdBgNVHQ4EFgQUL96+KAGrvUgJnXwdVnA/uy+RlEcwDgYDVR0P # AQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwSgYD # VR0gBEMwQTA1BgwrBgEEAbIxAQIBAwIwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9z # ZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQQBMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6 # Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5nQ0FSMzYu # Y3JsMHkGCCsGAQUFBwEBBG0wazBEBggrBgEFBQcwAoY4aHR0cDovL2NydC5zZWN0 # aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIzNi5jcnQwIwYIKwYB # BQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMB4GA1UdEQQXMBWBE3N1cHBv # cnRAc2VwcG1haWwuY2gwDQYJKoZIhvcNAQEMBQADggGBAHnWpS4Jw/QiiLQi2EYv # THCtwKsj7O3G7wAN7wijSJcWF7iCx6AoCuCIgGdWiQuEZcv9pIUrXQ6jOSRHsDNX # SvIhCK9JakZJSseW/SCb1rvxZ4d0n2jm2SdkWf5j7+W+X4JHeCF9ZOw0ULpe5pFs # IGTh8bmTtUr3yA11yw4vHfXFwin7WbEoTLVKiL0ZUN0Qk+yBniPPSRRlUZIX8P4e # iXuw7lh9CMaS3HWRKkK89w//18PjUMxhTZJ6dszN2TAfwu1zxdG/RQqvxXUTTAxU # JrrCuvowtnDQ55yXMxkkSxWUwLxk76WvXwmohRdsavsGJJ9+yxj5JKOd+HIZ1fZ7 # oi0VhyOqFQAnjNbwR/TqPjRxZKjCNLXSM5YSMZKAhqrJssGLINZ2qDK/CEcVDkBS # 6Hke4jWMczny8nB8+ATJ84MB7tfSoXE7R0FMs1dinuvjVWIyg6klHigpeEiAaSaG # 5KF7vk+OlquA+x4ohPuWdtFxobOT2OgHQnK4bJitb9aDazGCAxowggMWAgEBMGgw # VDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkGA1UE # AxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNgIQDHCZR3nkTLyV # eD44b8q62zANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgACh # AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM # BgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAogbI9Wvs2XWgkKnMu9xJmMCte # 58/L+sBEfPjdZG19XzANBgkqhkiG9w0BAQEFAASCAgAiApHwLlgIgKzbz6t0wqsC # jx4G/hkyRYQmVxVcfSUFmHkBKnJyLhXWFaI0FU1/i6G1wgH2eapzuKvZu7Nve6H8 # AAaHO5SRZS1u6RoQqxm5pcmUZRO/BRZAtvxXN9TnE6ZNEZbMu75yj+HP8R8BocnP # fX+HYunTj9KP6C4vmpoaQzJypv9R3Iau7b7U+//LP0rSRCjUP4EnTk8GHkYnhFvN # G4Vg6svlKRR2rspL/MVX2AOVhV2eaxoTGpFXoHhaFy/HIn22+F5a+mKOcbeJ5FN0 # sXHNz65wTx7g4YMAO4WLiEZ9cHlVHif+OsWkAG938PnKHLxA9c9mEFOIWihYUKos # DGCPBgDObZx0e9goJu9/rQfjTsJvK0oQfglGBdfTagn2fcdGY46XLWNTbwaIifUX # QqoyKh9lRYD+60diztkQOya71TaCFu1TbmLaPaz2y1UhJo7oSgJhTSfyCCF8KI2y # Uel+MPNzTVi3em5Ks2lzUfdRmBR4Mku09IvrWI8CfyoYHkbwOJdwUs6MOQdo5QMV # fudSmQ0YlhtoJ3qJaieFAffXQ6IrQU5o/n7FDtsTYwsmHTYgbUHjHDq4o+lMENmI # i3o+ncEJjHz09J1/1abPhOBVdpjgh6OYOnky5SwSBIzY6u5KQH8dEKuolI9NruHz # lyYdklaQYrfpnCEdxWnsmw== # SIG # End signature block |