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] $UndoFix = $false;
    [bool] $ControlFixBackupRequired = $false;
    [bool] $BaselineConfigurationRequired = $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.ResourceContext.ResourceTypeName #$this.ResourceContext.ResourceTypeName bcz feature and rtn is same and feature name is coming from control.json file, in case of generic it will have generic name
            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 applicablecontrol is already there in singleton object case, then need to filter again for different resourcetype
        #Second condition (in case of singleton) ApplicableControls will not empty for second resource scan in and check if resource type is different
        if($null -eq $this.ApplicableControls -or ($this.ApplicableControls -and !($this.ApplicableControls[0].Id.StartsWith($this.ResourceContext.ResourceTypeName)) ) )
        {
            $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 };                
            }

            if ($this.ControlSettings -and [Helpers]::CheckMember($this.ControlSettings, "DisableInheritedPermControls") -and $this.ControlSettings.DisableInheritedPermControls -eq $true) {
                $filterControlsById = $filterControlsById | Where-Object { $_.ControlId -notlike "*Disable_Inherited_Permissions*" };
            }

            
            $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);
            
        }
        #filter control for generic common control
        if ($this.SVTConfig.FeatureName -eq "CommonSVTControls") {
            $controlstoscan = @();
            $controlstoscan += $this.ApplicableControls | Where {$_.Id.StartsWith($this.ResourceContext.ResourceTypeName)};
            $this.ApplicableControls = $controlstoscan;
        }
        
        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);
            if ($this.invocationContext.BoundParameters["UndoFix"]) { 
                $this.UndoFix =$true
            }
            if ($this.invocationContext.BoundParameters["PrepareForControlFix"]) { 
                $this.ControlFixBackupRequired =$true
            }
            if($this.invocationContext.MyCommand.Name -eq "Set-AzSKADOBaselineConfigurations"){
                $this.BaselineConfigurationRequired = $true;
            }

            try
            {
                $methodName = $controlItem.MethodName;
                if($this.invocationContext.MyCommand.Name -eq "Set-AzSKADOSecurityStatus")
                {
                    $methodName = $methodName+"AutomatedFix"
                }
                #$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
# MIInogYJKoZIhvcNAQcCoIInkzCCJ48CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB4zQvuCbjHzPp7
# hTc0TYCHtsWxI7Lm8dn1TtRz7vf7BaCCDYUwggYDMIID66ADAgECAhMzAAADTU6R
# phoosHiPAAAAAANNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI4WhcNMjQwMzE0MTg0MzI4WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDUKPcKGVa6cboGQU03ONbUKyl4WpH6Q2Xo9cP3RhXTOa6C6THltd2RfnjlUQG+
# Mwoy93iGmGKEMF/jyO2XdiwMP427j90C/PMY/d5vY31sx+udtbif7GCJ7jJ1vLzd
# j28zV4r0FGG6yEv+tUNelTIsFmmSb0FUiJtU4r5sfCThvg8dI/F9Hh6xMZoVti+k
# bVla+hlG8bf4s00VTw4uAZhjGTFCYFRytKJ3/mteg2qnwvHDOgV7QSdV5dWdd0+x
# zcuG0qgd3oCCAjH8ZmjmowkHUe4dUmbcZfXsgWlOfc6DG7JS+DeJak1DvabamYqH
# g1AUeZ0+skpkwrKwXTFwBRltAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUId2Img2Sp05U6XI04jli2KohL+8w
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMDUxNzAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# ACMET8WuzLrDwexuTUZe9v2xrW8WGUPRQVmyJ1b/BzKYBZ5aU4Qvh5LzZe9jOExD
# YUlKb/Y73lqIIfUcEO/6W3b+7t1P9m9M1xPrZv5cfnSCguooPDq4rQe/iCdNDwHT
# 6XYW6yetxTJMOo4tUDbSS0YiZr7Mab2wkjgNFa0jRFheS9daTS1oJ/z5bNlGinxq
# 2v8azSP/GcH/t8eTrHQfcax3WbPELoGHIbryrSUaOCphsnCNUqUN5FbEMlat5MuY
# 94rGMJnq1IEd6S8ngK6C8E9SWpGEO3NDa0NlAViorpGfI0NYIbdynyOB846aWAjN
# fgThIcdzdWFvAl/6ktWXLETn8u/lYQyWGmul3yz+w06puIPD9p4KPiWBkCesKDHv
# XLrT3BbLZ8dKqSOV8DtzLFAfc9qAsNiG8EoathluJBsbyFbpebadKlErFidAX8KE
# usk8htHqiSkNxydamL/tKfx3V/vDAoQE59ysv4r3pE+zdyfMairvkFNNw7cPn1kH
# Gcww9dFSY2QwAxhMzmoM0G+M+YvBnBu5wjfxNrMRilRbxM6Cj9hKFh0YTwba6M7z
# ntHHpX3d+nabjFm/TnMRROOgIXJzYbzKKaO2g1kWeyG2QtvIR147zlrbQD4X10Ab
# rRg9CpwW7xYxywezj+iNAc+QmFzR94dzJkEPUSCJPsTFMIIHejCCBWKgAwIBAgIK
# 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/Xmfwb1tbWrJUnMTDXpQzTGCGXMwghlvAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA
# A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIDLx
# NWCG1FjoUwhM7ADpFeQy+FwxQNJdFdoJ0cYPlPKyMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAaa4ejGxFHsMVDBXibn83DzCYkyBUUQiIgVp2
# KACc086WOW55fPCjy6s2tDqjd4oOGSdhQb5+VDzzUwVrxWsgbW12Dr8dXmbXfVyH
# Wo0dg2dXa8qRBg06VK6iEhIGb8SPtaVcTSoDY0FuCIvIHiW30A9ShlnAnTtpgPf/
# ocvX8L037vuSYEbfP5nvgkQLl06495zKw6+nGw1knNRzhUXneFc/cTkH+9B/EtXH
# /HBz5Bw31R98aWb0ArO0NF9O7ziJVrDjinjtScNEU7vzz4o1IUGULrqphWMleELT
# 3zOc5MTCo1hmyEGHf6PsOHjpP25K7lpQ2BaApdNOjEKc0+rcUaGCFv0wghb5Bgor
# BgEEAYI3AwMBMYIW6TCCFuUGCSqGSIb3DQEHAqCCFtYwghbSAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCAAr+m7UJqU8mqs/ZHzwXJLVD3z3KTFIcLQ
# 72V4CyGf0wIGZIthurNNGBMyMDIzMDYxNjA4MjcwNC45MzJaMASAAgH0oIHQpIHN
# MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjo0OUJDLUUzN0EtMjMzQzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZaCCEVQwggcMMIIE9KADAgECAhMzAAABwFWkjcNkFcVLAAEA
# AAHAMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTIyMTEwNDE5MDEyNVoXDTI0MDIwMjE5MDEyNVowgcoxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy
# aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjQ5QkMtRTM3
# QS0yMzNDMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvO1g+2NhhmBQvlGlCTOMaFw3
# jbIhUdDTqkaQhRpdHVb+huU/0HNhLmoRYvrp7z5vIoL1MPAkVBFWJIkrcG7sSred
# nyZwreY207C9n8XivL9ZBOQeiUeL/TMlJ6VinrcafbhdnkNO5JDlPozC9dGySiub
# ryds5GKtu69D1wNat9DIQl6alFO6pncZK4RIzfv+KzkM7RkY3vHphV0C8EFUpF+l
# ysaGJXFf9QsUUHwj9XKWHfc9BfhLoCReXUzvgrspdFmVnA9ATYXmidSjrshf8A+E
# 0/FpTdhXPI9XXqsZDHBqr7DlYoSCU3lvrVDRu1p5pHHf7s3kM16HpK6arDtY3ai1
# soASmEpv3C2N/y5MDBApDd4SpSkLMa7+6es/daeS7zdH1qdCa2RoJPM6Eh/6YmBf
# ofhfLQofKPJl34ALlZWK5AzVtFRNOXacoj6MAG2dT8Rc5fpKCH1E3n7Zje0dK24Q
# VfSv/YOxw52ECaMLlW5PhHT3ZINNaCmRgcHCTClOKzC2FOr03YBc2zPOW6bIVdXl
# oPmBMVaE+thXqPmANBw0YsncaOkVggjDb5O5VqOp98MklHpJoJI6pk5zAlx8/OtC
# 7FutrdtYNUC6ykXzMAPFuYkWGgx/W7A0itKW8WzYzwO3bAhprwznouGZmRiw2k8p
# en80BzqzdyPvbzTxQsMCAwEAAaOCATYwggEyMB0GA1UdDgQWBBQARMZ480jwpK3P
# 6quVWUEJ0c30hTAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G
# CSqGSIb3DQEBCwUAA4ICAQCtTh0EQn16kKQyCeVk9Vc10m6L0EwLRo3ATRouP7Yd
# 2hWeEB2Y4ZF4CJKe9qfXWGJKzV7tMUm6DAsBKYH/nT+8ybI8uJiHGnfnVi6Sh7gF
# jnTpfh1j1T90H/uLeoFjpOn/+eoCoJmorW5Gb2ezlTlo5I0kNAubxtCxqbLizuPN
# Pob8kRAKQgv+4/CC1JmiUFG0uKINlKj9SsHcrWeBBQHX62nNgziIwT44JqHrA02I
# 6cmQAi9BZcsf57OOLpRYlzoPH3x/+ldSySXAmyLq2uSbWtQuD84I/0ZgS/B5L3ew
# qTdiE1KbKX89MW5JqCK/yI/mAIQammAlHPqU9eZZTMPOHQs0XrpCijlk+qyo2JaH
# iySww6nuPqXzU3sEj3VW00YiVSayKEu1IrRzzX3La8qe6OqLTvK/6gu5XdKq7TT8
# 52nB6IP0QM+Budtr4Fbx4/svpKHGpK9/zBuaHHDXX5AoSksh/kSDYKfefQIhIfQJ
# JzoE3X+MimMJrgrwZXltb6j1IL0HY3qCpa03Ghgi0ITzqfkw3Man3G8kB1Ql+SeN
# ciPUj73Kn2veJenGLtT8JkUM9RUi0woO0iuY4tJnYuS+SeqavXUOWqUYVY19FIr1
# PLqpmWkbrO5xKjkyOHoAmLxjNbKjOnkAwft+1G00kulKqzqPbm+Sn+47JsGQFhNG
# bTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL
# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV
# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X
# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM
# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm
# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB
# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb
# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO
# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw
# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW
# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w
# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK
# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2
# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH
# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB
# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v
# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM
# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF
# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW
# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH
# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF
# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx
# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+
# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2
# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw
# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7
# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO
# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL
# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L
# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5
# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE
# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLLMIICNAIB
# ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE
# CxMdVGhhbGVzIFRTUyBFU046NDlCQy1FMzdBLTIzM0MxJTAjBgNVBAMTHE1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVABAQ7ExF19Kk
# wVL1E3Ad8k0Peb6doIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTAwDQYJKoZIhvcNAQEFBQACBQDoNojvMCIYDzIwMjMwNjE2MTUwODMxWhgPMjAy
# MzA2MTcxNTA4MzFaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOg2iO8CAQAwBwIB
# AAICEbgwBwIBAAICEqowCgIFAOg32m8CAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYK
# KwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUF
# AAOBgQB5hrAjhDwLOLUGuQGPEQXnLtg07nhh4up/60H9nffx5dwkRw4sXGYcsC4H
# rIu5NFHB0YbYtoizLSuLJgjNkwSm6A7KStVVflG6fmFnsCl75ONjP68nb7vdCzkT
# D/aF38W1wM3ukr8cwlzpG01UAWeRI7/lw+4mKFZyNpMpXw6TyzGCBA0wggQJAgEB
# MIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABwFWkjcNkFcVL
# AAEAAAHAMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcN
# AQkQAQQwLwYJKoZIhvcNAQkEMSIEINFG5eHQ4Hf+O5G2Uu0lzRBXwscRt6BDin6q
# mFgrAjYyMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgWvFYolIIXME0zK/W
# 6XsCkkYX7lYNb9yA8JxwY04Pk08wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMAITMwAAAcBVpI3DZBXFSwABAAABwDAiBCBcw+CFklPWX+w6JJ20
# DVz8/dNvemeX32PJoZ2QzCuBYDANBgkqhkiG9w0BAQsFAASCAgAU+EMbEgjfEVJA
# GlBhp+puJaELw7+6GE86D8g15NwSMerlG8SSX+UvtTr3VycHBntDvCcTcCtIEPnl
# PnBZIwzgqByoqQw6iOEKbq08CfpycAAwcRUC+Eia9acg1f37QW/iPegiqGglqP1y
# DX7i9VAA5vb1aJqXhTkwbfMgINCBgRRfOAogLox3VBR5wfVAoLBXkCgP+e+QX/pg
# lQ4E+CNt/lhQu6S4E/1ZnbAvmSRNmnqJ5mMI2EQpZdmfcT/EkR4p4Np5YjiA51gE
# brY7ZR9yqLrIkAr0a39zCvI2pnIP/WDjN9fHdzKZ1yqn0ediRZ3aWekzaKNzqj7l
# PQC1w73sxJckywYJDFZ2XYHS2QtgCpuIsL8Vkax3Byt8Ptx1R8qdU4WopH/5Sv27
# 2VQhzleBX8b22WDk9uPr+V1j+zivaSAwMouWhlo7CLEBCGIrDhPEseHV2374B5zC
# tRVmdNJiuixsrhTHV+8FlVDqOmRLceig0tTCnghp0pA2sAMURw5J9LO6UhuY3GeK
# +htDIcoagDiapZgTRzlSrw6gm+pTXYuvvgfEbirDNJkNjw2nq0mP3PkvQug/X5gu
# QP86rvYQxcPP5sqWx/VizlA1mv1UMYsYlsqWwk8RqIcCJq5j09LwSo2Kw2GciuRD
# tx8Cmo9XiCe6XBhCtn1PQFu53UBs9g==
# SIG # End signature block