Framework/Listeners/RemoteReports/AIOrgTelemetry.ps1

Set-StrictMode -Version Latest

class AIOrgTelemetry: ListenerBase {
    [Microsoft.ApplicationInsights.TelemetryClient] $TelemetryClient;

    hidden AIOrgTelemetry() {
        $this.TelemetryClient = [Microsoft.ApplicationInsights.TelemetryClient]::new()
    }

    hidden static [AIOrgTelemetry] $Instance = $null;

    static [AIOrgTelemetry] GetInstance() {
        if ( $null  -eq [AIOrgTelemetry]::Instance -or  $null  -eq [AIOrgTelemetry]::Instance.TelemetryClient) {
            [AIOrgTelemetry]::Instance = [AIOrgTelemetry]::new();
        }
        return [AIOrgTelemetry]::Instance
    }

    [void] RegisterEvents() {
        $this.UnregisterEvents();

        $this.RegisterEvent([AzSKRootEvent]::GenerateRunIdentifier, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                $runIdentifier = [AzSKRootEventArgument] ($Event.SourceArgs | Select-Object -First 1)
                $currentInstance.SetRunIdentifier($runIdentifier);
            }
            catch
            {
                $currentInstance.PublishException($_);
            }
        });

        $this.RegisterEvent([SVTEvent]::EvaluationCompleted, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                if(![RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { return; };
                $invocationContext = [System.Management.Automation.InvocationInfo] $currentInstance.InvocationContext
                $SVTEventContexts = [SVTEventContext[]] $Event.SourceArgs
                $featureGroup = [RemoteReportHelper]::GetFeatureGroup($SVTEventContexts)
                if($featureGroup -eq [FeatureGroup]::Organization){
                    $currentInstance.PushOrganizationScanResults($SVTEventContexts)
                }elseif($featureGroup -eq [FeatureGroup]::Service){
                    $currentInstance.PushServiceScanResults($SVTEventContexts)
                }else{
                }
            }
            catch
            {
                $currentInstance.PublishException($_);
            }
        });

        $this.RegisterEvent([AzSKGenericEvent]::Exception, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                if(![RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { return; };
                [System.Management.Automation.ErrorRecord] $er = ($Event.SourceArgs | Select-Object -First 1)
                [AIOrgTelemetryHelper]::TrackException($er, $currentInstance.InvocationContext)
            }
            catch
            {
                # Handling error while registration of Exception event.
                # No need to break execution
            }
        });

        $this.RegisterEvent([AzSKRootEvent]::CommandError, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                if(![RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { return; };
                [System.Management.Automation.ErrorRecord] $er = $Event.SourceArgs.ExceptionMessage
                [AIOrgTelemetryHelper]::TrackException($er, $currentInstance.InvocationContext)
            }
            catch
            {
                # Handling error while registration of CommandError event at AzSKRoot.
                # No need to break execution
            }
        });

        $this.RegisterEvent([SVTEvent]::CommandError, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                if(![RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { return; };
                [System.Management.Automation.ErrorRecord] $er = $Event.SourceArgs.ExceptionMessage
                [AIOrgTelemetryHelper]::TrackException($er, $currentInstance.InvocationContext)
            }
            catch
            {
                # Handling error while registration of CommandError event at SVT.
                # No need to break execution
            }
        });

        $this.RegisterEvent([SVTEvent]::EvaluationError, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                if(![RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { return; };
                [System.Management.Automation.ErrorRecord] $er = $Event.SourceArgs.ExceptionMessage
                [AIOrgTelemetryHelper]::TrackException($er, $currentInstance.InvocationContext)
            }
            catch
            {
                # Handling error while registration of EvaluationError event at SVT.
                # No need to break execution
            }
        });

        $this.RegisterEvent([SVTEvent]::ControlError, {
            $currentInstance = [AIOrgTelemetry]::GetInstance();
            try
            {
                if(![RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { return; };
                [System.Management.Automation.ErrorRecord] $er = $Event.SourceArgs.ExceptionMessage
                [AIOrgTelemetryHelper]::TrackException($er, $currentInstance.InvocationContext)
            }
            catch
            {
                # Handling error while registration of ControlError event at SVT.
                # No need to break execution
            }
        });
        

    }

    hidden [void] PushOrganizationScanResults([SVTEventContext[]] $SVTEventContexts)
    {
        $SVTEventContextFirst = $SVTEventContexts[0]
        $baseProperties = @{
            "RunIdentifier" = $this.RunIdentifier;
            [TelemetryKeys]::FeatureGroup = [FeatureGroup]::Organization;
            "ScanKind" = [RemoteReportHelper]::GetOrganizationScanKind(
                $this.InvocationContext.MyCommand.Name,
                $this.InvocationContext.BoundParameters);
            "OrganizationMetadata" = [JsonHelper]::ConvertToJsonCustomCompressed($SVTEventContextFirst.OrganizationContext.OrganizationMetadata);
        }
        $this.PushControlResults($SVTEventContexts, $baseProperties)
    }

    hidden [void] PushServiceScanResults([SVTEventContext[]] $SVTEventContexts)
    {
        $SVTEventContextFirst = $SVTEventContexts[0]
        # PartialScanIdentifier for each control scanned event to get idea about all resources scanned for a subscription in case of partial run
        $PartialScanIdentifier = ""
        # try catch for cases if partial scan is not applicable
        try{
            $PartialScanIdentifier = $SVTEventContextFirst.PartialSCanIdentifier
        } 
        catch{
            $PartialScanIdentifier = ""
        }
        $baseProperties = @{
            "RunIdentifier" = $this.RunIdentifier;
            [TelemetryKeys]::FeatureGroup = [FeatureGroup]::Service;
            "ScanKind" = [RemoteReportHelper]::GetServiceScanKind(
                $this.InvocationContext.MyCommand.Name,
                $this.InvocationContext.BoundParameters);
            "Feature" = $SVTEventContextFirst.FeatureName;
            "ResourceGroup" = $SVTEventContextFirst.ResourceContext.ResourceGroupName;
            "ResourceName" = $SVTEventContextFirst.ResourceContext.ResourceName;
            "ResourceId" = $SVTEventContextFirst.ResourceContext.ResourceId;
            "ResourceMetadata" = [JsonHelper]::ConvertToJsonCustomCompressed($SVTEventContextFirst.ResourceContext.ResourceMetadata);
            "PartialScanIdentifier" = $PartialScanIdentifier 
        }
        $this.PushControlResults($SVTEventContexts, $baseProperties)
    }

    hidden [void] PushControlResults([SVTEventContext[]] $SVTEventContexts, [hashtable] $BaseProperties){
        $telemetryEvents = [System.Collections.ArrayList]::new()
        foreach($context in $SVTEventContexts){
            $propertiesCollection = $this.AttachControlProperties($BaseProperties, $context)
            foreach($properties in $propertiesCollection){
                $telemetryEvent = "" | Select-Object Name, Properties, Metrics
                $telemetryEvent.Name = "Control Scanned"
                $telemetryEvent.Properties = $properties
                $telemetryEvent = [AIOrgTelemetry]::SetCommonProperties($telemetryEvent);
                $telemetryEvents.Add($telemetryEvent) | Out-Null
            }
        }
        [AIOrgTelemetryHelper]::TrackEvents($telemetryEvents);
    }


    hidden [hashtable[]] AttachControlProperties([hashtable] $BaseProperties, [SVTEventContext] $context){
        if($null -eq $context) {return  ([hashtable[]]([System.Collections.ArrayList]::new()))}
        $properties = @{}
        if ($null -ne $BaseProperties) {
            $properties = $BaseProperties.Clone()
        }
        $propertiesArray = [System.Collections.ArrayList]::new()
        $properties.Add("ControlIntId", $context.ControlItem.Id);
        $properties.Add("ControlId", $context.ControlItem.ControlID);
        $properties.Add("ControlSeverity", $context.ControlItem.ControlSeverity);
        $properties.Add("IsBaselineControl", $context.ControlItem.IsBaselineControl)
        #add PreviewBaselineFlag
        $properties.Add("IsPreviewBaselineControl", $context.ControlItem.IsPreviewBaselineControl)
        
        if (!$context.ControlItem.Enabled) {
            $properties.Add("VerificationResult", [VerificationResult]::Disabled)
            $properties.Add("AttestationStatus", [AttestationStatus]::None)
            $propertiesArray.Add($properties) | Out-Null
        }else{
            $results = $context.ControlResults            
            if($results.Count -eq 1){
                $properties.Add("HasAttestationWritePermissions", $results[0].CurrentSessionContext.Permissions.HasAttestationWritePermissions)
                $properties.Add("HasAttestationReadPermissions", $results[0].CurrentSessionContext.Permissions.HasAttestationReadPermissions)
                $properties.Add("ActualVerificationResult", $results[0].ActualVerificationResult)
                $properties.Add("AttestationStatus", $results[0].AttestationStatus)
                $properties.Add("VerificationResult", $results[0].VerificationResult)
                $properties.Add("HasRequiredAccess", $results[0].CurrentSessionContext.Permissions.HasRequiredAccess)
                $properties.Add("TimeTakenInMs", $results[0].TimeTakenInMs)
                $properties.Add("ScanStartDateTime", $results[0].ScanStartDateTime)
                $properties.Add("ScanEndDateTime", $results[0].ScanEndDateTime)
                if($null -ne $context.ResourceContext){
                    if($context.ResourceContext.ResourceName -eq $results[0].ChildResourceName -or [string]::IsNullOrWhiteSpace($results[0].ChildResourceName)){
                        $properties.Add("IsNestedResource", 'No')
                        $properties.Add("NestedResourceName", "NA")
                    }else{
                        $properties.Add("IsNestedResource", 'Yes')
                        $properties.Add("NestedResourceName", $results[0].ChildResourceName)
                    }
                }
                if(($null -ne $results[0].StateManagement) -and ($null -ne $results[0].StateManagement.AttestedStateData)) {
                    $properties.Add("AttestedBy", $results[0].StateManagement.AttestedStateData.AttestedBy)
                    $properties.Add("Justification", $results[0].StateManagement.AttestedStateData.Justification)
                    $properties.Add("AttestedState", [JsonHelper]::ConvertToJsonCustomCompressed($results[0].StateManagement.AttestedStateData.DataObject))
                    $properties.Add("AttestedDate", ($results[0].StateManagement.AttestedStateData.AttestedDate).Tostring("yyyy_MM_dd_hh_mm"))
                    $properties.Add("ExpiryDate",  ([DateTime]$results[0].StateManagement.AttestedStateData.ExpiryDate).Tostring("yyyy_MM_dd_hh_mm"))
                }
                if(($null -ne $results[0].StateManagement) -and ($null -ne $results[0].StateManagement.CurrentStateData)) {
                    $properties.Add("CurrentState", [JsonHelper]::ConvertToJsonCustomCompressed($results[0].StateManagement.CurrentStateData.DataObject))
                }
                $propertiesArray.Add($properties) | Out-Null
            }elseif($results.Count -gt 1){
                $properties.Add("IsNestedResource", 'Yes')
                foreach($result in $results){
                    $propertiesIn = $properties.Clone()
                    $propertiesIn.Add("ActualVerificationResult", $result.ActualVerificationResult)
                    $propertiesIn.Add("AttestationStatus", $result.AttestationStatus)
                    $propertiesIn.Add("VerificationResult", $result.VerificationResult)
                    $propertiesIn.Add("NestedResourceName", $result.ChildResourceName)
                    $propertiesIn.Add("HasRequiredAccess", $result.CurrentSessionContext.Permissions.HasRequiredAccess)
                    if(($null -ne $result.StateManagement) -and ($null -ne $result.StateManagement.AttestedStateData)) {
                        $propertiesIn.Add("AttestedBy", $result.StateManagement.AttestedStateData.AttestedBy)
                        $propertiesIn.Add("Justification", $result.StateManagement.AttestedStateData.Justification)
                        $propertiesIn.Add("AttestedState", [JsonHelper]::ConvertToJsonCustomCompressed($result.StateManagement.AttestedStateData.DataObject))
                        $propertiesIn.Add("AttestedDate", ($result.StateManagement.AttestedStateData.AttestedDate).Tostring("yyyy_MM_dd_hh_mm"))
                        $propertiesIn.Add("ExpiryDate", ([DateTime]$result.StateManagement.AttestedStateData.ExpiryDate).Tostring("yyyy_MM_dd_hh_mm"))
                    }
                    if(($null -ne $result.StateManagement) -and ($null -ne $result.StateManagement.CurrentStateData)) {
                        $propertiesIn.Add("CurrentState", [JsonHelper]::ConvertToJsonCustomCompressed($result.StateManagement.CurrentStateData.DataObject))
                    }
                    $propertiesArray.Add($propertiesIn) | Out-Null
                }
            }
        }
        $returnObj = [hashtable[]] $propertiesArray
        return $returnObj;
    }

    static [psobject] SetCommonProperties([psobject] $telemetryEvent) 
    {
        try
        {
            $NA = "NA";
            try {
                $telemetryEvent.properties.Add("ScanSource", [RemoteReportHelper]::GetScanSource());
            }
            catch {
                # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                # No need to break execution
            }
            try {
                $module = Get-Module 'AzSK*' | Select-Object -First 1
                $telemetryEvent.properties.Add("ScannerModuleName", $module.Name);
                $telemetryEvent.properties.Add("ScannerVersion", $module.Version.ToString());
                $telemetryEvent.properties.Add("OrgVersion", [ConfigurationManager]::GetAzSKConfigData().GetLatestAzSKVersion($module.Name).ToString());    
                $telemetryEvent.properties.Add("PolicyOrgName", [ConfigurationManager]::GetAzSKConfigData().PolicyOrgName)
                $AzSKLatestVersion= [ConfigurationManager]::GetAzSKConfigData().GetAzSKLatestPSGalleryVersion($module.Name)        
                $telemetryEvent.properties.Add("LatestVersion", $AzSKLatestVersion);                
                
            }
            catch {
                # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                # No need to break execution
            }
            try {
                $organizationContext = [ContextHelper]::GetCurrentContext()
                try {
                    $telemetryEvent.properties.Add([TelemetryKeys]::OrganizationId, $organizationContext.Organization.Id)
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
                try {
                    $telemetryEvent.properties.Add([TelemetryKeys]::OrganizationName, $organizationContext.Organization.Name)
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
                try {
                    $telemetryEvent.properties.Add("ADOEnv", $organizationContext.Environment.Name)
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
                try {
                    $telemetryEvent.properties.Add("TenantId", $organizationContext.Tenant.Id)
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
                try {
                    $telemetryEvent.properties.Add("AccountId", $organizationContext.Account.Id)
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
                try {
                    if ($telemetryEvent.Properties.ContainsKey("RunIdentifier")) {
                        $actualRunId = $telemetryEvent.Properties["RunIdentifier"]
                        if ($telemetryEvent.Properties.ContainsKey("UniqueRunIdentifier")) {
                            $telemetryEvent.Properties["UniqueRunIdentifier"] = [RemoteReportHelper]::Mask($organizationContext.Account.Id + '##' + $actualRunId.ToString())
                        }
                        else
                        {
                            $telemetryEvent.properties.Add("UniqueRunIdentifier", [RemoteReportHelper]::Mask($organizationContext.Account.Id + '##' + $actualRunId.ToString()))
                        }
                    }
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
                try {
                    $telemetryEvent.properties.Add("AccountType", $organizationContext.Account.Type);
                }
                catch {
                    # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                    # No need to break execution
                }
            }
            catch {
                # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
                # No need to break execution
            }
        }
        catch {
            # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again
            # No need to break execution
        }
        return $telemetryEvent;
    }
}

# SIG # Begin signature block
# MIIjmAYJKoZIhvcNAQcCoIIjiTCCI4UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD9f55MdS/g3QqT
# VrRD6jwwrM2oOf+4e/7GBRtndKZpSaCCDYUwggYDMIID66ADAgECAhMzAAAB4HFz
# JMpcmPgZAAAAAAHgMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ2WhcNMjExMjAyMjEzMTQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDRXpc9eiGRI/2BlmU7OMiQPTKpNlluodjT2rltPO/Gk47bH4gBShPMD4BX/4sg
# NvvBun6ZOG2dxUW30myWoUJJ0iRbTAv2JFzjSpVQvPE+D5vtmdu6WlOR2ahF4leF
# 5Vvk4lPg2ZFrqg5LNwT9gjwuYgmih+G2KwT8NMWusBhO649F4Ku6B6QgA+vZld5S
# G2XWIdvS0pmpmn/HFrV4eYTsl9HYgjn/bPsAlfWolLlEXYTaCljK7q7bQHDBrzlR
# ukyyryFpPOR9Wx1cxFJ6KBqg2jlJpzxjN3udNJPOqarnQIVgB8DUm3I5g2v5xTHK
# Ovz9ucN21467cYcIxjPC4UkDAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUVBWIZHrG4UIX3uX4142l+8GsPXAw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2MzAxMDAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AE5msNzmYzYbNgpnhya6YsrM+CIC8CXDu10nwzZtkgQciPOOqAYmFcWJCwD5VZzs
# qFwad8XIOrfCylWf4hzn09mD87yuazpuCstLSqfDLNd3740+254vEZqdGxOglAGU
# ih2IiF8S0GDwucpLGzt/OLXPFr/d4MWxPuX0L+HB5lA3Y/CJE673dHGQW2DELdqt
# ohtkhp+oWFn1hNDDZ3LP++HEZvA7sI/o/981Sh4kaGayOp6oEiQuGeCXyfrIC9KX
# eew0UlYX/NHVDqr4ykKkqpHtzbUbuo7qovUHPbYKcRGWrrEtBS5SPLFPumqsRtzb
# LgU9HqfRAN36bMsd2qynGyWBVFOM7NMs2lTCGM85Z/Fdzv/8tnYT36Cmbue+IM+6
# kS86j6Ztmx0VIFWbOvNsASPT6yrmYiecJiP6H0TrYXQK5B3jE8s53l+t61ab0Eul
# 7DAxNWX3lAiUlzKs3qZYQEK1LFvgbdTXtBRnHgBdABALK3RPrieIYqPln9sAmg3/
# zJZi4C/c2cWGF6WwK/w1Nzw08pj7jaaZZVBpCeDe+y7oM26QIXxracot7zJ21/TL
# 70biK36YybSUDkjhQPP/uxT0yebLNBKk7g8V98Wna2MsHWwk0sgqpkjIp02TrkVz
# 26tcF2rml2THRSDrwpBa4x9c8rM8Qomiyeh2tEJnsx2LMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWkwghVlAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAHgcXMkylyY+BkAAAAA
# AeAwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIP3+
# 2lS2X3cCtSzFLvsWrDw2DLHIxThawheEvrI5Ehd/MEQGCisGAQQBgjcCAQwxNjA0
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu
# Y29tIDANBgkqhkiG9w0BAQEFAASCAQAD3yTNvDOAXQidDPO8eusqQFtWedaia/YV
# dqJ09Iro1zsW0AwMPxojXp+H1kowACJdxhCg3VFR0nCK9JAe5eJToekdcA5URu8u
# fu2y9jXZzTkNmegPtvPhQTSREx0f6h7BPA0k/JIisXIJxoVD48LKnNUTWCpti/re
# IwtaIZ1kz1wW7VBPhWIjx+cdW1cvTZFfR6SgTHpsuuBYLhdeck3kM4RJiPH+O9RE
# AiUoaf6wyyBLTb3soEnkuSoJ3Gn+FXIRck1IW0G4YXQjv5RxKakHQ5WaYT0h1+Ok
# KaHvtwPCKfUTcf8R/K1+wk5oZzESPh6/keI6jNwfCkMyh4XOZsFaoYIS8TCCEu0G
# CisGAQQBgjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglg
# hkgBZQMEAgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEE
# AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEILFRi8NoAat1dqn0kje9iXLQbI2ps4ax
# HWoEqvtB7A4CAgZg+vccEmMYEzIwMjEwODE2MDUxMjQ5LjA3MVowBIACAfSggdSk
# gdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNV
# BAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABYtD+AvMB
# 5c1JAAAAAAFiMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwMB4XDTIxMDExNDE5MDIyMloXDTIyMDQxMTE5MDIyMlowgc4xCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m
# dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT
# TjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO+GodT2ucL3
# Mr2DQsv2ELNbSvKyBpYdUKtUBWiZmFVy18pG/pucgkrc5i9tu8CY7GpWV/CQNmHG
# 2mVeSHMJvbwCc/AAv7JP3bFCt6Zg75IbVSNOGA1eqLbmQiC6UAfSKXLN3dHtQ5di
# ihb3YmzpNP9K0cVPZfv2MXm+ZVU0RES8cyPkXel7+UEGE+kqdiBNDdb8yBXd8sju
# +90+V4nzYC+ZWW7SFJ2FFZlASpVaHpjv+eGohXlQaSBvmM4Q0xe3LhzQM8ViGz9c
# LeFSKgFfSY7qizL7wUg+eqYvDUyjPX8axEQHmk0th23wWH5p0Wduws43qNIo0OQ0
# mRotBK71nykCAwEAAaOCARswggEXMB0GA1UdDgQWBBTLxEoRYEpDtzp84B5WlZN2
# kP4qazAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBN
# MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0
# cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoG
# CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAtQa3DoXYbW/cXACbcVSFG
# e4gC8GXsFxSHT3JgwFU/NdJOcbkcFTVvTp6vlmTvHm6sIjknRBB0Xi1NBTqPw20u
# 6u/T7Cnc/z0gT6mf9crI0VR9C+R1CtjezYKZEdZZ7fuNQWjsyftNDhQy+Rqnqryt
# 0VoezLalheiinHzZD/4Y4hZYPf0u8TSv1ZfKtdBweWG3QU0Lp/I9SbIoemDG97RU
# LMcPvq2ufhUp3OMiYQGL1WqkykSnqRJsM2IcA4l4dmoPNP6dLg5Dr7NVoYKIMIna
# QVZjSwDMZhWryvfizX0SrzyLgkMPhLMVkfLxQQSQ37NeFk7F1RfeAkNWAh6mCORB
# MIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z
# b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEz
# NjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1
# +n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROO
# fGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEby
# WEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqv
# S2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noq
# CjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVP
# Ik0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpc
# ijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV
# HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo
# 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m
# dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j
# cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCB
# oAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0w
# QAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQA
# YQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+B
# cQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RX
# CCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31a
# PxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E
# 4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517
# IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl
# 5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ
# 3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm7
# 7MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1Qn
# IffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1
# rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrv
# CScc1bN+NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jv
# c29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNT
# IEVTTjozMkJELUUzRDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAmrP6Chrbz0ax7s57n5Pop3VC8gyg
# gYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0B
# AQUFAAIFAOTEcC4wIhgPMjAyMTA4MTYwOTAzNDJaGA8yMDIxMDgxNzA5MDM0Mlow
# dzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5MRwLgIBADAKAgEAAgIQlwIB/zAHAgEA
# AgIQgDAKAgUA5MXBrgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC
# oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAHZrRLqI
# sWj+i621GsBSKHeTpBZi6Cft+9dctjxpOMmtS7vKjrg6NWNN0nVSWVlgEXSqTwwX
# WsuCWrR4mjTUa86PZKz4MiSaX4TEfuXm+SdHfloGhyv/luo+3dPghkuGfi1lJ9zl
# 9aSkbFaQ8MD2hGvFwG4pPMJ9n/+Iju7veJVzMYIDDTCCAwkCAQEwgZMwfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFi0P4C8wHlzUkAAAAAAWIwDQYJ
# YIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkq
# hkiG9w0BCQQxIgQgQ2DhnSPMLUPQurifs7sr4NxnFPBZg0prT/hcr5w2PB4wgfoG
# CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCKqhiV+zwNDrpU7DRB7Mi57xi6GBNY
# sGjgZqq2qVMKMjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# AhMzAAABYtD+AvMB5c1JAAAAAAFiMCIEIKcp8ozmKfJZa/sk+KuKhYctNlLPgQD6
# +ptIVSQTWPJHMA0GCSqGSIb3DQEBCwUABIIBAGXjhx7uK4cX55FwPgVQSKmtWaw7
# 0zUpsW0gdlq0GiDgi+Od8bUovAByw+7OmvdcaCGcI5jrfRMzoQTmS3EkyJ56Ld69
# IqY1hFuxV7mjqAPaSpk5p/jM5lS1gp9eFPEnPwCA2KC4oQ/+8vCp6OUsBOgnrMaR
# aOp4HigOdbrtVs9ikblstM3alqec9qJq1onMvfA0dmxLkgsqqeHRhzH216mW5c0w
# 2lwfeF8qy4VkphLrv38VvtSWyiczsiiMyrnP+9ysRWgTwLr2vJmBY7frUtFcjW+S
# +hnY+RlwcQWWaBXTEF68rIMAKuKOh6w9sWGPhMEC1cy+7jcI7LKggvztyec=
# SIG # End signature block