Private/PrivateFunctions.ps1
# Generic function to avoid code duplication <# .SYNOPSIS Sets properties on an input object based on a configuration JSON file. .DESCRIPTION The `Set-SC365PropertiesFromConfigJson` function takes an input object and updates its properties using values from a provided JSON object. It handles general properties, as well as properties specific to routing, options, and regions, ensuring configuration consistency across components. .PARAMETER InputObject The object whose properties will be updated based on the JSON configuration. .PARAMETER Json A JSON object containing the configuration settings. The function expects the JSON to include general properties and optionally, nested `Routing`, `Option`, or `Region` sections. .PARAMETER Routing An optional parameter specifying the routing type. If provided, the function will apply settings from the corresponding `Routing` section in the JSON object. .PARAMETER Option An optional array of configuration options. The function applies properties from the `Option` section in the JSON for the specified options. .PARAMETER Region An optional parameter specifying the geographic region. If provided, the function applies settings from the `Region` section in the JSON object. .OUTPUTS None The function modifies the `InputObject` in place. .EXAMPLE PS> $config = Get-Content "config.json" | ConvertFrom-Json PS> $obj = New-Object PSObject PS> Set-SC365PropertiesFromConfigJson -InputObject $obj -Json $config -Routing "Hybrid" -Option @("Option1", "Option2") -Region "NorthAmerica" This example updates the properties of `$obj` using the configuration in `config.json`, applying settings for the `Hybrid` routing type, `Option1` and `Option2`, and the `NorthAmerica` region. .NOTES - This function assumes the JSON structure includes general properties and optionally, nested sections for `Routing`, `Option`, and `Region`. - The function skips certain predefined keys (`Name`, `Option`, `Routing`, `Region`) for general property updates. .REQUIREMENTS - PowerShell 5.1 or later. - A properly structured JSON configuration file. .LINK For information on managing JSON data in PowerShell, see: https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/json #> function Set-SC365PropertiesFromConfigJson { [CmdLetBinding()] Param ( [psobject] $InputObject, [psobject] $Json, [SC365.MailRouting] $Routing, [SC365.ConfigOption[]] $Option, [SC365.GeoRegion] $Region ) # Set all properties that aren't version specific $json.psobject.properties | Foreach-Object { if ($_.Name -notin @("Name", "Option", "Routing", "Region")) { $InputObject.$($_.Name) = $_.Value } } if($routing -and $json.Routing) { $json.Routing.$Routing.psobject.properties | Foreach-Object { $InputObject.$($_.Name) = $_.Value } } if($Option -and $json.Option) { $Option | Where-Object {$json.Option.$_} | ForEach-Object{ $Json.Option.$_.psobject.properties | ForEach-Object{ $InputObject.$($_.Name) = $_.Value } } } if($Region -and $json.Region) { $json.Region.$Region.psobject.properties | %Foreach-Object { $InputObject.$($_.Name) = $_.Value } } } <# .SYNOPSIS Retrieves inbound connector settings for a specified routing type. .DESCRIPTION The `Get-SC365InboundConnectorSettings` function reads inbound connector settings from a JSON configuration file and retrieves the settings specific to the provided routing type. The function ensures case-insensitivity for routing type lookups and returns the relevant configuration as a hashtable. .PARAMETER Routing Specifies the routing type for which inbound connector settings are to be retrieved. This parameter is mandatory. .PARAMETER Option Specifies additional options for the function. This parameter is optional. .OUTPUTS Hashtable A hashtable containing the inbound connector settings for the specified routing type. .EXAMPLE PS> Get-SC365InboundConnectorSettings -Routing "HybridRouting" This example retrieves the inbound connector settings for the routing type `HybridRouting`. .EXAMPLE PS> Get-SC365InboundConnectorSettings -Routing "DirectRouting" -Option $customOption This example retrieves the inbound connector settings for the routing type `DirectRouting`, taking into account additional custom options provided via the `Option` parameter. .NOTES - The function reads the JSON file `InBound.json` located in the `ExOConfig\Connectors\` directory relative to the script root. - Ensure the JSON file is properly formatted and includes a `routing` section with routing-specific settings. - The `ToLower()` method ensures case-insensitive lookup for the routing type. .REQUIREMENTS - PowerShell 5.1 or later. - A valid `InBound.json` file located in the `ExOConfig\Connectors\` directory. .LINK For more information on configuring inbound connectors in Exchange Online, visit: https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/use-connectors-to-configure-mail-flow #> function Get-SC365InboundConnectorSettings { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] $routing, $option ) Write-Verbose "Loading inbound connector settings for routingtype $Routing" $inBoundRaw = (Get-Content "$PSScriptRoot\..\ExOConfig\Connectors\InBound.json" -Raw|Convertfrom-Json -AsHashtable) $ret = $inBoundRaw.routing.($routing.Tolower()) return $ret } <# .SYNOPSIS Retrieves outbound connector settings for a specified routing type. .DESCRIPTION The `Get-SC365OutboundConnectorSettings` function loads and returns outbound connector configuration settings from a JSON file based on the specified routing type. It parses the JSON file and retrieves the relevant settings for the provided routing option, ensuring case-insensitivity for routing type lookup. .PARAMETER Routing Specifies the routing type for which outbound connector settings are to be retrieved. This parameter is mandatory and must be provided. .PARAMETER Option Specifies additional configuration options that may influence the settings retrieval. This parameter is optional. .OUTPUTS Hashtable A hashtable containing the outbound connector settings for the specified routing type. .EXAMPLE PS> Get-SC365OutboundConnectorSettings -Routing "HybridRouting" This example retrieves the outbound connector settings for the routing type `HybridRouting`. .EXAMPLE PS> Get-SC365OutboundConnectorSettings -Routing "DirectRouting" -Option $customOption This example retrieves the outbound connector settings for the routing type `DirectRouting`, taking into account the additional custom option. .NOTES - The function reads outbound connector settings from the `OutBound.json` file located in the `$PSScriptRoot\..\ExOConfig\Connectors\` directory. - The JSON file must be properly formatted and include a `routing` section with routing-specific settings. - The `ToLower()` method ensures case-insensitive lookup for the routing type. .REQUIREMENTS - PowerShell 5.1 or later. - A valid `OutBound.json` file located in the `ExOConfig\Connectors\` directory. .LINK For more information on Exchange Online connectors, visit: https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/use-connectors-to-configure-mail-flow #> function Get-SC365OutboundConnectorSettings { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] $routing, $option ) Write-Verbose "Loading outbound connector settings" $outBoundRaw = (Get-Content "$PSScriptRoot\..\ExOConfig\Connectors\OutBound.json" -Raw|Convertfrom-Json -AsHashtable) $ret= $outBoundRaw.routing.($routing.ToLower()) return $ret } function Get-SC365TransportRuleSettings { [CmdLetBinding()] Param ( [Parameter(Mandatory = $true)] [string] $routing, [Parameter(Mandatory = $true)] [string] $file, [switch] $IncludeSkipped ) begin { $ret = $null $raw = $null } process { $raw = (Get-Content $File -Raw|Convertfrom-Json -AsHashtable) $ret = $raw.routing.($routing.ToLower()) } end { return $ret } } <# .SYNOPSIS Retrieves the cloud configuration for a specified geographic region. .DESCRIPTION The `Get-SC365CloudConfig` function loads and returns cloud configuration settings for a specific region by reading and parsing a JSON file. The function retrieves the relevant configuration based on the provided region and ensures that the region name is case-insensitive. .PARAMETER Region The geographic region for which the cloud configuration is to be retrieved. This parameter is mandatory and must be provided as a string. .OUTPUTS PSObject An object containing the cloud configuration settings for the specified region. .EXAMPLE PS> Get-SC365CloudConfig -Region "ch" This example retrieves the cloud configuration settings for the `NorthAmerica` region. .EXAMPLE PS> Get-SC365CloudConfig -Region "eu" This example retrieves the cloud configuration settings for the `Europe` region. The region name is not case-sensitive. .NOTES - The function reads from a file named `GeoRegion.json` located in the `CloudConfig` directory relative to the script root. - Ensure that the JSON file is properly formatted and includes a `GeoRegion` section with region-specific configuration. .REQUIREMENTS - PowerShell 5.1 or later. - A valid `GeoRegion.json` file located at `$PSScriptRoot\..\ExOConfig\CloudConfig\GeoRegion.json`. #> function Get-SC365CloudConfig { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$Region ) Write-Verbose "Loading inbound connector settings for region $Region" $ret = (ConvertFrom-Json (Get-Content -Path "$PSScriptRoot\..\ExOConfig\CloudConfig\GeoRegion.json" -Raw)).GeoRegion.($region.ToLower()) return $ret } <# .SYNOPSIS Converts a raw numeric value into a human-readable file size format (kB, MB, GB, TB). .DESCRIPTION The `Convertto-SC365Numberformat` function takes an integer input representing a raw numeric value (e.g., bytes) and converts it into a human-readable format, such as kilobytes (kB), megabytes (MB), gigabytes (GB), or terabytes (TB), based on the size of the input number. .PARAMETER RawNumber An integer value representing the size to be converted. The value is categorized into the appropriate unit based on its length and then formatted to two decimal places. .OUTPUTS String A formatted string indicating the size in the appropriate unit (e.g., "1.23 MB"). .EXAMPLE PS> Convertto-SC365Numberformat -RawNumber 123456 This example converts 123,456 into the string "120.56 kB". .EXAMPLE PS> Convertto-SC365Numberformat -RawNumber 1234567890 This example converts 1,234,567,890 into the string "1.15 GB". .NOTES - The function uses the `switch` statement to determine the appropriate size category. - The thresholds for determining the unit are based on the number of digits in the input number. #> function ConvertTo-SC365NumberFormat { param ( [Int64]$rawnumber ) $ConvertedNumber = switch ($rawNumber.ToString().Length) { {($_ -le 5)} {($rawNumber/1KB).ToString("N2") + " kB"} {(($_ -gt 5) -and ($_ -le 9))} {($rawNumber/1MB).ToString("N2") + " MB"} {(($_ -gt 9) -and ($_ -le 12))} {($rawNumber/1GB).ToString("N2") + " GB"} {($_ -gt 12)} {($rawNumber/1TB).ToString("N2") + " TB"} } return $ConvertedNumber } <# .SYNOPSIS Generates a hash value from a given input string using a specified hashing algorithm. .DESCRIPTION The `Get-SC365StringHash` function computes a hash value for an input string using the specified algorithm. It supports common hashing algorithms such as MD5, RIPEMD160, SHA1, SHA256, SHA384, and SHA512. The function outputs the computed hash as a hexadecimal string. .PARAMETER String The input string to be hashed. This parameter is mandatory and can accept input from the pipeline. .PARAMETER HashName Specifies the hashing algorithm to use. Valid values are "MD5", "RIPEMD160", "SHA1", "SHA256", "SHA384", and "SHA512". The default is "SHA1" if no value is specified. .OUTPUTS String The function returns a hexadecimal string representing the hash of the input. .EXAMPLE PS> Get-SC365StringHash -String "HelloWorld" This example computes the SHA1 hash of the string "HelloWorld" and outputs the hash value. .EXAMPLE PS> "MyString" | Get-SC365StringHash -HashName SHA256 This example pipes the string "MyString" to the function and computes its SHA256 hash. .NOTES - This function uses the .NET `System.Security.Cryptography` library to perform hashing. - Ensure the input string is not null or empty to avoid errors. #> Function Get-SC365StringHash { [cmdletbinding()] [OutputType([String])] param( [parameter(ValueFromPipeline, Mandatory = $true, Position = 0)] [String]$String, [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 1)] [ValidateSet("MD5", "RIPEMD160", "SHA1", "SHA256", "SHA384", "SHA512")] [String]$HashName = 'SHA1' ) begin { } Process { $StringBuilder = New-Object System.Text.StringBuilder [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))| foreach-object { [Void]$StringBuilder.Append($_.ToString("x2")) } $output = $StringBuilder.ToString() } end { return $output } } <# .SYNOPSIS Removes domains with the `.onmicrosoft.com` suffix from a given list of domains. .DESCRIPTION The `Remove-SC365OnMicrosoftDomain` function filters out all domains in the input `DomainList` that have the `.onmicrosoft.com` suffix. It returns a new list containing only the domains that do not match this suffix. This is useful for cleaning up domain lists when you want to exclude temporary or system-generated Microsoft 365 domains. .PARAMETER DomainList A mandatory parameter that accepts an array list of domain names to process. The list must be provided as a `[System.Collections.ArrayList]` object. .RETURNS [System.Collections.ArrayList] A new array list containing only the domains that do not have the `.onmicrosoft.com` suffix. .EXAMPLE PS> $domains = [System.Collections.ArrayList]@('example.com', 'tenant.onmicrosoft.com', 'anotherdomain.com') PS> $filteredDomains = Remove-SC365OnMicrosoftDomain -DomainList $domains PS> $filteredDomains This example filters out the `tenant.onmicrosoft.com` domain, returning: example.com anotherdomain.com .EXAMPLE PS> $domains = [System.Collections.ArrayList]@('domain1.com', 'domain2.onmicrosoft.com') PS> Remove-SC365OnMicrosoftDomain -DomainList $domains This example returns only `domain1.com`. .NOTES - The function uses the `-NotLike` operator to filter out `.onmicrosoft.com` domains. - The function returns a new array list while leaving the original `DomainList` unchanged. .REQUIREMENTS - PowerShell 5.1 or later. .LINK For more information on Microsoft 365 domains, see: https://learn.microsoft.com/en-us/microsoft-365/ #> Function Remove-SC365OnMicrosoftDomain { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Collections.ArrayList]$DomainList ) [System.Collections.ArrayList]$NewDomainList= @() Foreach ($domain in $DomainList) { if ($domain -Notlike '*.onmicrosoft.com') { [void]$NewDomainList.Add($Domain) } } return $NewDomainList } <# .SYNOPSIS Extracts a semantic version number from a given input string. .DESCRIPTION The `Get-SC365ModuleVersion` function searches a given input string for a semantic version number using a regular expression. The function supports versions in the format `MAJOR.MINOR.PATCH` (e.g., `1.3.8`). If a version number is found, it is returned; otherwise, a message indicating no version number was found is returned. .PARAMETER InputString A string that potentially contains a semantic version number. This parameter is mandatory. .OUTPUTS String - If a version number is found, the function returns the version in `MAJOR.MINOR.PATCH` format (e.g., `1.3.8`). - If no version number is found, the function returns the string "No version number found." .EXAMPLE PS> Get-SC365ModuleVersion -InputString "PowerShell Module version 1.3.8" Returns: 1.3.8 .EXAMPLE PS> Get-SC365ModuleVersion -InputString "No version in this string." Returns: No version number found. .NOTES - The function uses a regular expression to identify semantic version numbers in the input string. - Semantic version numbers must follow the pattern `MAJOR.MINOR.PATCH`, where each part consists of numeric digits. .LINK For more information about semantic versioning, see: https://semver.org/ #> Function Get-SC365ModuleVersion { param ( [string]$InputString ) # Check if the string contains "[SEPPMail.cloud]" if ($InputString -like "*[SEPPMail.cloud]*") { # Perform regex match to extract semantic version if ($InputString -match "\d+\.\d+\.\d+") { return $matches[0] } } else { #Write-Information "Not a SEPPmail.cloud connector/rule or no version number included" return "-not available in comments-" } } #Beginning with v 1.4.0 this function is obsolete <#function Get-ExoHTMLData { param ( [Parameter( Mandatory = $true, HelpMessage = 'Enter Cmdlte to ')] [string]$ExoCmd ) try { $allCmd = $exoCmd.Split('|')[0].Trim() $htmlSelectCmd = $exoCmd.Split('|')[1].Trim() $rawData = Invoke-Expression -Command $allCmd if ($null -eq $rawData) { $ExoHTMLData = New-object -type PSobject -property @{Result = '--- no information available ---'}|Convertto-HTML -Fragment } else { $ExoHTMLCmd = "{0}|{1}" -f $allcmd,$htmlSelectCmd $ExoHTMLData = Invoke-expression -Command $ExoHTMLCmd |Convertto-HTML -Fragment if ($jsonBackup) { $script:JsonData += '---' + $AllCmd + '---'|Convertto-Json $script:JsonData += $rawData|ConvertTo-Json } } return $ExoHTMLData } catch { Write-Warning "Could not fetch data from command '$exoCmd'" } }#> <# .SYNOPSIS Generates a unique report filename based on the current time and the default domain name. .DESCRIPTION The `New-SelfGeneratedReportName` function creates a unique, self-generated report filename. The filename includes the current time in `HHm-ddMMyyy` format and the default email domain name, retrieved from the output of the `Get-AcceptedDomain` cmdlet. The filename is appended with `.html`. .EXAMPLE PS> New-SelfGeneratedReportName This will return a string similar to `1507-10112024defaultdomain.com.html`, where: - `1507` represents the current time in hours and minutes. - `10112024` represents the date in `ddMMyyyy` format. - `defaultdomain.com` is the default email domain. .PARAMETER None The function does not accept parameters. .RETURNS String A string representing the self-generated filename. .NOTES - Ensure the `Get-AcceptedDomain` cmdlet is available and provides a `default` property to identify the default domain. - The function requires the `-ExpandProperty` flag in `Select-Object` to retrieve the `Domainname` property. .REQUIREMENTS - PowerShell 5.1 or later - Exchange Online PowerShell module or other modules providing the `Get-AcceptedDomain` cmdlet. #> function New-SelfGeneratedReportName { Write-Verbose "Creating self-generated report filename." return ("{0:HHm-ddMMyyy}" -f (Get-Date)) + (Get-AcceptedDomain|where-object default -eq $true|select-object -expandproperty Domainname) + '.html' } # SIG # Begin signature block # MIIVzAYJKoZIhvcNAQcCoIIVvTCCFbkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAvpibmjZJ+LkOF # /+y+3r8zTgn+4THm1KDjdbbTIAYXGKCCEggwggVvMIIEV6ADAgECAhBI/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 # BgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCDB+3a76K+cSroRd+cH6Az1uQQ2 # yXg3saJw/Mjs8WPjrTANBgkqhkiG9w0BAQEFAASCAgBfbZO392ZWmQ+SVSVn1U2M # Iu2d48RzCiOdK/WiNq+r6cRNea1V8oywR7mF3czR7LYhlcyDV7X0scaZDgbMtPpX # 9bDbhEi4kMYuclWHvDxYn6+bCmZ1SogQFg/lf1Q002cNNk0cjIE5UWAtEFR09bo1 # U08PbTcvsRQIXnRrNrcxJVgkWATBFQmVDBuBxzkvfHT/KCkfMCfl1G56f1pV/hl7 # gGyqUjZDMPGDpteVPhqr1CichJGQvhTV2u0jsJ4p9NCG3oLaZnAprsz6JVaX3SUz # ZFNTjWQZjY3LfcntDW+v8FK1tnIISw/2jFhXqWd9K242rlUJqwGORaJYgx+q7091 # iQ6LleW9m4vQS74j4hkHr34+WrFsVe+0+jyL8cOxH950HdL8nlSUA5nz3Q3L04Ul # 3HxU80Xpof58MNKbiRem+Cz+EfpSuScTD71nAmaBSDzrBGXngFcjdF4nlfi9CLty # tC6N1jaBVvyl7pBL4CwAmceO8C6G0kVaQujH7XzmMGxFhZN7CvTHuIQA24lVI9f5 # 364+u4fOXi4AWu/+5HFQwuJGm22pEVx9AQsFurMeqK+IZDHhBr0q0X5qOmmjp2VR # 2tiIO8agOE57dP8K1PBwSQREB8fwc5dYMnO4r6yjBlM8ZhvC8H/m5TO+/uvExEGB # 86ODGoqdaqIRmcg6CAHhYw== # SIG # End signature block |