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
# MIInvAYJKoZIhvcNAQcCoIInrTCCJ6kCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB4zQvuCbjHzPp7
# hTc0TYCHtsWxI7Lm8dn1TtRz7vf7BaCCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIZkTCCGY0CAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgMvE1YIbU
# WOhTCEzsAOkV5DL4XDFA0l0V2gnRxg+U8rIwRAYKKwYBBAGCNwIBDDE2MDSgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g
# MA0GCSqGSIb3DQEBAQUABIIBAEftbn3AOJyvkVpSsyO9GeCkME4oEsWrmR1wkIFM
# i12WdtGAn7IsIzZljtd1osIdNuCP+NTa0PhpBN0DoTaXrKSUB5hXRY/YLcQuTFg4
# DCrbFZZgYwJrwaRC2Fz1cFCrPJBMWYdS/hRy87XjvMlLvY2i32IcxyRfUBVfsKAf
# chovL8K02/t6AqclaZXtE7+jRpMR1Y+VQfrJ4RK3azvwnnepkMa9//AGuEjqKt17
# L4oWOPdQ3e5+LJLVfvWM6Bj0z+jSK+R92l/DAGmNakF60zj98/7KZ6xLpaI2Ic08
# xy57oIA8B2VMNLfIMKcOdoxie/jdztAJHW5wECU0faMtyvGhghcZMIIXFQYKKwYB
# BAGCNwMDATGCFwUwghcBBgkqhkiG9w0BBwKgghbyMIIW7gIBAzEPMA0GCWCGSAFl
# AwQCAQUAMIIBWQYLKoZIhvcNAQkQAQSgggFIBIIBRDCCAUACAQEGCisGAQQBhFkK
# AwEwMTANBglghkgBZQMEAgEFAAQgZCUZE70OUH0RbmL2fdlZq9fKrcEkgHw9/U5x
# ss28/1sCBmHCS7WR9RgTMjAyMjAxMTMxMjU0MDQuMDk0WjAEgAIB9KCB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjpGQzQxLTRCRDQtRDIyMDElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaCCEWgwggcUMIIE/KADAgECAhMzAAABjlnbRgCo
# EJTMAAEAAAGOMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwMB4XDTIxMTAyODE5Mjc0NVoXDTIzMDEyNjE5Mjc0NVowgdIxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29m
# dCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRT
# UyBFU046RkM0MS00QkQ0LUQyMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqIwKr
# o4zMtrnCu1x/sF/1/VZdb2Smps4HOA8m9oYutkbu3CDCpNFZ/UWSOkkoE4blA8hH
# LtrogVUDLRb60FoVfgifgNsF/a0a5vJj36FFT6FF7A8kfkJg6+k+HvsV8Yzrf46X
# BFenlgRHq0LRZEqfeavJam3gLAAO4a/QsPZCOeGGC1FWJ2yIOef35ouMy41qlSGy
# 0aoKvslxBm3Rms9Qdb9OpMnKZ5TV6qA2isRtN53pRDItpNUCaFc1BcMKF9rnbqdb
# tFDsvi1df0tzSiC6IJKQ+W7l2s0Do0zzP5RdA4AfFV8hBeaL5jdJXaGvZ1zFvL2e
# VPNi9/hkOvlalzC3u1N1EgmJtcexdYwquY7OKWIYNOgvHfhr9y1kTg04ueWspBY3
# 1kb47HnNFqsrUSFKFqEzS5A2FvoEmOnf5zR43MwUnaotmoOnb/diXlD7iT4wMctO
# Kk/pUF3Fx1V93iaCtVPHdp87ko1+AyeAYZ+FJrAatpFbbwSGss6ymYjGKL3YTu+O
# dwna1yOEsKMECtWk+HdxkbmDdlXLmKIBqgNqzCk2CcwUArlSfWt4r4wWE+L2Iye9
# Q/C1MMMm3lgQPkMBPeYFTGnlAfe3tGFnAAk0tYu2lN4YXu5vzWxIpi/Zqt1rMB71
# ctLXS1Xag4+tyeIYnOVdbtU+/GpA3F7yUIMDbQIDAQABo4IBNjCCATIwHQYDVR0O
# BBYEFOxB+TP2BPjg3/kby80drJ6n5pXkMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl
# 0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1T
# dGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAww
# CgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggIBADVQr3HMh5OUqFJsufRt6RHm
# BiHpsHVdCmW4KoIDXmyRnftSKVoUnDz4aSUMvOU2FI/aY6I7NXIKrySSmqd9RzTV
# loF7NDGDvup6+PaIzKDf1gl96IIiHFg4W6DB6lis87wyUH+i579WEiINvV41hOU9
# Ka/elqWyC2StvSijQXS5aZfgRBUYpGjRQQ21l61UjFrQn0OlR2NBY94SH1wQz5GA
# TbrnDlYBVv5Y3HSJaIXiJNsKatZpUQ5f3Z02oGb1tPVTucbA3kLKCk0CARpietMz
# HU3gCPE5sAIM3kN28aW787QN8xZVzqqTqIoMULpaldBKQyWuVcgj82Gn7T6ehTq1
# 8Skcou3t9ib2h/mL9CiZwLCj96SI4KNiS2nf3ei+gLU7a4u1sucxWUTmtoEsE1Js
# g1npAvGIDjWVedVUsjMOKFQvwxT0Iy8bix1uGTYVfzO+uw/k94EjDV9p6cxm7PRX
# dRcK1Tk6THl+aKhaKItlIKLWWNrFf4ETBCKKcKL68Tn1tNgjkVu5Hy0O4YuJW78l
# KlUVevNYw/YqfXWwIsAYSOolhSY7W1Fjb0p3sdwiPaeJIHQ9A4KNiWcKfLCcOKep
# LUJe9GyKyNWVLVGnhOa6Sz6kbGIwbMXnzNxv6GgUrI424vdv62DFMDPewXcFVf26
# T0zkX44Sh7IvIZ8t6Q0CMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAA
# FTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0
# aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9s
# SuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3
# po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2
# vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GP
# sjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3
# rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDP
# c31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8F
# A6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q
# 6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1f
# MHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLv
# jflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGj
# ggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+
# ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIw
# XAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMG
# A1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsG
# A1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJc
# YmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9z
# b2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0
# MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5H
# ZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2
# HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1
# JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8
# F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99J
# o3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4K
# WN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZ
# kWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58
# oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w
# /ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+
# 7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1iz
# oXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGQzQxLTRC
# RDQtRDIyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIj
# CgEBMAcGBSsOAwIaAxUAPWIr6k9OEeqClrnGw+aJiu5ZW4yggYMwgYCkfjB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOWKcyow
# IhgPMjAyMjAxMTMxNzQ1MTRaGA8yMDIyMDExNDE3NDUxNFowdzA9BgorBgEEAYRZ
# CgQBMS8wLTAKAgUA5YpzKgIBADAKAgEAAgIMtQIB/zAHAgEAAgIRPzAKAgUA5YvE
# qgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6Eg
# oQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBALVb+KvqJCyWOPcsG46ttM5a
# 7ROSZdVdcWQACMNBcHoVNzANj9c3teTNJy3aOV2PUREYAhoDsMloW4eLMTVk67KD
# osY0GOMVZXTJUbIfNTRShC0GzjqUHSnX6sBQDwr7j2ULRqb22J3W6aKhUvrhPNv/
# Z5dZv4RA31KC+zq6Y4WVMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTACEzMAAAGOWdtGAKgQlMwAAQAAAY4wDQYJYIZIAWUDBAIBBQCg
# ggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg
# Y4d0ey3gWhHlm/+SNn8sd1+UcoTVrNszVGP36emedhswgfoGCyqGSIb3DQEJEAIv
# MYHqMIHnMIHkMIG9BCC9BY8hO+KBpS5Xn5/+VazcFdnAn+XWZ/7J6W7mdebEJTCB
# mDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABjlnbRgCo
# EJTMAAEAAAGOMCIEIAyyIyFOA9hhdp8oK6mDo9GWvtfCq6cuxyeiRoaW+ZxVMA0G
# CSqGSIb3DQEBCwUABIICAEOYMFLVREX53LGjlMDcCf5RNf+584yG/yWbLhV83H3t
# oeK67ISfENvH7JK0va3LOmnD8U1bvWS3FURPBkkVYWjyrqAel0ZWPY0MlIIDJatM
# uKndExo9qe18Pwi1E1rUYDppMcVSJBEA72V5Hgfp8XENYA9wNq6AMuSnoY77XmOo
# 25PNOY+uA7fbNVTOgy8iaTmexdGAwSeBJqcpqOn3FjPvo36Wd0a2QGs0HaEG4k2E
# utgXHcAdwQsYTRMcy6gPJsF/ITDDm6437soOB07lbPv7lUxx7hZ5+0dmE5uSraK7
# BRx5XcVqtu67vScJAHza9+2k6t6CXMNRrZXYO79coO5/ABytlTdol6bvsIO4aQTl
# N3M62/fYSsCIo9qqUhDmK/8TInFVCpUuzCzaAykKbbwYG2vG480QP4J9uh3reAW5
# eIvKcZuhjgv8m7vyPW3s/RM443WliGWGADov1w9ZdvUAGQBQH3d8ANd/DeQEKPXl
# WuS9FAqVdPzcyXBWuWF1+pHY6nHcHDjmXfuwHYU1GmRt1+7Y7AszH70k+dqJMtJi
# KXg6paE/wRsa9C1PQPfym2JKj4OLY9Ljv3kne+Iabqe1t1TZ3Z0f9tHGY2TCniqS
# cME79ZBMc3s2KhgzEVOhL3e5SAwcYTkhBOt8VUE5vUbrwsEvQ+zQ6o1LvzMvCuRm
# SIG # End signature block