Framework/Core/SVT/Services/VirtualMachine.ps1

using namespace Microsoft.Azure.Commands.Network.Models
using namespace Microsoft.Azure.Commands.Compute.Models
using namespace Microsoft.Azure.Management.Compute.Models
Set-StrictMode -Version Latest 

class VirtualMachine: AzSVTBase
{       
    hidden [PSVirtualMachine] $ResourceObject;
    hidden [PSNetworkInterface[]] $VMNICs = $null;
    hidden [PSObject] $ASCSettings = $null;
    hidden [bool] $IsVMDeallocated = $false
    hidden [VMDetails] $VMDetails = [VMDetails]::new()
    hidden [PSObject] $VMControlSettings = $null;
    hidden [string] $Workspace = "";
    hidden [string] $WorkspaceResourceId = "";
    
    VirtualMachine([string] $subscriptionId, [SVTResource] $svtResource): 
        Base($subscriptionId, $svtResource) 
    { 
        $this.GetResourceObject();
        $this.GetVMDetails();
        $metadata= [PSObject]::new();
        $metadata| Add-Member -Name VMDetails -Value $this.VMDetails -MemberType NoteProperty;
        if([FeatureFlightingManager]::GetFeatureStatus("EnableVMASCMetadataCapture",$($this.SubscriptionContext.SubscriptionId)) -eq $true)
        {
            $metadata| Add-Member -Name VMASCDetails -Value $this.ASCSettings -MemberType NoteProperty;
        }

        # Add Installed Extensions deatils to resource metadata of VM
        if([FeatureFlightingManager]::GetFeatureStatus("EnableVMExtensionMetadataCapture",$($this.SubscriptionContext.SubscriptionId)) -eq $true)
        {
            if([Helpers]::CheckMember($this.ResourceObject, "Extensions") -and ($this.ResourceObject.Extensions | Measure-Object).Count -gt 0)
            {
                $vmExtensionList = $this.ResourceObject.Extensions | Select-Object "Publisher", "VirtualMachineExtensionType", "TypeHandlerVersion" 
                $metadata| Add-Member -Name VMExtensions -Value $vmExtensionList -MemberType NoteProperty;
            }
        }

        $this.AddResourceMetadata($metadata);        
        
        #OS type must always be present in configuration setting file
        if([Helpers]::CheckMember($this.ControlSettings.VirtualMachine, $this.VMDetails.OSType)){
            $this.VMControlSettings = $this.ControlSettings.VirtualMachine.$($this.VMDetails.OSType);
        }
    }

    [ControlItem[]] ApplyServiceFilters([ControlItem[]] $controls)
    {
        $result = @();
        # Get Applicable controls based on VM OS type
        $VMType = $this.ResourceObject.StorageProfile.OsDisk.OsType
        if($VMType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Linux)
        {
            $result += $controls | Where-Object { $_.Tags -contains "Linux" };
        }
        elseif($VMType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Windows)
        {
            $result += $controls | Where-Object { $_.Tags -contains "Windows" };;
        }
        if($this.VMDetails.IsVMConnectedToERvNet -and ($result | Where-Object { $_.Tags -contains "ERvNet" } | Measure-Object).Count -gt 0)
        {
            $result=$result | Where-Object { $_.Tags -contains "ERvNet" };
        }
        
        # Applying filter to exclude certain controls based on Tag Key-Value
        if([Helpers]::CheckMember($this.ControlSettings.VirtualMachine, "ControlExclusionsByService") -and [Helpers]::CheckMember($this.ResourceObject, "Tags")){
            $this.ControlSettings.VirtualMachine.ControlExclusionsByService | ForEach-Object {
                if($this.ResourceObject.Tags[$_.ResourceTag] -like $_.ResourceTagValue){
                    $controlTag = $_.ControlTag
                    $result=$result | Where-Object { $_.Tags -notcontains $controlTag };
                }
            }
        }
        
        # Filter not applicable control for VM with ephemeral OS disk
        if($this.IsVMHasEphemeralOSDisk()){
            $result = $result | Where-Object { $_.Tags -notcontains "ExcludeEphemeralDisk" };
        }
        
        # Return all applicable controls for current VM instance
        return $result;
    }

    hidden  GetVMDetails()
    {
        if($this.ResourceObject.StorageProfile.OsDisk)
        {
            $this.VMDetails.OSType = $this.ResourceObject.StorageProfile.OsDisk.OsType
        }
        else
        {
            if($this.ResourceObject.OSProfile -and $this.ResourceObject.OSProfile.LinuxConfiguration)
            {
                $this.VMDetails.OSType = [OperatingSystemTypes]::Linux
            }
            else
            {
                $this.VMDetails.OSType = [OperatingSystemTypes]::Windows
            }
        }

        if($this.ResourceObject.StorageProfile -and $this.ResourceObject.StorageProfile.ImageReference)
        {
            $this.VMDetails.Sku =  $this.ResourceObject.StorageProfile.ImageReference.Sku
            $this.VMDetails.Offer = $this.ResourceObject.StorageProfile.ImageReference.Offer
        }        
        #Get if VM is connected to ERvNet
        $this.VMDetails.IsVMConnectedToERvNet = $this.IsVMConnectedToERvNet()
        #Get VM deallocation status
        $this.VMDetails.IsVMDeallocated = $this.GetVMDeallocationStatus();
    }

    hidden [bool] IsVMConnectedToERvNet()
    {
        $IsVMConnectedToERvNet = $false
        $publicIPs = ""
        $privateIPs = ""
        $this.GetVMNICObjects() |
            ForEach-Object {         
        if($_.IpConfigurations)
                {
                    $_.IpConfigurations | 
                        ForEach-Object {
                            #Fetch Private IPs to persist in the virtual machine metadata
                            $privateIPs += $_.PrivateIpAddress + ";"
                            #Fetch Public IPs to persist in the virtual machine metadata
                            if($_.PublicIpAddress)
                            {
                                $ipResource = Get-AzResource -ResourceId $_.PublicIpAddress.Id 
                                if($ipResource)
                                {
                                    $publicIpObject = Get-AzPublicIpAddress -Name $ipResource.Name -ResourceGroupName $ipResource.ResourceGroupName
                                    if($publicIpObject)
                                    {
                                        #$_.PublicIpAddress = $publicIpObject;
                                        $publicIPs += $publicIpObject.IpAddress + ";";
                                    }
                                }
                            }
                            $subnetId = $_.Subnet.Id;        
                            $subnetName = $subnetId.Substring($subnetId.LastIndexOf("/") + 1);
                            #vnet id = trim '/subnets/' from subnet id
                            $vnetResource = Get-AzResource -ResourceId $subnetId.Substring(0, $subnetId.IndexOf("/subnets/"))

                            $vnetResource | ForEach-Object {
                                $vnet = $_
                                if($null-ne $vnet.properties -and $null -ne $vnet.properties.subnets)
                                {
                                    $vnet.properties.subnets | ForEach-Object {
                                        $subnet = $_;
                                        if($subnet.name -eq "GatewaySubnet" -and $null -ne $subnet.properties -and ([Helpers]::CheckMember($subnet.properties,"ipConfigurations")) -and ($subnet.properties.ipConfigurations | Measure-Object).Count -gt 0)
                                        {
                                            #41 number is the total character count of "Microsoft.Network/virtualNetworkGateways/"
                                            $gatewayname = $subnet.properties.ipConfigurations[0].id.Substring($subnet.properties.ipConfigurations[0].id.LastIndexOf("Microsoft.Network/virtualNetworkGateways/") + 41);
                                            $gatewayname = $gatewayname.Substring(0, $gatewayname.IndexOf("/"));

                                            $gatewayObject = Get-AzVirtualNetworkGateway -Name $gatewayname -ResourceGroupName $vnet.ResourceGroupName
                                            if( $gatewayObject.GatewayType -eq "ExpressRoute")
                                            {
                                                $IsVMConnectedToERvNet= $true
                                            }
                                        }
                                    }
                                }
                            }

                            #if($vnetResource.ResourceGroupName -in $this.ConvertToStringArray([ConfigurationManager]::GetAzSKConfigData().ERvNetResourceGroupNames))
                            #{
                            # $IsVMConnectedToERvNet= $true
                            #}
                        }
                }
            }


        $this.VMDetails.PublicIPs = $publicIPs;
        $this.VMDetails.PrivateIPs = $privateIPs;
        return $IsVMConnectedToERvNet;
    }

    hidden [bool] GetVMDeallocationStatus()
    {
        $vmStatusObj = Get-AzVM -ResourceGroupName $this.ResourceContext.ResourceGroupName -Name $this.ResourceContext.ResourceName -Status -WarningAction SilentlyContinue 
        if($vmStatusObj.Statuses -and ($vmStatusObj.Statuses | Where-Object { $_.Code.ToLower() -eq "powerState/running" }))
        {
            return $false            
        }
        else
        {
            return $true
        }
    }
     
    hidden [bool] IsVMHasEphemeralOSDisk()
    {
        $hasEphemeralOSDisk = $false

        if([Helpers]::CheckMember($this.ResourceObject.StorageProfile.OsDisk,"DiffDiskSettings.Option") -and [Helpers]::CheckMember($this.ResourceObject.StorageProfile.OsDisk,"Caching")){
            $diskDiffSettingOption = $this.ResourceObject.StorageProfile.OsDisk.DiffDiskSettings.Option
            $cachingPolicy = $this.ResourceObject.StorageProfile.OsDisk.Caching
            if($diskDiffSettingOption -eq "Local" -and $cachingPolicy -eq "ReadOnly"){
                $hasEphemeralOSDisk = $true
            } 
        }
        
        return $hasEphemeralOSDisk
    }

    hidden [PSVirtualMachine] GetResourceObject()
    {
        if (-not $this.ResourceObject) {
            $this.ResourceObject = Get-AzVM -ResourceGroupName $this.ResourceContext.ResourceGroupName -Name $this.ResourceContext.ResourceName  -WarningAction SilentlyContinue 

            if(-not $this.ResourceObject)
            {
                throw ([SuppressedException]::new(("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName)), [SuppressedExceptionType]::InvalidOperation))
            }
            
        }

        #compute ASC object for VM
        $this.ASCSettings = $this.GetASCSettings();

        if($null -ne $this.ASCSettings -and [Helpers]::CheckMember($this.ASCSettings,"properties.resourceDetails"))
        {
            $laWSSetting = $this.ASCSettings.properties.resourceDetails | Where-Object {$_.name -eq $this.ControlSettings.VirtualMachine.ASCPolicies.ResourceDetailsKeys.WorkspaceId };
            if($null -ne $laWSSetting)
            {
                $this.Workspace = $laWSSetting.value;
            }

            $laWSResourceId = $this.ASCSettings.properties.resourceDetails | Where-Object {$_.name -eq $this.ControlSettings.VirtualMachine.ASCPolicies.ResourceDetailsKeys.WorkspaceResourceId };
            if($null -ne $laWSResourceId)
            {
                $this.WorkspaceResourceId = $laWSResourceId.value;
            }
        }

        return $this.ResourceObject;
    }

    hidden [PSObject] GetASCSettings()
    {
        $result = $null;
        # Commenting this as it's costly call and expected to happen in Set-ASC/SSS/USS
        try 
        {     
            $result = [SecurityCenterHelper]::InvokeSecurityCenterSecurityStatus($this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceId);
            if(($result | Measure-Object).Count -gt 0)
            {            
                return $result;            
            }            
        }
        catch
        {
            #eat exception if no ASC settings can be found
        }
        return $null;
    }

    # Method to get ASC assessment for current reosurce by assessmentName
    hidden [PSObject] GetASCAssessment([string] $assessmentName)
    {
        $result = $null;
        try 
        {     
            $result = [SecurityCenterHelper]::InvokeSecurityCenterAssessment($assessmentName, $this.ResourceContext.ResourceId);
            if(($result | Measure-Object).Count -gt 0)
            {            
                return $result;            
            }            
        }
        catch
        {
            #eat exception if no ASC assessment can be found
        }
        return $null;
    }

    # Method to check if ASC assessment status is 'Not Applicable' with cause defined in Control settings
    hidden [bool] CheckForASCNotApplicableStatus([string] $assessmentKey){
        $isNotApplicable = $false
        $isFeatureFlagEnabled = [FeatureFlightingManager]::GetFeatureStatus("CheckASCForIrrelevantVMRecommendation",$($this.SubscriptionContext.SubscriptionId)) 
        # If feature flag is disabled then return initial false status
        if($isFeatureFlagEnabled){
            # If control settings does not have required configuration then return initial false status
            if([Helpers]::CheckMember($this.ControlSettings.VirtualMachine.ASCPolicies, "ASCAssessmentsForIrrelevantRecommendation.$assessmentKey")){
                $ascAssessment = $this.ControlSettings.VirtualMachine.ASCPolicies.ASCAssessmentsForIrrelevantRecommendation.$assessmentKey
                $ascAssessmentStatus = $this.GetASCAssessment($ascAssessment.assessmentName);
                if($null -ne $ascAssessmentStatus -and [Helpers]::CheckMember($ascAssessmentStatus, "properties.status") -and [Helpers]::CheckMember($ascAssessmentStatus.properties.status, "cause") -and [Helpers]::CheckMember($ascAssessmentStatus.properties.status, "code")){
                    $statusCode = $ascAssessmentStatus.properties.status.code
                    $statusCause = $ascAssessmentStatus.properties.status.cause
                    # Mark control as NA if ASC status is 'Not Applicable' and cause is in defined allowed cause list
                    if($statusCode -eq "NotApplicable" -and $ascAssessment.allowedCauses -contains $statusCause){
                        $isNotApplicable = $true
                    }
                }
            }
        }
        return $isNotApplicable
    }

    hidden [PSNetworkInterface[]] GetVMNICObjects()
    {
        if (-not $this.VMNICs) 
        {
            $this.VMNICs = @();
            if($this.ResourceObject.NetworkProfile -and $this.ResourceObject.NetworkProfile.NetworkInterfaces)
            {
                $this.ResourceObject.NetworkProfile.NetworkInterfaces | 
                    ForEach-Object {          
                        $currentNic = Get-AzResource -ResourceId $_.Id -ErrorAction SilentlyContinue
                        if($currentNic)
                        {
                            $nicResource = Get-AzNetworkInterface -Name $currentNic.Name `
                                                -ResourceGroupName $currentNic.ResourceGroupName `
                                                -ExpandResource NetworkSecurityGroup `
                                                -ErrorAction SilentlyContinue
                            if($nicResource)
                            {
                                $this.VMNICs += $nicResource;
                            }
                        }
                    }
            }            
        }
        return $this.VMNICs;
    }
    
    hidden [ControlResult] CheckOSVersion([ControlResult] $controlResult)
    {

        $vmSkuDetails = $this.VMDetails | Select-Object OSType,Offer,Sku
        #in the case of classic migrated VM, we have noticed that there is no Metadata present
        if([string]::IsNullOrWhiteSpace($this.VMDetails.Offer) -or [string]::IsNullOrWhiteSpace($this.VMDetails.Sku))
        {
            $controlResult.AddMessage([VerificationResult]::Manual,"No metadata found for this VM. Verify if you are using recommended OS Sku as per Org security policy",$vmSkuDetails); 
            return     $controlResult;
        }
        $supportedSkuList = $this.VMControlSettings.SupportedSkuList | Where-Object { $_.Offer -eq $this.VMDetails.Offer } 
        if($supportedSkuList)
        {
            if($supportedSkuList.Sku -contains $this.VMDetails.Sku)
            {
                $controlResult.AddMessage([VerificationResult]::Passed,"Virtual Machine OS Sku is compliant with the Org security policy",$vmSkuDetails);                
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Failed,"Virtual Machine OS Sku is not compliant with the Org security policy",$vmSkuDetails);                                
            }        
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Verify,"Verify if you are using recommended OS Sku as per Org security policy",$vmSkuDetails); 
        }        
        return $controlResult;
    }

    hidden [ControlResult] CheckOSAutoUpdateStatus([ControlResult] $controlResult)
    {        
        #TCP is not applicable for Linux. #This method is deprecated
        if($this.ResourceObject.OSProfile -and $this.ResourceObject.OSProfile.WindowsConfiguration)
        {
            $message = "";
            $verificationResult = [VerificationResult]::Failed;

            if($this.ResourceObject.OSProfile.WindowsConfiguration.EnableAutomaticUpdates -eq $true)
            {
                $verificationResult = [VerificationResult]::Passed;
                $message = "Automatic OS updates are enabled on Windows Virtual Machine";
            }
            else
            {
                $message = "Automatic OS updates are disabled on Windows Virtual Machine. Please enable OS automatic updates in order to comply.";
            }

            $controlResult.AddMessage($verificationResult, $message, $this.ResourceObject.OSProfile.WindowsConfiguration);    
        
        }
        elseif($this.VMDetails.OSType -eq [OperatingSystemTypes]::Linux)
        {
            if([Helpers]::CheckMember($this.ResourceObject.OSProfile,"LinuxConfiguration")){
                $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable in case of a Linux Virtual Machine. It's good practice to periodically update the OS of Virtual Machine.", $this.ResourceObject.OSProfile.LinuxConfiguration); 
            }
            else{
                $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable in case of a Linux Virtual Machine. It's good practice to periodically update the OS of Virtual Machine.")
            }
        }
        else
        {
            $controlResult.AddMessage([MessageData]::new("We are not able to fetch the required data for the resource", [MessageType]::Error)); 
        }
            
        return $controlResult;
    }

    hidden [ControlResult] CheckAntimalwareStatus([ControlResult] $controlResult)
    {
        #Do not check for deallocated status for the VM and directly show the status from ASC
        
        #Execute block if OS is Linux and WorkSpaceId is configured
        if($this.VMDetails.OSType -eq [OperatingSystemTypes]::Linux -and [FeatureFlightingManager]::GetFeatureStatus("EnableLinuxAntimalwareCheck",$($this.SubscriptionContext.SubscriptionId))) 
        {

            if($this.Workspace)
            {
                $LinuxAntimalwareStatusWSQuery =[string]::Format($this.VMControlSettings.QueryForLinuxAntimalwareStatus,($this.ResourceContext.ResourceId).ToLower());
                $queryStatusResult = [LogAnalyticsHelper]::QueryStatusfromWorkspace($this.Workspace, $LinuxAntimalwareStatusWSQuery);

                if($queryStatusResult -ne $null -and $queryStatusResult.Count -gt 0)
                {
                    $controlResult.AddMessage([VerificationResult]::Passed,"Antimalware is configured correctly on the VM. Validated the status through ASC workspace query."); 
                }
                else {

                    if(-not $this.VMDetails.IsVMDeallocated)
                    {
                        # Pass the control if ASC assessment status is Not Applicable for equivalent ASC assessment for current control
                        $isASCStatusNotApplicable = $this.CheckForASCNotApplicableStatus("EndpointProtectionAssessmentKey");
                        if($isASCStatusNotApplicable){
                            $controlResult.AddMessage([VerificationResult]::Passed,"Control is Not Applicable, validated the status through ASC."); 
                        }else{
                            $controlResult.AddMessage([VerificationResult]::Failed,"Antimalware is not configured on the VM. Validated the status through ASC workspace query."); 
                        }
                    }
                    else 
                    {
                        #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
                        $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
                        $controlResult.AddMessage([VerificationResult]::Manual, "VM is in deallocated state. We are not able to check Security Center workspace status. Please validate VM antimalware status manually.");
                    }
                }
            }
            else {
                # Pass the control if ASC assessment status is Not Applicable for equivalent ASC assessment for current control
                $isASCStatusNotApplicable = $this.CheckForASCNotApplicableStatus("EndpointProtectionAssessmentKey");
                if($isASCStatusNotApplicable){
                    $controlResult.AddMessage([VerificationResult]::Passed,"Control is Not Applicable, validated the status through ASC."); 
                }else{
                    $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center workspace status. Please validate VM antimalware status manually.");
                }
            }
            
        }
        elseif($null -ne $this.ASCSettings -and [Helpers]::CheckMember($this.ASCSettings, "properties.policyAssessments"))
        {
            $antimalwareSetting = $null
            $antimalwareSetting = $this.ASCSettings.properties.policyAssessments | Where-Object {$_.assessmentKey -eq $this.ControlSettings.VirtualMachine.ASCPolicies.PolicyAssignment.EndpointProtectionAssessmentKey};
            
            if($null -ne $antimalwareSetting)
            {
                $controlResult.AddMessage("VM endpoint protection details:", $antimalwareSetting);
                if($antimalwareSetting.assessmentResult -eq 'Healthy' )
                {                
                    $controlResult.AddMessage([VerificationResult]::Passed,"Antimalware is configured correctly on the VM. Validated the status through ASC."); 
                }
                elseif($antimalwareSetting.assessmentResult -eq 'Low')
                {                
                    $controlResult.AddMessage([VerificationResult]::Verify,"Validate configurations of antimalware using ASC."); 
                }
                elseif($antimalwareSetting.assessmentResult -eq 'None')
                {    
                    # Check if VM is in deallocated state
                    # Generally ASC shows NA status if VM is in deallocated state
                    #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
                    $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
                    $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable due to the ASC current policy."); 
                }
                else
                {                    
                    $controlResult.AddMessage([VerificationResult]::Failed,"Antimalware is not configured correctly on the VM. Validated the status through ASC."); 
                }
            }                        
        }
        else
        {
            #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
            $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
            $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually.");
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckVulnAgentStatus([ControlResult] $controlResult)
    {
        if(-not $this.VMDetails.IsVMDeallocated)
        {
            $requiredVulnExtension = $this.VMControlSettings.VulnAssessmentSolution.AgentName
            $requiredVulnExtensionVersion =  [System.Version] $this.VMControlSettings.VulnAssessmentSolution.RequiredVersion
            if([Helpers]::CheckMember($this.ResourceObject, "Extensions")){
                $installedVulnExtension = $this.ResourceObject.Extensions | Where-Object {$_.VirtualMachineExtensionType -eq $requiredVulnExtension} 
                if($null -ne $installedVulnExtension -and $installedVulnExtension.ProvisioningState -eq "Succeeded"){
                    $currentVulnExtensionVersion = $null
                    try {
                        $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl();
                        $AccessToken = [ContextHelper]::GetAccessToken($ResourceAppIdURI)
                        $header = "Bearer " + $AccessToken
                        $headers = @{"Authorization"=$header;"Content-Type"="application/json";}
                        $propertiesToReplace = @{}
                        $propertiesToReplace.Add("httpapplicationroutingzonename", "_httpapplicationroutingzonename")
                        $uri=[system.string]::Format("{0}subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Compute/virtualMachines/{3}/extensions/{4}?api-version=2018-06-01&`$expand=instanceView",$ResourceAppIdURI,$this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName,$requiredVulnExtension)
                        $result = [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Get, $uri, $headers, $null, $null, $propertiesToReplace); 
                        $currentVulnExtensionVersion = [System.Version] $result.properties.instanceView.typeHandlerVersion
                    }
                    catch {
                        # If any exception occurs, while fetching details of Extension mark control as manual
                        $currentVulnExtensionVersion = $null
                    }
                    if($null -eq $currentVulnExtensionVersion )
                    {
                        #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
                        $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
                        $controlResult.AddMessage([VerificationResult]::Manual, "Not able to fetch details of vulnerability assessment extension '$($requiredVulnExtension)'.");
                    }
                    elseif($currentVulnExtensionVersion -lt $requiredVulnExtensionVersion){

                        $controlResult.AddMessage([VerificationResult]::Failed, "Vulnerability assessment solution '$($requiredVulnExtension)' is present but current verison is not latest.");
                        $controlResult.AddMessage("Current version : $($currentVulnExtensionVersion), Required version: $($requiredVulnExtensionVersion)");
                        $controlResult.SetStateData("Current version of $($requiredVulnExtension) present is:", $currentVulnExtensionVersion.ToString());
                    
                    }else{

                        $controlResult.AddMessage([VerificationResult]::Passed, "Required vulnerability assessment solution '$($requiredVulnExtension)' is present in VM.");
                    }
                }else{
                    # Pass the control if ASC assessment status is Not Applicable for equivalent ASC assessment for current control
                    $isASCStatusNotApplicable = $this.CheckForASCNotApplicableStatus("VulnerabilitySolutionAssessmentKey");
                    if($isASCStatusNotApplicable){
                        $controlResult.AddMessage([VerificationResult]::Passed,"Control is Not Applicable, validated the status through ASC."); 
                    }else{
                        $controlResult.AddMessage([VerificationResult]::Failed, "Required vulnerability assessment solution '$($requiredVulnExtension)' is not present in VM.");
                    }
                }
            }else{
                # Pass the control if ASC assessment status is Not Applicable for equivalent ASC assessment for current control
                $isASCStatusNotApplicable = $this.CheckForASCNotApplicableStatus("VulnerabilitySolutionAssessmentKey");
                if($isASCStatusNotApplicable){
                    $controlResult.AddMessage([VerificationResult]::Passed,"Control is Not Applicable, validated the status through ASC."); 
                }else{
                    $controlResult.AddMessage([VerificationResult]::Failed, "Required vulnerability assessment solution '$($requiredVulnExtension)' is not present in VM.");
                }
            }
        }
        else
        {
            #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
            $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
            $controlResult.AddMessage([VerificationResult]::Verify, "This VM is currently in a 'deallocated' state. Unable to check security controls on it.");
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckGuestConfigExtension([ControlResult] $controlResult)
    {
        if(-not $this.VMDetails.IsVMDeallocated)
        {
            $guestConfigurationAssignmentName = $this.VMControlSettings.GuestExtension.AssignmentName
            $controlStatus = [VerificationResult]::Manual
            $requiredGuestExtension  = $this.VMControlSettings.GuestExtension.Name
            $requiredGuestExtensionVersion =  [System.Version] $this.VMControlSettings.GuestExtension.RequiredVersion
            $checkPolicyAssignment = $this.VMControlSettings.GuestExtension.CheckPolicyAssignment
            
            
            $ResourceAppIdURI = [WebRequestHelper]::GetResourceManagerUrl();
            $AccessToken = [ContextHelper]::GetAccessToken($ResourceAppIdURI)
            $header = "Bearer " + $AccessToken
            $headers = @{"Authorization"=$header;"Content-Type"="application/json";}
            $propertiesToReplace = @{}
            $propertiesToReplace.Add("httpapplicationroutingzonename", "_httpapplicationroutingzonename")

            # Check if Guest Configuration extension is present
            if([Helpers]::CheckMember($this.ResourceObject, "Extensions")){
                $installedGuestExtension = $this.ResourceObject.Extensions | Where-Object {$_.VirtualMachineExtensionType -eq $requiredGuestExtension -and $_.Publisher -eq "Microsoft.GuestConfiguration"} 
                if($null -ne $installedGuestExtension -and $installedGuestExtension.ProvisioningState -eq "Succeeded"){
                    $controlStatus = [VerificationResult]::Passed
                    $controlResult.AddMessage("Required guest configuration extension '$($requiredGuestExtension)' is present in VM.");
                    
                    # Commenting this section as we are skipping the check for Extension Version
                    <#$currentGuestExtensionVersion = $null
                    try {
                        $uri=[system.string]::Format("{0}subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Compute/virtualMachines/{3}/extensions/{4}?api-version=2018-06-01&`$expand=instanceView",$ResourceAppIdURI,$this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName,$requiredGuestExtension)
                        $result = [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Get, $uri, $headers, $null, $null, $propertiesToReplace);
                        $currentGuestExtensionVersion = [System.Version] $result.properties.instanceView.typeHandlerVersion
                    }
                    catch {
                        # If any exception occurs, while fetching details of Extension mark control as manual
                        # Skip check for version
                    } #>

                    
                }else{
                    $controlStatus = [VerificationResult]::Failed
                    $controlResult.AddMessage("Required guest configuration extension '$($requiredGuestExtension)' is not present in VM.");
                }
            }else{
                $controlStatus = [VerificationResult]::Failed
                $controlResult.AddMessage("Required guest configuration extension '$($requiredGuestExtension)' is not present in VM.");
            }

            # Check if reuired Guest Configuration Assignments is present
            if($checkPolicyAssignment -eq $true){
                try{
                    $uri=[system.string]::Format("{0}subscriptions/{1}/resourceGroups/{2}/providers/Microsoft.Compute/virtualMachines/{3}/providers/Microsoft.GuestConfiguration/guestConfigurationAssignments/{4}?api-version=2018-11-20",$ResourceAppIdURI,$this.SubscriptionContext.SubscriptionId, $this.ResourceContext.ResourceGroupName, $this.ResourceContext.ResourceName,$guestConfigurationAssignmentName)
                    $result = [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Get, $uri, $headers, $null, $null, $propertiesToReplace); 
                    if($null -ne $result)
                    {    
                        $controlResult.AddMessage("Required guest configuration assignments '$($guestConfigurationAssignmentName)' is present.");
                        $controlResult.AddMessage($result);
                    
                    }
                }catch{
                    $controlStatus = [VerificationResult]::Failed
                    $controlResult.AddMessage("Required guest configuration assignments '$($guestConfigurationAssignmentName)' is not present.");    
                }
            }
        

            # Check if Managed System Identity is enabled on VM
            # Using like "*SystemAssigned*" to get correct status, if both MSI and User Assigned Identity are enabled
            if([Helpers]::CheckMember($this.ResourceObject, "Identity") -and $this.ResourceObject.Identity.Type -like "*SystemAssigned*"){
                $controlResult.AddMessage("SystemAssigned managed identity is enabled on VM.");
            }else{
                $controlStatus = [VerificationResult]::Failed
                $controlResult.AddMessage("The VM does not have a SystemAssigned managed identity");
            }
        }
        else
        {
            #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
            $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
            $controlStatus = [VerificationResult]::Verify
            $controlResult.AddMessage("This VM is currently in a 'deallocated' state. Unable to check security controls on it.");
        }
        $controlResult.VerificationResult = $controlStatus
        return $controlResult;
    }

    hidden [ControlResult] CheckGuestConfigPolicyStatus([ControlResult] $controlResult)
    {
        $controlStatus = [VerificationResult]::Failed
        $requiredGuestConfigPolicies = $this.VMControlSettings.RequiredGuestConfigPolicies

        $requiredPolicyDefList = @()
        $requiredPolicySetDefList = @()
        $mandatoryPolicyAssignmentReq = $false
        $mandatoryPolicySetAssignmentReq = $false
        # Check if there is any mandatory policyId defined in control settings
        if([Helpers]::CheckMember($this.VMControlSettings.RequiredGuestConfigPolicies, "RequiredPolicyDefinitionIds")){
            $mandatoryPolicyAssignmentReq = $true
            # Keeping whole Policy stringId in control settings to handle custom Policy enforced at management group level
            $requiredGuestConfigPolicies.RequiredPolicyDefinitionIds | Foreach-Object {
                $policyDefinitionId = $_
                $policyDefStr = "(PolicyDefinitionId eq '$($policyDefinitionId)')"
                $requiredPolicyDefList += $policyDefStr
            }
        }
        # Check if there is any mandatory policySetId defined in control settings
        if([Helpers]::CheckMember($this.VMControlSettings.RequiredGuestConfigPolicies, "RequiredPolicySetDefinitionIds")){
            $mandatoryPolicySetAssignmentReq = $true
            # Keeping whole Policy Set stringId in control settings to handle custom Policy Set enforced at management group level
            $requiredGuestConfigPolicies.RequiredPolicySetDefinitionIds | Foreach-Object {
                $policyDefinitionSetId = $_
                $policyDefStr = "(PolicySetDefinitionId eq '$($policyDefinitionSetId)')"
                $requiredPolicySetDefList += $policyDefStr
            }
        }

        $requiredPolicyList = $requiredPolicyDefList + $requiredPolicySetDefList
        # If any required policy defined, check for compliance
        if(($requiredPolicyList| Measure-Object).Count -gt 0){
            $filterQuery = $requiredPolicyList  -join " or "
            # Get Policy State for all reqiured policies
            $policyState = Get-AzPolicyState -ResourceId  $this.ResourceContext.ResourceId -Filter $filterQuery
            # If required policy state found for resource, Check assignment and complaince for each policy
            if($policyState){
                $missingPolicyAssignments = @()
                $nonCompliantPolicies = @()
                if($mandatoryPolicyAssignmentReq){
                    # Check if all assignment for all rquired policy Ids are present and compliant
                    $requiredGuestConfigPolicies.RequiredPolicyDefinitionIds | ForEach-Object{
                        $currRequiredPolicyId = $_
                        $requiredPolicyStatus = @()
                        $requiredPolicyStatus += $policyState | Where-Object {$_.PolicyDefinitionId -eq $currRequiredPolicyId}
                        # Check if required assignment is present for required policy
                        if($requiredPolicyStatus){
                            # Check compliance of policy , if required assignment is present for the policy
                            $nonCompliantPolicy = $requiredPolicyStatus | Where-Object {$_.IsCompliant -eq $false}
                            if($nonCompliantPolicy){
                                $nonCompliantPolicies += $nonCompliantPolicy | Select-Object PolicyDefinitionId, PolicyAssignmentId, PolicyAssignmentScope, PolicyDefinitionAction, IsCompliant
                            }
                        }else{
                            $missingPolicyAssignments += $_
                        }
                    }
                }
                
                if($mandatoryPolicySetAssignmentReq){
                    # Check if all assignment for all rquired policy Set Ids are present and compliant
                    $requiredGuestConfigPolicies.RequiredPolicySetDefinitionIds  | ForEach-Object{
                        $currRequiredPolicySetId = $_
                        $requiredPolicyStatus = @()
                        $requiredPolicyStatus += $policyState | Where-Object {$_.PolicySetDefinitionId -eq $currRequiredPolicySetId}
                        # Check if required assignment is present for required policy set
                        if($requiredPolicyStatus){
                            # Check compliance of policy in the set, if required assignment is present for the policy set
                            $nonCompliantPolicy = $requiredPolicyStatus | Where-Object {$_.IsCompliant -eq $false}
                            if($nonCompliantPolicy){
                                $nonCompliantPolicies += $nonCompliantPolicy | Select-Object PolicySetDefinitionId, PolicyDefinitionId, PolicyAssignmentId, PolicyAssignmentScope, PolicyDefinitionAction, IsCompliant
                            }
                        }else{
                            $missingPolicyAssignments += $_
                        }
                    }
                }
                

                $hasControlFailed = $false
                # If assignment is missing for any required policy set or policy, set 'hasControlFailed' flag to true
                if(($missingPolicyAssignments | Measure-object).Count -gt 0){
                    $hasControlFailed = $true
                    $controlResult.AddMessage("Assignment is missing for following mandatory policy:",$missingPolicyAssignments);
                }

                # If assignment is non-compliant for any required policy set or policy, set 'hasControlFailed' flag to true
                if(($nonCompliantPolicies | Measure-object).Count -gt 0){
                    $hasControlFailed = $true
                    $controlResult.AddMessage("Following policies are in non compliant state:",$nonCompliantPolicies);
                }

                # Pass the control only if, $hasControlFailed flag is false
                if(-not $hasControlFailed){
                    $controlStatus = [VerificationResult]::Passed
                    $controlResult.AddMessage("All required guest config policies are configured and are in complinat state.");
                }

            }else{
                # If no policy state found for control, mark control as failed
                $controlStatus = [VerificationResult]::Failed
                $controlResult.AddMessage("Assignment is missing for following policy:",$requiredPolicyList);
            }
        }else{
            # If no required mandatory policy defined, mark control status as 'Passed'
            $controlStatus = [VerificationResult]::Passed
            $controlResult.AddMessage("No mandatory Guest Config Policy is required to be configured.");
        }

        $controlResult.VerificationResult = $controlStatus
        return $controlResult;
    }

    hidden [ControlResult] CheckRequiredExtensions([ControlResult] $controlResult)
    {
        if(-not $this.VMDetails.IsVMDeallocated)
        {
            $controlStatus = [VerificationResult]::Failed
            $requiredExtensions  = $this.VMControlSettings.RequiredExtensions
            
            if($null -ne $requiredExtensions -and ($requiredExtensions | Measure-Object).Count -gt 0){
                $unhealthyExtensions = @()
                $missingExtensions = @()
                $installedExtensions = @()
                $hasControlFailed = $false
                if([Helpers]::CheckMember($this.ResourceObject, "Extensions")){

                    $requiredExtensions | ForEach-Object {
                        $requiredExtension = $_
                        $installedExtension = $this.ResourceObject.Extensions | Where-Object {$_.VirtualMachineExtensionType -eq $requiredExtension.ExtensionType <# -and $_.Publisher -eq $requiredExtension.Publisher #> } 
                        if($null -eq $installedExtension){
                            $missingExtensions += $requiredExtension
                            $hasControlFailed = $true
                        }
                        elseif($installedExtension.ProvisioningState -eq "Succeeded"){
                            $installedExtensions += $requiredExtension
                        }else{
                            $unhealthyExtensions += $requiredExtension
                            $hasControlFailed = $true
                        }
                    }
                    
                    if($hasControlFailed){
                        $controlResult.AddMessage("Listing issues with VM extensions (extensions that are missing or *not healthy*)");
                    }
                    if(($missingExtensions | Measure-Object).Count -gt 0){
                        $controlResult.AddMessage("Following required extensions are not present in VM:",$missingExtensions);
                    }
                    if(($unhealthyExtensions | Measure-Object).Count -gt 0){
                        $controlResult.AddMessage("Following extensions are present in VM but are not healthy:",$unhealthyExtensions);
                    }
                    if(($installedExtensions | Measure-Object).Count -gt 0){
                        $controlResult.AddMessage("Following extensions are present and are in healthy state:",$installedExtensions);
                    }
                    
                    if($hasControlFailed){
                        $controlStatus = [VerificationResult]::Failed
                        $missingExtensions = $missingExtensions + $unhealthyExtensions
                        $controlResult.SetStateData("Missing or unhealthy extensions:", $missingExtensions);
                    }else{
                        $controlStatus = [VerificationResult]::Passed
                        $controlResult.AddMessage("All required extensions are present in VM.");
                    }

                }else{
                    $controlResult.AddMessage("Following required extensions are not present in VM:",$requiredExtensions);
                }
            }else{
                $controlStatus = [VerificationResult]::Passed
                $controlResult.AddMessage("No mandatory extensions need to be deployed on VM.");
            }
        }
        else
        {
            #Setting this property ensures that this control result wont be considered for the central telemetry. As control doesnt have the required permissions
            $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false; 
            $controlStatus = [VerificationResult]::Verify
            $controlResult.AddMessage("This VM is currently in a 'deallocated' state. Unable to check security controls on it.");
        }
        $controlResult.VerificationResult = $controlStatus
        return $controlResult;
    }

    hidden [ControlResult] CheckNSGConfig([ControlResult] $controlResult)
    {
        $controlResult.VerificationResult = [VerificationResult]::Failed;
        $this.GetVMNICObjects() |
            ForEach-Object {          
                #Check NSGs applied at NIC level
                if($_.NetworkSecurityGroup)
                {
                    if($_.NetworkSecurityGroup.SecurityRules.Count -gt 0)
                    {
                        $controlResult.AddMessage("Validate NSG security rules applied to NIC - [$($_.Name)], Total - $($_.NetworkSecurityGroup.SecurityRules.Count)", $_.NetworkSecurityGroup.SecurityRules);              
                    }
                    if($_.NetworkSecurityGroup.DefaultSecurityRules.Count -gt 0)
                    {
                        $controlResult.AddMessage("Validate default NSG security rules applied to NIC - [$($_.Name)], Total - $($_.NetworkSecurityGroup.DefaultSecurityRules.Count)", $_.NetworkSecurityGroup.DefaultSecurityRules); 
                    }
                    $controlResult.VerificationResult = [VerificationResult]::Verify;
                    $controlResult.SetStateData("NSG security rules", $_.NetworkSecurityGroup.SecurityRules);
                }  
            
                #check NSGs applied at subnet level
                if($_.IpConfigurations)
                {
                    $_.IpConfigurations | 
                        ForEach-Object {                   
                            $subnetId = $_.Subnet.Id;        
                            $subnetName = $subnetId.Substring($subnetId.LastIndexOf("/") + 1);
                            #vnet id = trim '/subnets/' from subnet id
                            $vnetResource = Get-AzResource -ResourceId $subnetId.Substring(0, $subnetId.IndexOf("/subnets/"))
                            if($vnetResource)
                            {
                                $vnetObject = Get-AzVirtualNetwork -Name $vnetResource.Name -ResourceGroupName $vnetResource.ResourceGroupName
                                if($vnetObject)
                                {
                                    $subnetConfig = Get-AzVirtualNetworkSubnetConfig -Name $subnetName -VirtualNetwork $vnetObject
                                    if($subnetConfig -and $subnetConfig.NetworkSecurityGroup -and $subnetConfig.NetworkSecurityGroup.Id)
                                    {
                                        $nsgResource = Get-AzResource -ResourceId $subnetConfig.NetworkSecurityGroup.Id
                                        if($nsgResource)
                                        {
                                            $nsgObject = Get-AzNetworkSecurityGroup -Name $nsgResource.Name -ResourceGroupName $nsgResource.ResourceGroupName
                                            if($nsgObject)
                                            {
                                                if($nsgObject.SecurityRules.Count -gt 0)
                                                {
                                                    $controlResult.AddMessage("Validate NSG security rules applied to Subnet - [$subnetName] in Virtual Network - [$($vnetResource.Name)]. Total - $($nsgObject.SecurityRules.Count)", $nsgObject.SecurityRules);                                       
                                                }
                                                
                                                if($nsgObject.DefaultSecurityRules.Count -gt 0)
                                                {
                                                    $controlResult.AddMessage("Validate default NSG security rules applied to Subnet - [$subnetName] in Virtual Network - [$($vnetResource.Name)]. Total - $($nsgObject.DefaultSecurityRules.Count)", $nsgObject.DefaultSecurityRules);
                                                }
                                                $controlResult.VerificationResult = [VerificationResult]::Verify;
                                                $controlResult.SetStateData("NSG security rules", $nsgObject.SecurityRules);
                                            }
                                        }
                                    }
                                }
                            }
                        }           
                }            
            }
        if($this.VMDetails.IsVMConnectedToERvNet)
        {
            $controlResult.AddMessage("This VM is part of an ExpressRoute connected virtual network.")    
        }
        if($controlResult.VerificationResult -ne [VerificationResult]::Verify -and $this.VMDetails.IsVMConnectedToERvNet)
        {            
            $controlResult.AddMessage("No NSG was found on Virtual Machine subnet or NIC.")    
            $controlResult.VerificationResult = [VerificationResult]::Passed;
        }
        elseif($controlResult.VerificationResult -ne [VerificationResult]::Verify)
        {
            $controlResult.AddMessage("No NSG was found on Virtual Machine subnet or NIC.")        
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckPublicIP([ControlResult] $controlResult)
    {    
        $publicIps = @();
        $this.GetVMNICObjects() | 
            ForEach-Object {
                $_.IpConfigurations | Where-Object { $_.PublicIpAddress } |
                    ForEach-Object {
                        $ipResource = Get-AzResource -ResourceId $_.PublicIpAddress.Id 
                        if($ipResource)
                        {
                            $publicIpObject = Get-AzPublicIpAddress -Name $ipResource.Name -ResourceGroupName $ipResource.ResourceGroupName
                            if($publicIpObject)
                            {
                                $_.PublicIpAddress = $publicIpObject;
                                $publicIps += $publicIpObject;
                            }
                        }
                    }
            }
         
        if($this.VMDetails.IsVMConnectedToERvNet)
        {
            $controlResult.AddMessage("This VM is part of an ExpressRoute connected virtual network. You must not have any Public IP assigned to such VM. ");
        }
        if($publicIps.Count -gt 0 -and $this.VMDetails.IsVMConnectedToERvNet)
        {              
            $controlResult.AddMessage([VerificationResult]::Failed, "Total Public IPs Found- $($publicIps.Count)", $publicIps);  
            $controlResult.SetStateData("Public IP(s) associated with Virtual Machine", $publicIps);
        }
        elseif($publicIps.Count -gt 0)
        {
            $controlResult.AddMessage([VerificationResult]::Verify, "Validate Public IP(s) associated with Virtual Machine. Total - $($publicIps.Count)", $publicIps);  
            $controlResult.SetStateData("Public IP(s) associated with Virtual Machine", $publicIps);
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Passed, "No Public IP is associated with Virtual Machine");
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckDiskEncryption([ControlResult] $controlResult)
    {    
        #Do not check for deallocated status for the VM and directly show the status from ASC
        $ascDiskEncryptionStatus = $false;

        if($null -ne $this.ASCSettings -and [Helpers]::CheckMember($this.ASCSettings, "properties.policyAssessments"))
        {
            $adeSetting = $null
            $adeSetting = $this.ASCSettings.properties.policyAssessments | Where-Object {$_.assessmentKey -eq $this.ControlSettings.VirtualMachine.ASCPolicies.PolicyAssignment.DiskEncryptionAssessmentKey};
            
            if($null -ne $adeSetting)
            {
                if($adeSetting.assessmentResult -eq 'Healthy')
                {
                    $ascDiskEncryptionStatus = $true;
                }
            }
            $controlResult.AddMessage("VM disk encryption details:", $adeSetting);
        }                
                
        if($ascDiskEncryptionStatus)
        {
            $verificationResult  = [VerificationResult]::Passed;
            $message = "All Virtual Machine disks (OS and Data disks) are encrypted. Validated the status through ASC.";
            $controlResult.AddMessage($verificationResult, $message);
        }
        else
        {            
            $verificationResult  = [VerificationResult]::Failed;
            $message = "All Virtual Machine disks (OS and Data disks) are not encrypted. Validated the status through ASC.";
            $controlResult.AddMessage($verificationResult, $message);
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckASCStatus([ControlResult] $controlResult)
    {
        #This is deprecated method. Commented the code below
        #$isManual = $false;
        # if($this.ASCSettings)
        # {
        # if($this.ASCSettings.SecurityState -ne 'Healthy')
        # {
        # $controlResult.VerificationResult = [VerificationResult]::Failed

        # }
        # else
        # {
        # $controlResult.VerificationResult = [VerificationResult]::Passed
        # }

        # $controlResult.AddMessage("Security Center status for Virtual Machine [$($this.ResourceContext.ResourceName)] is: [$($this.ASCSettings.SecurityState)]", $this.ASCSettings);
        # $controlResult.SetStateData("Security Center status for Virtual Machine", $this.ASCSettings);
        # }
        # else
        # {
        # $isManual = $true;
        # }

        # if($isManual)
           # {
        # $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually.");
        # }

         return $controlResult;
    }

    #No contol found with this method name
    hidden [ControlResult] CheckASCVulnerabilities([ControlResult] $controlResult)
    {
        $ascVMVulnerabilitiesStatusHealthy = $false;

        if($null -ne $this.ASCSettings -and [Helpers]::CheckMember($this.ASCSettings, "properties.policyAssessments"))
        {
            $vulnSetting = $null
            $vulnSetting = $this.ASCSettings.properties.policyAssessments | Where-Object {$_.assessmentKey -eq $this.ControlSettings.VirtualMachine.ASCPolicies.PolicyAssignment.VulnerabilityScanAssessmentKey};
            

            if($null -ne $vulnSetting)
            {
                if($vulnSetting.assessmentResult -eq 'Healthy')
                {
                    $ascVMVulnerabilitiesStatusHealthy = $true;
                }
            }
            $controlResult.AddMessage("VM vuln scan details:", $vulnSetting);
        }            
        
        if($ascVMVulnerabilitiesStatusHealthy)
        {
            $controlResult.VerificationResult = [VerificationResult]::Passed

        }
        else
        {
            $controlResult.VerificationResult = [VerificationResult]::Failed
        }

        $controlResult.AddMessage("Security Center VM Vulnerability status for Virtual Machine [$($this.ResourceContext.ResourceName)]", $ascVMVulnerabilitiesStatusHealthy);        

        return $controlResult;
    }

    hidden [ControlResult] CheckASCVMMissingPatchingStatus([ControlResult] $controlResult)
    {
        #Do not check for deallocated status for the VM and directly show the status from ASC
        $isVerify = $false;
        $ASCApprovedPatchStatus=$this.VMControlSettings.ASCApprovedPatchingHealthStatuses;
        #Get VM ASC Setting
        $ASCSettingsforVM=$this.ASCSettings;
        if($null -ne $ASCSettingsforVM)
        {
                # workspace associated by ASC to send logs for this VM
                $workspaceId=$this.Workspace;
                $queryforPatchDetails=[string]::Format($this.VMControlSettings.QueryforMissingPatches,($this.ResourceContext.ResourceId).ToLower());               

                $ascPatchStatus = "";

                if($null -ne $this.ASCSettings -and [Helpers]::CheckMember($this.ASCSettings, "properties.policyAssessments"))
                {
                    $patchSetting = $null
                    $patchSetting = $this.ASCSettings.properties.policyAssessments | Where-Object {$_.assessmentKey -eq $this.ControlSettings.VirtualMachine.ASCPolicies.PolicyAssignment.OSUpdatesAssessmentKey};
            
                    if($null -ne $patchSetting)
                    {
                        $ascPatchStatus = $patchSetting.assessmentResult;
                    }
                    $controlResult.AddMessage("VM patch status details:", $patchSetting);
                }        
                
                if($ascPatchStatus -in $ASCApprovedPatchStatus)
                {
                    $controlResult.VerificationResult = [VerificationResult]::Passed    
                }
                else
                {
                    $controlResult.VerificationResult=[VerificationResult]::Verify;
                    $controlResult.AddMessage("Details of missing patches can be obtained from the following workspace");
                    $controlResult.AddMessage("Workspace ResourceId: ",($this.WorkspaceResourceId));
                    $controlResult.AddMessage("Workspace Id: ",$workspaceId);
                    $controlResult.AddMessage("The following query can be used to obtain patch details:");
                    $controlResult.AddMessage("Query : ",$queryforPatchDetails);
                }                
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually.");
        }
      return $controlResult;
    }

    hidden [ControlResult] CheckASCVMRecommendations([ControlResult] $controlResult)
    {

        $isManual = $false;
        $result = $null 
        
        $activeRecommendations = @()
        $ASCWhitelistedRecommendations = @();
        $ASCWhitelistedRecommendations += $this.VMControlSettings.ASCRecommendations;
        #[ResourceHelper]::RegisterResourceProviderIfNotRegistered([SecurityCenterHelper]::ProviderNamespace);
        $tasks = [SecurityCenterHelper]::InvokeGetASCTasks($this.SubscriptionContext.SubscriptionId);
        $found = $false;
        if($null -ne $ASCWhitelistedRecommendations -and $null -ne $tasks)
        {
            $tasks | ForEach-Object {
                $recommendation = $_;
                if(($ASCWhitelistedRecommendations | Where-Object { $_ -eq $recommendation.Name -and `
                                                                            $recommendation.State -eq "Active" -and`
                                                                            $recommendation.ResourceId -eq $this.ResourceContext.ResourceId} | Measure-Object).Count -gt 0)
                {
                    $found = $true;
                    $activeRecommendations += $_;
                }
            }
        }
        elseif($null -ne $tasks -and ($tasks | Where-Object { $_.State -eq "Active" -and $_.ResourceId -eq $this.ResourceContext.ResourceId} | Measure-Object).Count -gt 0)
        {
            $found = $true;
            $activeRecommendations = $tasks | Where-Object { $_.State -eq "Active" -and $_.ResourceId -eq $this.ResourceContext.ResourceId} 
        }

        if($found)
        {
            $controlResult.SetStateData("Active recommendations in Security Center", $activeRecommendations);
            $controlResult.AddMessage([VerificationResult]::Failed,"Azure Security Center has active recommendations that need to resolved.")
        }
        else {
            $controlResult.VerificationResult =[VerificationResult]::Passed
        }

        $controlResult.AddMessage(($activeRecommendations | Select-Object Name, State, ResourceId));

        return $controlResult
    }

    hidden [ControlResult] CheckASCVMSecurityBaselineStatus([ControlResult] $controlResult)
    {
        
        $isVerfiy= $false;
        $baselineIds = @();
        $baselineIds += $this.VMControlSettings.BaselineIds
        $queryforFailingBaseline=[string]::Format($this.VMControlSettings.QueryforBaselineRule,($this.ResourceContext.ResourceId).ToLower());
        $ASCApprovedStatuses=$this.VMControlSettings.ASCApprovedBaselineStatuses
        # Get ASC Settings for the VM
        $ASCSettingsforVM=$this.ASCSettings;

        if($null -ne $ASCSettingsforVM)
        {
            # workspace associated by ASC to send logs for this VM
            $workspaceId=$this.Workspace;
            $vulnStatus = "";

            if($null -ne $this.ASCSettings -and [Helpers]::CheckMember($this.ASCSettings, "properties.policyAssessments"))
            {
                $vulnSetting = $null
                $vulnSetting = $this.ASCSettings.properties.policyAssessments | Where-Object {$_.assessmentKey -eq $this.ControlSettings.VirtualMachine.ASCPolicies.PolicyAssignment.VulnerabilityScanAssessmentKey};
                
                if($null -ne $vulnSetting)
                {
                    $vulnStatus = $vulnSetting.assessmentResult;
                }
                $controlResult.AddMessage("VM patch status details:", $vulnSetting);
            }        

            if($vulnStatus -in  $ASCApprovedStatuses)
            {
                $controlResult.VerificationResult = [VerificationResult]::Passed
            }
            else
            {
                $controlResult.VerificationResult = [VerificationResult]::Verify
                $controlResult.AddMessage("Unable to validate baseline status from workspace.Please verify.");
                $controlResult.AddMessage("Details of failing baseline rules can be obtained from Log Analytics workspace.");
                $controlResult.AddMessage("Workspace ResourceId: ",($this.WorkspaceResourceId));
                $controlResult.AddMessage("Workspace Id: ",$workspaceId);
                $controlResult.AddMessage("The following query can be used to obtain failing baseline rules : ",$queryforFailingBaseline);
            }
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually.");
        }
        return $controlResult;
    }
    
    hidden [ControlResult] CheckVMDiagnostics([ControlResult] $controlResult)
    {        
        if($this.ResourceObject.Extensions)
        {
            $diagnosticExtensionType = if($this.VMDetails.OSType -eq [OperatingSystemTypes]::Linux) { "LinuxDiagnostic" } else { "IaaSDiagnostics" }
            
            $diagExtension = $this.ResourceObject.Extensions | Where-Object { $_.VirtualMachineExtensionType -eq $diagnosticExtensionType } | Select-Object -First 1
            if($diagExtension -and ($diagExtension.ProvisioningState -eq "Succeeded"))
            {
                $controlResult.AddMessage([VerificationResult]::Passed, "'$diagnosticExtensionType' extension is installed on Virtual Machine");
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Failed, "'$diagnosticExtensionType' extension is either not installed or provisioning failed on Virtual Machine");
            }
        }
        else
        {
            $controlResult.AddMessage([MessageData]::new("We are not able to fetch the required data for the resource", [MessageType]::Error)); 
        }

        return $controlResult;
    }

    hidden [ControlResult] CheckOpenPorts([ControlResult] $controlResult)
    {    
        if(-not $this.VMDetails.IsVMDeallocated)
        {
            
            $isManual = $false
            $controlResult.AddMessage("Checking for Virtual Machine management ports",$this.VMControlSettings.ManagementPortList);
            $vulnerableNSGsWithRules = @();
            $effectiveNSG = $null;
            $openPortsList =@();
            $this.GetVMNICObjects() | 
                ForEach-Object {    
                    try
                    {
                        $effectiveNSG = Get-AzEffectiveNetworkSecurityGroup -NetworkInterfaceName $_.Name -ResourceGroupName $_.ResourceGroupName -WarningAction SilentlyContinue -ErrorAction Stop
                    }
                    catch
                    {
                        $isManual = $true
                        $statusCode = ($_.Exception).InnerException.Response.StatusCode;
                        if($statusCode -eq [System.Net.HttpStatusCode]::BadRequest -or $statusCode -eq [System.Net.HttpStatusCode]::Forbidden -or $statusCode -eq [System.Net.HttpStatusCode]::Conflict)
                        {                            
                            $controlResult.AddMessage(($_.Exception).InnerException.Message);    
                        }
                        # else
                        # {
                        # throw $_
                        # }
                    }
                if($effectiveNSG)
                    {
                        $vulnerableRules = @()
                        
                        if($this.VMControlSettings -and $this.VMControlSettings.ManagementPortList)
                        {
                            Foreach($PortDetails in  $this.VMControlSettings.ManagementPortList)
                            {
                                $portVulnerableRules = $this.CheckIfPortIsOpened($effectiveNSG,$PortDetails.Port)
                                if(($null -ne $portVulnerableRules) -and ($portVulnerableRules | Measure-Object).Count -gt 0)
                                {
                                    $openPortsList += $PortDetails
                                    $vulnerableRules += $openPortsList
                                }
                            }                            
                        }                
                        
                        if($vulnerableRules.Count -ne 0)
                        {
                            $vulnerableNSGsWithRules += @{
                                Association = $effectiveNSG.Association;
                                NetworkSecurityGroup = $effectiveNSG.NetworkSecurityGroup;
                                VulnerableRules = $vulnerableRules;
                                NicName = $_.Name
                            };
                        }                        
                    }                    
                }

            if($isManual)
            {
                $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check the NSG rules for some NICs. Please validate manually.");
                #Setting this property ensures that this control result will not be considered for the central telemetry, as control does not have the required permissions
                $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess = $false;
                if($vulnerableNSGsWithRules.Count -ne 0)
                {
                    $controlResult.AddMessage([VerificationResult]::Manual, "Management ports are open on Virtual Machine. Please verify and remove the NSG rules in order to comply.", $vulnerableNSGsWithRules);
                }
            }
            elseif($null -eq $effectiveNSG)
            {
                #If the VM is connected to ERNetwork and there is no NSG, then we should not fail as this would directly conflict with the NSG control as well.
                if($this.VMDetails.IsVMConnectedToERvNet)
                {
                    $controlResult.AddMessage([VerificationResult]::Passed, "VM is part of ER Network. And no NSG found for Virtual Machine");  
                }
                else
                {
                    $controlResult.AddMessage([VerificationResult]::Failed, "Verify if NSG is attached to VM.");
                }
                
            }
            else
            {
                #If the VM is connected to ERNetwork or not and there is NSG, then teams should apply the recommendation and attest this control for now.
                if($vulnerableNSGsWithRules.Count -eq 0)
                {              
                    $controlResult.AddMessage([VerificationResult]::Passed, "No management ports are open on Virtual Machine");  
                }
                else
                {
                    $controlResult.AddMessage("List of open ports: ",$openPortsList);
                    $controlResult.AddMessage([VerificationResult]::Verify, "Management ports are open on Virtual Machine. Please verify and remove the NSG rules in order to comply.", $vulnerableNSGsWithRules);

                    $controlResult.SetStateData("Management ports list on Virtual Machine", $vulnerableNSGsWithRules);
                }
            }        
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Verify, "This VM is currently in a 'deallocated' state. Unable to check security controls on it.");
        }
        return $controlResult;
    } 

    hidden [PSObject] CheckIfPortIsOpened([PSObject] $effectiveNSG,[int] $port )
    {
        $vulnerableRules = @();
        $inbloundRules = $effectiveNSG.EffectiveSecurityRules | Where-Object { ($_.direction -eq "Inbound" -and $_.Name -notlike "defaultsecurityrules*") }
        foreach($securityRule in $inbloundRules){
            foreach($destPort in $securityRule.destinationPortRange) {
                $range =$destPort.Split("-")
                #For ex. if we provide the input 22 in the destination port range field, it will be interpreted as 22-22
                if($range.Count -eq 2) {
                    $startPort = $range[0]
                    $endPort = $range[1]
                    if(($port -ge $startPort -and $port -le $endPort) -and $securityRule.access.ToLower() -eq "deny")
                    {
                        break;
                    }
                    elseif(($port -ge $startPort -and $port -le $endPort) -and $securityRule.access.ToLower() -eq "allow")
                    {
                        $vulnerableRules += $securityRule
                    }
                    else
                    {
                        continue;
                    }
                }
                else 
                {
                    throw "Error while reading port range $($destPort)."
                }
    
            }
        }
        return $vulnerableRules;
    }

    
}


Class VMDetails{
[OperatingSystemTypes] $OSType
[string] $Offer
[string] $Sku
[bool] $IsVMConnectedToERvNet
[bool] $IsVMDeallocated
[string] $PublicIPs
[string] $PrivateIPs
}