Framework/Managers/BatchScanManagerForMultipleProjects.ps1

Set-StrictMode -Version Latest

class BatchScanManagerForMultipleProjects 
{
    hidden [string] $OrgName = $null;
    [PSObject] $ControlSettings;
    hidden [string] $BatchScanTrackerFileName=$null;
    hidden [string] $AzSKTempStatePath = (Join-Path $([Constants]::AzSKAppFolderPath) "TempState" | Join-Path -ChildPath "BatchScanData");
    hidden [string] $MasterFilePath;
    hidden [PSObject] $BatchScanTrackerObj = $null;
    hidden [PSObject] $ScanPendingForBatch = $null;
    hidden static [BatchScanManagerForMultipleProjects] $Instance =$null;
    hidden [int] $BatchSize = 0;
    hidden [bool] $isUpdated = $false;

    static [BatchScanManagerForMultipleProjects] GetInstance( [string] $OrganizationName)
    {
        if ( $null -eq  [BatchScanManagerForMultipleProjects]::Instance)
        {
            [BatchScanManagerForMultipleProjects]::Instance = [BatchScanManagerForMultipleProjects]::new($OrganizationName);
        }
        [BatchScanManagerForMultipleProjects]::Instance.OrgName = $OrganizationName;
        return [BatchScanManagerForMultipleProjects]::Instance
    }
    static [BatchScanManagerForMultipleProjects] GetInstance()
    {
        if ( $null -eq  [BatchScanManagerForMultipleProjects]::Instance)
        {
            [BatchScanManagerForMultipleProjects]::Instance = [BatchScanManagerForMultipleProjects]::new();
        }
        return [BatchScanManagerForMultipleProjects]::Instance
    }
    static [void] ClearInstance()
    {
       [BatchScanManagerForMultipleProjects]::Instance = $null
    }
    BatchScanManagerForMultipleProjects([string] $OrganizationName){
        $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json");
        $this.OrgName = $OrganizationName;
        if($this.CheckIfProjectValid($PSCmdlet.MyInvocation.BoundParameters.ProjectNames) -eq $false){
            Write-Host "You have supplied one or more invalid project name. Re run the command with correct project." -ForegroundColor Red
            throw;
        }
        if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("BatchSize")){
            $this.BatchSize = $PSCmdlet.MyInvocation.BoundParameters["BatchSize"]
        }
        else {
            $this.BatchSize = $this.ControlSettings.BatchScan.BatchTrackerUpdateFrequency
        }
        if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq "Build_Release"){
            if($this.BatchSize%2 -eq 0){
                $this.BatchSize=$this.BatchSize/2;
            }
            else {
                $this.BatchSize=($this.BatchSize-1)/2;
            }           
            
        }
        if ([string]::isnullorwhitespace($this.BatchScanTrackerFileName))
        {              
            $this.BatchScanTrackerFileName = [Constants]::BatchScanTrackerBlobName           
        }
        $this.GetBatchScanTrackerObject();
    }
    BatchScanManagerForMultipleProjects()
    {
        $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json");
        if ([string]::isnullorwhitespace($this.BatchScanTrackerFileName))
        {
            $this.BatchScanTrackerFileName =  [Constants]::BatchScanTrackerBlobName
        }
        $this.GetBatchScanTrackerObject();
    }

    [bool] CheckIfProjectValid($ProjectNames){
        $apiURL = 'https://dev.azure.com/{0}/_apis/projects?$top=1000&api-version=6.0' -f $($this.OrgName);
        $responseObj = "";
        try {
            $responseObj = [WebRequestHelper]::InvokeGetWebRequest($apiURL) ;
        }
        catch {
            Write-Host 'Project not found: Incorrect project name or you do not have necessary permission to access the project.' -ForegroundColor Red
            throw;
        }
        if (([Helpers]::CheckMember($responseObj, "count") -and $responseObj[0].count -gt 0) -or (($responseObj | Measure-Object).Count -gt 0 -and [Helpers]::CheckMember($responseObj[0], "name")))
        {
            if($ProjectNames -ne "*"){
                $Projects += $ProjectNames.Split(',', [StringSplitOptions]::RemoveEmptyEntries) | 
                Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
                ForEach-Object { $_.Trim() } |
                Select-Object -Unique;

                $projects = $responseObj | Where-Object { $Projects -contains $_.name }

                if (!$projects) {
                    return $false;                  
                }
            }
            
        }
        return $true;
    }

    [int] GetBatchSize()
    {
        return $this.BatchSize
    }

    hidden [void] GetBatchScanTrackerObject(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(-not (Test-Path (Join-Path $this.AzSKTempStatePath $this.OrgName)))
            {
                New-Item -ItemType Directory -Path (Join-Path $this.AzSKTempStatePath $this.OrgName) -ErrorAction Stop | Out-Null
            }
        }
        else{
            if(-not (Test-Path "$this.AzSKTempStatePath"))
            {
                New-Item -ItemType Directory -Path "$this.AzSKTempStatePath" -ErrorAction Stop | Out-Null
            }
        }
        if($null -ne $this.MasterFilePath){
            $this.BatchScanTrackerObj = Get-content $this.MasterFilePath | ConvertFrom-Json
        }
        else {
            $this.BatchScanTrackerObj=$null;
        }
        
    }

   [PSObject] GetBatchStatus(){
        return Get-content $this.MasterFilePath | ConvertFrom-Json;
    }

    hidden [void] GetBatchTrackerFile($OrgName){
        $this.OrgName=$OrgName;
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.OrgName) $this.BatchScanTrackerFileName))    
            {
                $this.ScanPendingForBatch = Get-Content (Join-Path (Join-Path $this.AzSKTempStatePath $this.OrgName) $this.BatchScanTrackerFileName) -Raw
            }
            $this.MasterFilePath = (Join-Path (Join-Path $this.AzSKTempStatePath $this.OrgName) $this.BatchScanTrackerFileName)
        }
        else {
            $this.MasterFilePath = (Join-Path $this.AzSKTempStatePath $this.BatchScanTrackerFileName)
        }
    }
    [void] RemoveBatchScanData(){
        if($null -ne $this.BatchScanTrackerObj){
            if(![string]::isnullorwhitespace($this.OrgName)){
                if(Test-Path (Join-Path $this.AzSKTempStatePath $this.OrgName))
                {
                    Remove-Item -Path (Join-Path (Join-Path $this.AzSKTempStatePath $this.OrgName) $this.BatchScanTrackerFileName)
                    
                }
            }
            $this.BatchScanTrackerObj=$null;
        }
    }
    hidden[bool] IsBatchScanInProgress($OrgName){
        $this.GetBatchTrackerFile($OrgName);
        if($null -ne $this.ControlSettings.BatchScan){
            $batchTrackerFileValidForDays = [Int32]::Parse($this.ControlSettings.BatchScan.BatchTrackerValidforDays);
            $this.GetBatchScanTrackerObject();
            if($null -eq $this.BatchScanTrackerObj){
                return $false;
            }
            if( (Get-Item $this.MasterFilePath).creationtime.AddDays($batchTrackerFileValidforDays) -lt [DateTime]::UtcNow)
            {
                $this.RemoveBatchScanData();
                $this.ScanPendingForBatch = $null;
                return $false;
            }
            return $true;

        }
        else {
            $this.ScanPendingForBatch=$null;
            return $false;
        }
        return $true;
    }

    [void] CreateBatchMasterList(){
        $batchStatus = [BatchScanResourceMap]@{
            Skip = 0;
            Top = $this.GetBatchSize();
            BuildCurrentContinuationToken=$null;
            BuildNextContinuationToken=$null;
            ReleaseCurrentContinuationToken=$null;
            ReleaseNextContinuationToken=$null;
            BatchScanState= [BatchScanState]::INIT;
            TokenLastModifiedTime = [DateTime]:: UtcNow;
            ResourceCount=0;
            SkipMarker = 'False';
            UpcError = 'False'
           
        }
        if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq "Build"){
            $batchStatus = $batchStatus | Select-Object -Property * -ExcludeProperty ReleaseCurrentContinuationToken
            $batchStatus = $batchStatus | Select-Object -Property * -ExcludeProperty ReleaseNextContinuationToken 
            $batchStatus = $batchStatus | Select-Object -Property * -ExcludeProperty SkipMarker 
                  
        }
        if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq "Release"){
            $batchStatus = $batchStatus | Select-Object -Property * -ExcludeProperty BuildCurrentContinuationToken 
            $batchStatus = $batchStatus | Select-Object -Property * -ExcludeProperty BuildNextContinuationToken
            $batchStatus = $batchStatus | Select-Object -Property * -ExcludeProperty SkipMarker             
            
        }
        $projects=@()
        
        $apiURL = 'https://dev.azure.com/{0}/_apis/projects?$top=1000&api-version=6.0' -f $($this.OrgName);
        $responseObj = "";
        try {
                $responseObj = [WebRequestHelper]::InvokeGetWebRequest($apiURL);
                $projects = $responseObj.name
        }
        catch {

        }
        if($PSCmdlet.MyInvocation.BoundParameters.ProjectNames -ne "*"){
            $projectsFromUser=@()
            $projectsFromUser += $PSCmdlet.MyInvocation.BoundParameters.ProjectNames.Split(',', [StringSplitOptions]::RemoveEmptyEntries) | 
                                    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
                                    ForEach-Object { $_.Trim() } |
                                    Select-Object -Unique;
            $validProjects = $responseObj | Where-Object { $projectsFromUser -contains $_.name }
            $projects = @()
            $projects += $validProjects.name
            
        }
        $batchStatus | Add-Member -NotePropertyName Projects -NotePropertyValue $projects
        $batchStatus | Add-Member -NotePropertyName ProjectsCount -NotePropertyValue $projects.Count
        $this.BatchScanTrackerObj=$batchStatus;
        $this.WriteToBatchTrackerFile()
        
    }

    [void] WriteToBatchTrackerFile() {
        if($null -ne $this.BatchScanTrackerObj){
            if(![string]::isnullorwhitespace($this.OrgName)){
                if(-not (Test-Path (Join-Path $this.AzSKTempStatePath $this.OrgName)))
                {
                    New-Item -ItemType Directory -Path (Join-Path $this.AzSKTempStatePath $this.OrgName) -ErrorAction Stop | Out-Null
                }    
            }
            else{
                if(-not (Test-Path "$this.AzSKTempStatePath"))
                {
                    New-Item -ItemType Directory -Path "$this.AzSKTempStatePath" -ErrorAction Stop | Out-Null
                }
            }
            Write-Host "Updating batch tracker file" -ForegroundColor Green
           [JsonHelper]::ConvertToJsonCustom( $this.BatchScanTrackerObj) | Out-File $this.MasterFilePath -Force
            Write-Host "Batch tracker file updated" -ForegroundColor Green
            
        }
    }

    #to check if anyone either builds or releases have been scanned and other resource is still left, is useful only when build_release is resource type
    [bool] isPreviousScanPartiallyComplete(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if( ($null -ne $this.MasterFilePath) -and (Test-Path $this.MasterFilePath)){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                if($batchStatus.ResourceCount -gt 0 -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken) -and $batchStatus.BatchScanState -eq [BatchScanState]::COMP) {
                   return $true;
                    
                }
                if($batchStatus.ResourceCount -gt 0 -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken) -and $batchStatus.BatchScanState -eq [BatchScanState]::COMP) {
                    return $true;
                }
                if($batchStatus.ResourceCount -gt 0 -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken) -and [string]::IsNullOrEmpty($batchStatus.ReleaseCurrentContinuationToken)) {
                    return $true;
                     
                }
                 if($batchStatus.ResourceCount -gt 0 -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken) -and [string]::IsNullOrEmpty($batchStatus.BuildCurrentContinuationToken)) {
                     return $true;
                }

            }
        }
        return $false;
    }
    hidden static [PSObject] GetBaseFrameworkPath() {
        $moduleName = $([Constants]::AzSKModuleName)

        #Remove Staging from module name before forming config base path
        $moduleName = $moduleName -replace "Staging", ""

        #Irrespective of whether Dev-Test mode is on or off, base framework path will now remain same as the new source code repo doesn't have AzSK.Framework folder.
        $basePath = (Get-Item $PSScriptRoot).Parent.FullName

        return $basePath
    }

    hidden static [PSObject] LoadFrameworkConfigFile([string] $fileName, [bool] $parseJson) {
        #Load file from AzSK App folder"
        $fileName = $fileName.Split('\')[-1]
        $extension = [System.IO.Path]::GetExtension($fileName);
 
        $basePath = [BatchScanManagerForMultipleProjects]::GetBaseFrameworkPath()
        $rootConfigPath = $basePath | Join-Path -ChildPath "Configurations";
 
        $filePath = (Get-ChildItem $rootConfigPath -Name -Recurse -Include $fileName) | Select-Object -First 1
        if ($filePath) {
            if ($parseJson) {
                if ($extension -eq ".json" -or $extension -eq ".lawsview") {
                    $fileContent = (Get-Content -Raw -Path (Join-Path $rootConfigPath $filePath)) | ConvertFrom-Json
                }
                else {
                    $fileContent = (Get-Content -Raw -Path (Join-Path $rootConfigPath $filePath))
                }
            }
            else {
                $fileContent = (Get-Content -Raw -Path (Join-Path $rootConfigPath $filePath))
            }
        }
        else {
            throw "Unable to find the specified file '$fileName'"
        }
        if (-not $fileContent) {
            throw "The specified file '$fileName' is empty"
        }
 
        return $fileContent;        
    }

    [void] UpdateBatchMasterList(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path $this.MasterFilePath){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                $isReleaseScan=$false;
                $isBuildScan=$false;
                if($batchStatus.PSobject.Properties.name -match "ReleaseCurrentContinuationToken") {
                    $isReleaseScan=$true;
                }
                if($batchStatus.PSobject.Properties.name -match "BuildCurrentContinuationToken") {
                    $isBuildScan=$true;
                }
                if($batchStatus.BatchScanState -eq [BatchScanState]:: INIT ){
                    if($batchStatus.Skip -eq 0){
                        if($isReleaseScan) {
                            $batchStatus.ReleaseCurrentContinuationToken=$null;
                        }
                        if($isBuildScan) {
                            $batchStatus.BuildCurrentContinuationToken=$null;
                        }
                        
                        Write-Host "Found a previous batch scan with $($batchStatus.ResourceCount) resources scanned. Scanned $($batchStatus.ProjectsCount - $batchStatus.Projects.Count) out of $($batchStatus.ProjectsCount) projects. Continuing the scan from start for project $($batchStatus.Projects[0]) `n " -ForegroundColor Green
                        
                    }
                    else
                    {
                        #anyone of the resource has been completely scanned need to make batch size double again
                        if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build_Release' -and $this.isPreviousScanPartiallyComplete() ){
                            if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("BatchSize")){
                                $this.BatchSize = $PSCmdlet.MyInvocation.BoundParameters["BatchSize"]
                            }
                            else {
                                $this.BatchSize = $this.ControlSettings.BatchScan.BatchTrackerUpdateFrequency
                            }
                            if($this.BatchSize%2 -ne 0){
                                $this.BatchSize=$this.BatchSize-1
                            }                           
                            

                        }
                        Write-Host "Found a previous batch scan in progress with $($batchStatus.ResourceCount) resources scanned. Scanned $($batchStatus.ProjectsCount - $batchStatus.Projects.Count) out of $($batchStatus.ProjectsCount) projects. Resuming the scan from last batch for $($batchStatus.Projects[0]). `n " -ForegroundColor Green
                        
                        if($this.CheckContTokenValidity($batchStatus.TokenLastModifiedTime)){
                            return;
                        }
                        else {
                            #find the updated token
                            if($isBuildScan -and $batchStatus.Skip -ne 0 -and (-not [string]::IsNullOrEmpty($batchStatus.BuildCurrentContinuationToken))){
                                $batchStatus.BuildCurrentContinuationToken=$this.GetUpdatedContToken($batchStatus.Skip,$batchStatus.Top,'Build');
                            }
                            if($isReleaseScan -and $batchStatus.Skip -ne 0 -and (-not [string]::IsNullOrEmpty($batchStatus.ReleaseCurrentContinuationToken))){
                                $batchStatus.ReleaseCurrentContinuationToken=$this.GetUpdatedContToken($batchStatus.Skip,$batchStatus.Top,'Release');
                            }
                            
                            $batchStatus.TokenLastModifiedTime=[DateTime]::UtcNow;
                        }
                    }
                }
                else {
                    

                    if($isBuildScan -and $isReleaseScan -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken) -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken)){
                        if($batchStatus.Projects.Length -eq 1){
                            $batchStatus.Projects=@()
                        }
                        else {
                            $batchStatus.Projects = $batchStatus.Projects[1..($batchStatus.Projects.Length-1)]
                        }
                        $batchStatus.Skip=0
                        $batchStatus.ReleaseCurrentContinuationToken=$batchStatus.ReleaseNextContinuationToken
                        $batchStatus.BuildCurrentContinuationToken=$batchStatus.BuildNextContinuationToken
                        $batchStatus.SkipMarker = "False"
                        $batchStatus.BatchScanState=[BatchScanState]::INIT
                        Write-Host "Found a previous batch scan with $($batchStatus.ResourceCount) resources scanned. Scanned $($batchStatus.ProjectsCount - $batchStatus.Projects.Count) out of $($batchStatus.ProjectsCount) projects. Starting fresh scan for the next batch. `n " -ForegroundColor Green
                    }
                    elseif($isReleaseScan -and $isBuildScan -eq $false -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken)){
                        if($batchStatus.Projects.Length -eq 1){
                            $batchStatus.Projects=@()
                        }
                        else {
                            $batchStatus.Projects = $batchStatus.Projects[1..($batchStatus.Projects.Length-1)]
                        }
                        $batchStatus.Skip=0
                        $batchStatus.ReleaseCurrentContinuationToken=$batchStatus.ReleaseNextContinuationToken
                        $batchStatus.BatchScanState=[BatchScanState]::INIT
                        Write-Host "Found a previous batch scan with $($batchStatus.ResourceCount) resources scanned. Scanned $($batchStatus.ProjectsCount - $batchStatus.Projects.Count) out of $($batchStatus.ProjectsCount) projects. Starting fresh scan for the next batch. `n " -ForegroundColor Green
                    }
                    elseif($isBuildScan -and $isReleaseScan -eq $false -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken)) {
                        if($batchStatus.Projects.Length -eq 1){
                            $batchStatus.Projects=@()
                        }
                        else {
                            $batchStatus.Projects = $batchStatus.Projects[1..($batchStatus.Projects.Length-1)]
                        }
                        $batchStatus.Skip=0;
                        $batchStatus.BuildCurrentContinuationToken=$batchStatus.BuildNextContinuationToken
                        $batchStatus.BatchScanState=[BatchScanState]::INIT
                        Write-Host "Found a previous batch scan with $($batchStatus.ResourceCount) resources scanned. Scanned $($batchStatus.ProjectsCount - $batchStatus.Projects.Count) out of $($batchStatus.ProjectsCount) projects. Starting fresh scan for the next batch. `n " -ForegroundColor Green
                    }

                    else {
                    #anyone of the resource has been completely scanned need to make batch size double again
                    if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build_Release' -and $this.isPreviousScanPartiallyComplete() ){
                        if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("BatchSize")){
                            $this.BatchSize = $PSCmdlet.MyInvocation.BoundParameters["BatchSize"]
                        }
                        else {
                            $this.BatchSize = $this.ControlSettings.BatchScan.BatchTrackerUpdateFrequency
                        }
                        if($this.BatchSize%2 -ne 0){
                            $this.BatchSize=$this.BatchSize-1
                        }  
                    }
                    Write-Host "Found a previous batch scan with $($batchStatus.ResourceCount) resources scanned. Scanned $($batchStatus.ProjectsCount - $batchStatus.Projects.Count) projects out of $($batchStatus.ProjectsCount). Starting fresh scan for the next batch. `n " -ForegroundColor Green
                    #anyone of the resource has been completely scanned need to update skip by original batch size. However for the first time skip should only be updated by half
                    if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build_Release' -and $batchStatus.SkipMarker -eq "False" -and $this.isPreviousScanPartiallyComplete() ){
                        
                        $batchStatus.Skip=$batchStatus.Skip + ($this.GetBatchSize()/2);
                        $batchStatus.SkipMarker = "True"
                    }
                    else {
                        $batchStatus.Skip+=$this.GetBatchSize();
                    }
                    
                    
                    
                    $batchStatus.BatchScanState=[BatchScanState]::INIT
                    
                    if($this.CheckContTokenValidity($batchStatus.TokenLastModifiedTime)){
                        
                        if($isReleaseScan) {
                            $batchStatus.ReleaseCurrentContinuationToken=$batchStatus.ReleaseNextContinuationToken
                        }
                        if($isBuildScan){
                            $batchStatus.BuildCurrentContinuationToken=$batchStatus.BuildNextContinuationToken
                        }
                        
                    }
                    else {
                        #find the updated token
                        if($isBuildScan -and $batchStatus.Skip -ne 0 -and (-not [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken))){
                            $batchStatus.BuildCurrentContinuationToken=$this.GetUpdatedContToken($batchStatus.Skip,$batchStatus.Top,'Build');
                        }
                        if($isReleaseScan -and $batchStatus.Skip -ne 0 -and (-not [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken))){
                            $batchStatus.ReleaseCurrentContinuationToken=$this.GetUpdatedContToken($batchStatus.Skip,$batchStatus.Top,'Release');
                        }
                        #in case builds have been completely scanned, i.e end of builds
                        if($isBuildScan -and $batchStatus.Skip -ne 0 -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken)){
                            $batchStatus.BuildCurrentContinuationToken=$batchStatus.BuildNextContinuationToken
                        }
                        #in case releases have been completely scanned, i.e end of releases
                        if($isReleaseScan -and $batchStatus.Skip -ne 0 -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken)){
                            $batchStatus.ReleaseCurrentContinuationToken=$batchStatus.ReleaseNextContinuationToken
                        }
                       
                        $batchStatus.TokenLastModifiedTime=[DateTime]::UtcNow;
                        
                    }
                    }
                   
                }
                $this.BatchScanTrackerObj=$batchStatus;
                $this.WriteToBatchTrackerFile();
            }
        }
       
    }

    hidden [bool] CheckContTokenValidity([DateTime] $lastModifiedTime){
       
        if($lastModifiedTime.AddHours([INT32]::Parse(15)) -lt [DateTime]::UtcNow){
            return $false
        }
        return $true;

    }

    [bool] IsScanComplete(){
        if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build' -and [string]::IsNullOrEmpty($this.GetBuildContinuationToken()) -and [string]::IsNullOrEmpty($this.GetProjectsForCurrentScan())){
            return $true;
        }
        elseif($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Release' -and [string]::IsNullOrEmpty($this.GetReleaseContinuationToken()) -and [string]::IsNullOrEmpty($this.GetProjectsForCurrentScan())){
            return $true;            
        }
        elseif($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build_Release' -and [string]::IsNullOrEmpty($this.GetReleaseContinuationToken()) -and [string]::IsNullOrEmpty($this.GetBuildContinuationToken()) -and [string]::IsNullOrEmpty($this.GetProjectsForCurrentScan())) {
            return $true;
        }
        else{
            $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
            if($batchStatus.UpcError -eq 'False'){
                Write-Host "Another project seems to be in partial commit. Therefore the scan for this project cannot be completed. Either complete the scan for the previous project using the GADS command or delete the file %LOCALAPPDATA%/Microsoft/AzSK.ADO/TempState/PartialScanData/[org_name]" -ForegroundColor Yellow
                return $true;
            }
            else{
                return $false;
            }
        }
        return $false;
    }

    [string] GetProjectsForCurrentScan(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path $this.MasterFilePath){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                if($batchStatus.Projects.Count -eq 0){
                    return $null;
                }
                return $batchStatus.Projects[0]
            }
        }
        return $null
    }

    [bool] IsCurrentProjectComplete(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path $this.MasterFilePath){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                $isReleaseScan=$false;
                $isBuildScan=$false;
                if($batchStatus.PSobject.Properties.name -match "ReleaseCurrentContinuationToken") {
                    $isReleaseScan=$true;
                }
                if($batchStatus.PSobject.Properties.name -match "BuildCurrentContinuationToken") {
                    $isBuildScan=$true;
                }
                if($isBuildScan -and $isReleaseScan -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken) -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken)){
                    return $true
                }
                elseif($isReleaseScan -and $isBuildScan -eq $false -and [string]::IsNullOrEmpty($batchStatus.ReleaseNextContinuationToken)){
                    return $true
                }
                elseif($isBuildScan -and $isReleaseScan -eq $false -and [string]::IsNullOrEmpty($batchStatus.BuildNextContinuationToken)) {
                    return $true
                }

            }
        }
        return $false;
    }

    [string] GetBuildContinuationToken(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path $this.MasterFilePath){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                return $batchStatus.BuildNextContinuationToken;
            }
        }
        return $null;
    }

    [string] GetReleaseContinuationToken(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path $this.MasterFilePath){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                return $batchStatus.ReleaseNextContinuationToken;
            }
        }
        return $null;
    }

    [BatchScanState] GetBatchScanState(){
        if(![string]::isnullorwhitespace($this.OrgName)){
            if(Test-Path $this.MasterFilePath){
                $batchStatus = Get-Content $this.MasterFilePath | ConvertFrom-Json
                return $batchStatus.BatchScanState;
            }
        }
        return $null;
    }

    [string] GetUpdatedContToken([int] $skip, [string] $top, [string] $resourceType){
        $tempSkip=0;
        if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build_Release' -and $this.isPreviousScanPartiallyComplete() ){
            $topNQueryString = '&$top={0}' -f ($this.GetBatchSize()/2); 
        }
        else {
            $topNQueryString = '&$top={0}' -f $this.GetBatchSize();
        }
        
        if($resourceType -eq 'Build'){
            $resourceDefnURL = ("https://dev.azure.com/{0}/{1}/_apis/build/definitions?queryOrder=lastModifiedDescending&api-version=6.0" +$topNQueryString) -f $this.OrgName, $this.GetProjectsForCurrentScan();
        }
        else {
            $resourceDefnURL = ("https://vsrm.dev.azure.com/{0}/{1}/_apis/release/definitions?api-version=6.0" +$topNQueryString) -f $this.OrgName, $this.GetProjectsForCurrentScan();
        }
        
        $continuationToken=$null;
        $originalUri=$resourceDefnURL;
        $validationUrl=$null;
        while($tempSkip -lt $skip){
           $validationUrl=$originalUri;
           $originalUri=$resourceDefnURL;
            
            if($PSCmdlet.MyInvocation.BoundParameters.ResourceTypeName -eq 'Build_Release' -and $this.isPreviousScanPartiallyComplete() ){
                $tempSkip+=($this.GetBatchSize()/2); 
            }
            else {
                $tempSkip+=$this.GetBatchSize(); 
            }            
            $updatedUriAndContToken=[WebRequestHelper]:: InvokeWebRequestForContinuationToken($validationUrl,$originalUri,$tempSkip,$resourceType);
            $continuationToken=$updatedUriAndContToken[0];
            $originalUri=$updatedUriAndContToken[1];

        }
        return $continuationToken;

    }



}
# SIG # Begin signature block
# MIInwQYJKoZIhvcNAQcCoIInsjCCJ64CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBTVCoRyTtjKuOH
# KmwmUdyB6ZuQ4zIZR9r2wgDMraCLiKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGaEwghmdAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHsurvCK4YrMd9CxwhMmXmQc
# xxI51SFsApMnPVX9JgghMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAy4mtzCXRKvfLnExduKHcliecNqeVTRzZ5sJy2IVNJdoW9p+3oirmM7+C
# tjHKwKjSsKmVIc76eLNjZ42UBGXDLVPF4oX7q0bhT14f3mFjZ213gtYv6Pgc4iUS
# 2QeX1RdZQLWyKPwxid8qA3pX+RiEzYCkT0s3RvMk7yg7W+TmMMefHHHkba3MEnrE
# +reQJuacOWZu0Pkm0Atp5P0MITTw4M7pJquGdWD/MtoiUYuuaH4UCTHMBI8t8lvp
# VNlvxpnthYmAjLonbMcrDKzdBUeg/Rv120DyoDesDcISHJIf+mDjfIJt5exzWC1K
# /vFvnsyIr6b1Ai3meVodgXC732UWFqGCFyswghcnBgorBgEEAYI3AwMBMYIXFzCC
# FxMGCSqGSIb3DQEHAqCCFwQwghcAAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCwWVJWmu+nsPISv7+tNif00Nq6YCiM3Pr+UwJ6AcUHzgIGZfHN8Wds
# GBMyMDI0MDMxNDEwMTYwOC4xOTFaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRejCCBycwggUPoAMCAQICEzMAAAHajtXJWgDREbEAAQAAAdowDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMx
# MDEyMTkwNjU5WhcNMjUwMTEwMTkwNjU5WjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJOQBgh2tVFR1j8jQA4NDf8b
# cVrXSN080CNKPSQo7S57sCnPU0FKF47w2L6qHtwm4EnClF2cruXFp/l7PpMQg25E
# 7X8xDmvxr8BBE6iASAPCfrTebuvAsZWcJYhy7prgCuBf7OidXpgsW1y8p6Vs7sD2
# aup/0uveYxeXlKtsPjMCplHkk0ba+HgLho0J68Kdji3DM2K59wHy9xrtsYK+X9er
# bDGZ2mmX3765aS5Q7/ugDxMVgzyj80yJn6ULnknD9i4kUQxVhqV1dc/DF6UBeuzf
# ukkMed7trzUEZMRyla7qhvwUeQlgzCQhpZjz+zsQgpXlPczvGd0iqr7lACwfVGog
# 5plIzdExvt1TA8Jmef819aTKwH1IVEIwYLA6uvS8kRdA6RxvMcb//ulNjIuGceyy
# kMAXEynVrLG9VvK4rfrCsGL3j30Lmidug+owrcCjQagYmrGk1hBykXilo9YB8Qyy
# 5Q1KhGuH65V3zFy8a0kwbKBRs8VR4HtoPYw9z1DdcJfZBO2dhzX3yAMipCGm6Smv
# mvavRsXhy805jiApDyN+s0/b7os2z8iRWGJk6M9uuT2493gFV/9JLGg5YJJCJXI+
# yxkO/OXnZJsuGt0+zWLdHS4XIXBG17oPu5KsFfRTHREloR2dI6GwaaxIyDySHYOt
# vIydla7u4lfnfCjY/qKTAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUoXyNyVE9ZhOV
# izEUVwhNgL8PX0UwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBALmDVdTtuI0jAEt4
# 1O2OM8CU237TGMyhrGr7FzKCEFaXxtoqk/IObQriq1caHVh2vyuQ24nz3TdOBv7r
# cs/qnPjOxnXFLyZPeaWLsNuARVmUViyVYXjXYB5DwzaWZgScY8GKL7yGjyWrh78W
# JUgh7rE1+5VD5h0/6rs9dBRqAzI9fhZz7spsjt8vnx50WExbBSSH7rfabHendpeq
# bTmW/RfcaT+GFIsT+g2ej7wRKIq/QhnsoF8mpFNPHV1q/WK/rF/ChovkhJMDvlqt
# ETWi97GolOSKamZC9bYgcPKfz28ed25WJy10VtQ9P5+C/2dOfDaz1RmeOb27Kbeg
# ha0SfPcriTfORVvqPDSa3n9N7dhTY7+49I8evoad9hdZ8CfIOPftwt3xTX2RhMZJ
# CVoFlabHcvfb84raFM6cz5EYk+x1aVEiXtgK6R0xn1wjMXHf0AWlSjqRkzvSnRKz
# FsZwEl74VahlKVhI+Ci9RT9+6Gc0xWzJ7zQIUFE3Jiix5+7KL8ArHfBY9UFLz4sn
# boJ7Qip3IADbkU4ZL0iQ8j8Ixra7aSYfToUefmct3dM69ff4Eeh2Kh9NsKiiph58
# 9Ap/xS1jESlrfjL/g/ZboaS5d9a2fA598mubDvLD5x5PP37700vm/Y+PIhmp2fTv
# uS2sndeZBmyTqcUNHRNmCk+njV3nMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtYwggI/AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAQqIfIYljHUbNoY0/wjhXRn/sSA2ggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOmcTC8wIhgPMjAyNDAzMTQwMDAwNDdaGA8yMDI0MDMxNTAwMDA0N1owdjA8Bgor
# BgEEAYRZCgQBMS4wLDAKAgUA6ZxMLwIBADAJAgEAAgEHAgH/MAcCAQACAhFhMAoC
# BQDpnZ2vAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA
# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAUtw5/12H6Bjfp9H+
# y1wZVb0O8HHKf2tRl61ucv4CpI7+pfEStFmaY8HY665sKsz/HK0MxivPNx//vnEW
# XPPoyZjJjbrzVVUyooP64gE8RX4Oh8yLHubGa2sJ/N+OIQ3ERkELINztMWgEgZ7z
# Be4OLx6fhJ/JFhhwpSNhwnGFBg8xggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdqO1claANERsQABAAAB2jANBglghkgBZQME
# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ
# BDEiBCCOWP9Gd2Bp2MqBGaARjCvY8FLddWDBGJG2LNuHy5jJSjCB+gYLKoZIhvcN
# AQkQAi8xgeowgecwgeQwgb0EICKlo2liwO+epN73kOPULT3TbQjmWOJutb+d0gI7
# GD3GMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHa
# jtXJWgDREbEAAQAAAdowIgQg+oO2g4ovb9oFZR0kgk5V/qCklSarfpJDgb8tlNi6
# wIkwDQYJKoZIhvcNAQELBQAEggIAjDh3/9LMBcAHkkgbYIoIfnxqefX0mEpDiiIT
# 0BomoMRmL9CfXHiaghtwPF1BIfW6WVTzdMu7p/W7JCy1VCKOFieqkBVu5l3Iir8+
# E4IlYGH3tAVfBr3bYCMRx/NFOR1lplGvz4kDf2LkDteGHJQiBkL/yFtgUSCH5DQp
# Q+rLY9ByFe3+YneIc8q54Fqb3oryi3eGH/7ZXTiMTBmSsAjtle8/Svn4lMO6jetk
# fa2UUcsrVEOc86EBSVQewnOdGwmRqPX7ao5JLsZ9Oh68Ua04YgAOupE3vZD22Mrb
# oeWE7EtYxwXxo1jVGEPiJfk6jQ0jqF7wirQgCWHODeZ5eM3epF78+z2tlH4ZtHPc
# o5O/BWbwzLfaBHTnEiNMk88KmmPI6QFme/bavd65j/KYT/bzVlXmylGYrd4Z4+up
# OIScGak1XLlLIWh/1TvF2P28yfWl1MgqAY7AV+ngw5/LTNHkTGJE/8w+D8Y5u6eU
# pXxDGWxYLb6wSBe4r0Ry1ENaJEPPtnaUsJxv7new69VxLvTxrksjXAluxGpeAfB4
# 5+zsAv3UKUpzWtkuIufhyF9SJK50xoR9FV5YEHpWo2NeQr8OzsuXmsDWhbPzRk3j
# IKe9evfmD8eOlzObomoCEGpFmxMmK/nZSHErAYW8R9Az3Q5HBRtOwxbD85aovKWU
# NG35izA=
# SIG # End signature block