Framework/Core/SVT/Services/VirtualMachineScaleSet.ps1

using namespace Microsoft.Azure.Commands.Network.Models
using namespace Microsoft.Azure.Commands.Compute.Models
using namespace Microsoft.Azure.Commands.Compute.Automation.Models
using namespace Microsoft.Azure.Management.Compute.Models

Set-StrictMode -Version Latest 

class VirtualMachineScaleSet: AzSVTBase
{       
    hidden [PSVirtualMachineScaleSet] $ResourceObject;
    hidden [PSObject] $VMInstances;
    hidden [PSNetworkInterface[]] $VMNICs = $null;
    hidden [PSObject] $ASCSettings = $null;
    hidden [bool] $IsVMSSDeallocated = $false
    hidden [VMSSDetails] $VMSSDetails = [VMSSDetails]::new()
    hidden [PSObject] $VMSSControlSettings = $null;
    hidden [string] $Workspace = "";
    
    VirtualMachineScaleSet([string] $subscriptionId, [SVTResource] $svtResource): 
        Base($subscriptionId, $svtResource) 
    { 
        $this.GetResourceObject();
        $this.GetVMSSDetails();
        #OS type must always be present in configuration setting file
        if([Helpers]::CheckMember($this.ControlSettings.VirtualMachineScaleSet, $this.VMSSDetails.OSType)){
            $this.VMSSControlSettings = $this.ControlSettings.VirtualMachineScaleSet.$($this.VMSSDetails.OSType);
        }
        # Add VMSS resource meta data details to telemetry
        $metadata= [PSObject]::new();
        $metadata| Add-Member -Name VMSSDetails -Value $this.VMSSDetails -MemberType NoteProperty;
        $this.AddResourceMetadata($metadata);
    }

    [ControlItem[]] ApplyServiceFilters([ControlItem[]] $controls)
    {
        $result = @();
        #Check VMSS type
        $VMSSType = $this.VMSSDetails.OSType
        # Filter control base on OS Image
        if($VMSSType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Linux)
        {
            $result += $controls | Where-Object { $_.Tags -contains "Linux" };
        }
        elseif($VMSSType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Windows)
        {
            $result += $controls | Where-Object { $_.Tags -contains "Windows" };;
        }
        # Filter control for ERvNet connected scale set
        if($this.VMSSDetails.IsVMSSConnectedToERvNet -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.VirtualMachineScaleSet, "ControlExclusionsByService") -and [Helpers]::CheckMember($this.ResourceObject, "Tags")){
            $this.ControlSettings.VirtualMachineScaleSet.ControlExclusionsByService | ForEach-Object {
                if($this.ResourceObject.Tags[$_.ResourceTag] -like $_.ResourceTagValue){
                    $controlTag = $_.ControlTag
                    $result=$result | Where-Object { $_.Tags -notcontains $controlTag };
                }
            }
        }
        return $result;
    }


    hidden  GetVMSSDetails()
    {
        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.StorageProfile") -and [Helpers]::CheckMember($this.ResourceObject.VirtualMachineProfile.StorageProfile,"OsDisk.OsType"))
        {
            $this.VMSSDetails.OSType = $this.ResourceObject.StorageProfile.OsDisk.OsType
        }
        else
        {
            if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.OSProfile") -and $this.ResourceObject.VirtualMachineProfile.OSProfile.LinuxConfiguration)
            {
                $this.VMSSDetails.OSType = [OperatingSystemTypes]::Linux
            }
            else
            {
                $this.VMSSDetails.OSType = [OperatingSystemTypes]::Windows
            }
        }

        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.StorageProfile") -and $this.ResourceObject.VirtualMachineProfile.StorageProfile.ImageReference)
        {
            $this.VMSSDetails.Sku =  $this.ResourceObject.VirtualMachineProfile.StorageProfile.ImageReference.Sku
            $this.VMSSDetails.Offer = $this.ResourceObject.VirtualMachineProfile.StorageProfile.ImageReference.Offer
        }        

        if([Helpers]::CheckMember($this.ResourceObject,"Sku.Capacity")){
            $this.VMSSDetails.Capacity = $this.ResourceObject.Sku.Capacity
        }
        
        #Get if VM is connected to ERvNet
        $this.VMSSDetails.IsVMSSConnectedToERvNet = $this.IsVMSSConnectedToERvNet()

    }

    hidden [PSObject] GetVMSSInstances(){
        if(-not $this.VMInstances){
            $this.VMInstances = Get-AzVmssVM -ResourceGroupName $this.ResourceContext.ResourceGroupName -VMScaleSetName $this.ResourceContext.ResourceName -ErrorAction SilentlyContinue
        }
        return $this.VMInstances;
    }

    hidden [PSVirtualMachineScaleSet] GetResourceObject()
    {
        if (-not $this.ResourceObject) {
            $this.ResourceObject = Get-AzVmss -ResourceGroupName $this.ResourceContext.ResourceGroupName -VMScaleSetName $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 VMSS
            # Commenting this as we are not using any ASC data for now
            #$this.ASCSettings = $this.GetASCSettings();
        }
        return $this.ResourceObject;
    }

    hidden [PSObject] GetASCSettings()
    {
        $result = $null;
        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;
    }

    hidden [bool] IsVMSSConnectedToERvNet()
    {
        $IsVMSSConnectedToERvNet = $false
        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.NetworkProfile")){
            $this.ResourceObject.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations |
            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-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")
                                            {
                                                $IsVMSSConnectedToERvNet= $true
                                            }
                                        }
                                    }
                                }
                            }

    
                        }
                }
            }
        }
        return $IsVMSSConnectedToERvNet;
    }

    hidden [controlresult[]] CheckVMSSMonitoringAgent([controlresult] $controlresult)
    {
    
        $VMSSType = $this.VMSSDetails.OSType
        if($VMSSType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Linux)
        {
            $requiredExtensionType = "OmsAgentForLinux"
            $requiredPublisher = "Microsoft.EnterpriseCloud.Monitoring" 
        }else{
            $requiredExtensionType = "MicrosoftMonitoringAgent"
            $requiredPublisher = "Microsoft.EnterpriseCloud.Monitoring"
        }

        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.ExtensionProfile") -and [Helpers]::CheckMember($this.ResourceObject.VirtualMachineProfile.ExtensionProfile,"Extensions"))
        {
            $configuredExtensions = $this.ResourceObject.VirtualMachineProfile.ExtensionProfile.Extensions
            $installedExtension = $configuredExtensions | Where-Object { $_.Type -eq $requiredExtensionType -and $_.Publisher -eq $requiredPublisher}
            if($null -ne $installedExtension -and ($installedExtension | Measure-Object).Count -gt 0){
                $controlResult.VerificationResult = [VerificationResult]::Passed
                $controlResult.AddMessage("Required Monitoring Agent '$($requiredExtensionType)' is present in VM Scale Set.");
            }else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
                $controlResult.AddMessage("Required Monitoring Agent '$($requiredExtensionType)' is missing in VM Scale Set.");
            }
        
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Manual
            $controlResult.AddMessage("Not able to fetch extension details for VM Scale Set.");
        }

        return $controlResult;
    }

    hidden [controlresult[]] CheckVMSSAntimalwareStatus([controlresult] $controlresult)
    {
        $requiredExtensionType = "IaaSAntimalware"
        $requiredPublisher = "Microsoft.Azure.Security"

        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.ExtensionProfile") -and [Helpers]::CheckMember($this.ResourceObject.VirtualMachineProfile.ExtensionProfile,"Extensions"))
        {
            $configuredExtensions = $this.ResourceObject.VirtualMachineProfile.ExtensionProfile.Extensions
            $installedExtension = $configuredExtensions | Where-Object { $_.Type -eq $requiredExtensionType -and $_.Publisher -eq $requiredPublisher}
            if($null -ne $installedExtension -and ($installedExtension | Measure-Object).Count -gt 0){
                $controlResult.VerificationResult = [VerificationResult]::Passed
                $controlResult.AddMessage("Anti Malware solution '$($requiredExtensionType)' is deployed on VM Scale Set.");
            }else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
                $controlResult.AddMessage("Anti Malware solution '$($requiredExtensionType)' is missing in VM Scale Set.");
            }
        
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Manual
            $controlResult.AddMessage("Not able to fetch extension details for VM Scale Set.");
        }

        return $controlResult;
    }

    hidden [controlresult[]] CheckVMSSDiagnostics([controlresult] $controlresult)
    {
        $VMSSType = $this.VMSSDetails.OSType
        if($VMSSType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Linux)
        {
            $requiredExtensionType = "LinuxDiagnostic"
            $requiredPublisher = "Microsoft.OSTCExtensions"
        }else{
            $requiredExtensionType = "IaaSDiagnostics"
            $requiredPublisher = "Microsoft.Azure.Diagnostics"
        }

        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.ExtensionProfile") -and [Helpers]::CheckMember($this.ResourceObject.VirtualMachineProfile.ExtensionProfile,"Extensions"))
        {
            $configuredExtensions = $this.ResourceObject.VirtualMachineProfile.ExtensionProfile.Extensions
            $installedExtension = $configuredExtensions | Where-Object { $_.Type -eq $requiredExtensionType -and $_.Publisher -eq $requiredPublisher}
            if($null -ne $installedExtension -and ($installedExtension | Measure-Object).Count -gt 0){
                $controlResult.VerificationResult = [VerificationResult]::Passed
                $controlResult.AddMessage("Required diagnostics extension '$($requiredExtensionType)' is present in VM Scale Set.");
            }else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
                $controlResult.AddMessage("Required diagnostics extension '$($requiredExtensionType)' is missing in VM Scale Set.");
            }
        
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Manual
            $controlResult.AddMessage("Not able to fetch extension details for VM Scale Set.");
        }

        return $controlResult;
    }

    hidden [controlresult[]] CheckVMSSAppHealthMonitoring([controlresult] $controlresult)
    {
        $VMSSType = $this.VMSSDetails.OSType
        if($VMSSType -eq [Microsoft.Azure.Management.Compute.Models.OperatingSystemTypes]::Linux)
        {
            $requiredExtensionType = "ApplicationHealthLinux"
            $requiredPublisher = "Microsoft.ManagedServices"
        }else{
            $requiredExtensionType = "ApplicationHealthWindows"
            $requiredPublisher = "Microsoft.ManagedServices"
        }


        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.ExtensionProfile") -and [Helpers]::CheckMember($this.ResourceObject.VirtualMachineProfile.ExtensionProfile,"Extensions"))
        {
            $configuredExtensions = $this.ResourceObject.VirtualMachineProfile.ExtensionProfile.Extensions
            $installedExtension = $configuredExtensions | Where-Object { $_.Type -eq $requiredExtensionType -and $_.Publisher -eq $requiredPublisher}
            if($null -ne $installedExtension -and ($installedExtension | Measure-Object).Count -gt 0){
                $controlResult.VerificationResult = [VerificationResult]::Passed
                $controlResult.AddMessage("Required Application Health extension '$($requiredExtensionType)' is present in VM Scale Set.");
            }else{
                $controlResult.VerificationResult = [VerificationResult]::Failed
                $controlResult.AddMessage("Required Application Health extension '$($requiredExtensionType)' is missing in VM Scale Set.");
            }
        
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Manual
            $controlResult.AddMessage("Not able to fetch extension details for VM Scale Set.");
        }

        return $controlResult;
    }

    hidden [controlresult[]] CheckVMSSDiskEncryption([controlresult] $controlresult)
    {
        # Check if disk encryption is enabled or not on Vm Scale Set Model
        $vmssEncryptionStatus = Get-AzVmssDiskEncryptionStatus -ResourceGroupName $this.ResourceContext.ResourceGroupName -VMScaleSetName $this.ResourceContext.ResourceName -WarningAction SilentlyContinue
        if($null -ne $vmssEncryptionStatus){
            if([Helpers]::CheckMember($vmssEncryptionStatus,"EncryptionEnabled") -and $vmssEncryptionStatus.EncryptionEnabled -eq $true -and [Helpers]::CheckMember($vmssEncryptionStatus,"EncryptionExtensionInstalled") -and $vmssEncryptionStatus.EncryptionExtensionInstalled -eq $true){
                #If Disk encryption is enbled for VMSS, Check encryption status of each OS and Data disk
                $encryptionStatusForVMS = Get-AzVmssVMDiskEncryptionStatus -ResourceGroupName $this.ResourceContext.ResourceGroupName -VMScaleSetName $this.ResourceContext.ResourceName
                # Check for OS Disk encryption
                $nonCompliantOSDisks = $encryptionStatusForVMS | Where-Object {$_.OsVolumeEncrypted -eq 'NotEncrypted'}
                #Check for Data Disk encrytpion
                $nonCompliantDataDisks = $encryptionStatusForVMS | Where-Object {$_.DataVolumesEncrypted -eq 'NotEncrypted'}
                $allDiskAreCompliant = $null
                # If OS disk is not encrypted for any VM instnce fail control
                if($null -ne $nonCompliantOSDisks -and ($nonCompliantOSDisks | Measure-Object).Count -gt 0){
                    $allDiskAreCompliant = $false
                    $controlResult.AddMessage("OS disk is not encrypted for following VMSS instances:", $nonCompliantOSDisks)    
                }
                # If Data disk is not encrypted for any VM instnce fail control
                if($null -ne $nonCompliantDataDisks -and ($nonCompliantDataDisks | Measure-Object).Count -gt 0){
                    $allDiskAreCompliant = $false
                    $controlResult.AddMessage("Data disk is not encrypted for following VMSS instances:", $nonCompliantDataDisks)
                }

                if($allDiskAreCompliant -eq $false){
                    $controlResult.VerificationResult = [VerificationResult]::Failed
                    $controlResult.AddMessage("All Virtual Machine Scale Set disks (OS and Data disks) are not encrypted.")
                }else{
                    $controlResult.VerificationResult = [VerificationResult]::Passed
                    $controlResult.AddMessage("All Virtual Machine Scale Set disks (OS and Data disks) are encrypted.")
                }

            }else {
                if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.StorageProfile") ){
                    $controlResult.VerificationResult = [VerificationResult]::Failed
                    $controlResult.AddMessage("Disk encryption is not enabled for VM Scale Set.")
                }else{
                    $controlResult.VerificationResult = [VerificationResult]::Manual
                    $controlResult.AddMessage("Not able to fetch 'Encryption' state for OS and Data disks. Please verify manually that both Data and OS disks should be encrypted.");
                }
            }
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Manual
            $controlResult.AddMessage("Not able to fetch 'Encryption' state for OS and Data disks. Please verify manually that both Data and OS disks should be encrypted.");
        }
        return $controlResult;
    }

    hidden [controlresult[]] CheckVMSSInstancesStatus([controlresult] $controlresult)
    {
        $upgradeModeIsManual = $true
        # First Get Upgrade Policy defined for VMSS
        if([Helpers]::CheckMember($this.ResourceObject,"UpgradePolicy")){
            if($this.ResourceObject.UpgradePolicy.Mode -eq 'Manual'){
                $upgradeModeIsManual = $true
                $controlResult.AddMessage("Upgrade Policy for VM Scale Set is configured as 'Manual'");    
            }else{
                $upgradeModeIsManual = $false
            }
        }else{
            $upgradeModeIsManual = $true
            $controlResult.AddMessage("Not able to fetch Upgrade Policy details for VM Scale Set.");    
        }

        # If Upgrade Policy is defined as 'Manual', Validate that all VM instances must be running on latest VMSS model
        if($upgradeModeIsManual -eq $true){
            $allVMInstances = $this.GetVMSSInstances();
            if($null -ne $allVMInstances -and ($allVMInstances | Measure-Object).Count -gt 0){
                $nonCompliantInstances = $allVMInstances | Where-Object {$_.LatestModelApplied -ne $true}
                if($null -ne $nonCompliantInstances -and ($nonCompliantInstances|Measure-Object).Count -gt 0){
                    $controlResult.VerificationResult = [VerificationResult]::Failed
                    $nonCompliantInstances = $nonCompliantInstances | Select-Object "Name","InstanceId"
                    $controlResult.AddMessage("Following VM instances are not running on latest VM Scale Set model:", $nonCompliantInstances);
                }else{
                    $controlResult.VerificationResult = [VerificationResult]::Passed
                    $controlResult.AddMessage("All VM instances are running on latest VM Scale Set model.");    
                }
            }else{
                $controlResult.VerificationResult = [VerificationResult]::Manual
                $controlResult.AddMessage("Not able to fetch individual VM instance details for VM Scale Set. Please verify manually that all VM instances are running on latest VM scale set model.");
            }
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Passed
            $controlResult.AddMessage("Upgrade Policy for VM Scale Set is defined as either 'Automatic' or 'Rolling'");        
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckVMSSPublicIP([ControlResult] $controlResult)
    {    
        $publicIps = @();
        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.NetworkProfile")){
            $vmssPublicIPs = Get-AzPublicIpAddress -ResourceGroupName $this.ResourceContext.ResourceGroupName -VirtualMachineScaleSetName $this.ResourceContext.ResourceName  -WarningAction SilentlyContinue 
            if($null -ne $vmssPublicIPs -and ($vmssPublicIPs | Measure-Object).Count -gt 0){
                $publicIps += $vmssPublicIPs |  Select-Object "Name", "ResourceGroupName", "PublicIpAllocationMethod", "IpAddress", "Id"
            }
            if($this.VMSSDetails.IsVMSSConnectedToERvNet)
            {
                $controlResult.AddMessage("This VMSS is part of an ExpressRoute connected virtual network. You must not have any Public IP assigned to such VMSS.");
            }
            if($publicIps.Count -gt 0 -and $this.VMSSDetails.IsVMSSConnectedToERvNet)
            {              
                $controlResult.AddMessage([VerificationResult]::Failed, "Following Public IPs are configured on VMSS", $publicIps);  
                #$controlResult.SetStateData("Public IP(s) associated with Virtual Machine Scale Set", $publicIps);
            }
            elseif($publicIps.Count -gt 0)
            {
                $controlResult.AddMessage([VerificationResult]::Verify, "Validate Public IP(s) associated with Virtual Machine Scale Set. Total - $($publicIps.Count)", $publicIps);  
                #$controlResult.SetStateData("Public IP(s) associated with Virtual Machine Scale Set", $publicIps);
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed, "No Public IP is associated with Virtual Machine Scale Set.");
            }
        }else{
            $controlResult.VerificationResult = [VerificationResult]::Manual;
            $controlResult.AddMessage("Not able to fetch Network configurations for VM Scale Set.");    
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckVMSSNSGConfig([ControlResult] $controlResult)
    {
        $controlResult.VerificationResult = [VerificationResult]::Failed;
        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.NetworkProfile")){
            $this.ResourceObject.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations|
            ForEach-Object {          
                #Check NSGs applied at NIC level
                if($_.NetworkSecurityGroup)
                {
                    $nsgResource = Get-AzResource -ResourceId $_.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 NIC - [$($_.Name)], Total - $($nsgObject.SecurityRules.Count)", $nsgObject.SecurityRules);                                          
                            }
                            
                            if($nsgObject.DefaultSecurityRules.Count -gt 0)
                            {
                                $controlResult.AddMessage("Validate default NSG security rules applied to NIC - [$($_.Name)], Total - $($nsgObject.DefaultSecurityRules.Count)", $nsgObject.DefaultSecurityRules); 
                            }
                            $controlResult.VerificationResult = [VerificationResult]::Verify;
                            $controlResult.SetStateData("NSG security rules", $nsgObject.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.VMSSDetails.IsVMSSConnectedToERvNet)
        {
            $controlResult.AddMessage("This VM Scale Set is part of an ExpressRoute connected virtual network.")    
        }
        if($controlResult.VerificationResult -ne [VerificationResult]::Verify -and $this.VMSSDetails.IsVMSSConnectedToERvNet)
        {            
            $controlResult.AddMessage("No NSG was found on Virtual Machine Scale Set subnet or NIC.")    
            $controlResult.VerificationResult = [VerificationResult]::Passed;
        }
        elseif($controlResult.VerificationResult -ne [VerificationResult]::Verify)
        {
            $controlResult.AddMessage("No NSG was found on Virtual Machine Scale Set subnet or NIC.")        
        }
        }else{

            $controlResult.VerificationResult = [VerificationResult]::Manual;
            $controlResult.AddMessage("Not able to fetch Network configurations for VM Scale Set.");    
        }
    
        return $controlResult;
    }

    hidden [ControlResult] CheckVMSSOpenPorts([ControlResult] $controlResult)
    {    
    
        $isManual = $false
        $controlResult.AddMessage("Checking for Virtual Machine Scale Set management ports",$this.VMSSControlSettings.ManagementPortList);
        $vulnerableNSGsWithRules = @();
        $effectiveNSG = $null;
        $openPortsList =@();
        if([Helpers]::CheckMember($this.ResourceObject,"VirtualMachineProfile.NetworkProfile")){
            $this.ResourceObject.VirtualMachineProfile.NetworkProfile.NetworkInterfaceConfigurations|
            ForEach-Object {   
                #Get the 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)
                                            {
                                                $effectiveNSG = $nsgObject
                                            }
                                        }
                                    }
                                }
                            }
                        }           
                }          
                #Get NSGs applied at NIC level
                if($_.NetworkSecurityGroup)
                {
                    $nsgResource = Get-AzResource -ResourceId $_.NetworkSecurityGroup.Id
                    if($nsgResource){
                        $nsgObject = Get-AzNetworkSecurityGroup -Name $nsgResource.Name -ResourceGroupName $nsgResource.ResourceGroupName
                        if($nsgObject)
                        {
                            $effectiveNSG = $nsgObject
                        }
                    }
                }  
                         
            }
            if($effectiveNSG)
                {
                    $vulnerableRules = @()
                    
                    if($this.VMSSControlSettings -and $this.VMSSControlSettings.ManagementPortList)
                    {
                        Foreach($PortDetails in  $this.VMSSControlSettings.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 += @{
                            NetworkSecurityGroupName = $effectiveNSG.Name;
                            NetworkSecurityGroupId = $effectiveNSG.Id;
                            VulnerableRules = $vulnerableRules
                        };
                    }                        
                }                    
        }else{
            $isManual = $true
        }

        if($isManual)
        {
            $controlResult.AddMessage([VerificationResult]::Manual, "We are not able to check the NSG rules for VM Scale Set. 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;
        }
        elseif($null -eq $effectiveNSG)
        {
            #If the VMSS 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.VMSSDetails.IsVMSSConnectedToERvNet)
            {
                $controlResult.AddMessage([VerificationResult]::Passed, "VM Scale Set is part of ER Network. And no NSG found for Virtual Machine Scale Set.");  
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Failed, "Verify if NSG is attached to VM Scale Set.");
            }
            
        }
        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 Scale Set.");  
            }
            else
            {
                $controlResult.AddMessage("List of open ports: ",$openPortsList);
                $controlResult.AddMessage([VerificationResult]::Verify, "Management ports are open on Virtual Machine Scale Set. Please verify and remove the NSG rules in order to comply.", $vulnerableNSGsWithRules);

                $controlResult.SetStateData("Management ports list on Virtual Machine Scale Set.", $vulnerableNSGsWithRules);
            }
        }        
        return $controlResult;
    } 
    
    
    hidden [controlresult[]] CheckVMSSAutoOSUpgrade([controlresult] $controlresult)
    {
        # First Get Upgrade Policy defined for VMSS
        if([Helpers]::CheckMember($this.ResourceObject,"UpgradePolicy")){
            if([Helpers]::CheckMember($this.ResourceObject.UpgradePolicy,"automaticOSUpgradePolicy.enableAutomaticOSUpgrade") -and $this.ResourceObject.UpgradePolicy.automaticOSUpgradePolicy.enableAutomaticOSUpgrade -eq $true){
                # Set control status to 'Passed' if automaticOSUpgradePolicy.enableAutomaticOSUpgrade property is set to true in the scale set model definition
                $controlResult.VerificationResult = [VerificationResult]::Passed
                $controlResult.AddMessage("Automatic OS image upgrade is configured for VM Scale Set.");    
            }else{
                # Set control status to 'Failed' if automaticOSUpgradePolicy.enableAutomaticOSUpgrade property is missing or set to false in the scale set model definition
                $controlResult.VerificationResult = [VerificationResult]::Failed
                $controlResult.AddMessage("Automatic OS image upgrade is not configured for VM Scale Set.");
            }
        }else{
            # If not able to fetch Upgrade Policy defined for VMSS (for any reason), mark control as 'Manual'
            $controlResult.VerificationResult = [VerificationResult]::Manual
            $controlResult.AddMessage("Not able to fetch Upgrade Policy details for VM Scale Set.");    
        }
        return $controlResult;
    }

    # Helper method to check if specific port is opened in NSG
    hidden [PSObject] CheckIfPortIsOpened([PSObject] $effectiveNSG,[int] $port )
    {
        $vulnerableRules = @();
        $inbloundRules = $effectiveNSG.SecurityRules | Where-Object { ($_.direction -eq "Inbound" ) }
        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;
                    }
                }
                elseif($range.Count -eq 1 -and $destPort -eq $port) 
                {
                    $vulnerableRules += $securityRule
                }
    
            }
        }
        return $vulnerableRules;
    }

    
}


Class VMSSDetails{
[OperatingSystemTypes] $OSType
[string] $Offer
[string] $Sku
[bool] $IsVMSSConnectedToERvNet
[bool] $IsVMSSDeallocated
[int] $Capacity
}