Framework/Abstracts/CommandBase.ps1
<#
.Description Base class for all command classes. Provides functionality to fire events/operations at command levels like command started, command completed and perform operation like generate run-identifier, invoke auto module update, open log folder at the end of commmand execution etc #> using namespace System.Management.Automation Set-StrictMode -Version Latest class CommandBase: AzSKRoot { #Region: Properties [string[]] $FilterTags = @(); [bool] $DoNotOpenOutputFolder = $false; [bool] $Force = $false #EndRegion #Region: Constructor CommandBase([string] $organizationName, [InvocationInfo] $invocationContext): Base($organizationName) { [Helpers]::AbstractClass($this, [CommandBase]); if (-not $invocationContext) { throw [System.ArgumentException] ("The argument 'invocationContext' is null. Pass the `$PSCmdlet.MyInvocation from PowerShell command."); } $this.InvocationContext = $invocationContext; #Validate if privacy is accepted by user #Ensure that AzSKSettings statics are setup at this point (before calling Privacy notice) [AzSKSettings]::InitContexts($this.OrganizationContext, $this.InvocationContext); [PrivacyNotice]::ValidatePrivacyAcceptance() #Initialize common parameter sets if($null -ne $this.InvocationContext.BoundParameters["DoNotOpenOutputFolder"]) { $this.DoNotOpenOutputFolder = $this.InvocationContext.BoundParameters["DoNotOpenOutputFolder"]; } if($null -ne $this.InvocationContext.BoundParameters["Force"]) { $this.Force = $this.InvocationContext.BoundParameters["Force"]; } #Check multiple AzSK* module should not be loaded in same session $this.CheckMultipleAzSKModuleLoaded(); } #EndRegion #Region: Command level listerner events [void] CommandStarted() { $this.PublishAzSKRootEvent([AzSKRootEvent]::CommandStarted, $this.CheckModuleVersion()); } [void] PostCommandStartedAction() { } [void] CommandError([System.Management.Automation.ErrorRecord] $exception) { [AzSKRootEventArgument] $arguments = $this.CreateRootEventArgumentObject(); $arguments.ExceptionMessage = $exception; $this.PublishEvent([AzSKRootEvent]::CommandError, $arguments); } [void] CommandCompleted([MessageData[]] $messages) { $this.PublishAzSKRootEvent([AzSKRootEvent]::CommandCompleted, $messages); } [void] CommandProgress([int] $totalItems, [int] $currentItem) { $this.CommandProgress($totalItems, $currentItem, 1); } [void] CommandProgress([int] $totalItems, [int] $currentItem, [int] $granularity) { if ($totalItems -gt 0) { # $granularity indicates the number of items after which percentage progress will be printed # Set the max granularity to total items if ($granularity -gt $totalItems) { $granularity = $totalItems; } # Conditions for posting progress: 0%, 100% and based on granularity if ($currentItem -eq 0 -or $currentItem -eq $totalItems -or (($currentItem % $granularity) -eq 0)) { $this.PublishCustomMessage("$([int](($currentItem / $totalItems) * 100))% Completed"); } } } # Dummy function declaration to define the function signature [void] PostCommandCompletedAction([SVTEventContext[]] $arguments) { } [void] PostCommandCompletedAction([MessageData[]] $messages) { } #EndRegion #Region: Helper function to invoke function based on method name. # This is method called from command(GRS/GSS etc) files and resposinble for printing command start/end messages using listeners [string] InvokeFunction([PSMethod] $methodToCall) { return $this.InvokeFunction($methodToCall, @()); } [string] InvokeFunction([PSMethod] $methodToCall, [System.Object[]] $arguments) { if (-not $methodToCall) { throw [System.ArgumentException] ("The argument 'methodToCall' is null. Pass the reference of method to call. e.g.: [YourClass]::new().YourMethod"); } #if attestation then rescan the controls if ($null -eq $arguments) { $folderPath = $this.GetOutputFolderPath(); $methodResult = $methodToCall.Invoke(@()); #$this.CommandCompleted($methodResult); this will update CSV but issue is there will be duplicate entries if(-not $this.DoNotOpenOutputFolder) { if (Test-Path $folderPath) { Invoke-Item -Path $folderPath; } } } else { # Publish runidentifier(YYYYMMDD_HHMMSS) used by all listener as identifier for scan,creating log folder $this.PublishRunIdentifier($this.InvocationContext); # <TODO Framework: Move command time calculation methods to AIOrgTelmetry Listener> [AIOrgTelemetryHelper]::TrackCommandExecution("Command Started", @{"RunIdentifier" = $this.RunIdentifier}, @{}, $this.InvocationContext); $sw = [System.Diagnostics.Stopwatch]::StartNew(); # Publish command init events $this.CommandStarted(); $this.PostCommandStartedAction(); # Invoke method with arguments $methodResult = @(); try { $methodResult = $methodToCall.Invoke($arguments); } catch { # Unwrapping the first layer of exception which is added by Invoke function [AIOrgTelemetryHelper]::TrackCommandExecution("Command Errored", @{"RunIdentifier" = $this.RunIdentifier; "ErrorRecord"= $_.Exception.InnerException.ErrorRecord}, @{"TimeTakenInMs" = $sw.ElapsedMilliseconds; "SuccessCount" = 0}, $this.InvocationContext); $this.CommandError($_.Exception.InnerException.ErrorRecord); } $folderPath = $this.GetOutputFolderPath(); #the next two bug log classes have been called here as we need all the control results at one place for #dumping them in json file and auto closing them(to minimize api calls and auto close them in batches) #if bug logging is enabled and path is valid, create the JSON file for bugs #AutoBugLog and AutoCloseBug Conditions # $isPartialScan=$false # $bugsClosed=$null if($this.InvocationContext.BoundParameters["AutoBugLog"] -or $this.InvocationContext.BoundParameters["AutoCloseBugs"]){ $this.sendBugInfo($methodResult,$folderPath) #sendBugInfo } #SARIF Logs generation.Note if upc with Auto Bug Log we have controls available in ControlResultsWithBugSummary static variable. if($this.InvocationContext.BoundParameters["GenerateSarifLogs"]){ $sarifMethodResults=$methodResult if(!$sarifMethodResults){ if(([PartialScanManager]::ControlResultsWithBugSummary| Measure-Object).Count -gt 0){ $sarifMethodResults=[PartialScanManager]::ControlResultsWithBugSummary } else{ $sarifMethodResults=[PartialScanManager]::ControlResultsWithSARIFSummary } } if($sarifMethodResults){ [SARIFLogsGenerator]::new($sarifMethodResults,$folderPath,$this.RunIdentifier) } [PartialScanManager]::ControlResultsWithSARIFSummary=@() } # Publish command complete events $this.CommandCompleted($methodResult); [AIOrgTelemetryHelper]::TrackCommandExecution("Command Completed", @{"RunIdentifier" = $this.RunIdentifier}, @{"TimeTakenInMs" = $sw.ElapsedMilliseconds; "SuccessCount" = 1}, $this.InvocationContext) $this.PostCommandCompletedAction($methodResult); # <TODO Framework: Move PDF generation method based on listener> #Generate PDF report $GeneratePDFReport = $this.InvocationContext.BoundParameters["GeneratePDF"]; try { if (-not [string]::IsNullOrEmpty($folderpath)) { switch ($GeneratePDFReport) { None { # Do nothing } Landscape { [AzSKPDFExtension]::GeneratePDF($folderpath, $this.OrganizationContext, $this.InvocationContext, $true); } Portrait { [AzSKPDFExtension]::GeneratePDF($folderpath, $this.OrganizationContext, $this.InvocationContext, $false); } } } } catch { # Unwrapping the first layer of exception which is added by Invoke function $this.CommandError($_); } # $AttestControlParamFound = $this.InvocationContext.BoundParameters["AttestControls"]; if($null -eq $AttestControlParamFound) { #If controls are attested then open folder when rescan of attested controls is complete $controlAttested = $false if( ([FeatureFlightingManager]::GetFeatureStatus("EnableScanAfterAttestation","*"))) { #Global variable "AttestationValue" is set to true when one or more controls are attested in current scan #Ignore if variable AttestationValue is not found if (Get-Variable AttestationValue -Scope Global -ErrorAction Ignore){ if ( $Global:AttestationValue){ $controlAttested = $true } } } if ( !$controlAttested){ if((-not $this.DoNotOpenOutputFolder) -and (-not [string]::IsNullOrEmpty($folderPath))) { try { Invoke-Item -Path $folderPath; } catch { #ignore if any exception occurs } } } } } return $folderPath; } #EndRegion # Function to get output log folder from WriteFolder listener [string] GetOutputFolderPath() { return [WriteFolderPath]::GetInstance().FolderPath; } #Sends bug information to Json and CSV. In non upc scan closes bugs and sends info to LA as well. [void] sendBugInfo([SVTEventContext[]] $methodResult, [string] $folderPath){ [SVTEventContext[]] $bugsClosed=$null if ($this.InvocationContext.BoundParameters["UsePartialCommits"]) { $methodResult = [PartialScanManager]::ControlResultsWithBugSummary $bugsClosed=[PartialScanManager]::ControlResultsWithClosedBugSummary } else { $AutoClose=[AutoCloseBugManager]::new($this.OrganizationContext.OrganizationName); $AutoClose.AutoCloseBug($methodResult) $bugsClosed=[AutoCloseBugManager]::ClosedBugs if($bugsClosed){ $laInstance= [LogAnalyticsOutput]::Instance $laInstance.WriteControlResult($bugsClosed) } } #If condition publishes information about New, Active and Closed bugs if($this.InvocationContext.BoundParameters["AutoBugLog"]){ if([BugLogPathManager]::GetIsPathValid()){ [PublishToJSONAndCSV]::new($methodResult,$folderPath,$bugsClosed) } } #condition publishes only closed bugs. $null is passed instead of $methodResult to avoid performance slow down in PublishToJSONAndCSV else{ if($bugsClosed){ [PublishToJSONAndCSV]::new($null,$folderPath,$bugsClosed) } } } # <TODO Framework: Move to module helper class> # Function to validate module version based on Org policy and showcase warning for update or block commands if version is less than last two minor version [void] CheckModuleVersion() { $serverVersion = [System.Version] ([ConfigurationManager]::GetAzSKConfigData().GetLatestAzSKVersion($this.GetModuleName())); $currentModuleVersion = [System.Version] $this.GetCurrentModuleVersion() if($currentModuleVersion -ne "0.0.0.0" -and $currentModuleVersion -ne "1.0.0.0" -and $serverVersion -gt $currentModuleVersion) { $this.RunningLatestPSModule = $false; $this.InvokeAutoUpdate() $this.PublishCustomMessage(([Constants]::VersionCheckMessage -f $serverVersion), [MessageType]::Warning); $this.PublishCustomMessage(([ConfigurationManager]::GetAzSKConfigData().InstallationCommand + "`r`n"), [MessageType]::Update); $this.PublishCustomMessage([Constants]::VersionWarningMessage, [MessageType]::Warning); $serverVersions = @() [ConfigurationManager]::GetAzSKConfigData().GetAzSKVersionList($this.GetModuleName()) | ForEach-Object { #Take major and minor version and ignore build version for comparision $serverVersions+= [System.Version] ("$($_.Major)" +"." + "$($_.Minor)") } $serverVersions = $serverVersions | Select-Object -Unique $latestVersionList = $serverVersions | Where-Object {$_ -gt $currentModuleVersion} if(($latestVersionList | Measure-Object).Count -gt [ConfigurationManager]::GetAzSKConfigData().BackwardCompatibleVersionCount) { throw ([SuppressedException]::new(("Your version of $([Constants]::AzSKModuleName) is too old. Please update now!"),[SuppressedExceptionType]::Generic)) } } $psGalleryVersion = [System.Version] ([ConfigurationManager]::GetAzSKConfigData().GetAzSKLatestPSGalleryVersion($this.GetModuleName())); if($psGalleryVersion -ne $serverVersion) { $serverVersions = @() [ConfigurationManager]::GetAzSKConfigData().GetAzSKVersionList($this.GetModuleName()) | ForEach-Object { #Take major and minor version and ignore build version for comparision $serverVersions+= [System.Version] ("$($_.Major)" +"." + "$($_.Minor)") } $serverVersions = $serverVersions | Select-Object -Unique $latestVersionAvailableFromGallery = $serverVersions | Where-Object {$_ -gt $serverVersion} if(($latestVersionAvailableFromGallery | Measure-Object).Count -gt [ConfigurationManager]::GetAzSKConfigData().BackwardCompatibleVersionCount) { $this.PublishCustomMessage("Your Org AzSK.ADO version [$serverVersion] is too old. It must be updated to latest available version [$psGalleryVersion].",[MessageType]::Error); } } #Validate if detailed scan results is required in control evaluation $this.CheckDetailedScanStatus(); } # <TODO Framework: Move to module helper class> # Funtion to execute module auto update flow based on switch [void] InvokeAutoUpdate() { $AutoUpdateSwitch= [ConfigurationManager]::GetAzSKSettings().AutoUpdateSwitch; $AutoUpdateCommand = [ConfigurationManager]::GetAzSKSettings().AutoUpdateCommand; if($AutoUpdateSwitch -ne [AutoUpdate]::On) { if($AutoUpdateSwitch -eq [AutoUpdate]::NotSet) { $AutoUpdateMsg = [Constants]::AutoUpdateMessage Write-Host $AutoUpdateMsg -ForegroundColor Yellow } return; } #Step 1: Get the list of active running powershell prcesses including the current running PS Session $PSProcesses = Get-Process | Where-Object { ($_.Name -eq 'powershell' -or $_.Name -eq 'powershell_ise' -or $_.Name -eq 'powershelltoolsprocesshost')} $userChoice = "" if(($PSProcesses | Measure-Object).Count -ge 1) { Write-Host([Constants]::ModuleAutoUpdateAvailableMsg) -ForegroundColor Cyan; } #User choice that captures the decision to close the active PS Sessions $secondUserChoice ="" $InvalidOption = $true; while($InvalidOption) { if([string]::IsNullOrWhiteSpace($userChoice) -or ($userChoice.Trim() -ne 'y' -and $userChoice.Trim() -ne 'n')) { $userChoice = Read-Host "Continue (Y/N)" if([string]::IsNullOrWhiteSpace($userChoice) -or ($userChoice.Trim() -ne 'y' -and $userChoice.Trim() -ne 'n')) { Write-Host "Enter the valid option." -ForegroundColor Yellow } continue; } elseif($userChoice.Trim() -eq 'n') { $InvalidOption = $false; } elseif($userChoice.Trim() -eq 'y') { #Get the number of PS active sessions $PSProcesses = Get-Process | Where-Object { ($_.Name -eq 'powershell' -or $_.Name -eq 'powershell_ise' -or $_.Name -eq 'powershelltoolsprocesshost') -and $_.Id -ne $PID} if(($PSProcesses | Measure-Object).Count -gt 0) { Write-Host "`nThe following other PS sessions are still active. Please save your work and close them. You can also use Task Manager to close these sessions." -ForegroundColor Yellow Write-Host ($PSProcesses | Select-Object Id, ProcessName, Path | Out-String) $secondUserChoice = Read-Host "Continue (Y/N)" } elseif(($PSProcesses | Measure-Object).Count -eq 0) { Write-Host "`nThe current PS session will be closed now. Have you saved your work?" -ForegroundColor Yellow $secondUserChoice = Read-Host "Continue (Y/N)" } if(-not [string]::IsNullOrWhiteSpace($secondUserChoice) -and ` (($PSProcesses | Measure-Object).Count -eq 0 -and $secondUserChoice.Trim() -eq 'y') -or ` $secondUserChoice.Trim() -eq 'n') { $InvalidOption = $false; } } } #Check if the first user want to continue with auto-update using userChoice field and then check if user still wants to continue with auto-update after finding the active PS sessions. #In either case it is no it would exit the auto-update process if($userChoice.Trim() -eq "n" -or $secondUserChoice.Trim() -eq 'n') { Write-Host "Exiting auto-update workflow. To disable auto-update permanently, run the command below:" -ForegroundColor Yellow Write-Host "Set-AzSKADOPolicySettings -AutoUpdate Off`n" -ForegroundColor Green return } $AzSKTemp = Join-Path $([Constants]::AzSKAppFolderPath) "Temp"; try { $fileName = "au_" + $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss") + ".ps1"; $autoUpdateContent = [ConfigurationHelper]::LoadOfflineConfigFile("ModuleAutoUpdate.ps1"); if(-not (Test-Path -Path $AzSKTemp)) { New-Item -Path $AzSKTemp -ItemType Directory -Force } Remove-Item -Path (Join-Path $AzSKTemp "au_*") -Force -Recurse -ErrorAction SilentlyContinue $autoUpdateContent = $autoUpdateContent.Replace("##installurl##",$AutoUpdateCommand); $autoUpdateContent | Out-File (Join-Path $AzSKTemp $fileName) -Force Start-Process -WindowStyle Normal -FilePath "powershell.exe" -ArgumentList (Join-Path $AzSKTemp $fileName) } catch { $this.CommandError($_.Exception.InnerException.ErrorRecord); } } [void] CheckMultipleAzSKModuleLoaded(){ $loadedAzSKModules= Get-Module | Where-Object { $_.Name -like "AzSK*"}; if($env:AzSKSkipMultiModuleCheck -ne $true -and $null -ne $loadedAzSKModules -and ($loadedAzSKModules| Measure-Object).Count -gt 1){ throw [SuppressedException]::new("ERROR: Multiple AzSK modules loaded in same session, this will lead to issues when running AzSK cmdlets.",[SuppressedExceptionType]::Generic) } } [void] CheckDetailedScanStatus(){ if(-not([string]::IsNullOrEmpty($this.InvocationContext.BoundParameters['ControlIds'])) -or -not([string]::IsNullOrEmpty($this.InvocationContext.BoundParameters['DetailedScan'])) -or -not( [string]::IsNullOrEmpty($this.InvocationContext.BoundParameters['ControlsToAttest'])) ) { [AzSKRoot]::IsDetailedScanRequired = $true } else { [AzSKRoot]::IsDetailedScanRequired = $false } } } # SIG # Begin signature block # MIIjmAYJKoZIhvcNAQcCoIIjiTCCI4UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAsDCLx4mSzrh0X # My0wLX2Xd+jPWRDgwoW4s89nCVtfQaCCDYUwggYDMIID66ADAgECAhMzAAAB4HFz # 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 # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFb/ # r/T02LVOSs66Hl6z9DRBbdReXHh52+mZIYNcNOzNMEQGCisGAQQBgjcCAQwxNjA0 # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu # Y29tIDANBgkqhkiG9w0BAQEFAASCAQDQAbtg4caUX+QEpybZZbj+QrNbKpOV4RAL # 9mhKJc6oBtHnACOp+4CgTS0p2XUxVGglg/fc1ai8sD+mj+UsAC88wd2Z1arML5Bw # lEQ3pl7MkUX/7Mze1VXUWnyX9C8aWzwK9qx24+CZMqksbcr3Np9XQsrgr+8hlNou # KX7l7Dsou5d2zxt1rhVJhwPUVRB6H8iK2yS8c+HU6S6I/CPJc7VYnnDclwMtZTTO # HxLiOsXvNlmLVRXW0FXSjTlp3THaabIkjEvECBaLeKZ7CmuXvQIqYIrUOyKuX1K/ # KC4Jwrrgp8DAelfOzXACsmzfZe90+TH2KY41t3JaSbS4pFKYkH4woYIS8TCCEu0G # CisGAQQBgjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglg # hkgBZQMEAgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEE # AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIHcxqzw0NCUgm0oZhIpWCXnq4tXISqyX # WUhQQEuUKgV8AgZg+vccTaIYEzIwMjEwODE2MDU0MzQwLjMwM1owBIACAfSggdSk # 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 # hkiG9w0BCQQxIgQg4XTANv0VuKXPYHx2wUT2uCyKXjIBbY7UKEwdjf3wVw8wgfoG # CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCKqhiV+zwNDrpU7DRB7Mi57xi6GBNY # sGjgZqq2qVMKMjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # AhMzAAABYtD+AvMB5c1JAAAAAAFiMCIEIKcp8ozmKfJZa/sk+KuKhYctNlLPgQD6 # +ptIVSQTWPJHMA0GCSqGSIb3DQEBCwUABIIBAB5y3RhKDglIgPoe/JYKb6lUJAFe # BImu3XVmZXT+kE6j8t52e3kZFJNlCTlo25601/ooPBil+N6i1c8ljXUuoCkLifvw # 88hAT9yX2EFuAhI5T/HaAtoBxsqvSNIdTNc5nNXisjvneb5j9WK25prOL74sWCoC # jtV27JeisK76zUfgbe/IwZMg8qnkGZaDy71k9hRLW/ki05RTLglS1jHObotn5LBt # dz5Od/pWYB+QP0vrW3ce9v0dFPlm0n+gbUI1MRDcy08Ouik+ZbPbRdWg1om3bjuv # iscX8AoVHokxCEwihOkmOB6zULdDYp5qhVCXWdFyNL9K7rwwUJOYC+9fmfE= # SIG # End signature block |