Framework/Listeners/UserReports/WriteSummaryFile.ps1

Set-StrictMode -Version Latest 
class WriteSummaryFile: FileOutputBase
{   
    hidden static [WriteSummaryFile] $Instance = $null;

    static [WriteSummaryFile] GetInstance()
    {
        if ( $null -eq  [WriteSummaryFile]::Instance)
        {
            [WriteSummaryFile]::Instance = [WriteSummaryFile]::new();
        }
    
        return [WriteSummaryFile]::Instance
    }

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

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

        $this.RegisterEvent([SVTEvent]::CommandStarted, {
            $currentInstance = [WriteSummaryFile]::GetInstance();
            try 
            {
                $currentInstance.SetFilePath($Event.SourceArgs.SubscriptionContext, ("SecurityReport-" + $currentInstance.RunIdentifier + ".csv"));
                [FileOutputBase]::CSVFilePath = $currentInstance.FilePath
            }
            catch 
            {
                $currentInstance.PublishException($_);
            }
        });
        
        $this.RegisterEvent([SVTEvent]::CommandCompleted, {
            $currentInstance = [WriteSummaryFile]::GetInstance();
            
            if(($Event.SourceArgs.ControlResults|Where-Object{$_.VerificationResult -ne[VerificationResult]::NotScanned}|Measure-Object).Count -gt 0)
            {
                $currentInstance.SetFilePath($Event.SourceArgs[0].SubscriptionContext, ("SecurityReport-" + $currentInstance.RunIdentifier + ".csv"));
            }
            else
            {
                # While running GAI -InfoType AttestationInfo, no controls are evaluated. So the value of VerificationResult is by default NotScanned for all controls.
                # In that case the csv file should be renamed to AttestationReport.
                $currentInstance.SetFilePath($Event.SourceArgs[0].SubscriptionContext, ("AttestationReport-" + $currentInstance.RunIdentifier + ".csv"));
            }

            # Export CSV Report
            if(-not [PartialScanManager]::IsCsvUpdatedAtCheckpoint)
            {
                try 
                {
                    $currentInstance.WriteToCSV($Event.SourceArgs);
                    $currentInstance.FilePath = "";
                }
                catch 
                {
                    $currentInstance.PublishException($_);
                }
            }

        });

        $this.RegisterEvent([AzSKRootEvent]::UnsupportedResources, {
            $currentInstance = [WriteSummaryFile]::GetInstance();
            try 
            {
                $message = $Event.SourceArgs.Messages | Select-Object -First 1
                if($message -and $message.DataObject)
                {
                    $filePath = $currentInstance.CalculateFilePath($Event.SourceArgs.SubscriptionContext, [FileOutputBase]::ETCFolderPath, ("UnsupportedResources-" + $currentInstance.RunIdentifier + ".csv.LOG"));
                    $message.DataObject | Export-Csv $filePath -NoTypeInformation
                }
            }
            catch 
            {
                $currentInstance.PublishException($_);
            }
        });

        $this.RegisterEvent([AzSKRootEvent]::WriteCSV, {
            $currentInstance = [WriteSummaryFile]::GetInstance();
            try 
            {
                $fileName = 'Control Details';
                $folderPath = '';
                $fileExtension = 'csv';

                $message = $Event.SourceArgs.Messages | Select-Object -First 1
                if($message -and $message.DataObject)
                {
                    if(-not [string]::IsNullOrEmpty($message.DataObject.FileName))
                    {
                        $fileName = $message.DataObject.FileName
                    }
                    if(-not [string]::IsNullOrEmpty($message.DataObject.FolderPath))
                    {
                        $folderPath = $message.DataObject.FolderPath
                    }
                    if(-not [string]::IsNullOrEmpty($message.DataObject.FileExtension))
                    {
                        $fileExtension = $message.DataObject.FileExtension
                    }
                        
                    $filePath = $currentInstance.CalculateFilePath($Event.SourceArgs.SubscriptionContext, $folderPath, ($fileName + "." + $fileExtension));
                    $message.DataObject.MessageData | Export-Csv $filePath -NoTypeInformation
                }
            }
            catch 
            {
                $currentInstance.PublishException($_);
            }
        });
        # Event for Writing File Detailed Log
        $this.RegisterEvent([AzSKRootEvent]::WriteExcludedResources,{
            $currentInstance = [WriteSummaryFile]::GetInstance();
            try 
            {
                $message = $Event.SourceArgs.Messages | Select-Object -First 1
                $printMessage="";
                if($message -and $message.DataObject)
                {
                    $filePath = $currentInstance.CalculateFilePath($Event.SourceArgs.SubscriptionContext, [FileOutputBase]::ETCFolderPath, ("ExcludedResources-" + $currentInstance.RunIdentifier + ".txt.LOG"));
                    
                    $ExcludedType = $message.DataObject.ExcludedResourceType
                    if($ExcludedType -eq 'All')
                    {
                        $ExcludedType = 'None'
                    }
                    
                    $ExcludeResourceName = $message.DataObject.ExcludeResourceNames
                    $ExcludedResources = $message.DataObject.ExcludedResources  

                    $ExcludedTypeResources = $ExcludedResources | Select-Object -ExpandProperty ResourceTypeMapping |Where-Object {$_.ResourceTypeName -in $ExcludedType}
                    $ExplicitlyExcludedResource =$ExcludedResources| Where-Object {$_.ResourceName -in $ExcludeResourceName}
                    
                    $printMessage += "`r`nNumber of resources excluded: $(($ExcludedResources | Measure-Object).Count | Out-String)"
                    $printMessage += "`r`n`nDistribution of resources being excluded is as follows:"+"`r`n"+[Constants]::SingleDashLine
                    $printMessage += "`r`nNumber of resources excluded due to excluding resource type '$ExcludedType': $(($ExcludedTypeResources | Measure-Object).Count | Out-String)"
                    $printMessage += "`r`nNumber of resources excluded explicitly: $(($ExplicitlyExcludedResource| Measure-Object).Count|Out-String)"
                    $printMessage += "`r`n"+[Constants]::SingleDashLine +"`r`n"+[Constants]::DoubleDashLine+"`r`nFollowing are the list of resource groups and resources being excluded" 
                    $printMessage += "`r`n"+[Constants]::SingleDashLine+"`r`nResource groups excluded:"
                    $detailedList += "`r`n-------------------------"

                    $detailedList += "`r`nResources excluded:"
                    $detailedList += "`r`n-------------------------"
                    if(($ExcludedResources | Measure-Object).Count -gt 0)
                    {
                        $detailedList += "`r`n$($ExcludedResources| Sort-Object -Property "ResourceGroupName"|Select-Object -Property ResourceName,ResourceGroupName -ExpandProperty ResourceTypeMapping| Select-Object -Property ResourceName,ResourceGroupName,ResourceTypeName,ResourceType|Format-Table | Out-String)"
                    }
                    else 
                    {
                        $detailedList += "`r`n N/A"                        
                    }
                    $printMessage += $detailedList
                    
                    Add-Content -Value $printMessage -Path $filePath 
                                                
                }
                
            }
            catch 
            {
                $currentInstance.PublishException($_);
            }
        });
    }
    
    

   [void] WriteToCSV([SVTEventContext[]] $arguments)
    {
        if ([string]::IsNullOrEmpty($this.FilePath)) {
            return;
        }
        [CsvOutputItem[]] $csvItems = @();
        $anyAttestedControls = $null -ne ($arguments | 
            Where-Object { 
                $null -ne ($_.ControlResults | Where-Object { $_.AttestationStatus -ne [AttestationStatus]::None } | Select-Object -First 1) 
            } | Select-Object -First 1);

        #$anyFixableControls = $null -ne ($arguments | Where-Object { $_.ControlItem.FixControl } | Select-Object -First 1);
        #Validate if preview baseline control flag is passed to mark csv
        #Commented below code as don't have any preview naseline controls
        #$UsePreviewBaselineControls = $false
        #if($this.InvocationContext.BoundParameters['UsePreviewBaselineControls'] -eq $True)
        #{
        # [PartialScanManager] $partialScanMngr = [PartialScanManager]::GetInstance();
        # $previewBaselineControlsDetails = $partialScanMngr.GetPreviewBaselineControlDetails()
        # if($previewBaselineControlsDetails)
        # {
        # $UsePreviewBaselineControls =$True
        # }
        #}
        $arguments | ForEach-Object {
            $item = $_
            if ($item -and $item.ControlResults) {
                

                $item.ControlResults | ForEach-Object{
                    $csvItem = [CsvOutputItem]@{
                        ControlID = $item.ControlItem.ControlID;
                        ControlSeverity = $item.ControlItem.ControlSeverity;
                        Description = $item.ControlItem.Description;
                        FeatureName = $item.FeatureName;
                        ChildResourceName = $_.ChildResourceName;
                        Recommendation = $item.ControlItem.Recommendation;    
                        Rationale = $item.ControlItem.Rationale
                    };
                    if($_.VerificationResult -ne [VerificationResult]::NotScanned)
                    {
                        $csvItem.Status = $_.VerificationResult.ToString();
                    }
                    if($this.InvocationContext.BoundParameters['IncludeUserComments'] -eq $True)
                    {
                      $csvItem.UserComments=$_.UserComments;    
                    }
                    #if($anyFixableControls)
                    #{
                    if($item.ControlItem.FixControl)
                    {
                        $csvItem.SupportsAutoFix = "Yes";
                    }
                    else
                    {
                        $csvItem.SupportsAutoFix = "No";
                    }
                    #}
                    
                    if($item.ControlItem.IsBaselineControl)
                    {
                        $csvItem.IsBaselineControl = "Yes";
                    }
                    else
                    {
                        $csvItem.IsBaselineControl = "No";
                    }
                    #Commented below code as don't have any preview baseline controls
                    #if($item.ControlItem.IsPreviewBaselineControl)
                    #{
                    # $csvItem.IsPreviewBaselineControl = "Yes";
                    #}
                    #else
                    #{
                    # $csvItem.IsPreviewBaselineControl = "No";
                    #}

                    if($anyAttestedControls)
                    {
                        $csvItem.ActualStatus = $_.ActualVerificationResult.ToString();
                    }

                    if($item.IsResource())
                    {
                        $csvItem.ResourceName = $item.ResourceContext.ResourceName;
                        $csvItem.ResourceGroupName = $item.ResourceContext.ResourceGroupName;
                        try {
                            if($item.ResourceContext.ResourceDetails -ne $null -and ([Helpers]::CheckMember($item.ResourceContext.ResourceDetails,"ResourceLink")))
                            {
                                $csvItem.ResourceLink = $item.ResourceContext.ResourceDetails.ResourceLink;                            
                            }
                        }
                        catch {
                            $_
                        }
                        $csvItem.ResourceId = $item.ResourceContext.ResourceId;
                        $csvItem.DetailedLogFile = "/$([Helpers]::SanitizeFolderName($item.ResourceContext.ResourceGroupName))/$($item.FeatureName).LOG";

                        
                    }
                    else
                    {
                        $csvItem.ResourceId = $item.SubscriptionContext.scope;
                        $csvItem.DetailedLogFile = "/$([Helpers]::SanitizeFolderName($item.SubscriptionContext.SubscriptionName))/$($item.FeatureName).LOG"
                        
                    }

                    if($_.AttestationStatus -ne [AttestationStatus]::None)
                    {
                        $csvItem.AttestedSubStatus = $_.AttestationStatus.ToString();
                        if($null -ne $_.StateManagement -and $null -ne $_.StateManagement.AttestedStateData)
                        {
                            $csvItem.AttesterJustification = $_.StateManagement.AttestedStateData.Justification
                            $csvItem.AttestedBy =  $_.StateManagement.AttestedStateData.AttestedBy
                            if(![string]::IsNullOrWhiteSpace($_.StateManagement.AttestedStateData.ExpiryDate))
                            {
                                $csvItem.AttestationExpiryDate =  $_.StateManagement.AttestedStateData.ExpiryDate
                            }
                            if(![string]::IsNullOrWhiteSpace($_.StateManagement.AttestedStateData.AttestedDate))
                            {
                                $csvItem.AttestedOn=  $_.StateManagement.AttestedStateData.AttestedDate
                            }
                        }
                    }
                    if($_.IsControlInGrace -eq $true)
                    {
                        $csvItem.IsControlInGrace = "Yes"
                    }
                    else 
                    {
                        $csvItem.IsControlInGrace = "No"
                    }                    
                    $csvItems += $csvItem;
                }                                
            }
        } 

        if ($csvItems.Count -gt 0) {
            # Remove Null properties
            $nonNullProps = @();
            
            [CsvOutputItem].GetMembers() | Where-Object { $_.MemberType -eq [System.Reflection.MemberTypes]::Property } | ForEach-Object {
                $propName = $_.Name;
                if(($csvItems | Where-object { -not [string]::IsNullOrWhiteSpace($_.$propName) } | Measure-object).Count -ne 0)
                {
                    $nonNullProps += $propName;
                }
            };
            if($this.InvocationContext.BoundParameters['IncludeUserComments'] -eq $true -and -not ([Helpers]::CheckMember($nonNullProps, "UserComments")))
            {
              $nonNullProps += "UserComments";
            }
            #larg file was stucking becaes all are dumping in one slot
            #$csvItems | Select-Object -Property $nonNullProps | Export-Csv $this.FilePath -NoTypeInformation
            ($csvItems | Select-Object -Property $nonNullProps) | Group-Object -Property FeatureName | Foreach-Object {$_.Group | Export-Csv -Path $this.FilePath -append -NoTypeInformation}
        }
    }    

}



# SIG # Begin signature block
# MIIjmAYJKoZIhvcNAQcCoIIjiTCCI4UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCKfqvLgzTWksC1
# C7lbMr4Ep3pvlmDxrwhXJ9YQTbQVr6CCDYUwggYDMIID66ADAgECAhMzAAABiK9S
# 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0
# t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs
# 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd
# vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv
# V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W
# MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q
# qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X
# 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P
# mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM
# i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT
# GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz
# LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM
# SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa
# 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV
# Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+
# r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK
# 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
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA
# AYgwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIP+X
# j0BXUmpMMFkUU69WHXF4srRZWXgCLPhnP8B781ZGMEQGCisGAQQBgjcCAQwxNjA0
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu
# Y29tIDANBgkqhkiG9w0BAQEFAASCAQAfpcH8KkWxImcv2sx1FiPW3jYM7HnqfjW0
# Ld8Eto6R8hL2C8tePSRoUvAod+ea4JzdBuem+4J6V2lvI4JbFoV6g9ylHPIbRI2o
# 3YpRtaUj5f5rQ8BhVg8/JwKd5LOKejnwoDGSZSZEcBFXrW1i50iGwUYcOJUm/04Q
# /OUiDM23K4o2hSzRf8WJ2eHulsi5uox4tMbVfsjztZp6eZoB8t6Lef93RdOhOiCE
# VQHRTC5RFVlB6r0YNFyuM4i2G6eMX9+fRLSTX5VJP/c8qqHdZVcEcAIsGDRwEmWB
# vpmDuFNIDw7wk+MMZEkwMRka20QfE2GMgBf6RcZdZMXrd/YaJHRDoYIS8TCCEu0G
# CisGAQQBgjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglg
# hkgBZQMEAgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEE
# AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEICwFIyRTwfs9kQTIy2HA8c1Cy11N3rI7
# KqacEbqrakc7AgZf25gXMEQYEzIwMjEwMTE1MTEwODQ5LjI3OFowBIACAfSggdSk
# gdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNV
# BAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjpEOURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABLS5NQcpj
# ZTOgAAAAAAEtMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwMB4XDTE5MTIxOTAxMTUwNFoXDTIxMDMxNzAxMTUwNFowgc4xCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m
# dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT
# TjpEOURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKlhjfR1STqY
# RTS3s0i4jIcSMV+G4N0oYgwlQK+pl4DVMFmr1iTZHFLj3Tt7V6F+M/BXx0h9i0uu
# 1yBnqCqNOkuJERTbVnM4u3JvRxzsQfCjBfqD/CNwoMNekoylIBzxP50Skjp1pPsn
# QBKHaCP8tguvYVzoTQ54q2VpYEP/+OYTQeEPqWFi8WggvsckuercUGkhYWM8DV/4
# JU7N/rbDrtamYbe8LtkViTQYbigUSCAor9DhtAZvq8A0A73XFH2df2wDlLtAnKCc
# sVvXSmZ35bAqneN4uEQVy8NQdReGI1tI6UxoC7XnjGvK4McDdKhavNJ7DAnSP5+G
# /DTkdWD+lN8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTZbGR8QgEh+E4Oiv8vQ740
# 8p2GzTAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBN
# MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0
# cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoG
# CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB9awNk906recBuoO7Ezq7B
# 8UGu9EoFXiL8ac0bbsZDBY9z/3p8atVZRCxHN43a3WGbCMZoKYxSBH6UCkcDcwXI
# fNKEbVMznF1mjpQEGbqhR+rPNqHXZotSV+vn85AxmefAM3bcLt+WNBpEuOZZ4kPZ
# VcFtMo4YyQjxoNRPiwmp+B0HkhQs/l/VIg0XJY6k5FRKE/JFEcVY4256NdqUZ+3j
# ou3b4OAktE2urr4V6VRw1fffOlxZb8MyvE5mqvTVJOStVxCuhuqg1rIe8la1gZ5i
# iuIyWeftONfMw0nSZchGLigDeInw6XfwwgFnC5Ql8Pbf2jOxCUluAYbzykI+MnBi
# 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
# IEVTTjpEOURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAn85fx36He7F0vgmyUlz2w82l0LGg
# gYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0B
# AQUFAAIFAOOrqCwwIhgPMjAyMTAxMTUwOTM1NDBaGA8yMDIxMDExNjA5MzU0MFow
# dzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA46uoLAIBADAKAgEAAgIcqgIB/zAHAgEA
# AgIR0TAKAgUA46z5rAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC
# oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAF8y18lW
# KfpMdNkXD+qzFg4UHa6OAhrBRjEJjqhJF0Awqm+cAvEKifpGelhIDAlI9FmwIzme
# lNG3nEii5P2xha2WWNju1AuTJkB7hDoiRZQwQl3tN7M+eVOR7tm0dDB596yPInaH
# tDY+4OHguKeYzHUbks0J4z5o2mD+k0E3m+FYMYIDDTCCAwkCAQEwgZMwfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEtLk1BymNlM6AAAAAAAS0wDQYJ
# YIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkq
# hkiG9w0BCQQxIgQggNtf/6+alYOj+QJeioLtHwjFaMzJInd50TApzc0t7T0wgfoG
# CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCO8Vpycn0gB4/ilRAPPDbS+Cmbqj/u
# C011moc5oeGDwTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# AhMzAAABLS5NQcpjZTOgAAAAAAEtMCIEIKztYQZBCBJjfNrfVGqnkOk2CE8IMdZa
# skoBJVUTNVi9MA0GCSqGSIb3DQEBCwUABIIBAJSaJUAxj+i19aXZyLu6lxlc7Y1e
# 4/cI6ZFYkYyQM2uro/ELROxfWyF2Vjq4jVer/jJcOKu5r8ParVtGMWAxpB9DEZLE
# FOgjkRSq89lIXAF3SfoxSnuGOHgi4aFGsh/3ucc5qjqu4b0YFX5T/8vInxZVhIDs
# gfSlsgRic2EpJ9K7jnTs4XRSOsHo71ASPS4W0sBLDDA9HOVsCjgz1ibtEvK9dmm4
# Sf8wjQ6QZnSe5mr+aFaeW4N69V7LZaTmKbjZXXpTEntmkbvOF/xaUcupVfFee7rt
# gLoJUVF5jqnkRwQfiEg9maJaAJ7EU5M43IpRcapx8GoDHVl1+5f41r5hSc8=
# SIG # End signature block