CAMP.psm1

#Requires -Version 5.1

<#
    .SYNOPSIS
        CAMP - Configuration Analyzer for Microsoft Purview (CAMP)
 
    .DESCRIPTION
 
    .NOTES
        Neha Pandey
        Senior Software Engineer - Microsoft
               
        Kritika Mishra
        Software Engineer - Microsoft
              
 
 
        Output report uses open source components for HTML formatting
        - bootstrap - MIT License - https://getbootstrap.com/docs/4.0/about/license/
        - fontawesome - CC BY 4.0 License - https://fontawesome.com/license/free
         
        ############################################################################
 
        This sample script is not supported under any Microsoft standard support program or service.
        This sample script is provided AS IS without warranty of any kind.
        Microsoft further disclaims all implied warranties including, without limitation, any implied
        warranties of merchantability or of fitness for a particular purpose. The entire risk arising
        out of the use or performance of the sample script and documentation remains with you. In no
        event shall Microsoft, its authors, or anyone else involved in the creation, production, or
        delivery of the scripts be liable for any damages whatsoever (including, without limitation,
        damages for loss of business profits, business interruption, loss of business information,
        or other pecuniary loss) arising out of the use of or inability to use the sample script or
        documentation, even if Microsoft has been advised of the possibility of such damages.
 
        ############################################################################
 
    .LINK
        about_functions_advanced
 
#>

[bool] $global:ErrorOccurred = $false

# TelemetryEnabled
[bool] $global:TelemetryEnabled = $false

# Connection Established
[bool] $global:ConnectionEstablished = $false

[string] $global:EnvironmentName = ""
[string] $global:UserName = ""
function Get-CAMPDirectory {
    <#
 
        Gets or creates the CAMP directory in AppData
         
    #>

    If ($IsWindows) {
        $Directory = "$($env:LOCALAPPDATA)\Microsoft\CAMP"
    }
    elseif ($IsLinux -or $IsMac) {
        $Directory = "$($env:HOME)/CAMP"
    }
    else {
        $Directory = "$($env:LOCALAPPDATA)\Microsoft\CAMP"
    }
    
    If (Test-Path $Directory) {
        Return $Directory
    } 
    else {
        mkdir $Directory | out-null
        Return $Directory
    }
}

Function Invoke-CAMPConnections {
    Param
    (
        [String]$ExchangeEnvironmentName,
        [String]$LogFile
    )


    try {
        try {
            $ExchangeVersion = (Get-InstalledModule -name "ExchangeOnlineManagement" -ErrorAction:SilentlyContinue | Sort-Object Version -Desc)[0].Version
        }
        catch {
            $ExchangeVersion = "Error"
            write-host "$(Get-Date) Exchange Online Management module is not installed. Installing.."
            Write-Verbose "Installing ExchangeOnlineManagement"
            Install-Module -Name "ExchangeOnlineManagement" -force
        }
        
        
        if ($ExchangeVersion -eq "Error") {
            $ExchangeVersion = (Get-InstalledModule -name "ExchangeOnlineManagement" -ErrorAction:SilentlyContinue | Sort-Object Version -Desc)[0].Version
        }
        
        if ("$ExchangeVersion" -lt "2.0.3") {
            write-host "$(Get-Date) Your Exchange Online Management module is not updated. Updating.."
            Update-Module -Name "ExchangeOnlineManagement" -RequiredVersion 2.0.3
        }

        $global:ConnectionEstablished = $true

        $userName = Read-Host -Prompt 'Input the user name' -ErrorAction:SilentlyContinue
        $global:UserName = $userName
        $InfoMessage = "Connecting to Exchange Online (Modern Module).."
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        Connect-ExchangeOnline -Prefix EXOP -UserPrincipalName $userName -ExchangeEnvironmentName $ExchangeEnvironmentName -ShowBanner:$false -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
    }
    catch {
        Write-Host "Error:$(Get-Date) There was an issue in connecting to Exchange Online. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    }

    try {
        switch ($ExchangeEnvironmentName) {
            #O365China { }
            #O365GermanyCloud { $ConnectionUri = 'https://ps.compliance.protection.outlook.de/' }
            O365USGovDoD { $ConnectionUri = 'https://l5.ps.compliance.protection.office365.us/powershell-liveid/' }
            O365USGovGCCHigh { $ConnectionUri = 'https://ps.compliance.protection.office365.us/powershell-liveid/' }
            Default { $ConnectionUri = '' }
        }

        $InfoMessage = "Connecting to Security & Compliance Center"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        if ($ConnectionUri -eq '') {
            Connect-IPPSSession -UserPrincipalName $userName -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
        }
        else {
            Connect-IPPSSession -UserPrincipalName $userName -ConnectionUri $ConnectionUri -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
        }
        try { $statusCode = wget http://aka.ms/mcca-execution -Method head | % { $_.StatusCode } }catch {}
    }
    catch {
        Write-Host "Error:$(Get-Date) There was an issue in connecting to Security & Compliance Center. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
        throw 'There was an issue in connecting to Security & Compliance Center. Please try running the tool again after some time.'
    }
}

enum CheckType {
    ObjectPropertyValue
    PropertyValue
}

[Flags()]
enum CAMPService {
    DLP = 1
    OATP = 2
}

enum CAMPConfigLevel {
    None = 0
    Recommendation = 4
    Ok = 5
    Informational = 10
    TooStrict = 15
}

enum CAMPResult {
    Pass = 1
    Recommendation = 2
    Fail = 3
}

Class CAMPCheckConfig {

    CAMPCheckConfig() {
        # Constructor

        $this.Results += New-Object -TypeName CAMPCheckConfigResult -Property @{
            Level = [CAMPConfigLevel]::Recommendation
        }

        $this.Results += New-Object -TypeName CAMPCheckConfigResult -Property @{
            Level = [CAMPConfigLevel]::Ok
        }

        $this.Results += New-Object -TypeName CAMPCheckConfigResult -Property @{
            Level = [CAMPConfigLevel]::Informational 
        }

        $this.Results += New-Object -TypeName CAMPCheckConfigResult -Property @{
            Level = [CAMPConfigLevel]::TooStrict
        }

    }

    # Set the result for this mode
    SetResult([CAMPConfigLevel]$Level, $Result) {
        ($this.Results | Where-Object { $_.Level -eq $Level }).Value = $Result
        # The level of this configuration should be its strongest result (e.g if its currently Ok and we have a Informational pass, we should make the level Informational)
        if ($Result -eq "Pass" -and ($this.Level -lt $Level -or $this.Level -eq [CAMPConfigLevel]::None)) {
            $this.Level = $Level
        } 
        elseif ($Result -eq "Fail" -and ($Level -eq [CAMPConfigLevel]::Recommendation -and $this.Level -eq [CAMPConfigLevel]::None)) {
            $this.Level = $Level
        }

    }

    $Check
    $Object
    $ConfigItem
    $ConfigData
    $InfoText
    [string]$RemediationAction = ""
    [array]$Results
    [CAMPConfigLevel]$Level
}

Class CAMPCheckConfigResult {
    [CAMPConfigLevel]$Level = [CAMPConfigLevel]::Ok
    $Value
}
Class CAMPRemediationInfo {
    [bool]$RemediationAvailable = $false
    [string]$RemediationText = ""
}

Class CAMPCheck {
    <#
 
        Check definition
 
        The checks defined below allow contextual information to be added in to the report HTML document.
        - Control : A unique identifier that can be used to index the results back to the check
        - Area : The area that this check should appear within the report
        - PassText : The text that should appear in the report when this 'control' passes
        - FailRecommendation : The text that appears as a title when the 'control' fails. Short, descriptive. E.g "Do this"
        - Importance : Why this is important
        - ExpandResults : If we should create a table in the callout which points out which items fail and where
        - ObjectType : When ExpandResults is set to, For Object, Property Value checks - what is the name of the Object, e.g a Spam Policy
        - ItemName : When ExpandResults is set to, what does the check return as ConfigItem, for instance, is it a Transport Rule?
        - DataType : When ExpandResults is set to, what type of data is returned in ConfigData, for instance, is it a Domain?
 
    #>


    [Array] $Config = @()
    [string] $Control
    [string] $ParentArea
    [String] $Area
    [String] $Name
    [String] $PassText
    [String] $FailRecommendation
    [Boolean] $ExpandResults = $false
    [String] $ObjectType
    [String] $ItemName
    [String] $DataType
    [String] $Importance
    [CAMPService]$Services = [CAMPService]::DLP
    [CheckType] $CheckType = [CheckType]::PropertyValue
    [CAMPRemediationInfo] $CAMPRemediationInfo
    [string] $LogFile 
    [string] $ExchangeEnvironmentNameForCheck = $global:EnvironmentName
    $Links
    $CAMPParams

    [CAMPResult] $Result = [CAMPResult]::Pass
    [int] $FailCount = 0
    [int] $PassCount = 0
    [int] $InfoCount = 0
    [Boolean] $Completed = $false
    
    # Overridden by check
    GetResults($Config) { }

    AddConfig([CAMPCheckConfig]$Config) {
        $this.Config += $Config

        $this.FailCount = @($this.Config | Where-Object { $_.Level -eq [CAMPConfigLevel]::None }).Count
        $this.PassCount = @($this.Config | Where-Object { $_.Level -eq [CAMPConfigLevel]::Ok -or $_.Level -eq [CAMPConfigLevel]::Informational }).Count
        $this.InfoCount = @($this.Config | Where-Object { $_.Level -eq [CAMPConfigLevel]::Recommendation }).Count

        If ($this.FailCount -eq 0 -and $this.InfoCount -eq 0) {
            $this.Result = [CAMPResult]::Pass
        }
        elseif ($this.FailCount -eq 0 -and $this.InfoCount -gt 0) {
            $this.Result = [CAMPResult]::Recommendation
        }
        else {
            $this.Result = [CAMPResult]::Fail    
        }
        
       

    }

    # Run
    Run($Config) {
        Write-Host "$(Get-Date) Analysis - $($this.Area) - $($this.Name)"

        $this.GetResults($Config)

        # If there is no results to expand, turn off ExpandResults
        if ($this.Config.Count -eq 0) {
            $this.ExpandResults = $false
        }

        
    }

}

Class CAMPOutput {

    [String]    $Name
    [Boolean]   $Completed = $False
    $VersionCheck
    $DefaultOutputDirectory
    $Result

    # Function overridden
    RunOutput($Checks, $Collection) {

    }

    Run($Checks, $Collection) {

        $this.RunOutput($Checks, $Collection)

        $this.Completed = $True
    }

}
Class RemediationAction {

    [String]    $Name
    [Boolean]   $Completed = $False
    $VersionCheck
    $DefaultOutputDirectory
    $Result

    # Function overridden
    RunOutput($Checks, $Collection) {

    }

    Run($Checks, $Collection) {

        $this.RunOutput($Checks, $Collection)

        $this.Completed = $True
    }

}

Function Get-CAMPCheckDefs {
    Param
    (
        [string]$LogFile,
        $CAMPParams,
        $Collection
    )

    $Checks = @()

    # Load individual check definitions
    $CheckFiles = Get-ChildItem "$PSScriptRoot\Checks"

    # DLP check file full name
    $DLPCheckFileName = $null
    
    #Setting DLP check file name
    ForEach ($CheckFile in $CheckFiles) {
        if (($CheckFile.BaseName -match '^check-(.*)$') -and ($matches[1] -like "DLP")) {
            $DLPCheckFileName = $CheckFile.FullName
        }
    }
    
    
    #Creating DLP check objects for each improvement actions

    #read xml doc
    if ($($Collection["GetRequiredSolution"]) -icontains "DLP") {
        [xml]$CheckData = Get-Content "$PSScriptRoot\DLPImprovementActions\ActionsInformation.xml"
        if ($null -eq $CheckData -or $CheckData -eq "") {
            Write-Host "$(Get-Date) ActionsInformation.xml file does not exist/is corrupt in $PSScriptRoot\DLPImprovementActions\ActionsInformation.xml." -ForegroundColor Orange           
        }

        if ($null -ne $DLPCheckFileName -or $DLPCheckFileName -ne "") {
            Write-Verbose "Importing DLP"
            . $DLPCheckFileName
            foreach ($Item in $CheckData.ImprovementActions.ActionItem) {
                #List of SIT
                $ListOfSIT = @()
                $AllSITS = $Item.SITs.SIT

            
                #Adding custom SITS
                <# if($($Collection["GetDLPCustomSIT"]) -ne "Error")
            {
                $CustomSIT = $($Collection["GetDLPCustomSIT"]).Name
                foreach ($sit in $CustomSIT) {
                    $ListOfSIT += $sit
                }
            }
    #>


                if ($($Collection["GetOrganisationRegion"]) -eq "Error") {
                    foreach ($sit in $AllSITS) {
                        $ListOfSIT += $sit.InnerText
                    }
                }
                else {
                    foreach ($sit in $AllSITS) {
                        if ($($Collection["GetOrganisationRegion"]) -contains $($sit.Geo)) {
                            $ListOfSIT += $sit.InnerText
                        }
                    }

                }
    
                #Hash table of links
                $LinksInfo = @{}
                if ($global:EnvironmentName -ieq "O365USGovGCCHigh") {
                    $AllLinks = $Item.GCCLinks.Link
                }
                elseif ($global:EnvironmentName -ieq "O365USGovDoD") {
                    $AllLinks = $Item.DODLinks.Link
                }
                else {
                    $AllLinks = $Item.Links.Link
                }
                foreach ($url in $AllLinks) {
                    $LinksInfo[$url.LinkText] = $url.ActualURL
                }
                $InfoParams = @{}
                $InfoParams["Control"] = $Item.CheckName
                $InfoParams["ParentArea"] = $Item.ParentArea
                $InfoParams["Area"] = $Item.Area
                $InfoParams["Name"] = $Item.Name
                $InfoParams["RemediationPolicyName"] = $Item.RemediationPolicyName
                $InfoParams["PassText"] = $Item.PassText
                $InfoParams["FailRecommendation"] = $Item.FailRecommendation
                $InfoParams["Importance"] = $Item.Importance
                $InfoParams["SIT"] = $ListOfSIT
                $InfoParams["Links"] = $LinksInfo
                $Check = New-Object -TypeName "DLP" -ArgumentList $InfoParams
                # Set the CAMPParams
                $Check.CAMPParams = $CAMPParams
                $Check.LogFile = $LogFile
    
                $Checks += $Check
            }
    
        }
    }


    # Creating Non-DLP check objects for each improvement actions
    ForEach ($CheckFile in $CheckFiles) {
        if ($CheckFile.BaseName -match '^check-(.*)$' -and ($matches[1] -notlike "DLP")) {
            $solutioname = $matches[1]
            $length = $solutioname.length
            $solutioname = $solutioname.substring(0, $length - 3)
            
            if (($null -ne $($Collection["GetRequiredSolution"])) -and ($($Collection["GetRequiredSolution"]) -icontains "$solutioname")) {
                Write-Verbose "Importing $($matches[1])"
                . $CheckFile.FullName
                $Check = New-Object -TypeName $matches[1]
                # Set the CAMPParams
                $Check.CAMPParams = $CAMPParams
                $Check.LogFile = $LogFile
                $Checks += $Check
            }
        
        }
    }

    ForEach ($CheckFile in $CheckFiles) {
        if ($CheckFile.BaseName -match '^check-(.*)$' -and ($matches[1] -like "ComplianceManager")) {
            #write-host "abc"
            Write-Verbose "Importing $($matches[1])"
            . $CheckFile.FullName
            $Check = New-Object -TypeName $matches[1]
            # Set the CAMPParams
            $Check.CAMPParams = $CAMPParams
            $Check.LogFile = $LogFile
            $Checks += $Check
        
        
        }
    }
    $Checks = $Checks | Sort-Object -Property @{ expression = 'ParentArea' ; descending = $true }, @{expression = 'Area' ; descending = $false }

    Return $Checks
}

Function Get-CAMPRemediationAction {
    Param
    (
        $VersionCheck
    )

    $RemediationActions = @()
    # Load individual check definitions
    $RemediationActionOutputFiles = Get-ChildItem "$PSScriptRoot\Remediation"

    ForEach ($RemediationActionOutputFile in $RemediationActionOutputFiles) {
        if ($RemediationActionOutputFile.BaseName -match '^remediation(.*)$') {
            Write-Verbose "Importing $($matches[1])"
            . $RemediationActionOutputFile.FullName
            $RemediationAction = New-Object -TypeName $matches[1]

            # For default output directory
            $RemediationAction.DefaultOutputDirectory = Get-CAMPDirectory

            # Provide versioncheck
            $RemediationAction.VersionCheck = $VersionCheck

            $RemediationActions += $RemediationAction
        }

    }

    Return $RemediationActions
}
Function Get-CAMPOutputs {
    Param
    (
        $VersionCheck,
        $Modules,
        $Options
    )

    $Outputs = @()

    # Load individual check definitions
    $OutputFiles = Get-ChildItem "$PSScriptRoot\Outputs"

    ForEach ($OutputFile in $OutputFiles) {
        if ($OutputFile.BaseName -match '^output-(.*)$') {
            # Determine if this type should be loaded
            If ($Modules -contains $matches[1]) {
                Write-Verbose "Importing $($matches[1])"
                . $OutputFile.FullName
                $Output = New-Object -TypeName $matches[1]

                # Load any of the options in to the module
                If ($Options) {

                    If ($Options[$matches[1]].Keys) {
                        ForEach ($Opt in $Options[$matches[1]].Keys) {
                            # Ensure this property exists before we try set it and get a null ref error
                            $ModProperties = $($Output | Get-Member | Where-Object { $_.MemberType -eq "Property" }).Name
    
                            If ($ModProperties -contains $Opt) {
                                $Output.$Opt = $Options[$matches[1]][$Opt]
                            }
                            else {
                                Throw("There is no option $($Opt) on output module $($matches[1])")
                            }
                        }
                    }
                }

                # For default output directory
                $Output.DefaultOutputDirectory = Get-CAMPDirectory

                # Provide versioncheck
                $Output.VersionCheck = $VersionCheck
                
                $Outputs += $Output
            }

        }
    }

    Return $Outputs
}

# Get DLP settings
Function Get-DataLossPreventionSettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetDlpComplianceRule"] = Get-DlpComplianceRule -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage 
        $Collection["GetDLPCustomSIT"] = Get-DlpSensitiveInformationType -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage | Where-Object { $_.Publisher -ne "Microsoft Corporation" } 
        $Collection["GetDlpCompliancePolicy"] = Get-DlpCompliancePolicy -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage 
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {        
        $Collection["GetDlpComplianceRule"] = "Error"
        $Collection["GetDLPCustomSIT"] = "Error"
        $Collection["GetDlpCompliancePolicy"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Data Loss Prevention information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    }

    Return $Collection
}

# Get Information Protection settings
Function Get-InformationProtectionSettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetLabel"] = Get-Label -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage 
        try {
            $Collection["GetLabelPolicy"] = Get-LabelPolicy -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage 
        }
        catch {
            $Collection["GetLabelPolicy"] = "Error"
        }
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetLabel"] = "Error"
        $Collection["GetLabelPolicy"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Information Protection information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
         
    }
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetAutoSensitivityLabelPolicy"] = Get-AutoSensitivityLabelPolicy -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetAutoSensitivityLabelPolicy"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching AutoSensitivity Label Policy information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetIRMConfiguration"] = Get-EXOPIRMConfiguration -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetIRMConfiguration"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching IRM Configuration information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    
    }
    Return $Collection
}

# Get Communication Compliance settings
Function Get-CommunicationComplianceSettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetSupervisoryReviewPolicyV2"] = Get-SupervisoryReviewPolicyV2 -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        try {
            $Collection["GetSupervisoryReviewOverallProgressReport"] = Get-SupervisoryReviewOverallProgressReport -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        
        }
        catch {
            $Collection["GetSupervisoryReviewOverallProgressReport"] = "Error"
        }
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetSupervisoryReviewPolicyV2"] = "Error"
        $Collection["GetSupervisoryReviewOverallProgressReport"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Communication Compliance information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
         
    }
    Return $Collection
}

# Get Information Governance settings
Function Get-InformationGovernanceSettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetRetentionCompliancePolicy"] = Get-RetentionCompliancePolicy -DistributionDetail -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        $Collection["GetRetentionComplianceRule"] = Get-RetentionComplianceRule -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        $Collection["GetComplianceTag"] = Get-ComplianceTag -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetRetentionCompliancePolicy"] = "Error"
        $Collection["GetRetentionComplianceRule"] = "Error"
        $Collection["GetComplianceTag"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Retention Compliance information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
         
    }
    Return $Collection
}

# Get Audit settings
Function Get-AuditSettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetAdminAuditLogConfig"] = Get-EXOPAdminAuditLogConfig -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetAdminAuditLogConfig"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Audit Configuration information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue      
    }
    Return $Collection
}
#get eDiscovery
Function Get-eDiscoverySettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetComplianceCase"] = Get-ComplianceCase -CaseType AdvancedEdiscovery -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        $Collection["GetComplianceCaseCore"] = Get-ComplianceCase  -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetComplianceCase"] = "Error"
        $Collection["GetComplianceCaseCore"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Audit Configuration information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue      
    }
    Return $Collection
}

#Get Insider Risk Management Settings
Function Get-InsiderRiskManagementSettings {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetInsiderRiskPolicy"] = Get-InsiderRiskPolicy -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetInsiderRiskPolicy"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Insider Risk Management information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue      
    }
    Return $Collection
}

# Get Accepted Domains
Function Get-AcceptedDomains {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["AcceptedDomains"] = Get-EXOPAcceptedDomain -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["AcceptedDomains"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching tenant name information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue      
    }
    Return $Collection
}

#Get Alert Policies
Function Get-AlertPolicies {
    Param(
        $Collection,
        [string]$LogFile
    )
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        $Collection["GetProtectionAlert"] = Get-ProtectionAlert | Where-Object { $_.Severity -eq "High" } -ErrorAction:SilentlyContinue -WarningVariable +WarnMessage
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    catch {
        $Collection["GetProtectionAlert"] = "Error"
        Write-Host "Error:$(Get-Date) There was an issue in fetching Alert Policies Configuration information. Please try running the tool again after some time." -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue      
    }
    Return $Collection
}


#Get Organisation Region
Function Get-OrganisationRegion {
    Param(
        $Collection,
        [string]$LogFile,
        [System.Collections.ArrayList] $GeoList
    )
    
    
    try {
        [System.Collections.ArrayList]$WarnMessage = @()
        [System.Collections.ArrayList] $RegionNamesList = @()
        $Collection["GetOrganisationConfig"] = Get-EXOPOrganizationConfig -ErrorAction:SilentlyContinue
            
        if ($($GeoList.Count) -gt 0) {
            $Collection["GetOrganisationRegion"] = $GeoList
            $Collection["GetOrganisationRegion"].add("INTL") | out-null
        }
        else {
            $RegionsList = $Collection["GetOrganisationConfig"].AllowedMailboxRegions 
            foreach ($region in $RegionsList) {
                $RegionName = $($region.Split("="))[0]
                $RegionName = $RegionName.ToUpper()
                $RegionNamesList.add($RegionName) | Out-Null
            }
            $Collection["GetOrganisationRegion"] = $RegionNamesList
            $Collection["GetOrganisationRegion"].add("INTL") | out-null
        }
        Write-Log -IsWarn -WarnMessage $WarnMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        
            
    }
    catch {
        $Collection["GetOrganisationConfig"] = "Error"
        if ($($GeoList.Count) -gt 0) {
            $Collection["GetOrganisationRegion"] = $GeoList
            $Collection["GetOrganisationRegion"].add("INTL") | out-null
        }
        else {
            $Collection["GetOrganisationRegion"] = "Error"
            Write-Host "Warning:$(Get-Date) There was an issue in fetching your tenant's geolocation. The generated report will have recommendations for all geos across the globe." -ForegroundColor:Yellow
            
        }
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue      
    }
    
        
    Return $Collection
}

#Get Solution Config
Function Get-PersonalizedSolution {
    Param(
        $Collection,
        [string]$LogFile,
        [System.Collections.ArrayList] $SolutionList
    )
       
    [System.Collections.ArrayList] $SolutionsList = @()
    if ($($SolutionList.Count) -gt 0) {
        $Collection["GetRequiredSolution"] = $SolutionList
        $Collection["GetRequiredSolution"].add("INTL") | out-null
    }
    else {
        $SolutionTable = Get-SolutionTable
        [int] $count = 1
        while ($count -le 8) {
            $SolutionList.add($($($SolutionTable[$count]).Code)) | out-null
            $count = $count + 1
        }

        $Collection["GetRequiredSolution"] = $SolutionsList
        $Collection["GetRequiredSolution"].add("INTL") | out-null
    }
    Return $Collection
}
               
# Get user configurations
Function Get-CAMPCollection {
    Param
    (
        [String]$LogFile,
        [System.Collections.ArrayList] $GeoList,
        [System.Collections.ArrayList] $SolutionList
    )
    $Collection = @{}

    [CAMPService]$Collection["Services"] = [CAMPService]::DLP
    try {
        Write-EXOPAdminAuditLog -Comment "Configuration Analyzer for Microsoft Purview Started at- $(Get-Date)"

    }
    catch {
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    if ($SolutionList -icontains "DLP") {
        $InfoMessage = "Getting DLP Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-DataLossPreventionSettings -Collection $Collection -LogFile $LogFile
    }

    if ($SolutionList -icontains "IP") {
        $InfoMessage = "Getting Information Protection Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-InformationProtectionSettings -Collection $Collection -LogFile $LogFile
    }

    if ($SolutionList -icontains "CC") {
        $InfoMessage = "Getting Communication Compliance Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-CommunicationComplianceSettings -Collection $Collection -LogFile $LogFile
    }
    
    if (($SolutionList -icontains "IG") -or ($SolutionList -icontains "RM")) {
        $InfoMessage = "Getting Information Governance Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-InformationGovernanceSettings -Collection $Collection -LogFile $LogFile
    }
    
    if ($SolutionList -icontains "Audit" ) {
        $InfoMessage = "Getting Audit Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-AuditSettings -Collection $Collection -LogFile $LogFile
    }

    if ($SolutionList -icontains "eDiscovery") {
        $InfoMessage = "Getting eDiscovery Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-eDiscoverySettings -Collection $Collection -LogFile $LogFile
    }

    if ($SolutionList -icontains "IRM") {
        $InfoMessage = "Getting Insider Risk Management Settings"
        Write-Host "$(Get-Date) $InfoMessage"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-InsiderRiskManagementSettings -Collection $Collection -LogFile $LogFile
    }

    $InfoMessage = "Getting Accepted Domains"
    Write-Host "$(Get-Date) $InfoMessage"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $Collection = Get-AcceptedDomains -Collection $Collection -LogFile $LogFile
    
    $InfoMessage = "Getting Alert Policies Settings"
    Write-Host "$(Get-Date) $InfoMessage"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $Collection = Get-AlertPolicies -Collection $Collection -LogFile $LogFile

    $InfoMessage = "Getting Organization's region information"
    Write-Host "$(Get-Date) $InfoMessage"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $Collection = Get-OrganisationRegion -GeoList $GeoList -Collection $Collection -LogFile $LogFile

    $InfoMessage = "Getting Organization's solution preference information"
    Write-Host "$(Get-Date) $InfoMessage"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $Collection = Get-PersonalizedSolution -SolutionList $SolutionList -Collection $Collection -LogFile $LogFile

    Return $Collection
}


Function Get-CAMPReport {
    <#
     
        .SYNOPSIS
            The Configuration Analyzer for Microsoft Purview (CAMP)
 
        .DESCRIPTION
            Configuration Analyzer for Microsoft Purview (CAMP)
 
            The Get-CAMPReport command generates a HTML report highlighting known issues in your compliance configurations in achieving data protection guidelines and recommends best practices to follow.
 
            Output report uses open source components for HTML formatting:
            - Bootstrap - MIT License https://getbootstrap.com/docs/4.0/about/license/
            - Fontawesome - CC BY 4.0 License - https://fontawesome.com/license/free
 
        
        .PARAMETER NoVersionCheck
            Prevents Configuration Analyzer for Microsoft Purview from determining if it's running the latest version. It's always very important to be running the latest
            version of Configuration Analyzer for Microsoft Purview. We will change guidelines as the product and the recommended practices article changes. Not running the
            latest version might provide recommendations that are no longer valid.
         
        .PARAMETER TurnOffDataCollection
            Disables data collection. It can be used by users who wish to turn off data collection by Microsoft. Turning it off
            will delete the UserConsent file present in the output Report folder and ultimately will not consider acceptance in
            further running instance of the tool.
 
        .PARAMETER Geo
            This will generate a report based on the geolocations entered by you.You need to input appropriate numbers from the following list corresponding to the regions.
            Input Region
                1 Asia-Pacific
                2 Australia
                3 Canada
                4 Europe (excl. France) / Middle East / Africa
                5 France
                6 India
                7 Japan
                8 Korea
                9 North America (excl. Canada)
                10 South America
                11 South Africa
                12 Switzerland
                13 United Arab Emirates
                14 United Kingdom
 
 
        .PARAMETER Solution
            This will generate a report only for the solutions entered by you. You need to input appropriate numbers from the following list corresponding to the solution.
            Input Solution
                1 Data Loss Prevention
                2 Information Protection
                3 Information Governance
                4 Records Management
                5 Communication Compliance
                6 Insider Risk Management
                7 Audit
                8 eDiscovery
 
        .PARAMETER ExchangeEnvironmentName
        This will generate CAMP report for Security & Compliance Center PowerShell in a Microsoft 365 DoD organization or Microsoft GCC High organization
         O365USGovDoD
           This will generate CAMP report for Security & Compliance Center PowerShell in a Microsoft 365 DoD organization.
         O365USGovGCCHigh
           This will generate CAMP report for Security & Compliance Center PowerShell in a Microsoft GCC High organization.
 
        .PARAMETER Collection
            Internal only.
        .EXAMPLE
            Get-CAMPReport
            This will generate a customized report based on the geolocation of your tenant. If an error occurs while fetching your tenant's geolocation, you will get a report covering all supported geolocations.
            .EXAMPLE
            Get-CAMPReport -Geo @(1,7)
            This will generate a customized report based on the geolocations entered by you.
        .EXAMPLE
            Get-CAMPReport -Solution @(1,7)
            This will generate a customized report for the solutions entered by you.
        .EXAMPLE
            Get-CAMPReport -Solution @(1,7) -Geo @(9)
            This will generate a report only on for the solutions entered by you and based on the regions you have selected.
 
     
#>

    Param(
        [CmdletBinding()]
        [Switch]$NoVersionCheck,    
        [Switch]$TurnOffDataCollection,
        [System.Collections.ArrayList] $Geo = @(),
        [System.Collections.ArrayList] $Solution = @(),
        [string][validateset('O365Default', 'O365USGovDoD', 'O365USGovGCCHigh')] $ExchangeEnvironmentName = 'O365Default',
        $Collection
    )
    $OutputDirectoryName = Get-CAMPDirectory
    if(($TurnOffDataCollection -eq $true) -and ($(Test-Path -Path "$OutputDirectoryName\UserConsent.txt" -PathType Leaf) -eq $true))
    {
        Remove-Item "$OutputDirectoryName\UserConsent.txt"
    }
    if ((Test-Path -Path "$OutputDirectoryName\UserConsent.txt" -PathType Leaf) -and ($(Get-Content "$OutputDirectoryName\UserConsent.txt") -ieq "Yes")) {
        $global:TelemetryEnabled = $true
    }
    else {
        $cntOfIterations = 1
        Write-Host "Data Collection: The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices." -ForegroundColor Yellow
        while ($cntOfIterations -lt 3) {
            Write-Host "Do you accept(Y/N):" -NoNewline -ForegroundColor Yellow
            $telemetryConsent = Read-Host -ErrorAction:SilentlyContinue
            $telemetryConsent = $telemetryConsent.Trim()
            if (($telemetryConsent -ieq "y") -or ($telemetryConsent -ieq "yes")) {
                if (Test-Path -Path "$OutputDirectoryName\UserConsent.txt" -PathType Leaf) {
                    Remove-Item "$OutputDirectoryName\UserConsent.txt"
                }
                New-Item "$OutputDirectoryName\UserConsent.txt" | Out-Null
                Set-Content "$OutputDirectoryName\UserConsent.txt" 'Yes' 
                $global:TelemetryEnabled = $true
                break
            }
            elseif (($telemetryConsent -ieq "n") -or ($telemetryConsent -ieq "no")) {
                break
            }
            Write-Host "Invalid input! Please try again." -ForegroundColor Red
            $cntOfIterations += 1
        }
        if ($cntOfIterations -eq 3) {
            return
        }
    }
    
    $global:EnvironmentName = $ExchangeEnvironmentName
    $LogDirectory = "$OutputDirectoryName\Logs"
    $FileName = "CAMP-$(Get-Date -Format 'yyyyMMddHHmmss').log"
    $LogFile = "$LogDirectory\$FileName"
    #Creating the logfiles folder if not present
    if ($(Test-Path -Path $LogDirectory) -eq $false) {
        New-Item -Path $LogDirectory -ItemType Directory -ErrorAction:SilentlyContinue | Out-Null
        #Creating the logfile
        New-Item -Path $LogFile -ItemType File -ErrorAction:SilentlyContinue | Out-Null
    }
    else {
        New-Item -Path $LogFile -ItemType File -ErrorAction:SilentlyContinue | Out-Null
    }
    #Check if log file exists
    if ($(Test-Path -Path $LogFile) -eq $False) {
        Write-Host "$(Get-Date) Log file cannot be created." -ForegroundColor:Red
    }
    Write-Log -MachineInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    if (($(Get-GeoAcceptance -Geo $Geo) -eq $false ) -and ($(Get-SolutionAcceptance -Solution $Solution) -eq $false)) {     
        Show-GeoOptions
        Show-SolutionOptions 
        return
    }
    #Get actual region names
    
    [System.Collections.ArrayList] $GeoList = @()
    if (($(Get-GeoAcceptance -Geo $Geo) -eq $false )) {
               
        Show-GeoOptions 
        return
    }
    else {
        #Number To Region Mapping
        $NumberToRegionMapping = Get-NumberRegionMappingHashTable

        #Mapping numbers to the actual region
        foreach ($RegionNumber in $Geo) {
            [string] $RegionName = $NumberToRegionMapping[$RegionNumber].Code
            $GeoList.add($RegionName) | out-null  
        }
    }

    #Get actual region names

    [System.Collections.ArrayList] $SolutionList = @()
    if ($(Get-SolutionAcceptance -Solution $Solution) -eq $false) {
                  
        Show-SolutionOptions 
        return
    }
    else {
        $ShowSolutionList = ""
        $SolutionTable = Get-SolutionTable
        if ($Solution.count -gt 0) {
            foreach ($count in $Solution) {
                [string] $Name = "$($($SolutionTable[$count]).Code)"
                #write-host "$Name"
                $SolutionList.add($Name) | out-null
                $ShowSolutionList += "$($($SolutionTable[$count]).FullName), "
            }
            $ShowSolutionList = $ShowSolutionList.TrimEnd(", ")
        }
        else {
            
            [int] $count = 1
            while ($count -le 8) {
                $SolutionList.add($($($SolutionTable[$count]).Code)) | out-null
                $count = $count + 1
            }
            $ShowSolutionList += "All Solutions"   
        }
    }
    # Easy to use for quick Configuration Analyzer for Microsoft Purview report to HTML
    If ($NoVersionCheck) {
        $PerformVersionCheck = $False
    }
    Else {
        $PerformVersionCheck = $True
    }
    

    try {
        $Result = Invoke-CAMP -PerformVersionCheck $PerformVersionCheck -Collection $Collection -Output @("HTML") -GeoList $GeoList -SolutionList $SolutionList -LogFile $LogFile -ExchangeEnvironmentName $ExchangeEnvironmentName-ErrorAction:SilentlyContinue
        $InfoMessage = "Complete! Output is in $($Result.Result)"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        Write-Host "$(Get-Date) $InfoMessage"

        try {
            Write-EXOPAdminAuditLog -Comment "Configuration Analyzer for Microsoft Purview Completed at - $(Get-Date)"
    
        }
        catch {
            $ErrorMessage = $_.ToString()
            $StackTraceInfo = $_.ScriptStackTrace
            Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
        }
    }
    catch {
        Write-Host "Error:$(Get-Date) There was an issue in running the tool. Please try running the tool again after some time." -ForegroundColor:Red
        Write-Host "Please refer to the documentation and FAQs available at https://github.com/OfficeDev/CAMP/blob/main/README.md to get guidance for resolving common issues. If the issue persists, please write to us at CAMPhelp@microsoft.com along with log file at $LogFile" -ForegroundColor:Red
        $ErrorMessage = $_.ToString()
        $StackTraceInfo = $_.ScriptStackTrace
        Write-Log -IsError -ErrorMessage $ErrorMessage -StackTraceInfo $StackTraceInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
    }
    finally {
        Write-Log -StopInfo -LogFile $LogFile -ErrorAction:SilentlyContinue
        
        $InfoMessage = "Get the log at $LogFile"  
        Write-Host "$(Get-Date) $InfoMessage" 

        try {
            if($($global:ConnectionEstablished) -eq $true)
            {
                Disconnect-ExchangeOnline -Confirm:$false -ErrorAction:SilentlyContinue  
            }      
        }
        catch {
            
        }
    }
   
}


Function Invoke-CAMP {
    Param(
        [CmdletBinding()]
        [Boolean]$PerformVersionCheck = $True,     
        $Output,
        $OutputOptions,
        $Collection,
        [System.Collections.ArrayList] $GeoList = @(),
        [System.Collections.ArrayList] $SolutionList = @(),
        [String]$LogFile
    )
    $InfoMessage = "Configuration Analyzer for Microsoft Purview Started"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue

    # Version check
    If ($PerformVersionCheck) {
        $InfoMessage = "Version Check Started"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $VersionCheck = Invoke-CAMPVersionCheck 
        $InfoMessage = "Version Check Completed"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }

  
    $InfoMessage = "Establishing Connections"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    Invoke-CAMPConnections -LogFile $LogFile -ExchangeEnvironmentName $ExchangeEnvironmentName
    $InfoMessage = "Connections Established"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
  


    # Get the collection in to memory. For testing purposes, we support passing the collection as an object
    If ($Null -eq $Collection) {
        $InfoMessage = "Fetching User Configurations"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        $Collection = Get-CAMPCollection -GeoList $GeoList -SolutionList $SolutionList -LogFile $LogFile
        $InfoMessage = "User Configurations Fetched"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    }

    # Get the output modules
    $InfoMessage = "Creating Output Objects"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $OutputModules = Get-CAMPOutputs -VersionCheck $VersionCheck -Modules $Output -Options $OutputOptions
    $InfoMessage = "Output Objects Created"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    
    # Get the object of Configuration Analyzer for Microsoft Purview checks
    $InfoMessage = "Creating Check Objects"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $Checks = Get-CAMPCheckDefs -CAMPParams $CAMPParams -Collection $Collection -LogFile $LogFile
    $InfoMessage = "Check Objects Created"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue

    
    
    # Perform checks inside classes/modules
    ForEach ($Check in ($Checks | Sort-Object Area)) {

        # Run DLP checks by default
        if ($check.Services -band [CAMPService]::DLP) {
            $Check.Run($Collection)
        }

       
    }

    # Get the Remedition Steps
    $InfoMessage = "Creating Remediation Objects"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    $RemediationActionModules = Get-CAMPRemediationAction -VersionCheck $VersionCheck  
    $InfoMessage = "Remediation Objects Created"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    ForEach ($a in $RemediationActionModules) {

        $a.Run($Checks, $Collection)
       
        
    }

    $TenantGeoLocations = $Collection["GetOrganisationRegion"] | Where-Object { $_ -ne "INTL" }
    if ($TenantGeoLocations -ne "Error") {
        $RegionString = ""
        $NumberToRegionMapping = Get-NumberRegionMappingHashTable
        foreach ($Region in $TenantGeoLocations) {
            foreach ($Numbers in $($NumberToRegionMapping.Keys)) {
                if ($($NumberToRegionMapping[$Numbers].Code) -eq $Region) {
                    if ($RegionString -eq "") {
                        $RegionString += "$($NumberToRegionMapping[$Numbers].Description)" 
                    }
                    else {
                        $RegionString += ", $($NumberToRegionMapping[$Numbers].Description)" 
                    }
                }
            }

        }
    }
    else {
        $RegionString = ""
        $RegionString += "All Geolocations"
    }
    $InfoMessage = "The following report is generated for following solutions:$ShowSolutionList" 
    Write-Host "$(Get-Date) $InfoMessage" -ForegroundColor Yellow
    $InfoMessage = "The following report is for following geolocations:$RegionString"  
    Write-Host "$(Get-Date) $InfoMessage" -ForegroundColor Yellow
    $OutputResults = @()
    $InfoMessage = "Generating Output"
    Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
    Write-Host "$(Get-Date) $InfoMessage" -ForegroundColor Green
    # Perform required outputs
    ForEach ($o in $OutputModules) {

        $o.Run($Checks, $Collection)
        $OutputResults += New-Object -TypeName PSObject -Property @{
            Name      = $o.name
            Completed = $o.completed
            Result    = $o.Result
        }

    }
    
    # If Telemetry is enabled (For Customers), then collect telemetry
    if ($($global:TelemetryEnabled) -eq $true) {
        $InfoMessage = "Collecting Telemetry"
        Write-Log -IsInfo -InfoMessage $InfoMessage -LogFile $LogFile -ErrorAction:SilentlyContinue

        $CAMPVersion = $VersionCheck.Version.ToString()

        # Setting tenant name
        if (($Collection["AcceptedDomains"] -eq "Error") -or ($Collection["AcceptedDomains"] -eq "") -or ($null -eq $Collection["AcceptedDomains"]) ) {
            if($null -ne $global:UserName)
            {
                if($global:UserName.Contains("@"))
                {
                    $DomainName = $global:UserName.Split("@")[1];
                }
                else 
                {
                    $DomainName = "Error"
                } 
            }
            else {
                $DomainName = "Error" 
            }          
        }
        else {
            $DomainName = ($Collection["AcceptedDomains"] | Where-Object { $_.InitialDomain -eq $True }).DomainName
        }

        # Setting organization name
        if ($Collection["GetOrganisationConfig"] -eq "Error") {
            $OrganizationName = "Error"
        }
        else {
            $OrganizationName = $Collection["GetOrganisationConfig"].DisplayName
        }
        
        $SolutionSummaryResult = @{}
        ForEach ($Area in ($Checks | Where-Object { $_.Completed -eq $true } | Group-Object Area)) {
            if($($Area.Name) -eq "Compliance Manager")
            {
                continue
            }
            $Pass = @($Area.Group | Where-Object { $_.Result -eq "Pass" }).Count
            $Fail = @($Area.Group | Where-Object { $_.Result -eq "Fail" }).Count
            $Info = @($Area.Group | Where-Object { $_.Result -eq "Recommendation" }).Count
            $SolutionSummaryResult[$($Area.Name)] = New-Object -TypeName PSObject -Property @{
                Pass = $Pass
                Info = $Info
                Fail = $Fail
            }
        }
        # Set the parameter for the URI
        $Parameters = @{
            CAMPVersion  = $CAMPVersion
            Domain       = $DomainName
            Organization = $OrganizationName
        }
        $AllSolutions = Get-SolutionTable
        foreach ($solution in $($AllSolutions.Values.FullName)) {
            $solutionName = $solution -replace '\s', ''
            if ($SolutionSummaryResult.ContainsKey($solution)) {
                $Parameters.Add($($solutionName + "_Pass"), $SolutionSummaryResult[$solution].Pass)
                $Parameters.Add($($solutionName + "_Info"), $SolutionSummaryResult[$solution].Info)
                $Parameters.Add($($solutionName + "_Fail"), $SolutionSummaryResult[$solution].Fail)           
            }
            else {
                $Parameters.Add($($solutionName + "_Pass"), 0)
                $Parameters.Add($($solutionName + "_Info"), 0)
                $Parameters.Add($($solutionName + "_Fail"), 0)
            }
        }
        $Parameters = $Parameters | ConvertTo-Json

        try
        {
            # URI to trigger the Telemetry Function
            $URI = "https://mccatelemetryapi.azure-api.net/MCCATelemetryServiceApp/MCCAFunction"

            # Call the URI
            $ResponseMessage = Invoke-WebRequest -Uri $URI -ContentType "application/json" -Method POST -Body $Parameters -ErrorAction:SilentlyContinue                     
            Write-Log -IsInfo -InfoMessage $ResponseMessage -LogFile $LogFile -ErrorAction:SilentlyContinue

        }
        catch
        {
            $ResponseMessage = "Telemetry execution failed!"
            Write-Log -IsInfo -InfoMessage $ResponseMessage -LogFile $LogFile -ErrorAction:SilentlyContinue
        }
    }
    Return $OutputResults

}


function Invoke-CAMPVersionCheck {
    Param
    (
        $Terminate
    )

    Write-Host "$(Get-Date) Performing Configuration Analyzer for Microsoft Purview Version check... "

    # When detected we are running the preview release
    $CAMP = ""

    try {
        $CAMPVersion = (Get-InstalledModule CAMP -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue | Sort-Object Version -Desc)[0].Version
    }
    catch {  
            $CAMPVersion = 0
    }
    finally{
        $CAMPVersion = 0
    }

    try
    {
        $PSGalleryVersion = (Find-Module CAMP -Repository PSGallery -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue).Version
    }
    
    catch {
        $PSGalleryVersion = 0
    }
    If ($PSGalleryVersion -gt $CAMPVersion) {
        $Updated = $False
        If ($Terminate) {
            Throw "CAMP is out of date. Your version is $CAMPVersion and the published version is $PSGalleryVersion. Run Update-Module CAMP."
        }
        else {
            Write-Host "$(Get-Date) CAMP is out of date. Your version: $($CAMPVersion) published version is $($PSGalleryVersion)"
        }
    }
    else {
        $Updated = $True
    }

    Return New-Object -TypeName PSObject -Property @{
        Updated        = $Updated
        Version        = $CAMPVersion
        GalleryVersion = $PSGalleryVersion
        Preview        = $Preview
    }
}

#Creating log file and directory

#Writing in log file
function Write-Log {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Switch]$IsError = $false,
        [Parameter(Mandatory = $false)]
        [Switch]$IsWarn = $false,
        [Parameter(Mandatory = $false)]
        [Switch]$IsInfo = $false,
        [Parameter(Mandatory = $false)]
        [Switch]$MachineInfo = $false,
        [Parameter(Mandatory = $false)]
        [Switch]$StopInfo = $false,
        [Parameter(Mandatory = $false)]
        [string]$ErrorMessage,
        [Parameter(Mandatory = $false)]
        [System.Collections.ArrayList]$WarnMessage,
        [Parameter(Mandatory = $false)]
        [string]$InfoMessage,
        [Parameter(Mandatory = $false)]
        [string]$StackTraceInfo,
        [String]$LogFile
    )   

    if ($MachineInfo) {
        $ComputerInfoObj = Get-ComputerInfo 
        $CompName = $ComputerInfoObj.CsName
        $OSName = $ComputerInfoObj.OsName
        $OSVersion = $ComputerInfoObj.OsVersion
        $PowerShellVersion = $PSVersionTable.PSVersion
        try {
            "********************************************************************************************" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "Logging Started" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "Start time: $(Get-Date)" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "Computer Name: $CompName" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "Operating System Name: $OSName" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "Operating System Version: $OSVersion" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "PowerShell Version: $PowerShellVersion" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "********************************************************************************************" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
         
        }
        catch {
            Write-Host "$(Get-Date) The local machine information cannot be logged." -ForegroundColor:Yellow
        }

    }
    if ($StopInfo) {
        try {
            "********************************************************************************************" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "Logging Ended" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "End time: $(Get-Date)" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "********************************************************************************************" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            
            if ($($global:ErrorOccurred) -eq $true) {
                Write-Host "Warning:$(Get-Date) The report generated may have reduced information due to errors in running the tool. These errors may occur due to multiple reasons. Please refer documentation for more details." -ForegroundColor:Yellow
            }
         
        }
        catch {
            Write-Host "$(Get-Date) The finishing time information cannot be logged." -ForegroundColor:Yellow
        }
    }
    #Error
    if ($IsError) {
        if ($($global:ErrorOccurred) -eq $false) {
            $global:ErrorOccurred = $true
        }
        $Log_content = "$(Get-Date) ERROR: $ErrorMessage"
        try {
            $Log_content | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            "TRACE: $StackTraceInfo" | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
        }
        catch {
            Write-Host "$(Get-Date) An error event cannot be logged." -ForegroundColor:Yellow  
        }           
    }
    #Warning
    if ($IsWarn) {
        foreach ($Warnmsg in $WarnMessage) {
            $Log_content = "$(Get-Date) WARN: $Warnmsg"
            try {
                $Log_content | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
            }
            catch {
                Write-Host "$(Get-Date) A warning event cannot be logged." -ForegroundColor:Yellow 
            }
        }
    }
    #General
    if ($IsInfo) {
        $Log_content = "$(Get-Date) INFO: $InfoMessage"
        try {
            $Log_content | Out-File $LogFile -Append -ErrorAction:SilentlyContinue
        }
        catch {
            Write-Host "$(Get-Date) A general event cannot be logged." -ForegroundColor:Yellow 
        }
        
    }
}

# Get the Number Region Mapping HashTable
function Get-NumberRegionMappingHashTable {
    #Number To Region Mapping
    $NumberToRegionMapping = @{}
    $NumberToRegionMapping[1] = New-Object -TypeName PSObject -Property @{
        Code        = "APC"
        Description = "Asia-Pacific"
    }
    $NumberToRegionMapping[2] = New-Object -TypeName PSObject -Property @{
        Code        = "AUS"
        Description = "Australia"
    }
    $NumberToRegionMapping[3] = New-Object -TypeName PSObject -Property @{
        Code        = "CAN"
        Description = "Canada"
    }
    $NumberToRegionMapping[4] = New-Object -TypeName PSObject -Property @{
        Code        = "EUR"
        Description = "Europe (excl. France) / Middle East / Africa"
    }
    $NumberToRegionMapping[5] = New-Object -TypeName PSObject -Property @{
        Code        = "FRA"
        Description = "France"
    }
    $NumberToRegionMapping[6] = New-Object -TypeName PSObject -Property @{
        Code        = "IND"
        Description = "India"
    }
    $NumberToRegionMapping[7] = New-Object -TypeName PSObject -Property @{
        Code        = "JPN"
        Description = "Japan"
    }
    $NumberToRegionMapping[8] = New-Object -TypeName PSObject -Property @{
        Code        = "KOR"
        Description = "Korea"
    }
    $NumberToRegionMapping[9] = New-Object -TypeName PSObject -Property @{
        Code        = "NAM"
        Description = "North America (excl. Canada)"
    }
    $NumberToRegionMapping[10] = New-Object -TypeName PSObject -Property @{
        Code        = "LAM"
        Description = "South America"
    }
    $NumberToRegionMapping[11] = New-Object -TypeName PSObject -Property @{
        Code        = "ZAF"
        Description = "South Africa"
    }
    $NumberToRegionMapping[12] = New-Object -TypeName PSObject -Property @{
        Code        = "CHE"
        Description = "Switzerland"
    }
    $NumberToRegionMapping[13] = New-Object -TypeName PSObject -Property @{
        Code        = "ARE"
        Description = "United Arab Emirates"
    }
    $NumberToRegionMapping[14] = New-Object -TypeName PSObject -Property @{
        Code        = "GBR"
        Description = "United Kingdom"
    }
    
    
    return $NumberToRegionMapping
}


#Check if the geo param is in right format
function Get-GeoAcceptance {
    param (
        $Geo
    )

    $LegitimateGeo = $Geo | Where-Object { ($_ -ge 1) -and ($_ -le 14) }

    return ($($LegitimateGeo.Count) -eq $($Geo.Count))
}

# Display options for the user to choose
function Show-GeoOptions {
    Write-Host "Error:$(Get-Date) Please input appropriate numbers from the following list corresponding to the regions for which you wish to customize the report & run the tool again." -ForegroundColor:Red 
    #Number To Region Mapping
    $NumberToRegionMapping = Get-NumberRegionMappingHashTable
    Write-Host "*******************************************************************************"
    write-host "For Geo Location"
    Write-Host "*******************************************************************************"
    [int] $count = 1
    while ($count -le 14) {
        Write-Host "$count--->$($($NumberToRegionMapping[$count]).Description)"
        $count = $count + 1
    }

    
    Write-Host "*******************************************************************************"
    Write-Host "Example: Get-CAMPReport -Geo @(1,7) -Solution @(1,7)"
    Write-Host "or"
    Write-Host "Get-CAMPReport -Geo @(1,7)"
    Write-Host ""
    Write-Host ""
    
}

function Get-SolutionTable {
    #Number To Region Mapping
    $SolutionTable = @{}
    $SolutionTable[1] = New-Object -TypeName PSObject -Property @{
        Code     = "DLP"
        FullName = "Data Loss Prevention"
         
    }
    $SolutionTable[2] = New-Object -TypeName PSObject -Property @{
        Code     = "IP"
        FullName = "Information Protection"
    }
    $SolutionTable[3] = New-Object -TypeName PSObject -Property @{
        Code     = "IG"
        FullName = "Information Governance"
    }
    $SolutionTable[4] = New-Object -TypeName PSObject -Property @{
        Code     = "RM"
        FullName = "Records Management"
    }
    $SolutionTable[5] = New-Object -TypeName PSObject -Property @{
        Code     = "CC"
        FullName = "Communication Compliance"
    }
    $SolutionTable[6] = New-Object -TypeName PSObject -Property @{
        Code     = "IRM"
        FullName = "Insider Risk Management"
    }
    $SolutionTable[7] = New-Object -TypeName PSObject -Property @{
        Code     = "Audit"
        FullName = "Audit"
    }
    $SolutionTable[8] = New-Object -TypeName PSObject -Property @{
        Code     = "eDiscovery"
        FullName = "eDiscovery"
    }

    return $SolutionTable
}


#Check if the geo param is in right format
function Get-SolutionAcceptance {
    Param (
        $Solution
    )
    
    $ValidSolution = $Solution | Where-Object { ($_ -ge 1) -and ($_ -le 8) }
    return ($($ValidSolution.Count) -eq $($Solution.Count))
}

function Show-SolutionOptions {
  
    Write-Host "Error:$(Get-Date) Please input appropriate numbers from the following list corresponding to solution for which you wish to customize the report & run the tool again." -ForegroundColor:Red 
    $SolutionTable = Get-SolutionTable
    Write-Host "*******************************************************************************"
    write-host "Solution"
    Write-Host "*******************************************************************************"
    [int] $count = 1
    while ($count -le 8) {
        Write-Host "$count--->$($($SolutionTable[$count]).FullName)"
        $count = $count + 1
    }

    Write-Host "*******************************************************************************"
    Write-Host "Example: Get-CAMPReport -Geo @(1,7) -Solution @(1,7)"
    Write-Host "or"
    Write-Host "Get-CAMPReport -Solution @(1,7)"
    Write-Host ""
    Write-Host ""
    
}

# SIG # Begin signature block
# MIIl1QYJKoZIhvcNAQcCoIIlxjCCJcICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBo9/+P3vUJMdfz
# hbC5xkHpkw+zWKxDSmOO2vqAWt9OfqCCC6EwggUGMIID7qADAgECAhMzAAAE4xrK
# 0/aegtm7AAEAAATjMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBUZXN0aW5nIFBD
# QSAyMDEwMB4XDTIxMDkwMjE5MjYyOFoXDTIyMDkwMTE5MjYyOFowfDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdQ29kZSBTaWdu
# IFRlc3QgKERPIE5PVCBUUlVTVCkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQDokaNBqvPFgH8yAnEicnWyLoQ58nf7j+B20b5uqsDq9EXWyt87+Wc9CCFh
# +9gxWx6wx+iRsO/yT8SZhxi/w00uxM5jDWnbAbSIMB2VrNnLGNOxAQj674zPskBs
# ecseXAMHA/+o5ujZOse4EorLmTfJ5f/Zzun8KVVwlSd1CVlj0hgk28xmTdLV7ZWw
# 0wV9e/0p/XOHSRUNWw8gFfIfzli3oVV4H/DzT2o1jD4HIt3QIX1kRRXVqleASUgD
# Z6/6JthrKti4xfyMdSUGyXuxEoe6zZ4EOXltaHw7hqZzH2Ufl3UxB6HmdxvG9BI4
# 3LVKq3pFO+Nj3NdMVkyptnjDJnGpAgMBAAGjggGCMIIBfjATBgNVHSUEDDAKBggr
# BgEFBQcDAzAdBgNVHQ4EFgQUPMsdBtvwLsgDf0B50WrJSL+NYrEwUAYDVR0RBEkw
# R6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNv
# MRYwFAYDVQQFEw0yMzAwNzIrNDY3NjA2MB8GA1UdIwQYMBaAFL9loqtvdaNORZZX
# Bc85h/TAFRwcMFwGA1UdHwRVMFMwUaBPoE2GS2h0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRlc3RpbmclMjBQQ0ElMjAyMDEw
# KDEpLmNybDBpBggrBgEFBQcBAQRdMFswWQYIKwYBBQUHMAKGTWh0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGVzdGluZyUy
# MFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD
# ggEBAAylFduuUCCNS+ejRtNeqm7geSoNzl+E0wbGZi4W9+rqo972KaS338FB5Y5G
# 8PNqaN3RNqNaHra/zI97JaLYjrZ+iCmKMzROdVS5cR/WpAla36J/tpuVSvNwwvW1
# 5KHRv6PEzGQ7BQYU55cMTkUwNtRVbARMdjcFa3pbuIkioVsoFNVOZ4e8aOSPV+Vz
# PVmOU2xltmM/8IOgTSfFTCJhqY7d/vjqajXAXg2olbbJ2vX051uUuFfIyfo/5xJS
# uyWOUCZ1opzXHy5EwrkmdZs6orQtMy1akT6UYjl4x5VZsqiszDshDNjVgRUdR97M
# d7NxQZEScdZfoTf0byI8Cwtp8JIwggaTMIIEe6ADAgECAhMzAAAALTV6RojJB3HY
# AAAAAAAtMA0GCSqGSIb3DQEBCwUAMIGQMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTowOAYDVQQDEzFNaWNyb3NvZnQgVGVzdGluZyBSb290IENl
# cnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTIwMTIxMDIwNDMyMFoXDTM1MDYx
# NzIxMDQxMVoweTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEj
# MCEGA1UEAxMaTWljcm9zb2Z0IFRlc3RpbmcgUENBIDIwMTAwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQC/PGCBq77s/9cXY/KmQtETYpywF866Rhp7Ii5V
# c5pUucbU0cqY/ndHDY6d8F7M+FTv8s8q1GLxqTQKBNfFBYbyEPVN05YIZoXbAn0Y
# 3OJ6I6eaKV2ueRjPDGPwGv+BqalDP6kLuVHxs0g6/EQuXxHFVjr/yg7NiAjB/cfV
# 9T7v6k4bcosjvUU3nh9wwafJuCBz7fNsPYshBc93Ev6h3AfpDVt4CWwyVRBH0DfI
# x/f6c/5GNORWFqg3ZFPWNfvDgLaOtoaRoRDWwUr243rUEQqy3i6eEJfgga4SXYgB
# XcykxChSnGDwCuaIr0vpV4rsow83Hin2XSHT19pdDslO241tAgMBAAGjggH6MIIB
# 9jAOBgNVHQ8BAf8EBAMCAYYwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3
# FQIEFgQU6p9fM7bQX56t6y2mwQK7QDjL57UwHQYDVR0OBBYEFL9loqtvdaNORZZX
# Bc85h/TAFRwcMFQGA1UdIARNMEswSQYEVR0gADBBMD8GCCsGAQUFBwIBFjNodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0w
# GQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
# HSMEGDAWgBSjAQR+MIgz67kxnMrrhXZn/GW00TBZBgNVHR8EUjBQME6gTKBKhkho
# dHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUZXNS
# b29DZXJBdXRfMjAxMC0wNi0xNy5jcmwwgY0GCCsGAQUFBwEBBIGAMH4wTQYIKwYB
# BQUHMAKGQWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGVz
# Um9vQ2VyQXV0XzIwMTAtMDYtMTcuY3J0MC0GCCsGAQUFBzABhiFodHRwOi8vb25l
# b2NzcC5taWNyb3NvZnQuY29tL29jc3AwDQYJKoZIhvcNAQELBQADggIBACe00IWy
# nswPpCpTc9Vt2tcxAMj2/2PI9gERtRiS2ty0+a8m6PxKeb3BZ7CIA3llzzU1BViO
# 4oMGlvW6fhJZUJvg8NU9nQnzu0bNh7104nfQRgZ6IhppK2QsuYs5gAzqgiciojBX
# IiTZIT+raUa3w6/Bxl682Y+RViOTqfImLkR29LpyI9GZb1tFZFPilZTxtkhAv3fw
# sTiUc5ACLGsBCJZ6zlLOnZTzkTSct1oOvnD8jbwn9nX7jNbU1qjWmtUI6quiyeg1
# R9V/WS+zB9bcVjaXU+IBinywbI7nsZrO1d0GgQ4FwMt519w+tr340t0QXfo2QTSD
# 711uiQVjsrGFQnxakyIKY3jKP0soUu9Bty0Ywklj0TAcWJ+bamBqJpp8oXUbLP42
# u4nMQSCnKwyj9/0BUFD+oJ09BAYRandAMhp5/i2z1BPH5GYMoIEMxMUuAWaaFu1R
# jQlaMuQqfuyjP8shwNSV7+nlOIUPhWF9mcBy9hAdd+6yWfKn7K6FbMBKcBA2c4g7
# p9zUkUjCQORwz6T9Oyl8BqPMeihIDBeSs5auNENXrAjRjMxUesTJDSFnk2chdsve
# WtOQ4+N/c0G7WdpI0RrJJ25K0NsVWNXdNegQ0SdmK6AkphLCaHOhgczWeCKk3ms1
# 2omIrm6SnoTwsPOLeXn/tdy0lBjf5mAcMmiyMYIZijCCGYYCAQEwgZAweTELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEjMCEGA1UEAxMaTWljcm9z
# b2Z0IFRlc3RpbmcgUENBIDIwMTACEzMAAATjGsrT9p6C2bsAAQAABOMwDQYJYIZI
# AWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGC
# NwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEII6kGW1vuNiElGSx
# ftQh/OgVQnYBZL05sWacRuBrnFsWMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkA
# YwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZI
# hvcNAQEBBQAEggEAtnL/Nd1FCYL1KI667p2kFvYv+ItPHuFcKHIbP12jOmac1IqH
# WsMEiK3iE7TJNrRuz/saU9hL+YmQLSa4fxtEpAg2K8iQW3ishPGcIJ7yDyfdVcTy
# AaUGcxLhwTloD1HpR+ZgM2debFRxSoOKIxWSIu2xmpJuyW89w6lZnKDEbH6pw9kO
# t4RUjlfOgUMZU3tQRbL2mV1z5c7qQmljL2/OjY6DjuRUv/irscdOKwBq5uITPGuj
# oDcOM1t6X/RJ+1l7wQQsvTG5FmUuyke6FGx5AvHrepoJImGrgLbzANa4rgSyzM0F
# yCIFN9fMifMo4NOH/hYi8KegS9eTxra6UaGqm6GCFxkwghcVBgorBgEEAYI3AwMB
# MYIXBTCCFwEGCSqGSIb3DQEHAqCCFvIwghbuAgEDMQ8wDQYJYIZIAWUDBAIBBQAw
# ggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0G
# CWCGSAFlAwQCAQUABCDne9+2SLvofd4KSVxFzUoelxt6hUT2SuCJiR1ngQbrPAIG
# YoZWFBhnGBMyMDIyMDYxMDA0MTEzNi42OTJaMASAAgH0oIHYpIHVMIHSMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3Nv
# ZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBU
# U1MgRVNOOjNCRDQtNEI4MC02OUMzMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T
# dGFtcCBTZXJ2aWNloIIRaDCCBxQwggT8oAMCAQICEzMAAAGJtL+GMIQcS48AAQAA
# AYkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# HhcNMjExMDI4MTkyNzQxWhcNMjMwMTI2MTkyNzQxWjCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoz
# QkQ0LTRCODAtNjlDMzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL0GV8WRZmuqZZrj
# srzaVfMTjTsHGKJWRvwY8mVhkpOSThmi8qyiHeVcVR1h5bJiROEr587HabCplcfK
# LTjb3iFBb0nHhGafFV5ruZtX7vC+3Pt5cF3Im43HKrRL7ULJaJEFcdK/i+eGm6zQ
# 2q8BRu9yGkYnSEtYvXPrpyfKGMoQ0S6wsrBQFcckITzWZFiu2fP1RrpGiiwFh1wo
# f/ked4eNoBS/vf5gAC8cwl17qH4vH/1ygpu8TcFXNYTjQgs+qKveALn81TZJCFuG
# 61EIGKQnCZvVNFzZkL7a6KWA5/VLWPGENDSnp1z7XYCx3UPDZ794oBKyi61iNGuZ
# +Y43Sn8JPvJr2pKnWZpTrHnjktV7KUDSQCtbmZZQCE3J0GTnDuaH4zkN97o1nJAF
# 3c/v8d6O5eAFP00jjMxmTMIVHbVcAt3UmyLaUlRYJ4zNgjhCfc4AmnbzoqxgyzeO
# 9Y2SNowpZI7CU3YD5N+N00AOCRb3bP7p2atLi6/p4md1+ODgcdsfoFZZZ9nOFG2V
# zbngOMktUyRm2yRSCCwJk1APQLo+XiEhk2zYslse/R5wjk2q9/UBCqM5uC505g18
# tPyiPx/52GRirkx33JD9vMEEtOqw/nw0ucS8HETAlvdg5B15rW4RskYpQTi+S8WX
# pUH8beeMJeFlAtAHQBKJT3pDg8DvAgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUl28f
# s0daeCCAHoLgOqxypK35e1AwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ
# 6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br
# aW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSku
# Y3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNy
# b3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIw
# UENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEF
# BQcDCDANBgkqhkiG9w0BAQsFAAOCAgEAjrS3aVlCOCsHyy632iqywfdg6mwLKRlj
# ONU2juCZfrB8T6OdtrrtxikAo5pEVq3h7ZX8svZDpOy1msd5N5HvBrX3rX24e6h9
# C3ldlzloN/QTpx3+pk3GauxWEmWXIdSQ0I3PfPjnZaMPqFoodA27eAlf3tfWXBPt
# Z9c81pLJFBHdH+YzyFIrN96fr5GPLM3bgLQnCHDxVISPB2+WpT1ADzIxs8Cm+zSC
# m53/I/HD9fALOSL3nJBdKIdXMOt0WP7zyutiw2HaYu1pxtjm754H1lSrcIsEyOIx
# 49nDvat+xw3vzz5dteoEqVGYdGqduJipjA33CqdTeJhHbMc+KLHjqz2HhbBx1iRS
# egIr76p+9Ck3iaaea/g8Uqm3kstJsSFDqv5QGlMYDUkFVF9urfK/n3IpKHyr9t1h
# 67UVd7e61U7AfWM60WoopJs+vCuR1nbfTKlC8T0D6PqaWdC0apDmnuOuvlCkWNCc
# VrXazHObx5R2X56o2sI/0bDNkukOn2vU/Qp2NTc+w2ARt8mScgjxbK4FNObPZY6n
# 7EqbaRXVIfUeHHvi+9UlgyzNsf9TBSyxwDG17BKfCpaBBrWg1C58bX0trWIX7ihq
# kV6BHwzwDJyHU70D4dxh0OEo5JAQERy9DGO+WpYRkyh1owtmi1TqPKGyiAZPIX5x
# Q1H/xMlcOLkwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqG
# SIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkg
# MjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
# AgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4X
# YDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTz
# xXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7
# uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlw
# aQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedG
# bsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXN
# xF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03
# dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9
# ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5
# UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReT
# wDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZ
# MBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8
# RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAE
# VTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAww
# CgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQD
# AgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb
# 186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29t
# L3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoG
# CCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZI
# hvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9
# MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2Lpyp
# glYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OO
# PcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8
# DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA
# 0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1Rt
# nWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjc
# ZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq7
# 7EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJ
# C4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328
# y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC
# 1zCCAkACAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25z
# IExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjNCRDQtNEI4MC02OUMz
# MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYF
# Kw4DAhoDFQAhpQmt5Hrcnrnsu2yTaVpDLognEKCBgzCBgKR+MHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA5k0rqDAiGA8yMDIy
# MDYxMDEwMzIwOFoYDzIwMjIwNjExMTAzMjA4WjB3MD0GCisGAQQBhFkKBAExLzAt
# MAoCBQDmTSuoAgEAMAoCAQACAgnEAgH/MAcCAQACAhHxMAoCBQDmTn0oAgEAMDYG
# CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEA
# AgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAovF0X/q2xTr0F+Ph+bMynj9dRHsiwBLR
# u/10YaQoH9h6UVa3oWslrA31sF8Wy8QbM7GGQbo0QySStIG+1utiO9PA0haGCMJ/
# 4HHgXrVMN07zZwKEWbOjM1TKyYaRaFaB7nXPDN1eGZRCr4Wppdj49XtD+NfjU70J
# 5MuWaQgvm4MxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMAITMwAAAYm0v4YwhBxLjwABAAABiTANBglghkgBZQMEAgEFAKCCAUowGgYJ
# KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDSPtCWMM5q
# z9uhwPjwa1DjGPthG+DFBt6dizhxYmVXazCB+gYLKoZIhvcNAQkQAi8xgeowgecw
# geQwgb0EIGZ3RzHcUFdVbG6Vhzkx6lhMnL3ESZu3GOvZf1Jk/I9FMIGYMIGApH4w
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGJtL+GMIQcS48AAQAA
# AYkwIgQgDCmq3CE+3aDX5pRYKIm4b+i5hoWDLHCcnpw+rMUs6c0wDQYJKoZIhvcN
# AQELBQAEggIAfMuh3xJ7thg+wR3VB9rRWcim75s48Ah6bEclNdR3BB+HMncTAErS
# YbyxhEVdDoqkeHND+lF9SZA+uFwKDPNZYpjitwq/ksD2WwZnRx9wNCPAFf/JMtV4
# xLWwGtDS0rKmK7PKw6Uns9voJc8c2jp/9xcD/Fsu3DNuLDyTRodS7S2b+pKnayQK
# 49rBU4NVkN+Ag0Euqu09OuebM8cDKLBOw0rMo89o1DX9b31hNq1f93ZbHCXeBxa0
# nnea5Uzj7+G5xyKHQsKi6+kxJ/VY8eOke0sMGxpQdU8eUOo0w2U2qRyGN9cCPslc
# QZ2HHPwQsMCaAyH36e4do/Opl4tLD2d1OJUyU3KY8jXA9LaWJJnXRSbh1eU0DU7v
# w6DfjebKzS9V/BP58JzaNJOgQvlvRnqyhL+YgzzFS1ESye3g6uw3YlwuX03jBmDe
# aGRI8kFVRJTn5lE7nbMrcjhk/HIQD9EoGCZQoiE2GQeNDa4tK9rUCEzS3jg+3s1i
# pyp/6YyxImN/rv2Gl9SZIp8VVyvP2yivTGgrV0nDcLm9m7YMLTrHhnVAjhwT4qux
# S97Z67fNjQ+XtofJwm7S4Q5Wb9E11/z6l9lfUDFtSeU3oREO4T/sSWn8KaoR8hBY
# b5Qq05a3aDSqZgvl4bqGPtubuAQ0IVSXmkLAmpyIzLACKfZ0AkY4Drw=
# SIG # End signature block