Framework/Abstracts/SVTBase.ps1

<#
.Description
# SVTBase class for all service classes.
# Provides functionality to create context object for resources, load controls for resource,
#>

Set-StrictMode -Version Latest
class SVTBase: AzSKRoot
{
    #Region: Properties
    hidden [string] $ResourceId = ""
    [ResourceContext] $ResourceContext = $null;
    hidden [SVTConfig] $SVTConfig
    hidden [PSObject] $ControlSettings
    
    hidden [ControlStateExtension] $ControlStateExt;
    
    hidden [ControlState[]] $ResourceState;
    hidden [ControlState[]] $DirtyResourceStates;

    hidden [ControlItem[]] $ApplicableControls = $null;
    hidden [ControlItem[]] $FeatureApplicableControls = $null;
    [string[]] $ChildResourceNames = $null;
    [System.Net.SecurityProtocolType] $currentSecurityProtocol;
    #User input parameters for controls
    [string[]] $FilterTags = @();
    [string[]] $ExcludeTags = @();
    [string[]] $ControlIds = @();
    [string[]] $Severity = @();
    [string[]] $ExcludeControlIds = @();
    [hashtable] $ResourceTags = @{}
    [bool] $GenerateFixScript = $false;

    [bool] $IncludeUserComments = $false;
    [string] $PartialScanIdentifier = [string]::Empty
    [ComplianceStateTableEntity[]] $ComplianceStateData = @();
    [PSObject[]] $ChildSvtObjects = @();
    [System.Diagnostics.Stopwatch] $StopWatch
    [Datetime] $ScanStart
    [Datetime] $ScanEnd
    [bool] $IsAIEnabled = $false;
    #EndRegion

    SVTBase([string] $organizationName):
        Base($organizationName)
    {        

    }
    SVTBase([string] $organizationName, [SVTResource] $svtResource):
    Base($organizationName, [SVTResource] $svtResource)
    {        
        $this.CreateInstance($svtResource);
    }


    #Create instance for resource scan
    hidden [void] CreateInstance([SVTResource] $svtResource)
    {
        [Helpers]::AbstractClass($this, [SVTBase]);

        #Region: validation for resource object
        if(-not $svtResource)
        {
            throw [System.ArgumentException] ("The argument 'svtResource' is null");
        }

        if([string]::IsNullOrEmpty($svtResource.ResourceName))
        {
            throw [System.ArgumentException] ("The argument 'ResourceName' is null or empty");
        }
        #EndRegion

        if (-not $svtResource.ResourceTypeMapping)
        {
            throw [System.ArgumentException] ("No ResourceTypeMapping found");
        }

        if ([string]::IsNullOrEmpty($svtResource.ResourceTypeMapping.JsonFileName))
        {
            throw [System.ArgumentException] ("JSON file name is null or empty");
        }

        $this.ResourceId = $svtResource.ResourceId;

        $this.LoadSvtConfig($svtResource.ResourceTypeMapping.JsonFileName);

        $this.ResourceContext = [ResourceContext]@{
            ResourceGroupName = $svtResource.ResourceGroupName;
            ResourceName = $svtResource.ResourceName;
            ResourceType = $svtResource.ResourceTypeMapping.ResourceType;
            ResourceTypeName = $svtResource.ResourceTypeMapping.ResourceTypeName;
            ResourceId = $svtResource.ResourceId
            ResourceDetails = $svtResource.ResourceDetails
        };
        
        #<TODO Framework: Fetch resource group details from resolver itself>
        $this.ResourceContext.ResourceGroupTags = $this.ResourceTags;

        if ([RemoteReportHelper]::IsAIOrgTelemetryEnabled()) { 
            $this.IsAIEnabled =$true
        }
    }

       hidden [void] LoadSvtConfig([string] $controlsJsonFileName)
    {
        $this.ControlSettings = $this.LoadServerConfigFile("ControlSettings.json");

        if (-not $this.SVTConfig) {

            #Check if SVTConfig is present in cache. If so, use that.
            $cachedPolicyContent = [ConfigurationHelper]::PolicyCacheContent | Where-Object { $_.Name -eq $controlsJsonFileName }
            if ($cachedPolicyContent)
            {
                $this.SVTConfig = $cachedPolicyContent.Content
                if ($this.SVTConfig)
                {
                    return
                }
            }
            $this.SVTConfig =  [ConfigurationManager]::GetSVTConfig($controlsJsonFileName);
            
            $this.SVTConfig.Controls | Foreach-Object {

                #Expand description and recommendation string if any dynamic values defined field using control settings
                $_.Description = $global:ExecutionContext.InvokeCommand.ExpandString($_.Description)
                $_.Recommendation = $global:ExecutionContext.InvokeCommand.ExpandString($_.Recommendation)
                
                $ControlSeverity = $_.ControlSeverity
                
                #Check if ControlSeverity is customized/overridden using controlsettings configurations
                if([Helpers]::CheckMember($this.ControlSettings,"ControlSeverity.$ControlSeverity"))
                {
                    $_.ControlSeverity = $this.ControlSettings.ControlSeverity.$ControlSeverity
                }

                if(-not [string]::IsNullOrEmpty($_.MethodName))
                {
                    $_.MethodName = $_.MethodName.Trim();
                }

                #Check if
                if($this.CheckBaselineControl($_.ControlID))
                {
                    $_.IsBaselineControl = $true
                }
                #AddPreviewBaselineFlag
                if($this.CheckPreviewBaselineControl($_.ControlID))
                {
                    $_.IsPreviewBaselineControl = $true
                }
            }
            #Save the final, fully resolved SVTConfig JSON in cache
            #Because we may have the network/local-module content already in cached from a call to [ConfigurationHelper]::LoadServerConfigFile, we need to check first.
            #If there is an entry, we just overwrite the Content portion. If there is on entry, we create a new one.
            [bool] $ConfigFoundInCache = $false
            [ConfigurationHelper]::PolicyCacheContent | Foreach-Object {
                if ($_.Name -eq $controlsJsonFileName)
                {
                    $_.Content = $this.SVTConfig   #Overwrite the cached entry.
                    $ConfigFoundInCache = $true
                } 
            }
            if (-not $ConfigFoundInCache)
            {
                $policy = [Policy]@{
                    Name    = $controlsJsonFileName
                    Content = $this.SVTConfig
                }
                [ConfigurationHelper]::PolicyCacheContent += $policy #Create a new entry.
            }
        }
    }
    #stub to be used when Baseline configuration exists
    hidden [bool] CheckBaselineControl($controlId)
    {
        return $false
    }
    #stub to be used when PreviewBaseline configuration exists
    hidden [bool] CheckPreviewBaselineControl($controlId)
    {
        return $false
    }


    #Check if service is under mentainance and display maintenance warning message
    [bool] ValidateMaintenanceState()
    {
        if ($this.SVTConfig.IsMaintenanceMode) {
            $this.PublishCustomMessage(([ConfigurationManager]::GetAzSKConfigData().MaintenanceMessage -f $this.SVTConfig.FeatureName), [MessageType]::Warning);
        }
        return $this.SVTConfig.IsMaintenanceMode;
    }

    hidden [ControlResult] CreateControlResult([string] $childResourceName, [VerificationResult] $verificationResult)
    {
        [ControlResult] $control = [ControlResult]@{
            VerificationResult = $verificationResult;
        };

        if(-not [string]::IsNullOrEmpty($childResourceName))
        {
            $control.ChildResourceName = $childResourceName;
        }

        [SessionContext] $sc = [SessionContext]::new();
        $sc.IsLatestPSModule = $this.RunningLatestPSModule;
        $control.CurrentSessionContext = $sc;

        return $control;
    }

    [ControlResult] CreateControlResult()
    {
        return $this.CreateControlResult("", [VerificationResult]::Manual);
    }

    hidden [ControlResult] CreateControlResult([FixControl] $fixControl)
    {
        $control = $this.CreateControlResult();
        if($this.GenerateFixScript -and $fixControl -and $fixControl.Parameters -and ($fixControl.Parameters | Get-Member -MemberType Properties | Measure-Object).Count -ne 0)
        {
            $control.FixControlParameters = $fixControl.Parameters | Select-Object -Property *;
        }
        return $control;
    }

    [ControlResult] CreateControlResult([string] $childResourceName)
    {
        return $this.CreateControlResult($childResourceName, [VerificationResult]::Manual);
    }

    [ControlResult] CreateChildControlResult([string] $childResourceName, [ControlResult] $controlResult)
    {
        $control = $this.CreateControlResult($childResourceName, [VerificationResult]::Manual);
        if($controlResult.FixControlParameters -and ($controlResult.FixControlParameters | Get-Member -MemberType Properties | Measure-Object).Count -ne 0)
        {
            $control.FixControlParameters = $controlResult.FixControlParameters | Select-Object -Property *;
        }
        return $control;
    }

    hidden [SVTEventContext] CreateSVTEventContextObject()
    {
        return [SVTEventContext]@{
            FeatureName = $this.SVTConfig.FeatureName;
            Metadata = [Metadata]@{
                Reference = $this.SVTConfig.Reference;
            };

            OrganizationContext = $this.OrganizationContext;
            ResourceContext = $this.ResourceContext;
            PartialScanIdentifier = $this.PartialScanIdentifier
            
        };
    }

    hidden [SVTEventContext] CreateErrorEventContext([System.Management.Automation.ErrorRecord] $exception)
    {
        [SVTEventContext] $arg = $this.CreateSVTEventContextObject();
        $arg.ExceptionMessage = $exception;

        return $arg;
    }

    hidden [void] ControlStarted([SVTEventContext] $arg)
    {
        $this.PublishEvent([SVTEvent]::ControlStarted, $arg);
    }

    hidden [void] ControlDisabled([SVTEventContext] $arg)
    {
        $this.PublishEvent([SVTEvent]::ControlDisabled, $arg);
    }

    hidden [void] ControlCompleted([SVTEventContext] $arg)
    {
        $this.PublishEvent([SVTEvent]::ControlCompleted, $arg);
    }

    hidden [void] ControlError([ControlItem] $controlItem, [System.Management.Automation.ErrorRecord] $exception)
    {
        $arg = $this.CreateErrorEventContext($exception);
        $arg.ControlItem = $controlItem;
        $this.PublishEvent([SVTEvent]::ControlError, $arg);
    }

    hidden [void] EvaluationCompleted([SVTEventContext[]] $arguments)
    {
        $this.PublishEvent([SVTEvent]::EvaluationCompleted, $arguments);
    }

    hidden [void] EvaluationStarted()
    {
        $this.PublishEvent([SVTEvent]::EvaluationStarted, $this.CreateSVTEventContextObject());
    }
    
    hidden [void] EvaluationError([System.Management.Automation.ErrorRecord] $exception)
    {
        $this.PublishEvent([SVTEvent]::EvaluationError, $this.CreateErrorEventContext($exception));
    }

    [SVTEventContext[]] EvaluateAllControls()
    {
        [SVTEventContext[]] $resourceSecurityResult = @();
        if (-not $this.ValidateMaintenanceState()) {
            if($this.GetApplicableControls().Count -eq 0)
            {
                if($this.ResourceContext)
                {
                    $this.PublishCustomMessage("No controls have been found to evaluate for Resource [$($this.ResourceContext.ResourceName)]", [MessageType]::Warning);
                    $this.PublishCustomMessage("$([Constants]::SingleDashLine)");
                }
                else
                {
                    $this.PublishCustomMessage("No controls have been found to evaluate for organization", [MessageType]::Warning);
                }
            }
            else
            {
                $this.PostTelemetry();
                $this.EvaluationStarted();    
                $resourceSecurityResult += $this.GetAutomatedSecurityStatus();
                $resourceSecurityResult += $this.GetManualSecurityStatus();            
                
                $this.InvokeExtensionMethod($resourceSecurityResult)
                #Call the ADOSVTBase PostEvaluationCompleted method which read the attestation data and modify conntrol result.
                $this.PostEvaluationCompleted($resourceSecurityResult);
                $this.EvaluationCompleted($resourceSecurityResult);
            }
        }
        return $resourceSecurityResult;
    }

    [SVTEventContext[]] RescanAndPostAttestationData()
    {
        [SVTEventContext[]] $resourceScanResult = @();
        [SVTEventContext[]] $stateResult = @();
        [ControlItem[]] $controlsToBeEvaluated = @();

        $this.PostTelemetry();
        #Publish event to display host message to indicate start of resource scan
        $this.EvaluationStarted();    
        #Fetch attested controls list from Blob
        $stateResult = $this.GetControlsStateResult($true)
        If (($stateResult | Measure-Object).Count -gt 0 )
        {
            #Get controls list which were attested in last 24 hours
            $attestedControlsinBlob = $stateResult | Where-Object {$_.ControlResults.StateManagement.AttestedStateData.AttestedDate -gt ((Get-Date).AddDays(-1))}
            if (($attestedControlsinBlob | Measure-Object).Count -gt 0 )
            {
                $attestedControlsinBlob | ForEach-Object {
                    $controlsToBeEvaluated += $_.ControlItem
                };
                $this.ApplicableControls = @($controlsToBeEvaluated);
                $resourceScanResult += $this.GetAutomatedSecurityStatus();
                $resourceScanResult += $this.GetManualSecurityStatus();

                $this.PostEvaluationCompleted($resourceScanResult);
                $this.EvaluationCompleted($resourceScanResult);
            }
            else {
                Write-Host "No attested control found.`n$([Constants]::SingleDashLine)" 
            }
        }
        else {
            Write-Host "No attested control found.`n$([Constants]::SingleDashLine)" 
        }
         return $resourceScanResult;
    }

    [void] PostTelemetry()
    {
        # Setting the protocol for databricks
        if([Helpers]::CheckMember($this.ResourceContext, "ResourceType") -and $this.ResourceContext.ResourceType -eq "Microsoft.Databricks/workspaces")
        {
            $this.currentSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        }
        $this.PostFeatureControlTelemetry()
    }

    [void] PostFeatureControlTelemetry()
    {
        #todo add check for latest module version
        if($this.RunningLatestPSModule -and ($this.FeatureApplicableControls | Measure-Object).Count -gt 0)
        {
            [CustomData] $customData = [CustomData]::new();
            $customData.Name = "FeatureControlTelemetry";
            $ResourceObject = "" | Select ResourceContext, Controls, ChildResourceNames;
            $ResourceObject.ResourceContext = $this.ResourceContext;
            $ResourceObject.Controls = $this.FeatureApplicableControls;
            $ResourceObject.ChildResourceNames = $this.ChildResourceNames;
            $customData.Value = $ResourceObject;
            $this.PublishCustomData($customData);        
        }
    }

    [SVTEventContext[]] FetchStateOfAllControls()
    {
        [SVTEventContext[]] $resourceSecurityResult = @();
        if (-not $this.ValidateMaintenanceState()) {
            if($this.GetApplicableControls().Count -eq 0)
            {
                $this.PublishCustomMessage("No security controls match the input criteria specified", [MessageType]::Warning);
            }
            else
            {
                $this.EvaluationStarted();
                $resourceSecurityResult += $this.GetControlsStateResult();
                if(($resourceSecurityResult | Measure-Object).Count -gt 0)
                {
                    $this.EvaluationCompleted($resourceSecurityResult);
                }
            }
        }
        return $resourceSecurityResult;
    }


    [ControlItem[]] ApplyServiceFilters([ControlItem[]] $controls)
    {
        return $controls;
    }

    hidden [ControlItem[]] GetApplicableControls()
    {
        #Lazy load the list of the applicable controls
        if($null -eq $this.ApplicableControls)
        {
            $this.ApplicableControls = @();
            $this.FeatureApplicableControls = @();
            $filterControlsById = @();
            $filteredControls = @();

            #Apply service filters based on default set of controls
            $this.FeatureApplicableControls += $this.ApplyServiceFilters($this.SVTConfig.Controls);

            if($this.ControlIds.Count -ne 0)
            {
                $filterControlsById += $this.FeatureApplicableControls | Where-Object { $this.ControlIds -Contains $_.ControlId };
            }
            else
            {
                $filterControlsById += $this.FeatureApplicableControls
            }

            if($this.ExcludeControlIds.Count -ne 0)
            {
                $filterControlsById = $filterControlsById | Where-Object { $this.ExcludeControlIds -notcontains $_.ControlId };
            }

            #Filter controls based on filterstags and excludetags
            $filterTagsCount = ($this.FilterTags | Measure-Object).Count
            $excludeTagsCount = ($this.ExcludeTags | Measure-Object).Count

            #filters controls based on Severity
            if($this.Severity.Count -ne 0 -and ($filterControlsById | Measure-Object).Count -gt 0)
            {
                $filterControlsById = $filterControlsById | Where-Object {$_.ControlSeverity -in $this.Severity };                
            }

            
            $unfilteredControlsCount = ($filterControlsById | Measure-Object).Count

            if($unfilteredControlsCount -gt 0) #If we have any controls at this point...
            {
                #If FilterTags are specified, limit the candidate set to matching controls
                if ($filterTagsCount -gt 0)
                {
                    #Look at each candidate control's tags and see if there's a match in FilterTags
                    $filterControlsById | ForEach-Object {
                        Set-Variable -Name control -Value $_ -Scope Local
                        Set-Variable -Name filterMatch -Value $false -Scope Local
                        
                        $filterMatch = $false
                                                
                        $control.Tags | ForEach-Object {
                                                    Set-Variable -Name cTag -Value $_ -Scope Local

                                                    if( ($this.FilterTags | Where-Object { $_ -like $cTag} | Measure-Object).Count -ne 0)
                                                    {
                                                        $filterMatch = $true
                                                    }
                                                }

                        #Add if this control has a tag that matches FilterTags
                        if ($filterMatch) 
                        {
                            $filteredControls += $control
                        }   
                    }                     
                }
                else #No FilterTags specified, so all controls qualify
                {
                    $filteredControls = $filterControlsById
                }

                #Note: Candidate controls list is now in $filteredControls...we will use that to calculate $filteredControlsFinal
                $filteredControlsFinal = @()
                if ($excludeTagsCount -eq 0)
                {
                    #If exclude tags are not specified, then not much to do.
                    $filteredControlsFinal = $filteredControls
                }
                else 
                {
                    #ExludeTags _are_ specified, we need to check if candidate set has to be reduced...
                    
                    #Look at each candidate control's tags and see if there's a match in ExcludeTags
                    $filteredControls | ForEach-Object {
                        Set-Variable -Name control -Value $_ -Scope Local
                        Set-Variable -Name excludeMatch -Value $false -Scope Local
                        $excludeMatch = $false

                        $control.Tags | ForEach-Object {
                              Set-Variable -Name cTag -Value $_ -Scope Local

                              if(($this.ExcludeTags | Where-Object { $_ -like $cTag} | Measure-Object).Count -ne 0)
                              {
                                    $excludeMatch = $true
                              }
                        }
                        
                        #Add to final list if this control *does-not* have a tag that matches ExcludeTags
                        if (-not $excludeMatch) 
                        {
                            $filteredControlsFinal += $control
                        }   
                    }
                    $filteredControls = $filteredControlsFinal                
                } 
            }

            $this.ApplicableControls = $filteredControls;
            #this filtering has been done as the first step it self;
            #$this.ApplicableControls += $this.ApplyServiceFilters($filteredControls);
            
        }
        return $this.ApplicableControls;
    }

    hidden [SVTEventContext[]] GetManualSecurityStatus()
    {
        [SVTEventContext[]] $manualControlsResult = @();
        try
        {
            $this.GetApplicableControls() | Where-Object { $_.Automated -eq "No" -and $_.Enabled -eq $true } |
            ForEach-Object {
                $controlItem = $_;
                [SVTEventContext] $arg = $this.CreateSVTEventContextObject();

                $arg.ControlItem = $controlItem;
                [ControlResult] $control = [ControlResult]@{
                    VerificationResult = [VerificationResult]::Manual;
                };

                [SessionContext] $sc = [SessionContext]::new();
                $sc.IsLatestPSModule = $this.RunningLatestPSModule;
                $control.CurrentSessionContext = $sc;

                $arg.ControlResults += $control
                
                $this.PostProcessData($arg);

                $manualControlsResult += $arg;
            }
        }
        catch
        {
            $this.EvaluationError($_);
        }

        return $manualControlsResult;
    }

    hidden [SVTEventContext[]] GetAutomatedSecurityStatus()
    {
        [SVTEventContext[]] $automatedControlsResult = @();

        if ($this.IsAIEnabled)
        {
            $this.StopWatch = [System.Diagnostics.Stopwatch]::StartNew();
        }
        
        $this.DirtyResourceStates = @();
        try
        {
            $this.GetApplicableControls() | Where-Object { $_.Automated -ne "No" -and (-not [string]::IsNullOrEmpty($_.MethodName)) } |
            ForEach-Object {
                $evaluateControl = $true; 
                # if control is disabled and warning message is also disabled in org policy than do not evaluate the control.
                if ($this.ControlSettings -and [Helpers]::CheckMember($this.ControlSettings, "DisableWarningMessage") -and $this.ControlSettings.DisableWarningMessage -eq $true -and $_.Enabled -eq $false) {
                        $evaluateControl = $false;
                }
                if ($evaluateControl)
                {
                    $eventContext = $this.RunControl($_);
                    if($null -ne $eventContext -and $eventcontext.ControlResults.Length -gt 0)
                    {
                        $automatedControlsResult += $eventContext;
                    }
                }
            };
        }
        catch
        {
            $this.EvaluationError($_);
        }

        return $automatedControlsResult;
    }

    hidden [SVTEventContext[]] GetControlsStateResult($isRescan = $false)
    {
        [SVTEventContext[]] $automatedControlsResult = @();
        $this.DirtyResourceStates = @();
        try
        {
            $this.GetApplicableControls() |
            ForEach-Object {
                $eventContext = $this.FetchControlState($_, $isRescan);
                #filter controls if there is no state found
                if($eventContext)
                {
                    $eventContext.ControlResults = $eventContext.ControlResults | Where-Object{$_.AttestationStatus -ne [AttestationStatus]::None}
                    if($eventContext.ControlResults)
                    {
                        $automatedControlsResult += $eventContext;
                    }
                }
            };
        }
        catch
        {
            $this.EvaluationError($_);
        }

        return $automatedControlsResult;
    }
    
    hidden [SVTEventContext] RunControl([ControlItem] $controlItem)
    {
        [SVTEventContext] $singleControlResult = $this.CreateSVTEventContextObject();
        $singleControlResult.ControlItem = $controlItem;

        $this.ControlStarted($singleControlResult);
        if($controlItem.Enabled -eq $false)
        {
            $this.ControlDisabled($singleControlResult);
        }
        else
        {
            $azskScanResult = $this.CreateControlResult($controlItem.FixControl);
            try
            {
                $methodName = $controlItem.MethodName;
                #$this.CurrentControlItem = $controlItem;

                #Getting scan time for each control. This is being done to monitor perf issues in ADOScanner internally
                if ($this.IsAIEnabled)
                {
                    $this.ScanStart = [DateTime]::UtcNow
                    $this.StopWatch.Restart()
                    $scanResult = $this.$methodName($azskScanResult);
                    $this.StopWatch.Stop()
                    $this.ScanEnd = [DateTime]::UtcNow

                    $scanResult.TimeTakenInMs = $this.StopWatch.ElapsedMilliseconds
                    $scanResult.ScanStartDateTime = $this.ScanStart
                    $scanResult.ScanEndDateTime = $this.ScanEnd

                    $singleControlResult.ControlResults += $scanResult    
                }    
                else
                {
                    $singleControlResult.ControlResults += $this.$methodName($azskScanResult);
                }        
            }
            catch
            {
                $azskScanResult.VerificationResult = [VerificationResult]::Error                
                $azskScanResult.AddError($_);
                $singleControlResult.ControlResults += $azskScanResult
                $this.ControlError($controlItem, $_);
            }

            $this.PostProcessData($singleControlResult);

            $this.InvokeExtensionMethod($singleControlResult);

            # Check for the control which requires elevated permission to modify 'Recommendation' so that user can know it is actually automated if they have the right permission
            if($singleControlResult.ControlItem.Automated -eq "Yes")
            {
                $singleControlResult.ControlResults |
                    ForEach-Object {
                    $currentItem = $_;
                    if($_.VerificationResult -eq [VerificationResult]::Manual -and $singleControlResult.ControlItem.Tags.Contains([Constants]::OwnerAccessTagName))
                    {
                        $singleControlResult.ControlItem.Recommendation = [Constants]::RequireOwnerPermMessage + $singleControlResult.ControlItem.Recommendation
                    }
                }
            }
        }

        $this.ControlCompleted($singleControlResult);

        return $singleControlResult;
    }
    
    
    # Policy compliance methods begin
    hidden [ControlResult] ComputeFinalScanResult([ControlResult] $azskScanResult, [ControlResult] $policyScanResult)
    {
        if($policyScanResult.VerificationResult -ne [VerificationResult]::Failed -and $azskScanResult.VerificationResult -ne [VerificationResult]::Passed)
        {
            return $azskScanResult
        }
        else
        {
            return $policyScanResult;
        }
    }
    
    hidden AddResourceMetadata([PSObject] $metadataObj)
    {
        [hashtable] $resourceMetadata = New-Object -TypeName Hashtable;
            $metadataObj.psobject.properties |
                ForEach-Object {
                    $resourceMetadata.Add($_.name, $_.value)
                }

        if([Helpers]::CheckMember($this.ControlSettings, 'AllowedResourceTypesForMetadataCapture') )
        {
            if( $this.ResourceContext.ResourceTypeName -in $this.ControlSettings.AllowedResourceTypesForMetadataCapture)
            {
                $this.ResourceContext.ResourceMetadata = $resourceMetadata
            }
            else
            {
                $this.ResourceContext.ResourceMetadata = $null
            }
        }
        else 
        {
            $this.ResourceContext.ResourceMetadata = $resourceMetadata
        }

    }

    hidden [SVTResource] CreateSVTResource([string] $ConnectionResourceId,[string] $ResourceGroupName, [string] $ConnectionResourceName, [string] $ResourceType, [string] $Location, [string] $MappingName)
    {
        $svtResource = [SVTResource]::new();
        $svtResource.ResourceId = $ConnectionResourceId; 
        $svtResource.ResourceGroupName = $ResourceGroupName;
        $svtResource.ResourceName = $ConnectionResourceName
        $svtResource.ResourceType = $ResourceType; #
        $svtResource.Location = $Location;
        $svtResource.ResourceTypeMapping = ([SVTMapping]::AzSKADOResourceMapping |
                        Where-Object { $_.ResourceTypeName -eq $MappingName } |
                        Select-Object -First 1);

        return $svtResource;
    }
  
    #stub to be used when ComplianceState
    hidden [void] GetDataFromSubscriptionReport($singleControlResult)
       { 
        
    }

    [int] hidden CalculateGraceInDays([SVTEventContext] $context)
    {
        
        $controlResult=$context.ControlResults;
        $computedGraceDays=15;
        $ControlBasedGraceExpiryInDays=0;
        $currentControlItem=$context.controlItem;
        $controlSeverity=$currentControlItem.ControlSeverity;
        if([Helpers]::CheckMember($this.ControlSettings,"NewControlGracePeriodInDays"))
        {
            if([Helpers]::CheckMember($this.ControlSettings,"ControlSeverity"))
            {
                $controlsev = $this.ControlSettings.ControlSeverity.PSobject.Properties | Where-Object Value -eq $controlSeverity | Select-Object -First 1
                $controlSeverity = $controlsev.name
                $computedGraceDays=$this.ControlSettings.NewControlGracePeriodInDays.ControlSeverity.$ControlSeverity;
            }
            else
            {
                $computedGraceDays=$this.ControlSettings.NewControlGracePeriodInDays.ControlSeverity.$ControlSeverity;
            }
        }
        if($null -ne $currentControlItem.GraceExpiryDate)
        {
            if($currentControlItem.GraceExpiryDate -gt [DateTime]::UtcNow )
            {
                $ControlBasedGraceExpiryInDays=$currentControlItem.GraceExpiryDate.Subtract($controlResult.FirstScannedOn).Days
                if($ControlBasedGraceExpiryInDays -gt $computedGraceDays)
                {
                    $computedGraceDays = $ControlBasedGraceExpiryInDays
                }
            }            
        }

      return $computedGraceDays;
    }    
}

# SIG # Begin signature block
# MIIjkwYJKoZIhvcNAQcCoIIjhDCCI4ACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBFav2sP+P3I+Xd
# 3QyujBVZCmaWLmvQ5BRMbyKnelSoTqCCDYEwggX/MIID56ADAgECAhMzAAAB32vw
# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn
# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw
# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS
# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG
# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh
# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH
# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS
# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp
# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok
# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4
# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao
# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD
# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt
# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G
# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+
# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82
# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVaDCCFWQCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN
# BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgptbeejGP
# f35doKnkYNpvPMw1RJUylyzT0jY7kNQrUUQwRAYKKwYBBAGCNwIBDDE2MDSgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g
# MA0GCSqGSIb3DQEBAQUABIIBAJjrQoYROhA9MEfW7j+PivksPtrNxcGxikEA02G9
# Cjo4DY70i86RNhovFsr6Zs0d8GHb06aKteEfVw7KJ7tRyIDjNu1f59BiICxR6cMs
# 3Qt7gWrg2/Bbg6T5hB6cEcmcAHJDg5NpM0fUpkluTWDqFrgixG+XkrsKWzMi2x53
# xPRbxo2hguDxJWX68nt0xfj2nIqrgt3O2Z7KBfRa1ya7OzYTFgFD+uQKvU/fgbec
# iYMBIzQQq9NZ9p4Pdh7gxVo8iE0CGbTeeE+2QqasIMZL3XIhlVS/d07sNptX3uQd
# WB9oCOhuFTg+ul92ijxNQAxWvHKky2JDjQdmEGMNJd+5u1ShghLwMIIS7AYKKwYB
# BAGCNwMDATGCEtwwghLYBgkqhkiG9w0BBwKgghLJMIISxQIBAzEPMA0GCWCGSAFl
# AwQCAQUAMIIBVAYLKoZIhvcNAQkQAQSgggFDBIIBPzCCATsCAQEGCisGAQQBhFkK
# AwEwMTANBglghkgBZQMEAgEFAAQgfPIvHrPW5RQOZMEJe5XgTSb0mzswVPH+Z7VJ
# 1G/VRrsCBmBjLeOBMxgSMjAyMTA1MTcwNjM2NTguMDhaMASAAgH0oIHUpIHRMIHO
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBN
# aWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVz
# IFRTUyBFU046Rjg3QS1FMzc0LUQ3QjkxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2Wggg5EMIIE9TCCA92gAwIBAgITMwAAAWOLZMbJhZZldgAA
# AAABYzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDAeFw0yMTAxMTQxOTAyMjNaFw0yMjA0MTExOTAyMjNaMIHOMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3Bl
# cmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046Rjg3
# QS1FMzc0LUQ3QjkxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZp
# Y2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtcRf2Ep3JdGKS/6jd
# hZ38I39IvGvguBC8+VGctyTlOeFx/qx79cty4CmBrt8K7TUhGOB8+c+j0ZRCb7+2
# itrZu1rxUnrO4ixYUNPA1eIVcecxepZebjdrYtnyTWeiQ4zElWLmP8GmHTRaOzeJ
# fMfO/9UkyKG9zw4mqgKBGdYRG5rka+OBCj/90Q4KPwGNKNNcwBeJOR78q389Nxmi
# SGehCCIG2GxOhNi19nCWfet2jWD2S2FWzZ074ju6dnhh7WgJJ9PEK81vac9Whgk1
# JQy0VC5zIkFSzYoGlNb/Dk87+2pQCJ05UXxS7zyFdCSdkj6vsFS8TxoYlbMBK1/f
# P7M1AgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQUCTXK8XZyZ+4/MVqfRseQPtffPSkw
# HwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmg
# R4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWlj
# VGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEF
# BQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1T
# dGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggr
# BgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAAohBggfXjuJTzo4yAmH7E6mpvSKn
# UbTI9tFAQVS4bn7z/cb5aCPC2fcDj6uLAqCUnYTC2sFFmXeu7xZTP4gT/u15KtdP
# U2nkEhODXPbnjNeX5RL2qOGbcxqFk3MaQvmpWGNJFRiI+ksQUsZwpKGXrE+OFlSE
# wUC/+Nz5h8VQBQ9AtXA882uZ79Qkog752eKjcaT+mn/SGHymyQeGycQaudhWVUKk
# eHQOjWux+LE4YdQGP6mHOpM5kqYVLxMwqucT2fPk5bKDTWWM+kwEeqp3n09g/9w7
# J+15jvsDYyIugBFkCR2qsAe0eTlju0Ce6dO0Zf+E75DTM72ZfAQUn1+2IzCCBnEw
# ggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoX
# DTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRr
# dFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmx
# MEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKE
# HnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBi
# sV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpO
# BpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMB
# AAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPND
# e3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQD
# AgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb
# 186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29t
# L3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoG
# CCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1Ud
# IAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2j
# oSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJE
# Evu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5
# SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJK
# J/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yj
# ojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0
# v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgi
# CGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iC
# tHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO
# 2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyX
# UHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWz
# fjUeCLraNtvTX4/edIhJEqGCAtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg
# T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046
# Rjg3QS1FMzc0LUQ3QjkxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2WiIwoBATAHBgUrDgMCGgMVAO0sYB7dSd0qk00qsy3KzBmUAWHvoIGDMIGA
# pH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQAC
# BQDkTEpLMCIYDzIwMjEwNTE3MDU1MDAzWhgPMjAyMTA1MTgwNTUwMDNaMHcwPQYK
# KwYBBAGEWQoEATEvMC0wCgIFAORMSksCAQAwCgIBAAICIZcCAf8wBwIBAAICETkw
# CgIFAORNm8sCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgC
# AQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQA7i1GJ0tEy5XjS
# Eq0e1tv6IVKmLEE4k+jQKH2XPqQB9RF0Fbnbgd3JnpNn8XKgMhO9tB1W44aShzvL
# yhpfSHY7t7aDRTBO9s5P8xCWTalaZNni+ApEMPVzRulM9irDULfMHvkVcs8xqiF7
# PY8UpPcn/i1CqDrlpS0VluAeTCGSaDGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABY4tkxsmFlmV2AAAAAAFjMA0GCWCGSAFl
# AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN
# AQkEMSIEIMQ4a2iJLluWTqLMxfOJlO8syfqoPs6nJ25wCXKkbvcDMIH6BgsqhkiG
# 9w0BCRACLzGB6jCB5zCB5DCBvQQgnFndlx2hY6EopCm4uMvQGASKSwcvUW9ep7NR
# qxH0I2owgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# AWOLZMbJhZZldgAAAAABYzAiBCCaXIdpHyLtM7amcN3W0cPczk3vO2L5ABCH9ODZ
# CoShqjANBgkqhkiG9w0BAQsFAASCAQCj9Jp6qTlMf1KknAKXdR7kzsAsoHf6mkc2
# 079GHfYM8g+8pXON5uTAf2PO3MKHsqKmFHme1FmTFHeHJxrkax18NLpO/SaDjnWo
# BIaCNRYP5HrkX2tR24tfiEjJS9fxG0xkEaTHcSXB187zO3D6ipOZEuRX+qo6iWgo
# lAWhA+TftqysbpotvdxLyz23fP01+x/FDz368JRkAxN6NjDi+tMh2MsDgBysiB/V
# eCjGGjyaeSJu3NMOYt9AansYqLZdxdZtbJDqsDleDo1LqCV35h8g7LdFHPBBSNu8
# J5FphPlxNNBcZHOIHOrbav6r/SW6sZ7pJ1S9rvuxx32tuSaswlO1
# SIG # End signature block