Framework/Managers/PartialScanManager.ps1
Set-StrictMode -Version Latest class PartialScanManager { hidden [string] $SubId = $null; hidden [PSObject] $ScanPendingForResources = $null; hidden [string] $ResourceScanTrackerFileName=$null; hidden [PartialScanResourceMap] $ResourceScanTrackerObj = $null [PSObject] $ControlSettings; hidden [ActiveStatus] $ActiveStatus = [ActiveStatus]::NotStarted; hidden [string] $CAScanProgressSnapshotsContainerName = [Constants]::CAScanProgressSnapshotsContainerName hidden [string] $AzSKTempStatePath = (Join-Path $([Constants]::AzSKAppFolderPath) "TempState" | Join-Path -ChildPath "PartialScanData"); hidden [bool] $StoreResTrackerLocally = $false; hidden [string] $ScanSource = $null; hidden [bool] $IsRTFAlreadyAvailable = $false; hidden [bool] $IsDurableStorageFound = $false; hidden [string] $MasterFilePath; $StorageContext = $null; $ControlStateBlob = $null; hidden static $IsCsvUpdatedAtCheckpoint = $false; hidden static $CollatedSummaryCount = @(); # Matrix of counts for severity and control status hidden static $CollatedBugSummaryCount = @(); # Matrix of counts for severity and Bug status hidden static $ControlResultsWithBugSummary = @(); hidden [string] $SummaryMarkerText = "------"; hidden static [PartialScanManager] $Instance = $null; static [PartialScanManager] GetInstance([PSObject] $StorageAccount, [string] $SubscriptionId) { if ( $null -eq [PartialScanManager]::Instance) { [PartialScanManager]::Instance = [PartialScanManager]::new($SubscriptionId); } [PartialScanManager]::Instance.SubId = $SubscriptionId; return [PartialScanManager]::Instance } static [PartialScanManager] GetInstance() { if ( $null -eq [PartialScanManager]::Instance) { [PartialScanManager]::Instance = [PartialScanManager]::new(); } return [PartialScanManager]::Instance } static [void] ClearInstance() { [PartialScanManager]::Instance = $null } PartialScanManager([string] $SubscriptionId) { $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); $this.SubId = $SubscriptionId; if ([string]::isnullorwhitespace($this.ResourceScanTrackerFileName)) { if([ConfigurationManager]::GetAzSKSettings().IsCentralScanModeOn) { $this.ResourceScanTrackerFileName = Join-Path $SubscriptionId $([Constants]::ResourceScanTrackerCMBlobName) } else { $this.ResourceScanTrackerFileName = Join-Path $SubscriptionId $([Constants]::ResourceScanTrackerBlobName) } } $this.GetResourceScanTrackerObject(); } PartialScanManager() { $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); if ([string]::isnullorwhitespace($this.ResourceScanTrackerFileName)) { $this.ResourceScanTrackerFileName = [Constants]::ResourceScanTrackerBlobName } $this.GetResourceScanTrackerObject(); } hidden [void] GetResourceTrackerFile($subId) { $this.ScanSource = [AzSKSettings]::GetInstance().GetScanSource(); $this.SubId = $subId #Validating the configuration of storing resource tracker file if($null -ne $this.ControlSettings.PartialScan) { $this.StoreResTrackerLocally = [Bool]::Parse($this.ControlSettings.PartialScan.StoreResourceTrackerLocally); } #Use local Resource Tracker files for partial scanning if ($this.StoreResTrackerLocally -and ($this.ScanSource -ne "CA" -and $this.ScanSource -ne "CICD") ) { if($null -eq $this.ScanPendingForResources) { if(![string]::isnullorwhitespace($this.SubId)){ if(Test-Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName)) { $this.ScanPendingForResources = Get-Content (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName) -Raw } $this.MasterFilePath = (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName) } else { $this.MasterFilePath = (Join-Path $this.AzSKTempStatePath $this.ResourceScanTrackerFileName) } } } if ($this.ScanSource -eq "CA") # use storage in ADOScannerRG in case of CA scan { $this.MasterFilePath = (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName) try { #Validate if Storage is found $keys = Get-AzStorageAccountKey -ResourceGroupName $env:StorageRG -Name $env:StorageName $this.StorageContext = New-AzStorageContext -StorageAccountName $env:StorageName -StorageAccountKey $keys[0].Value -Protocol Https $containerObject = Get-AzStorageContainer -Context $this.StorageContext -Name $this.CAScanProgressSnapshotsContainerName -ErrorAction SilentlyContinue #If checkpoint container is found then get ResourceTracker.json (if exists) if($null -ne $containerObject) { $this.ControlStateBlob = Get-AzStorageBlob -Container $this.CAScanProgressSnapshotsContainerName -Context $this.StorageContext -Blob (Join-Path $this.SubId.ToLower() $this.ResourceScanTrackerFileName) -ErrorAction SilentlyContinue #If controlStateBlob is null then it will get created when we first write the resource tracker file to storage #If its not null this means Resource tracker file has been found in storage and will be used to continue pending scan if ($null -ne $this.ControlStateBlob) { if ($null -ne $this.MasterFilePath) { if (-not (Test-Path $this.MasterFilePath)) { $filePath = $this.MasterFilePath.Replace($this.ResourceScanTrackerFileName, "") New-Item -ItemType Directory -Path $filePath New-Item -Path $filePath -Name $this.ResourceScanTrackerFileName -ItemType "file" } #Copy existing RTF locally to handle any non ascii characters as ICloudBlob.DownloadText() was inserting non ascii charcaters Get-AzStorageBlobContent -CloudBlob $this.ControlStateBlob.ICloudBlob -Context $this.StorageContext -Destination $this.MasterFilePath -Force $this.ScanPendingForResources = Get-ChildItem -Path $this.MasterFilePath -Force | Get-Content | ConvertFrom-Json #Delete the local RTF file Remove-Item -Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName) } $this.IsRTFAlreadyAvailable = $true } else { $this.IsRTFAlreadyAvailable = $false } $this.IsDurableStorageFound = $true } #If checkpoint container is not found then create new else { $containerObject = New-AzStorageContainer -Name $this.CAScanProgressSnapshotsContainerName -Context $this.StorageContext -ErrorAction SilentlyContinue if ($null -ne $containerObject ) { $this.IsDurableStorageFound = $true } else { $this.PublishCustomMessage("Could not find/create partial scan container in storage.", [MessageType]::Warning); } } } catch { $this.PublishCustomMessage("Exception when trying to find/create partial scan container: $_.", [MessageType]::Warning); #Eat exception } } elseif ($this.ScanSource -eq "CICD") # use extension storage in case of CICD partial scan { if(![string]::isnullorwhitespace($this.SubId)) { $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $uri= ""; if (Test-Path env:partialScanURI) { #Uri is created in cicd task based on jobid $uri = $env:partialScanURI } else { $uri = [Constants]::StorageUri -f $this.SubId, $this.SubId, "ResourceTrackerFile" } try { $webRequestResult = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} $this.ScanPendingForResources = $webRequestResult.value | ConvertFrom-Json $this.IsRTFAlreadyAvailable = $true; } catch { $this.ScanPendingForResources = $null $this.IsRTFAlreadyAvailable = $false; } } } } #Update resource status in ResourceMapTable object [void] UpdateResourceStatus([string] $resourceId, [ScanState] $state) { $resourceValues = @(); #$this.GetResourceScanTrackerObject(); if($this.IsListAvailableAndActive()) { $resourceValue = $this.ResourceScanTrackerObj.ResourceMapTable | Where-Object { $_.Id -eq $resourceId}; if($null -ne $resourceValue) { $resourceValue.ModifiedDate = [DateTime]::UtcNow; $resourceValue.State = $state; } else { $resourceValue = [PartialScanResource]@{ Id = $resourceId; State = $state; ScanRetryCount = 1; CreatedDate = [DateTime]::UtcNow; ModifiedDate = [DateTime]::UtcNow; } $this.ResourceScanTrackerObj.ResourceMapTable +=$resourceValue; } } } [void] UpdateResourceScanRetryCount([string] $resourceId) { $resourceValues = @(); if($this.IsListAvailableAndActive()) { $resourceValue = $this.ResourceScanTrackerObj.ResourceMapTable | Where-Object { $_.Id -eq $resourceId}; if($null -ne $resourceValue) { $resourceValue.ModifiedDate = [DateTime]::UtcNow; $resourceValue.ScanRetryCount = $resourceValue.ScanRetryCount + 1; if($resourceValue.ScanRetryCount -ge [Constants]::PartialScanMaxRetryCount) { $resourceValue.State = [ScanState]::ERR } } else { #do nothing } } } # Method to remove obsolete Resource Tracker file [void] RemovePartialScanData() { if ($this.ScanSource -eq "CICD") { if($null -ne $this.ResourceScanTrackerObj) { $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $uri =""; if (Test-Path env:partialScanURI) { #Uri is created by cicd task based on jobid $uri = $env:partialScanURI } else { $uri = [Constants]::StorageUri -f $this.SubId, $this.SubId, "ResourceTrackerFile" } try { if ($this.ResourceScanTrackerObj.ResourceMapTable -ne $null){ $webRequestResult = Invoke-WebRequest -Uri $uri -Method Delete -ContentType "application/json" -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } $this.ResourceScanTrackerObj = $null } } catch { #do nothing } } } elseif ($this.ScanSource -eq "CA" -and $this.IsDurableStorageFound) { #Move resource tracker file to archive folder if($null -ne $this.ControlStateBlob) { $archiveName = "Checkpoint_" +(Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss") + ".json"; #Store final RTF file locally and then upload to archive folder [JsonHelper]::ConvertToJsonCustom($this.ResourceScanTrackerObj) | Out-File $this.MasterFilePath -Force Set-AzStorageBlobContent -File $this.MasterFilePath -Container $this.CAScanProgressSnapshotsContainerName -Blob (Join-Path $this.SubId.ToLower() (Join-Path "Archive" $archiveName)) -BlobType Block -Context $this.StorageContext -Force Remove-AzStorageBlob -CloudBlob $this.ControlStateBlob.ICloudBlob -Force -Context $this.StorageContext #Delete local RTF file if (Test-Path (Join-Path $this.AzSKTempStatePath $this.SubId)) { Remove-Item -Path (Join-Path $this.AzSKTempStatePath $this.SubId) -Recurse } } } #Use local Resource Tracker files for partial scanning elseif ($this.StoreResTrackerLocally) { if($null -ne $this.ResourceScanTrackerObj) { if(![string]::isnullorwhitespace($this.SubId)){ if(Test-Path (Join-Path $this.AzSKTempStatePath $this.SubId)) { Remove-Item -Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName) <#Create archive folder if not exists if(-not (Test-Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) "archive"))) { New-Item -ItemType Directory -Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) "archive") } $timestamp =(Get-Date -format "yyMMddHHmmss") Move-Item -Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) $this.ResourceScanTrackerFileName) -Destination (Join-Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.SubId) "archive")"Checkpoint_$($timestamp)") #> } } $this.ResourceScanTrackerObj = $null } } } #Method to fetch all applicable resources as per input command (including those with "COMP" status in ResourceTracker file) [void] CreateResourceMasterList([PSObject] $resourceIds) { if(($resourceIds | Measure-Object).Count -gt 0) { $resourceIdMap = @(); $resourceIds | ForEach-Object { $resourceId = $_; $resourceValue = [PartialScanResource]@{ Id = $resourceId; State = [ScanState]::INIT; ScanRetryCount = 0; CreatedDate = [DateTime]::UtcNow; ModifiedDate = [DateTime]::UtcNow; } #$resourceIdMap.Add($hashId,$resourceValue); $resourceIdMap +=$resourceValue } $masterControlBlob = [PartialScanResourceMap]@{ Id = [DateTime]::UtcNow.ToString("yyyyMMdd_HHmmss"); CreatedDate = [DateTime]::UtcNow; ResourceMapTable = $resourceIdMap; } if ($this.ScanPendingForResources -ne $null -and $this.ScanSource -eq "CICD"){ $this.ResourceScanTrackerObj = [PartialScanResourceMap]@{ Id = $this.ScanPendingForResources.Id; CreatedDate = $this.ScanPendingForResources.CreatedDate; ResourceMapTable = $this.ScanPendingForResources.ResourceMapTable.value; } } else{ $this.ResourceScanTrackerObj = $masterControlBlob; } if ($this.ScanSource -eq "CICD" -or $this.ScanSource -eq "CA") { $this.WriteToDurableStorage(); } else { $this.WriteToResourceTrackerFile(); } $this.ActiveStatus = [ActiveStatus]::Yes; } } [void] WriteToResourceTrackerFile() { if ($this.StoreResTrackerLocally) { if($null -ne $this.ResourceScanTrackerObj) { if(![string]::isnullorwhitespace($this.SubId)){ if(-not (Test-Path (Join-Path $this.AzSKTempStatePath $this.SubId))) { New-Item -ItemType Directory -Path (Join-Path $this.AzSKTempStatePath $this.SubId) -ErrorAction Stop | Out-Null } } else{ if(-not (Test-Path "$this.AzSKTempStatePath")) { New-Item -ItemType Directory -Path "$this.AzSKTempStatePath" -ErrorAction Stop | Out-Null } } [JsonHelper]::ConvertToJsonCustom($this.ResourceScanTrackerObj) | Out-File $this.MasterFilePath -Force } } } [void] WriteToDurableStorage() { if ($this.ScanSource -eq "CICD") { if($null -ne $this.ResourceScanTrackerObj) { if(![string]::isnullorwhitespace($this.SubId)) { $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $uri = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $scanObject = $this.ResourceScanTrackerObj | ConvertTo-Json $body = ""; if (Test-Path env:partialScanURI) { $uri = $env:partialScanURI $JobId =""; $JobId = $uri.Replace('?','/').Split('/')[$JobId.Length -2] if ($this.IsRTFAlreadyAvailable -eq $true){ $body = @{"id" = $Jobid; "__etag"=-1; "value"= $scanObject;} | ConvertTo-Json } else{ $body = @{"id" = $Jobid; "value"= $scanObject;} | ConvertTo-Json } } else { $uri = [Constants]::StorageUri -f $this.SubId, $this.SubId, "ResourceTrackerFile" if ($this.IsRTFAlreadyAvailable -eq $true){ $body = @{"id" = "ResourceTrackerFile";"__etag"=-1; "value"= $scanObject;} | ConvertTo-Json } else{ $body = @{"id" = "ResourceTrackerFile"; "value"= $scanObject;} | ConvertTo-Json } } try { $webRequestResult = Invoke-WebRequest -Uri $uri -Method Put -ContentType "application/json" -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } -Body $body $this.IsRTFAlreadyAvailable = $true; } catch { write-host "Could not update resource tracker file." } } } } elseif ($this.ScanSource -eq "CA" -and $this.IsDurableStorageFound) { if ($this.IsRTFAlreadyAvailable) # Copy RTF from memory { $this.ControlStateBlob.ICloudBlob.UploadText([JsonHelper]::ConvertToJsonCustom($this.ResourceScanTrackerObj) ) } else { # If file is not available in storage then upload it from local for the first instance if ($null -ne $this.MasterFilePath -and -not (Test-Path $this.MasterFilePath)) { # Create directory and resource tracker file $filePath = $this.MasterFilePath.Replace($this.ResourceScanTrackerFileName, "") if (-not (Test-Path $filePath)) { New-Item -ItemType Directory -Path $filePath } New-Item -Path $filePath -Name $this.ResourceScanTrackerFileName -ItemType "file" } [JsonHelper]::ConvertToJsonCustom($this.ResourceScanTrackerObj) | Out-File $this.MasterFilePath -Force Set-AzStorageBlobContent -File $this.MasterFilePath -Container $this.CAScanProgressSnapshotsContainerName -Blob (Join-Path $this.SubId.ToLower() $this.ResourceScanTrackerFileName) -BlobType Block -Context $this.StorageContext -Force $this.ControlStateBlob = Get-AzStorageBlob -Container $this.CAScanProgressSnapshotsContainerName -Context $this.StorageContext -Blob (Join-Path $this.SubId.ToLower() $this.ResourceScanTrackerFileName) -ErrorAction SilentlyContinue $this.IsRTFAlreadyAvailable = $true } } } #Method to fetch ResourceTrackerFile as an object hidden [void] GetResourceScanTrackerObject() { if($null -eq $this.ScanPendingForResources) { return; } if ($this.ScanSource -eq "CICD") # use extension storage in case of CICD partial scan { if(![string]::isnullorwhitespace($this.ScanPendingForResources)) { $this.ResourceScanTrackerObj = [PartialScanResourceMap]@{ Id = $this.ScanPendingForResources.Id; CreatedDate = $this.ScanPendingForResources.CreatedDate; ResourceMapTable = $this.ScanPendingForResources.ResourceMapTable.value; } } } elseif ($this.ScanSource -eq "CA") { if(![string]::isnullorwhitespace($this.ScanPendingForResources)) { $this.ResourceScanTrackerObj = $this.ScanPendingForResources } } elseif ($this.StoreResTrackerLocally) { if(![string]::isnullorwhitespace($this.SubId)){ if(-not (Test-Path (Join-Path $this.AzSKTempStatePath $this.SubId))) { New-Item -ItemType Directory -Path (Join-Path $this.AzSKTempStatePath $this.SubId) -ErrorAction Stop | Out-Null } } else{ if(-not (Test-Path "$this.AzSKTempStatePath")) { New-Item -ItemType Directory -Path "$this.AzSKTempStatePath" -ErrorAction Stop | Out-Null } } $this.ResourceScanTrackerObj = Get-content $this.MasterFilePath | ConvertFrom-Json } } [ActiveStatus] IsPartialScanInProgress($subId) { $this.GetResourceTrackerFile($subId); if($null -ne $this.ControlSettings.PartialScan) { $resourceTrackerFileValidforDays = [Int32]::Parse($this.ControlSettings.PartialScan.ResourceTrackerValidforDays); $this.GetResourceScanTrackerObject(); if($null -eq $this.ResourceScanTrackerObj) { return $this.ActiveStatus = [ActiveStatus]::No; } $shouldStopScanning = ($this.ResourceScanTrackerObj.ResourceMapTable | Where-Object {$_.State -notin ([ScanState]::COMP,[ScanState]::ERR)} | Measure-Object).Count -eq 0 if($this.ResourceScanTrackerObj.CreatedDate.AddDays($resourceTrackerFileValidforDays) -lt [DateTime]::UtcNow -or $shouldStopScanning) { $this.RemovePartialScanData(); $this.ScanPendingForResources = $null; return $this.ActiveStatus = [ActiveStatus]::No; } return $this.ActiveStatus = [ActiveStatus]::Yes } else { $this.ScanPendingForResources = $null; return $this.ActiveStatus = [ActiveStatus]::No; } } [PSObject] GetNonScannedResources() { $nonScannedResources = @(); $this.GetResourceScanTrackerObject(); if($this.IsListAvailableAndActive()) { $nonScannedResources +=[PartialScanResource[]] $this.ResourceScanTrackerObj.ResourceMapTable | Where-Object {$_.State -eq [ScanState]::INIT} return $nonScannedResources; } return $null; } [PSObject] GetAllListedResources() { $nonScannedResources = @(); $this.GetResourceScanTrackerObject(); if($this.IsListAvailableAndActive()) { $nonScannedResources += $this.ResourceScanTrackerObj.ResourceMapTable return $nonScannedResources; } return $null; } [Bool] IsListAvailableAndActive() { if($null -ne $this.ResourceScanTrackerObj -and $this.ActiveStatus -eq [ActiveStatus]::Yes -and $null -ne $this.ResourceScanTrackerObj.ResourceMapTable) { return $true } else { return $false } } # Collect control results summary data and append to it at every checkpoint. Any changes in this method should be synced with WritePSConsole.ps1 PrintSummaryData method [void] CollateSummaryData($event) { $summary = @($event | select-object @{Name="VerificationResult"; Expression = {$_.ControlResults.VerificationResult}},@{Name="ControlSeverity"; Expression = {$_.ControlItem.ControlSeverity}}) if(($summary | Measure-Object).Count -ne 0) { $severities = @(); $severities += $summary | Select-Object -Property ControlSeverity | Select-Object -ExpandProperty ControlSeverity -Unique; $verificationResults = @(); $verificationResults += $summary | Select-Object -Property VerificationResult | Select-Object -ExpandProperty VerificationResult -Unique; if($severities.Count -ne 0) { # Create summary matrix $totalText = "Total"; $MarkerText = "MarkerText"; $rows = @(); $rows += $severities; $rows += $MarkerText; $rows += $totalText; $rows += $MarkerText; #Execute below block only once (when first resource is scanned) if([PartialScanManager]::CollatedSummaryCount.Count -eq 0) { $rows | ForEach-Object { $result = [PSObject]::new(); Add-Member -InputObject $result -Name "Summary" -MemberType NoteProperty -Value $_.ToString() Add-Member -InputObject $result -Name $totalText -MemberType NoteProperty -Value 0 #Get all possible verificationResults initially [Enum]::GetNames([VerificationResult]) | ForEach-Object { Add-Member -InputObject $result -Name $_.ToString() -MemberType NoteProperty -Value 0 }; [PartialScanManager]::CollatedSummaryCount += $result; }; } $totalRow = [PartialScanManager]::CollatedSummaryCount | Where-Object { $_.Summary -eq $totalText } | Select-Object -First 1; $summary | Group-Object -Property ControlSeverity | ForEach-Object { $item = $_; $summaryItem = [PartialScanManager]::CollatedSummaryCount | Where-Object { $_.Summary -eq $item.Name } | Select-Object -First 1; if($summaryItem) { $summaryItem.Total = $_.Count; if($totalRow) { $totalRow.Total += $_.Count } $item.Group | Group-Object -Property VerificationResult | ForEach-Object { $propName = $_.Name; $summaryItem.$propName += $_.Count; if($totalRow) { $totalRow.$propName += $_.Count } }; } }; $markerRows = [PartialScanManager]::CollatedSummaryCount | Where-Object { $_.Summary -eq $MarkerText } $markerRows | ForEach-Object { $markerRow = $_ Get-Member -InputObject $markerRow -MemberType NoteProperty | ForEach-Object { $propName = $_.Name; $markerRow.$propName = $this.SummaryMarkerText; } }; } } } # Collect Bug summary data and append to it at every checkpoint. Any changes in this method should be synced with WritePSConsole.ps1 PrintBugSummaryData method [void] CollateBugSummaryData($event){ #gather all control results that have failed/verify as their control result #obtain their control severities $event | ForEach-Object { $item = $_ if ($item -and $item.ControlResults -and ($item.ControlResults[0].VerificationResult -eq "Failed" -or $item.ControlResults[0].VerificationResult -eq "Verify")) { $item $item.ControlResults[0].Messages | ForEach-Object{ if($_.Message -eq "New Bug" -or $_.Message -eq "Active Bug" -or $_.Message -eq "Resolved Bug"){ [PartialScanManager]::CollatedBugSummaryCount += [PSCustomObject]@{ BugStatus=$_.Message ControlSeverity = $item.ControlItem.ControlSeverity; }; } }; #Collecting control results where bug has been found (new/active/resolved). This is used to generate BugSummary at the end of scan [PartialScanManager]::ControlResultsWithBugSummary += $item } }; } # Write to csv and append to it at every checkpoint. Any changes in this method should be synced with WriteSummaryFile.ps1 WriteToCSV method [void] WriteToCSV([SVTEventContext[]] $arguments, $FilePath) { if ([string]::IsNullOrEmpty($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); $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; Recommendation = $item.ControlItem.Recommendation; Rationale = $item.ControlItem.Rationale }; if($_.VerificationResult -ne [VerificationResult]::NotScanned) { $csvItem.Status = $_.VerificationResult.ToString(); } if($item.ControlItem.IsBaselineControl) { $csvItem.IsBaselineControl = "Yes"; } else { $csvItem.IsBaselineControl = "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 = @(); $nonNullProps = [CsvOutputItem].GetMembers() | Where-Object { $_.MemberType -eq [System.Reflection.MemberTypes]::Property }| Select-object -Property Name ($csvItems | Select-Object -Property $nonNullProps.Name -ExcludeProperty SupportsAutoFix,ChildResourceName,IsPreviewBaselineControl,UserComments ) | Group-Object -Property FeatureName | Foreach-Object {$_.Group | Export-Csv -Path $FilePath -append -NoTypeInformation} [PartialScanManager]::IsCsvUpdatedAtCheckpoint = $true } } } # SIG # Begin signature block # MIIjiQYJKoZIhvcNAQcCoIIjejCCI3YCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA4fQCmPMjKvrmL # PrCblwQKz2UPDt3K33qHaMZ8QRQh0aCCDYUwggYDMIID66ADAgECAhMzAAABiK9S # 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/Xmfwb1tbWrJUnMTDXpQzTGCFVowghVWAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA # AYgwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPGO # yZhUYtDpgHFn7fVbGvjbcBU1Ot3YMFnU76U44eKZMEQGCisGAQQBgjcCAQwxNjA0 # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu # Y29tIDANBgkqhkiG9w0BAQEFAASCAQBwJKQPY/Fyn74/7iBwOzT7Q0sYRQ3W/745 # 5KXXHrDJXVDwH4Y7eiNIKpZGosonLdrQCUh52Oquh2FfQvYoQnd9xEsb8KeD5ugT # a4fzjPsd7oat/fgMmLcWOsH4qeaRJqnjdoylgrxv0npSdjZMlpgs1E3U5UofUEEo # c4TfcyJvrdlmT1TEhlVjKIInC5oWhbTbOKLiPFucJInKuowJzQUdjXbj6M32ccF9 # caBZbHuio9e+3LmwvI3xW3DOT8Ck7ZbI4KZHACMhCeC+/CNY2dkeOpHzqCn4U6oy # w7uwXqgnRReVaTy9emKQ7/Uqdyfa1jiezsrTw8oPPKL0jnMIfy5coYIS4jCCEt4G # CisGAQQBgjcDAwExghLOMIISygYJKoZIhvcNAQcCoIISuzCCErcCAQMxDzANBglg # hkgBZQMEAgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEE # AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIGipHTq2Xo/FfHg3ghjx4ZdSxSs8Z/cv # jluran5y2DhIAgZf2MoSJoMYEzIwMjEwMTE1MTEwNzUyLjYzMlowBIACAfSggdCk # gc0wgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNV # BAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxl # cyBUU1MgRVNOOjhBODItRTM0Ri05RERBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt # ZS1TdGFtcCBTZXJ2aWNloIIOOTCCBPEwggPZoAMCAQICEzMAAAFLT7KmSNXkwlEA # AAAAAUswDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTAwHhcNMjAxMTEyMTgyNTU5WhcNMjIwMjExMTgyNTU5WjCByjELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFt # ZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046OEE4Mi1F # MzRGLTlEREExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChNnpQx3YuJr/ivobPoLtp # Q9egUFl8THdWZ6SAKIJdtP3L24D3/d63ommmjZjCyrQm+j/1tHDAwjQGuOwYvn79 # ecPCQfAB91JnEp/wP4BMF2SXyMf8k9R84RthIdfGHPXTWqzpCCfNWolVEcUVm8Ad # /r1LrikRO+4KKo6slDQJKsgKApfBU/9J7Rudvhw1rEQw0Nk1BRGWjrIp7/uWoUIf # R4rcl6U1utOiYIonC87PPpAJQXGRsDdKnVFF4NpWvMiyeuksn5t/Otwz82sGlne/ # HNQpmMzigR8cZ8eXEDJJNIZxov9WAHHj28gUE29D8ivAT706ihxvTv50ZY8W51ux # AgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQUUqpqftASlue6K3LePlTTn01K68YwHwYD # VR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZF # aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGlt # U3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw # AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQ # Q0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEF # BQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAFtq51Zc/O1AfJK4tEB2Nr8bGEVD5qQ8l # 8gXIQMrMZYtddHH+cGiqgF/4GmvmPfl5FAYh+gf/8Yd3q4/iD2+K4LtJbs/3v6mp # yBl1mQ4vusK65dAypWmiT1W3FiXjsmCIkjSDDsKLFBYH5yGFnNFOEMgL+O7u4osH # 42f80nc2WdnZV6+OvW035XPV6ZttUBfFWHdIbUkdOG1O2n4yJm10OfacItZ08fzg # MMqE+f/STgVWNCHbR2EYqTWayrGP69jMwtVD9BGGTWti1XjpvE6yKdO8H9nuRi3L # +C6jYntfaEmBTbnTFEV+kRx1CNcpSb9os86CAUehZU1aRzQ6CQ/pjzCCBnEwggRZ # oAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290 # IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1 # MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ # 1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP # 8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRh # Z5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39 # dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2 # iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGj # ggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xG # G8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB # /wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUF # BwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0A # ZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFv # s+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5 # U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFS # AK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1V # ry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6 # f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35j # WSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHa # sFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLN # HfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4 # sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHX # odLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUe # CLraNtvTX4/edIhJEqGCAsswggI0AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4QTgyLUUz # NEYtOUREQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIj # CgEBMAcGBSsOAwIaAxUAkToz97fseHxNOUSQ5O/bBVSF+e6ggYMwgYCkfjB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOOrfdkw # IhgPMjAyMTAxMTUxMDM1MDVaGA8yMDIxMDExNjEwMzUwNVowdDA6BgorBgEEAYRZ # CgQBMSwwKjAKAgUA46t92QIBADAHAgEAAgIUYTAHAgEAAgIRpTAKAgUA46zPWQIB # ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQow # CAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJCXc2xVUeGGUHLMDT4Tx7l/nMWQ # XmwVBcU21bwariOYWmzQyCXvQlJPcyvCUjOIqCPgDTzewUC51jCPXqqg57iLlwtR # BLRLILBe4F7HGyObNiz+UfoUf5FKDfZ4+2e88Qs0gW9KRU6l8aKU+xEHH5rZuJvy # l/w6jdGxKm1v1xHdMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # UENBIDIwMTACEzMAAAFLT7KmSNXkwlEAAAAAAUswDQYJYIZIAWUDBAIBBQCgggFK # MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg+s+L # JXuJHdVmXNsPmCStoGZr83JFp8TkGR6/JC7sVn0wgfoGCyqGSIb3DQEJEAIvMYHq # MIHnMIHkMIG9BCBr9u6EInnsZYEts/Fj/rIFv0YZW1ynhXKOP2hVPUU5IzCBmDCB # gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABS0+ypkjV5MJR # AAAAAAFLMCIEIP7CBCmKpNAoqGlM17IIHxFF9eOEaSkIooocp3JUumoyMA0GCSqG # SIb3DQEBCwUABIIBAEpjIPFc3/0GfW2+Fn5oimQ3uFLlq7vzIX45GZc7F2LYyqLV # hT+eMznWAw4qTM9pyXptoSOWnoeBYN3PK1Qna+M01yp2TfYdyAr8kN+67wctHQ4V # ucMthsuagqTDXy7ekjjI/emF3kw/EA4lvs5nBWOyzaKVeVwYJW/dAaxtFc4mbtGh # 6ueCBuK92APkGQ5IWuMW840yjQtKZ0M1G5l3bpJiAV2IuXJh1+z016mqwQDx0MmT # XF92x7eM17P+AWuNcoSQ1p2lneghRMm5fxqIr92xeMdIKwARs1rqRX//2C2mKAeK # TJInnp3QyFqXaYPVp2tzJcwk7f76JhOu9HP0jU0= # SIG # End signature block |