Framework/Abstracts/SVTBase.ps1

Set-StrictMode -Version Latest 
class SVTBase: AzSdkRoot
{
    hidden [string] $ResourceId = ""
    [ResourceContext] $ResourceContext = $null;
    hidden [SVTConfig] $SVTConfig
    hidden [PSObject] $ControlSettings

    hidden [ControlStateExtension] $ControlStateExt;
    hidden [ControlState[]] $ResourceState;

    hidden [ControlItem[]] $ApplicableControls = $null;

    [string[]] $FilterTags = @();
    [string[]] $ExcludeTags = @();
    [string[]] $ControlIds = @();
    [bool] $GenerateFixScript = $false;

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

    SVTBase([string] $subscriptionId): 
        Base($subscriptionId)
    {
        $this.CreateInstance();
    }

    SVTBase([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): 
        Base($subscriptionId)
    {
        $this.CreateInstance([SVTResource]@{
            ResourceGroupName = $resourceGroupName;
            ResourceName = $resourceName;
        });
    }
    hidden [void] CreateInstance()
    {
        [Helpers]::AbstractClass($this, [SVTBase]);
        
        $this.LoadSvtConfig([SVTMapping]::SubscriptionMapping.JsonFileName);
      
        #$this.ControlStateIndexer = [ControlStateManager]::ComputeControlStateIndexer();
        #$this.ControlStates = $this.GetControlStates();
    }
    hidden [void] CreateInstance([SVTResource] $svtResource)
    {
        [Helpers]::AbstractClass($this, [SVTBase]); 

        if(-not $svtResource)
        {
            throw [System.ArgumentException] ("The argument 'svtResource' is null");
        }

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

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

        if(-not $svtResource.ResourceTypeMapping)
        {
            $svtResource.ResourceTypeMapping = [SVTMapping]::Mapping | 
                                        Where-Object { $_.ClassName -eq $this.GetType().Name } | 
                                        Select-Object -First 1
        }

        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;
        }; 
        $this.ResourceContext.ResourceId = $this.GetResourceId();

    }

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

        if (-not $this.SVTConfig) {
            $this.SVTConfig =  [ConfigurationManager]::GetSVTConfig($controlsJsonFileName); 
                        
            #Evaluate all code block in Description and Recommendation texts.
            #Can use '$this.ControlSettings.xyz' in text to fill dynamic settings.
            $this.SVTConfig.Controls | Foreach-Object {
                
                $_.Description = $global:ExecutionContext.InvokeCommand.ExpandString($_.Description)
                $_.Recommendation = $global:ExecutionContext.InvokeCommand.ExpandString($_.Recommendation)
                if(-not [string]::IsNullOrEmpty($_.MethodName))
                {
                    $_.MethodName = $_.MethodName.Trim();
                }
            }
        }
    }

    hidden [string] GetResourceId()
    {
        if ([string]::IsNullOrEmpty($this.ResourceId)) 
        {
            if($this.ResourceContext)
            {
                   $resource = Get-AzureRmResource -ResourceName $this.ResourceContext.ResourceName -ResourceGroupName $this.ResourceContext.ResourceGroupName 

                if($resource)
                {
                    $this.ResourceId = $resource.ResourceId;
                }
                else
                {
                    throw "Unable to find the Azure resource - [ResourceType: $($this.ResourceContext.ResourceType)] [ResourceGroupName: $($this.ResourceContext.ResourceGroupName)] [ResourceName: $($this.ResourceContext.ResourceName)]" 
                }
            }
            else
            {
                $this.ResourceId = $this.SubscriptionContext.Scope;
            }
        }
        
        return $this.ResourceId;
    }

    [bool] ValidateMaintenanceState()
    {  
        if ($this.SVTConfig.IsManintenanceMode) {
            $this.PublishCustomMessage(([ConfigurationManager]::GetAzSdkConfigData().MaintenanceMessage -f $this.SVTConfig.FeatureName), [MessageType]::Warning);
        }
        return $this.SVTConfig.IsManintenanceMode;
    }  

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

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

        return $control;
    }

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

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

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

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

            SubscriptionContext = $this.SubscriptionContext;
            ResourceContext = $this.ResourceContext;           
        };
    }

    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);
                }
                else
                {
                    $this.PublishCustomMessage("No controls have been found to evaluate for Subscription", [MessageType]::Warning);
                }
            }
            else
            {
                $this.EvaluationStarted();
                $resourceSecurityResult += $this.GetAutomatedSecurityStatus();
                $resourceSecurityResult += $this.GetManualSecurityStatus();
                $this.EvaluationCompleted($resourceSecurityResult);           
            }
        }
        return $resourceSecurityResult;         
    }

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

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

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

            if(($this.FilterTags | Measure-Object).Count -ne 0 -or ($this.ExcludeTags | Measure-Object).Count -ne 0)
            {
                $filterControlsById | ForEach-Object {
                    Set-Variable -Name control -Value $_ -Scope Local
                    Set-Variable -Name filterMatch -Value $false -Scope Local
                    Set-Variable -Name excludeMatch -Value $false -Scope Local
                    $control.Tags | ForEach-Object {
                        Set-Variable -Name cTag -Value $_ -Scope Local
                        
                        if(($this.FilterTags | Measure-Object).Count -ne 0 `
                            -and ($this.FilterTags | Where-Object { $_ -like $cTag} | Measure-Object).Count -ne 0)
                        {
                            $filterMatch = $true
                        }
                        elseif(($this.FilterTags | Measure-Object).Count -eq 0)
                        {
                            $filterMatch = $true
                        }
                        if(($this.ExcludeTags | Measure-Object).Count -ne 0 `
                            -and ($this.ExcludeTags | Where-Object { $_ -like $cTag} | Measure-Object).Count -ne 0)
                        {
                            $excludeMatch = $true
                        }                                            
                    }

                    if(($filterMatch  -and $excludeMatch -le 0) `
                            -or ($filterMatch -lt 0 -and $excludeMatch -le 0))                            
                    {
                        $filteredControls += $control            
                    }
                }
            }
            else
            {
                $filteredControls += $filterControlsById;
            }

            $this.ApplicableControls += $this.ApplyServiceFilters($filteredControls);
        }
        return $this.ApplicableControls;
    }

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

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

                $this.ProcessStateData($arg);

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

        return $manualControlsResult;
    }

    hidden [SVTEventContext[]] GetAutomatedSecurityStatus()
    {
        [SVTEventContext[]] $automatedControlsResult = @();
        try 
        {
            $this.GetApplicableControls() | Where-Object { $_.Automated -ne "No" -and (-not [string]::IsNullOrEmpty($_.MethodName)) } |
            ForEach-Object {
                $eventContext = $this.RunControl($_);
                if($eventContext)
                {
                    $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 
        {
            $controlResult = $this.CreateControlResult($controlItem.FixControl);

            try 
            {
                $methodName = $controlItem.MethodName;
                #$this.CurrentControlItem = $controlItem;
                $singleControlResult.ControlResults += $this.$methodName($controlResult);          
            }
            catch 
            {
                $controlResult.VerificationResult = [VerificationResult]::Error              
                $controlResult.AddError($_);
                $singleControlResult.ControlResults += $controlResult;
                $this.ControlError($controlItem, $_);                
            }
            $this.ProcessStateData($singleControlResult);
        }
    
        $this.ControlCompleted($singleControlResult);

        return $singleControlResult;
    }

    hidden [void] ProcessStateData([SVTEventContext] $eventContext)
    {
        $controlState = @();

        if($this.GetResourceState().Count -ne 0)
        {
            $controlStateValue =  $this.GetResourceState() | Where { $_.InternalId -eq $eventContext.ControlItem.Id };
            if($null -ne $controlStateValue -and $this.IsStateActive($eventContext.ControlItem,$controlStateValue))
            {
                $controlState += $controlStateValue ;
            }
        }
            
        $eventContext.ControlResults | 
        ForEach-Object {
            try
            {
                $currentItem = $_;
                # Copy the current result to Actual Result field
                $currentItem.ActualVerificationResult = $currentItem.VerificationResult;

                # Disable the fix control feature
                if(-not $this.GenerateFixScript)
                {
                    $currentItem.EnableFixControl = $false;
                }

                if($currentItem.StateManagement.CurrentStateData -and $currentItem.StateManagement.CurrentStateData.DataObject -and $eventContext.ControlItem.DataObjectProperties)
                {
                    $currentItem.StateManagement.CurrentStateData.DataObject = [Helpers]::SelectMembers($currentItem.StateManagement.CurrentStateData.DataObject, $eventContext.ControlItem.DataObjectProperties);
                }

                if($controlState.Count -ne 0)
                {
                    # Skip passed ones from State Management
                    if($currentItem.VerificationResult -ne [VerificationResult]::Passed)
                    {
                        # Process the state if its available
                        $childResourceState = $controlState | Where-Object { $_.ChildResourceName -eq  $currentItem.ChildResourceName } | Select-Object -First 1;
                        if($childResourceState -and ($childResourceState.ActualVerificationResult -eq $currentItem.ActualVerificationResult) -and $childResourceState.State)
                        {
                            $currentItem.StateManagement.AttestedStateData = $childResourceState.State;

                            # Compare dataobject property of State
                            if($null -ne $childResourceState.State.DataObject)
                            {
                                if($currentItem.StateManagement.CurrentStateData -and $null -ne $currentItem.StateManagement.CurrentStateData.DataObject)
                                {
                                    $currentStateDataObject = [Helpers]::ConvertToJsonCustom($currentItem.StateManagement.CurrentStateData.DataObject) | ConvertFrom-Json
                                    if([Helpers]::CompareObject($childResourceState.State.DataObject, $currentStateDataObject, $true))
                                    {
                                        # Objects match, change result based on attestation status
                                        $this.ModifyControlResult($currentItem, $childResourceState);
                                    }
                                    #else
                                    #{
                                        # Drift in security state. Objects are not matching. No modification of result required
                                    #}
                                }
                                #else
                                #{
                                    # Drift in security state. Control State found but no dataobject is passed in current execution
                                #}
                            }
                            else
                            {
                                if($currentItem.StateManagement.CurrentStateData)
                                {
                                    if($null -eq $currentItem.StateManagement.CurrentStateData.DataObject)
                                    {
                                        # No object is persisted, change result based on attestation status
                                        $this.ModifyControlResult($currentItem, $childResourceState);
                                    }
                                }
                                else
                                {
                                    # No object is persisted, change result based on attestation status
                                    $this.ModifyControlResult($currentItem, $childResourceState);
                                }
                            }                    
                        }
                        #else
                        #{
                            # No state object found which matches the verification result. Ignore the state. No modification of result required
                        #}
                    }
                    #else
                    #{
                        # Ignore State for passed ones. No modification of result required
                    #}
                }
            }
            catch 
            {
                $this.EvaluationError($_);
            }
        };
    }

    # State Machine implementation of modifying verification result
    hidden [void] ModifyControlResult([ControlResult] $controlResult, [ControlState] $controlState)
    {
        # No action required if Attestation status is None OR verification result is Passed
        if($controlState.AttestationStatus -ne [AttestationStatus]::None -or $controlResult.VerificationResult -ne [VerificationResult]::Passed)
        {
            $controlResult.AttestationStatus = $controlState.AttestationStatus;
            $controlResult.VerificationResult = [Helpers]::EvaluateVerificationResult($controlResult.VerificationResult, $controlState.AttestationStatus);            
        }
    }

    hidden [ControlState[]] GetResourceState()
    {        
        if($null -eq $this.ResourceState)
        {
            $this.ResourceState = @();
            if($this.ControlStateExt)
            {
                $this.ResourceState += $this.ControlStateExt.GetControlState($this.GetResourceId());            

            }
        }
        
        return $this.ResourceState;
    }

    #Function to validate attestation data expiry validation
    hidden [bool] IsStateActive([ControlItem] $controlItem,[ControlState] $controlState)
    {
        try
        {
            #Get controls expiry period. Default value is zero
            $controlAttestationExpiry = $controlItem.AttestationExpiryPeriodInDays
            $controlSeverity = $controlItem.ControlSeverity
            $controlSeverityExpiryPeriod = 0
            $numberOfDayToAdd =0;
                    
            #Check if control severity expiry is not default value zero
            if($controlAttestationExpiry -ne 0)
            {
                $numberOfDayToAdd = $controlAttestationExpiry
            }
            elseif([Helpers]::CheckMember($this.ControlSettings,"AttestationExpiryPeriodInDays"))
            {
                #Check if control severity has expiry period
                if([Helpers]::CheckMember($this.ControlSettings.AttestationExpiryPeriodInDays.ControlSeverity,$controlSeverity) )
                {
                    $numberOfDayToAdd = $this.ControlSettings.AttestationExpiryPeriodInDays.ControlSeverity.$controlSeverity
                }
                #If control item and severity does not contain expiry period, assign default value
                else
                {
                    $numberOfDayToAdd = $this.ControlSettings.AttestationExpiryPeriodInDays.Default
                }
            }
            #Return true when expiry period is not defined
            else{
                return $true
            }
            #Validate if expiry period is passed
            if($numberOfDayToAdd -ne 0 -and $controlState.State.AttestedDate.AddDays($numberOfDayToAdd) -lt [DateTime]::UtcNow)
            {
                return $false
            }
            else 
            {
                return $true
            }        
        }
        catch{
            #if any exception occurs while getting/validating expiry period, return true.
            $this.PublishCustomMessage("Not able to fetch control state expiry",$_);
            return $true
        }
    }

    hidden [ControlResult] CheckDiagnosticsSettings([ControlResult] $controlResult)
    {
        $diagnostics = Get-AzureRmDiagnosticSetting -ResourceId $this.GetResourceId()
        if($diagnostics -and ($diagnostics.Logs | Measure-Object).Count -ne 0)
        {
            $nonCompliantLogs = $diagnostics.Logs | 
                                Where-Object { -not ($_.Enabled -and                                              
                                            ($_.RetentionPolicy.Days -eq $this.ControlSettings.Diagnostics_RetentionPeriod_Forever -or 
                                            $_.RetentionPolicy.Days -eq $this.ControlSettings.Diagnostics_RetentionPeriod_Min))};

            $selectedDiagnosticsProps = $diagnostics | Select-Object -Property Logs, Metrics, StorageAccountId, ServiceBusRuleId, Name;

            if(($nonCompliantLogs | Measure-Object).Count -eq 0)
            {
                $controlResult.AddMessage([VerificationResult]::Passed, 
                    "Diagnostics settings are correctly configured for resource - [$($this.ResourceContext.ResourceName)]",
                    $selectedDiagnosticsProps);
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Failed, 
                    "Diagnostics settings are either disabled OR not retaining logs for at least $($this.ControlSettings.Diagnostics_RetentionPeriod_Min) days for resource - [$($this.ResourceContext.ResourceName)]",
                    $selectedDiagnosticsProps);
            }
        }
        else
        {
            $controlResult.AddMessage("Not able to fetch diagnostics settings. Please validate diagnostics settings manually for resource - [$($this.ResourceContext.ResourceName)].");
        }

        return $controlResult;
    }

    hidden [ControlResult] CheckRBACAccess([ControlResult] $controlResult)
    {
        $accessList = [RoleAssignmentHelper]::GetAzSDKRoleAssignmentByScope($this.GetResourceId(), $false, $true);   


        $resourceAccessList = $accessList | Where-Object { $_.Scope -eq $this.GetResourceId() };   

        $controlResult.VerificationResult = [VerificationResult]::Verify; 
        
        if(($resourceAccessList | Measure-Object).Count -ne 0)
        {
            $controlResult.SetStateData("Identities having RBAC access at resource level", ($resourceAccessList | Select-Object -Property ObjectId,RoleDefinitionId,RoleDefinitionName,Scope));

            $controlResult.AddMessage("Validate that the following identities have explicitly provided with RBAC access to resource - [$($this.ResourceContext.ResourceName)]");
            $controlResult.AddMessage([MessageData]::new($this.CreateRBACCountMessage($resourceAccessList), $resourceAccessList));
        }
        else
        {   
            $controlResult.AddMessage("No identities have been explicitly provided with RBAC access to resource - [$($this.ResourceContext.ResourceName)]");
        }  
            
        $inheritedAccessList = $accessList | Where-Object { $_.Scope -ne $this.GetResourceId() };
    
        if(($inheritedAccessList | Measure-Object).Count -ne 0)
        {
            $controlResult.AddMessage("Note: " + $this.CreateRBACCountMessage($inheritedAccessList) + " have inherited RBAC access to resource. It's good practice to keep the RBAC access to minimum.");
        }
        else
        {
            $controlResult.AddMessage("No identities have inherited RBAC access to resource");         
        }
      
        return $controlResult;
    }

    hidden [string] CreateRBACCountMessage([array] $resourceAccessList)
    {
        $nonNullObjectTypes = $resourceAccessList | Where-Object { -not [string]::IsNullOrEmpty($_.ObjectType) };
        if(($nonNullObjectTypes | Measure-Object).Count -eq 0)
        {
            return "$($resourceAccessList.Count) identities";
        }
        else
        {
            $countBreakupString = [string]::Join(", ", 
                                    ($nonNullObjectTypes | 
                                        Group-Object -Property ObjectType -NoElement | 
                                        ForEach-Object { "$($_.Name): $($_.Count)" }
                                    ));
            return "$($resourceAccessList.Count) identities ($countBreakupString)";
        }
    }

    hidden [bool] CheckMetricAlertConfiguration([PSObject[]] $metricSettings, [ControlResult] $controlResult, [string] $extendedResourceName)
    {
        $result = $false;
        if($metricSettings -and $metricSettings.Count -ne 0)
        {
            $resId = $this.GetResourceId() + $extendedResourceName;
            $resIdMessageString = "";
            if(-not [string]::IsNullOrWhiteSpace($extendedResourceName))
            {
                $resIdMessageString = "for nested resource [$extendedResourceName]";
            }

            $resourceAlerts = (Get-AzureRmAlertRule -ResourceGroup $this.ResourceContext.ResourceGroupName -DetailedOutput -WarningAction SilentlyContinue) | 
                                Where-Object { $_.Properties -and $_.Properties.Condition -and $_.Properties.Condition.DataSource } |
                                Where-Object { $_.Properties.Condition.DataSource.ResourceUri -eq $resId }; 
                     
            $nonConfiguredMetrices = @();
            $misConfiguredMetrices = @();

            $metricSettings    | 
            ForEach-Object {
                $currentMetric = $_;
                $matchedMetrices = @();
                $matchedMetrices += $resourceAlerts | 
                                    Where-Object { $_.Properties.Condition.DataSource.MetricName -eq $currentMetric.Condition.DataSource.MetricName }

                if($matchedMetrices.Count -eq 0)
                {
                    $nonConfiguredMetrices += $currentMetric;
                }
                else
                {
                    $misConfigured = @();
                    $misConfigured += $matchedMetrices | Where-Object { -not [Helpers]::CompareObject($currentMetric, $_.Properties) };

                    if($misConfigured.Count -eq $matchedMetrices.Count)
                    {
                        $misConfiguredMetrices += $misConfigured;
                    }
                }
            }

            $controlResult.AddMessage("Following metric alerts must be configured $resIdMessageString with settings mentioned below:", $metricSettings); 
            $controlResult.VerificationResult = [VerificationResult]::Failed;

            if($nonConfiguredMetrices.Count -ne 0)
            {
                $controlResult.AddMessage("Following metric alerts are not configured $($resIdMessageString):", $nonConfiguredMetrices); 
            }

            if($misConfiguredMetrices.Count -ne 0)
            {
                $controlResult.AddMessage("Following metric alerts are not correctly configured $resIdMessageString. Please update the metric settings in order to comply.", $misConfiguredMetrices); 
            }

            if($nonConfiguredMetrices.Count -eq 0 -and $misConfiguredMetrices.Count -eq 0)
            {
                $result = $true;
                $controlResult.AddMessage([VerificationResult]::Passed , "All mandatory metric alerts are correctly configured $resIdMessageString."); 
            }
        }
        else
        {
            throw [System.ArgumentException] ("The argument 'metricSettings' is null or empty");
        }

        return $result;
    }

    hidden AddResourceMetadata([PSObject] $metadataObj)
    {

        [hashtable] $resourceMetadata = New-Object -TypeName Hashtable;
        $metadataObj.psobject.properties | 
            foreach { 
                $resourceMetadata.Add($_.name, $_.value)
            }

        $this.ResourceContext.ResourceMetadata = $resourceMetadata

    }
}