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: SVTBase
{       
    hidden [PSVirtualMachine] $ResourceObject;
    hidden [PSNetworkInterface[]] $VMNICs = $null;
    hidden [PSObject] $ASCSettings = $null;
    hidden [bool] $IsVMDeallocated = $false
    hidden [VMDetails] $VMDetails = [VMDetails]::new()
    hidden [PSObject] $VMControlSettings = $null;
    

    VirtualMachine([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): 
        Base($subscriptionId, $resourceGroupName, $resourceName) 
    { 
        $this.GetResourceObject();        
    }
    
    VirtualMachine([string] $subscriptionId, [SVTResource] $svtResource): 
        Base($subscriptionId, $svtResource) 
    { 
        $this.GetResourceObject();
        $this.GetVMDetails();
        $this.AddResourceMetadata($this.VMDetails);
        
        #TODO: Validate if OSType exist.
        $this.VMControlSettings = $this.ControlSettings.VirtualMachine.$($this.VMDetails.OSType)        
    }

    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
        $this.GetVMNICObjects() |
            ForEach-Object {         
        if($_.IpConfigurations)
                {
                    $_.IpConfigurations | 
                        ForEach-Object {                   
                            $subnetId = $_.Subnet.Id;        
                            $subnetName = $subnetId.Substring($subnetId.LastIndexOf("/") + 1);
                            #vnet id = trim '/subnets/' from subnet id
                            $vnetResource = Get-AzureRmResource -ResourceId $subnetId.Substring(0, $subnetId.IndexOf("/subnets/"))
                            if($vnetResource.ResourceGroupName -in $this.ConvertToStringArray([ConfigurationManager]::GetAzSdkConfigData().ERvNetResourceGroupNames))
                            {
                                $IsVMConnectedToERvNet= $true
                            }
                        }
                }
            }

        return $IsVMConnectedToERvNet;
    }

    hidden [bool] GetVMDeallocationStatus()
    {
        $vmStatusObj = Get-AzureRmVM -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 [PSVirtualMachine] GetResourceObject()
    {
        if (-not $this.ResourceObject) {
            $this.ResourceObject = Get-AzureRmVM -ResourceGroupName $this.ResourceContext.ResourceGroupName -Name $this.ResourceContext.ResourceName  -WarningAction SilentlyContinue 

            if(-not $this.ResourceObject)
            {
                throw ("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName))
            }
            
        }

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

        return $this.ResourceObject;
    }

    hidden [PSObject] GetASCSettings()
    {
        try
        {
            $result = $null;
            [SecurityCenterHelper]::RegisterResourceProvider();
            $uri = [System.String]::Format("{0}subscriptions/{1}/providers/microsoft.Security/securityStatuses?api-version=2015-06-01-preview", [WebRequestHelper]::AzureManagementUri, $this.SubscriptionContext.SubscriptionId)
                
            try 
            {     
                $result = [WebRequestHelper]::InvokeGetWebRequest($uri) 
            } 
            catch
            { 
                return $null;
            }       
        
            if(($result | Measure-Object).Count -gt 0)
            {
                $vmSecurityState = $result | Where-Object { $_.name -eq $this.ResourceContext.ResourceName -and $_.properties.type -eq 'VirtualMachine' } | 
                                                Select-Object -First 1;
                if($vmSecurityState) 
                {        
                    $vmSecurityStateProperties = @{
                        SecurityState = $vmSecurityState.properties.securityState;
                        DetailedStatus = @{
                            Monitered = $vmSecurityState.properties.baselineScannerData.securityState;
                            SystemUpdates = $vmSecurityState.properties.patchScannerData.securityState;
                            EndpointProtection = $vmSecurityState.properties.antimalwareScannerData.securityState;
                            Vulnerabilities = $vmSecurityState.properties.vulnerabilityAssessmentScannerStatus.securityState;
                            DiskEncryption = $vmSecurityState.properties.encryptionDataState.securityState;
                        };
                    };
                    return     $vmSecurityStateProperties;                    
                }                
            }            
        }
        catch
        {
            return $null;
        }
        return $null;
    }

    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-AzureRmResource -ResourceId $_.Id -ErrorAction SilentlyContinue
                        if($currentNic)
                        {
                            $nicResource = Get-AzureRmNetworkInterface -Name $currentNic.ResourceName `
                                                -ResourceGroupName $currentNic.ResourceGroupName `
                                                -ExpandResource NetworkSecurityGroup `
                                                -ErrorAction SilentlyContinue
                            if($nicResource)
                            {
                                $this.VMNICs += $nicResource;
                            }
                        }
                    }
            }            
        }
        return $this.VMNICs;
    }
    
    hidden [ControlResult] CheckOSVersion([ControlResult] $controlResult)
    {
        $supportedSkuList = $this.VMControlSettings.SupportedSkuList | Where-Object { $_.Offer -eq $this.VMDetails.Offer } 
        $vmSkuDetails = $this.VMDetails | Select OSType,Offer,Sku
        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.
        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)
        {
            $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([MessageData]::new("We are not able to fetch the required data for the resource", [MessageType]::Error)); 
        }
            
        return $controlResult;
    }

    hidden [ControlResult] CheckAntimalwareStatus([ControlResult] $controlResult)
    {
        if(-not $this.VMDetails.IsVMDeallocated)
        {
            $verificationResult = [VerificationResult]::Failed;
            $ascAntimalwareStatus = $false;
            if($this.VMDetails.OSType -eq [OperatingSystemTypes]::Linux)
            {
                $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable in case of a Linux Virtual Machine."); 
                return $controlResult
            }

            if($null -ne $this.ASCSettings )
            {
                if( $this.ASCSettings.DetailedStatus.EndpointProtection -eq 'Healthy')
                {                
                    $controlResult.AddMessage([VerificationResult]::Passed,"Antimalware is configured correctly on the VM. Validated the status through ASC."); 
                }
                elseif( $this.ASCSettings.DetailedStatus.EndpointProtection -eq 'Low')
                {                
                    $controlResult.AddMessage([VerificationResult]::Verify,"Validate configurations of antimalware using ASC."); 
                }
                else
                {                    
                    $controlResult.AddMessage([VerificationResult]::Failed,"Antimalware is not configured correctly on the VM. Validated the status through ASC."); 
                }
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually.");
            }
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Verify, "This VM is currently in a ‘deallocated’ state. Unable to check security controls on it.");
        }
        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-AzureRmResource -ResourceId $subnetId.Substring(0, $subnetId.IndexOf("/subnets/"))
                            if($vnetResource)
                            {
                                $vnetObject = Get-AzureRmVirtualNetwork -Name $vnetResource.Name -ResourceGroupName $vnetResource.ResourceGroupName
                                if($vnetObject)
                                {
                                    $subnetConfig = Get-AzureRmVirtualNetworkSubnetConfig -Name $subnetName -VirtualNetwork $vnetObject
                                    if($subnetConfig -and $subnetConfig.NetworkSecurityGroup -and $subnetConfig.NetworkSecurityGroup.Id)
                                    {
                                        $nsgResource = Get-AzureRmResource -ResourceId $subnetConfig.NetworkSecurityGroup.Id
                                        if($nsgResource)
                                        {
                                            $nsgObject = Get-AzureRmNetworkSecurityGroup -Name $nsgResource.Name -ResourceGroupName $nsgResource.ResourceGroupName
                                            if($nsgObject)
                                            {
                                                if($nsgObject.SecurityRules.Count -gt 0)
                                                {
                                                    $controlResult.AddMessage("Validate default 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($controlResult.VerificationResult -ne [VerificationResult]::Verify)
        {
            $controlResult.AddMessage("No NSG is configured on Virtual Machine")                                                   
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckPublicIP([ControlResult] $controlResult)
    {    
        $publicIps = @();
        $this.GetVMNICObjects() | 
            ForEach-Object {
                $_.IpConfigurations | Where-Object { $_.PublicIpAddress } |
                    ForEach-Object {
                        $ipResource = Get-AzureRmResource -ResourceId $_.PublicIpAddress.Id 
                        if($ipResource)
                        {
                            $publicIpObject = Get-AzureRmPublicIpAddress -Name $ipResource.Name -ResourceGroupName $ipResource.ResourceGroupName
                            if($publicIpObject)
                            {
                                $_.PublicIpAddress = $publicIpObject;
                                $publicIps += $publicIpObject;
                            }
                        }
                    }
            }
         
        if($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)
    {    
        if(-not $this.VMDetails.IsVMDeallocated)
        {
            $verificationResult = [VerificationResult]::Failed;
            $ascDiskEncryptionStatus = $false;
            if($null -ne $this.ASCSettings -and $this.ASCSettings.DetailedStatus.DiskEncryption -eq 'Healthy')
            {
                $ascDiskEncryptionStatus = $true;
            }

            #TCP is not applicable for Linux.
            if($this.ResourceObject.OSProfile -and $this.ResourceObject.OSProfile.WindowsConfiguration)
            {
                $diskEncryptionStatus = Get-AzureRmVMDiskEncryptionStatus -ResourceGroupName $this.ResourceContext.ResourceGroupName -VMName $this.ResourceContext.ResourceName
                $message = "";
                $diskEncryptionStatusData = @{
                    VMDiskEncryptionStatus = $diskEncryptionStatus
                    ASCDiskEncryptionStatus = $ascDiskEncryptionStatus
                }
                #Need to convert the string values to Enum [Microsoft.Azure.Commands.Compute.Models.EncryptionStatus]
                #Enum type is not resolving here
                if(($diskEncryptionStatus.OsVolumeEncrypted -eq "NotEncrypted") -or ($diskEncryptionStatus.DataVolumesEncrypted -eq "NotEncrypted") -or -not $ascDiskEncryptionStatus)
                {
                    $message = "All Virtual Machine disks (OS and Data disks) are not encrypted";
                }
                else
                {
                    $verificationResult  = [VerificationResult]::Passed;
                    $message = "All Virtual Machine disks (OS and Data disks) are encrypted";
                }

                $controlResult.AddMessage($verificationResult, $message, $diskEncryptionStatusData);
                $controlResult.SetStateData("Virtual Machine disks encryption status", $diskEncryptionStatusData);
            }
            elseif($this.VMDetails.OSType -eq [OperatingSystemTypes]::Linux)
            {
                $controlResult.AddMessage([VerificationResult]::Manual, "The control is not applicable for Linux Virtual Machine."); 
            }
            elseif($ascDiskEncryptionStatus)
            {
                $verificationResult  = [VerificationResult]::Passed;
                $message = "All Virtual Machine disks (OS and Data disks) are encrypted. Validated the status through ASC.";
                $controlResult.AddMessage($message);
            }
            else
            {            
                $verificationResult  = [VerificationResult]::Failed;
                $message = "All Virtual Machine disks (OS and Data disks) are not encrypted. Validated the status through ASC.";
                $controlResult.AddMessage($message);
            }
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Verify, "This VM is currently in a ‘deallocated’ state. Unable to check security controls on it.");
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckASCStatus([ControlResult] $controlResult)
    {
        $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;
    }

    hidden [ControlResult] CheckASCVulnerabilities([ControlResult] $controlResult)
    {
        $ascVMVulnerabilitiesStatusHealthy = $false;
        if($null -ne $this.ASCSettings -and $this.ASCSettings.DetailedStatus.Vulnerabilities -eq 'Healthy')
        {
            $ascVMVulnerabilitiesStatusHealthy = $true;
        }
        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)
    {

        if(-not $this.VMDetails.IsVMDeallocated)
        {
            $isManual = $false;
            $result = $null 
        
            [Helpers]::RegisterResourceProviderIfNotRegistered([SecurityCenterHelper]::ProviderNamespace);
            $uri = [System.String]::Format("{0}{1}/providers/microsoft.Security/dataCollectionResults/patch?api-version=2015-06-01-preview", [WebRequestHelper]::AzureManagementUri, $this.ResourceContext.ResourceId);
            try 
            {     
                $result = [WebRequestHelper]::InvokeGetWebRequest($uri) 
            } 
            catch
            { 
                $isManual = $true;
            }         
        
            if(($result | Measure-Object).Count -gt 0)
            {
                $vmobject = $result[0];
                if($null -ne $vmobject -and (Get-Member -InputObject $vmobject -Name properties) -and  $null -ne $vmobject.properties `
                -and (Get-Member -InputObject $vmobject.properties -Name missingPatches) `
                -and ($vmobject.properties.missingPatches | Measure-Object).Count -gt 0)
                {                
                    $controlResult.VerificationResult = [VerificationResult]::Failed
                    $controlResult.AddMessage("Security Center details missing OS patch status for Virtual Machine [$($this.ResourceContext.ResourceName)]:", $vmobject.properties.missingPatches);
                }
                else
                {
                    $controlResult.VerificationResult = [VerificationResult]::Passed                
                }
            }
            else
            {            
                $isManual = $true;
            }

            if($isManual)
               {
                $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check Security Center status right now. Please validate manually.");
            }
        }
        else
        {
            $controlResult.AddMessage([VerificationResult]::Verify, "This VM is currently in a ‘deallocated’ state. Unable to check security controls on it.");
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckASCVMSecurityBaselineStatus([ControlResult] $controlResult)
    {
        $isManual = $false;
        $result = $null 
        
        $baselineIds = @();
        $baselineIds += $this.ControlSettings.VirtualMachine.Windows_OS_Baseline_Ids
        [Helpers]::RegisterResourceProviderIfNotRegistered([SecurityCenterHelper]::ProviderNamespace);
        $uri = [System.String]::Format("{0}{1}/providers/microsoft.Security/dataCollectionResults/baseline?api-version=2015-06-01-preview", [WebRequestHelper]::AzureManagementUri, $this.ResourceContext.ResourceId);
        try 
        {     
            $result = [WebRequestHelper]::InvokeGetWebRequest($uri) 
        } 
        catch
        { 
            $isManual = $true;
        }         
        
        if(($result | Measure-Object).Count -gt 0)
        {
            $vmobject = $result[0];
            if($null -ne $vmobject -and (Get-Member -InputObject $vmobject -Name properties) -and  $null -ne $vmobject.properties `
            -and (Get-Member -InputObject $vmobject.properties -Name failedBaselineRules) `
            -and ($vmobject.properties.failedBaselineRules | Measure-Object).Count -gt 0)
            {
                $missingBaselines = @()
                if($baselineIds.Count -gt 0)
                {
                    $foundMissingBaseline = $false;
                    foreach($failedBaseline in $vmobject.properties.failedBaselineRules)
                    {
                        if($null -ne $failedBaseline.baselineRuleResults -and $failedBaseline.baselineRuleResults.evalResult -eq "FAIL")
                        {
                            foreach($baselineId in $baselineIds)
                            {
                                if($baselineId.Trim() -eq $failedBaseline.baselineRuleData.cceid)
                                {
                                    $foundMissingBaseline = $true;
                                    $missingBaselines += $failedBaseline;
                                }
                            }                        
                        }
                    }
                    if($foundMissingBaseline)
                    {
                        $controlResult.VerificationResult = [VerificationResult]::Failed
                        $controlResult.AddMessage("Details of Security Center baseline status for Virtual Machine [$($this.ResourceContext.ResourceName)]:", $missingBaselines);
                    }
                }
                else
                {
                    $controlResult.VerificationResult = [VerificationResult]::Verify
                    $controlResult.AddMessage("Details of Security Center baseline status for Virtual Machine [$($this.ResourceContext.ResourceName)]:", $vmobject.properties.failedBaselineRules);
                }
            }
            else
            {
                $controlResult.VerificationResult = [VerificationResult]::Passed                
            }
        }
        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;
    }
    
    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)
            {
                $controlResult.AddMessage([VerificationResult]::Passed, "'$diagnosticExtensionType' extension is installed on Virtual Machine");
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Failed, "'$diagnosticExtensionType' extension is not installed 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-AzureRmEffectiveNetworkSecurityGroup -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)
                        {
                            $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]::Verify, "We are not able to check the NSG rules for some NICs. Please validate manually.");  
                if($vulnerableNSGsWithRules.Count -ne 0)
                {
                    $controlResult.AddMessage([VerificationResult]::Verify, "Management ports are open on Virtual Machine. Please verify and remove the NSG rules in order to comply.", $vulnerableNSGsWithRules);
                }
            }
            elseif($null -eq $effectiveNSG)
            {
                $controlResult.AddMessage([VerificationResult]::Failed, "Verify if NSG is attached to VM.");  
            }
            else
            {
                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]::Failed, "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){
            $range =$securityRule.destinationPortRange.Split("-")
            if(($range | Measure-Object).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 $($securityRule.destinationPortRange)."            
            }                
        }
        return $vulnerableRules;
    }
}


Class VMDetails{
[OperatingSystemTypes] $OSType
[string] $Offer
[string] $Sku
[bool] $IsVMConnectedToERvNet
[bool] $IsVMDeallocated
}
# SIG # Begin signature block
# MIIkAgYJKoZIhvcNAQcCoIIj8zCCI+8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCOm2vct8320AZT
# ZJUn0anh2abe+Z3Xu3eWs/bJeQiFFaCCDZMwggYRMIID+aADAgECAhMzAAAAjoeR
# pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q
# UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv
# 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc
# wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg
# 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm
# +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH
# uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww
# HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y
# FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw
# MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1
# ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw
# SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv
# ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG
# AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p
# Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq
# hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR
# avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO
# qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM
# tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d
# 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX
# jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW
# UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI
# BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd
# 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald
# G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF
# he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww
# ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5
# MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw
# MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ
# TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT
# iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS
# L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H
# DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ
# PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5
# bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS
# rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh
# vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON
# 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc
# v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw
# iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC
# AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM
# HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud
# IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0
# dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF
# BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC
# Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA
# YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI
# hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s
# PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL
# UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7
# pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft
# 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4
# MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv
# FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG
# 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf
# 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY
# S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv
# QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV
# xTCCFcECAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA
# AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx
# DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq
# hkiG9w0BCQQxIgQgdDf0BprG4NfUpPK/8VcZJzbftKVFJRbmX0E3YFimqCEwRAYK
# KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr
# YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBABd/iR4HD6Kr3XsR
# skqeOd7MPrx8zhK3GbRcu0omDTNvun9EibGr4nFxTbMZoVSm6W/unoKVGywPY2iI
# eH+2uaYwm6kHWBphlMpP4Pp9l9G4VUYWUoymFwblI3N7v1L2XWWQdjCjFKiI3Z/Y
# 04Ht04trUNhSVtoJtBNq7sztcVEPkQkTa226lGPAsdBnFGspJGjfMyKJxAk01wJi
# VxJT2RR6Zy6+VzfFkp6tYIkgiPuZ4kgnlXEpTFsl+FlrgtLZfIfvs0pg3VxLehI2
# wLlRuLEPLRUGwAuubglL4NSwQGdhiHLk5Lv48dEt5qfxpgErAwJLEtAb/VuFTmvb
# IXWXz1KhghNNMIITSQYKKwYBBAGCNwMDATGCEzkwghM1BgkqhkiG9w0BBwKgghMm
# MIITIgIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSgggEsBIIB
# KDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgHYqJ4mUTdlo4
# Akq0Ro9jThz6F8usp4HzG2EXMyMu8scCBlmSONxRFxgTMjAxNzA5MDUwOTM3MTYu
# MjU4WjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0Ug
# RVNOOjdEMkUtMzc4Mi1CMEY3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIO0DCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcN
# AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw
# BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw
# MB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp
# HQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVT
# JwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q
# 6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h
# /EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+
# 79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4
# zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAd
# BgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBT
# AHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw
# FoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
# XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAx
# MC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0G
# CCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BT
# L2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBs
# AGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4IC
# AQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efw
# eL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt0
# 70IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQi
# PM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93F
# SguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4a
# rgRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qA
# xdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995y
# fmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaY
# LeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL
# 32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4
# L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPCoAMCAQIC
# EzMAAACiTI4d2qkhfIQAAAAAAKIwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjQ5WhcNMTgwOTA3MTc1NjQ5
# WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE
# CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjdEMkUtMzc4Mi1CMEY3
# MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApgF3BXvDfQ52aL3GqIPGgnsBcKiwaoRy
# T1SObb5nIGq+7C3dL0dx4ZL9dGJrvnTbhTbbNMjHaQrmNhJJQEe4QTRZYj3xE6eu
# CQEo0RRYYx85sGg2BNhZ2k1b4JFxEvxBsw3OwXKhFSb285lb6OCKrhB1qjnX8Q7y
# CcExdQwpKind7I43Kt9rquMyuNhNe8hxEJDBvjqGGQvo6a0fuDQjrWGMli77Pkwv
# QmXGCx6xsFjCa5vnz4sEx5NBneZ5uOX3llfcgMUFBQCmQSI3Fvm060esLzmt0MXT
# DETXCVtp0QnzAytjJ1oHkPTvjKMzJY03LD8lmbPzFT6mkur5URjl1QIDAQABo4IB
# GzCCARcwHQYDVR0OBBYEFG672cHC2hawfK3CU3/n0BcTPbxXMB8GA1UdIwQYMBaA
# FNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8y
# MDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAt
# MDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ
# KoZIhvcNAQELBQADggEBADWZV20z9kidnkHczKlg7lxHSFQRCoa5Vz+nbZiOynxp
# pmqWg845sr1kDpkwu77c4HWCa0Ip4YVEjjF7c2hZeZidV7QWB/yiKbyjcIIhJh1l
# SxnmiSpVza+6kmreyJoJAlslFpfHq7la2vTzoBuCcKpKxka1eoDEYkKD93FaZCsm
# /fOOIwtOFvIb8tA1CkPaPPOpGpGKxDV42RCoYwajZH+svyjuqBwVeI+g98Jxxdi4
# ks6ql3I5TA9oZEROyoblLcuyArEoPf0ZvwDWSNPfDbTtDCSQRRS8lXk6A+xjhjw0
# 7nGyPS5qeZCCtusbGlm7r4uLefGp/Uox8jxqGxVdOsahggN5MIICYQIBATCB46GB
# uaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsG
# A1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjdEMkUtMzc4Mi1C
# MEY3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEw
# CQYFKw4DAhoFAAMVAF4vF+lxhNIyy+zXXko7+E63h+n1oIHCMIG/pIG8MIG5MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BS
# MScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NTdGNi1DMUUwLTU1NEMxKzApBgNV
# BAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcN
# AQEFBQACBQDdWK1sMCIYDzIwMTcwOTA1MDQ1ODIwWhgPMjAxNzA5MDYwNDU4MjBa
# MHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAN1YrWwCAQAwCgIBAAICDnkCAf8wBwIB
# AAICGLQwCgIFAN1Z/uwCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoD
# AaAKMAgCAQACAxbjYKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEAcmGj
# BBTr+m5dd4eEDdc2UUFm4Al/CGHzZYgdOFKvfZ1/YnsnH6MMuoUgAwG6+EhEWyMD
# oFUbsyNGdUyXXN9eQPDmjT4Gk1Tl7cQHgHEKVmqElgVAyDISE3V3CvqYlypCasYl
# iuOt/aE+EiBterVmkPPEB+XFz1M+WMFEjjA/l/9zTQj9XYjey4415ox5/k4TT02B
# GUr+0OcU1/7BzL3CqEHRpal9ES6i7+8jFsm8xgi7ifMZSTVeFlcw0AzO38+YUc7T
# +othjWn50Ll0INYWIE1uHMP6M+hGN9fD8TK0WdhaKP59pbcMpd9d3kjG+sNeggSh
# bwcVPJiNzcVIaGbjxDGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAAAokyOHdqpIXyEAAAAAACiMA0GCWCGSAFlAwQCAQUAoIIB
# MjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEILFp
# u0mUIl4C3qrwCVTp1Xd4r7WcuURgR9TN+pYGtE/CMIHiBgsqhkiG9w0BCRACDDGB
# 0jCBzzCBzDCBsQQUXi8X6XGE0jLL7NdeSjv4TreH6fUwgZgwgYCkfjB8MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAKJMjh3aqSF8hAAAAAAAojAWBBTM
# BEq1o6Nbw5EBNOEoqaWczrIaQDANBgkqhkiG9w0BAQsFAASCAQBdwhwNnib1kTYh
# 2R1YWVJPmvrHzxTh4iNkQk5hRdwYTpetaA/OBtZ8ItO+J3bDVuJfHUCC7doaUtsp
# /iWipTJH45O+kJ1CDVA7woqY/DyIixJqyk+Hzk3Gx/n5cLWOXl1hCyfCXywuNgsm
# wVeTIhW75lfNmKRs4wuAr6qw7t31lCl9x0tBwbhdAoGuSThSRU/qASZCO7lV5gZT
# q0qNJ+UR+LGhpEo4JHMVEpF0UctnvHLaado9VJ4XEkWwpJATDWYqF+4jXQxe/VyP
# lGSuvczk0eoFvufgn0U/lsC24gdvwDcUplIswvWWqZHe3p27tdZk+ERau385CDEY
# +4tg7HuL
# SIG # End signature block