Framework/Core/SubscriptionSecurity/Alerts.ps1

using namespace System.Management.Automation
Set-StrictMode -Version Latest 

# Class to implement Subscription alert controls
class Alerts: AzCommandBase
{    
    hidden [PSObject[]] $Policy = $null;
    
    hidden [PSObject[]] $ApplicableAlerts = $null;
    
    hidden [string] $TargetResourceGroup;
    hidden [string] $ResourceGroup ;
    hidden [string] $ResourceGroupLocation;
    hidden [PSObject] $AlertPolicyObj = $null
    hidden [string] $RunbookName=[Constants]::AlertRunbookName
    hidden [string] $Alert_ResourceCreation_Runbook=[Constants]::Alert_ResourceCreation_Runbook
    
    hidden [string] $AutomationWebhookName=[Constants]::AutomationWebhookName
    hidden [string] $AutomationAccountName=[Constants]::AutomationAccountName
    hidden [int] $WebhookExpiryInDays = [Constants]::AlertWebhookUriExpiryInDays
    Alerts([string] $subscriptionId, [InvocationInfo] $invocationContext, [string] $tags): 
        Base($subscriptionId, $invocationContext)
    {
        $this.ResourceGroup = [ConfigurationManager]::GetAzSKConfigData().AzSKRGName
        $this.AlertPolicyObj =  $this.LoadServerConfigFile("Subscription.InsARMAlerts.json");
        $this.FilterTags = $this.ConvertToStringArray($tags);
        $this.ResourceGroupLocation = [ConfigurationManager]::GetAzSKConfigData().AzSKLocation;
        $this.Policy = $this.AlertPolicyObj.AlertList 
    }

    hidden [PSObject[]] GetApplicableAlerts([string[]] $alertNames)
    {
        if($null -eq $this.ApplicableAlerts)
        {
            $this.ApplicableAlerts = @();

            if($alertNames -and $alertNames.Count -ne 0)
            {
                $this.ApplicableAlerts += $this.Policy | Where-Object { $alertNames -Contains $_.Name };
            }
            elseif(($this.FilterTags | Measure-Object).Count -ne 0)
            {
                $this.Policy |
                    ForEach-Object {
                        $currentResourceTypeItem = $_;
                        $applicableAlert = @{ Name=$currentResourceTypeItem.Name;Description = $currentResourceTypeItem.Description; OperationNameList =@(); Enabled = $currentResourceTypeItem.Enabled }
                        $currentResourceTypeItem.AlertOperationList | ForEach-Object{
                        $currentItem = $_                            
                        if(($currentItem | Where-Object { $this.FilterTags -Contains $_.Tags  -and $_.Enabled -eq $true} | Measure-Object).Count -ne 0)
                        {
                            $applicableAlert.OperationNameList  += $currentItem.OperationName;
                        }

                    }
                        if(($applicableAlert.OperationNameList | Measure-Object).Count -gt 0 )
                        {
                            $this.ApplicableAlerts += $applicableAlert
                        }
                    }
            }
        }
            
        return $this.ApplicableAlerts;
    }
    hidden [PSObject[]] GetApplicableAlerts()
    {
        return $this.GetApplicableAlerts(@());
    }

    [MessageData[]] RemoveAlerts([string] $alertNames, [bool] $DeleteActionGroup)
    {
        return $this.RemoveAlerts($this.ResourceGroup, $alertNames, $DeleteActionGroup)
    }

    [MessageData[]] RemoveAlerts([string] $rgName,  [string] $alertNames, [bool] $DeleteActionGroup)
    {
        [MessageData[]] $messages = @();

        # Check for existence of resource group

        $existingRG = Get-AzResourceGroup -Name $rgName -ErrorAction SilentlyContinue
        if($existingRG)
        {
            $startMessage = [MessageData]::new("Found alerts resource group: $rgName");
            $messages += $startMessage;
            $this.PublishCustomMessage($startMessage);

            $alertNameArray = @();        
            if(-not [string]::IsNullOrWhiteSpace($alertNames))
            {
                $alertNameArray += $this.ConvertToStringArray($alertNames);
                if($alertNameArray.Count -eq 0)
                {
                    throw ([SuppressedException]::new(("The argument 'alertNames' is null or empty"), [SuppressedExceptionType]::NullArgument))
                }
            }        
            $policyList=@();
            if(($this.FilterTags|Measure-Object).Count -gt 0)
            {
             ForEach($Tag in $this.FilterTags)
             {
               $policyList+= $this.Policy | Where-Object { $Tag -In $_.Tags }          
             }    
              $this.Policy=$policyList| Select-Object * -Unique    
              $this.Policy |ForEach-Object { $alertNameArray+=$_.Name }
            }
            else
            {
              $policyList+= $this.Policy
              $this.Policy=$policyList| Select-Object * -Unique
            }

            # User wants to remove only specific alerts
            if(($this.Policy | Measure-Object).Count -ne 0)
            {
                if($this.GetApplicableAlerts($alertNameArray) -ne 0)
                {
                    $startMessage = [MessageData]::new("Removing alerts. Tags:[$([string]::Join(",", $this.FilterTags))]. Total alerts: $($this.GetApplicableAlerts($alertNameArray).Count)");
                    $messages += $startMessage;
                    $this.PublishCustomMessage($startMessage);
                    $this.PublishCustomMessage("Note: Removing alerts can take few minutes depending on number of alerts to be processed...", [MessageType]::Warning);                

                    $disabledAlerts = $this.GetApplicableAlerts($alertNameArray) | Where-Object { -not $_.Enabled };
                    if(($disabledAlerts | Measure-Object).Count -ne 0)
                    {
                        $disabledMessage = "Found alerts which are disabled and will not be removed. This is intentional. Total disabled alerts: $($disabledAlerts.Count)";
                        $messages += [MessageData]::new($disabledMessage, $disabledAlerts);
                        #$this.PublishCustomMessage($disabledMessage, [MessageType]::Warning);
                    }

                    $enabledAlerts = @();
                    $enabledAlerts += $this.GetApplicableAlerts($alertNameArray) | Where-Object { $_.Enabled };
                    if($enabledAlerts.Count -ne 0)
                    {
                        $messages += [MessageData]::new([Constants]::SingleDashLine + "`r`nRemoving following alerts from the subscription. Total alerts: $($enabledAlerts.Count)", $enabledAlerts);                                            

                        $errorCount = 0;
                        $currentCount = 0;
                        $enabledAlerts | ForEach-Object {
                            $alertName = $_.Name;
                            $currentCount += 1;
                            # Remove alert
                            try
                            {
                                Remove-AzResource -ResourceType "Microsoft.Insights/activityLogAlerts" -ResourceGroupName  $rgName -Name $alertName -Force
                                #Remove-AzureRmAlertRule -ResourceGroup $this.ResourceGroup -Name $alertName -WarningAction SilentlyContinue
                            }
                            catch
                            {
                                $messages += [MessageData]::new("Error while removing alert [$alertName] from the subscription", $_, [MessageType]::Error);
                                $errorCount += 1;
                            }

                            $this.CommandProgress($enabledAlerts.Count, $currentCount, 20);
                        };

                        [MessageData[]] $resultMessages = @();
                        if($errorCount -eq 0)
                        {
                            #setting the tag at AzSKRG
                            [ResourceGroupHelper]::SetResourceGroupTags($rgName,@{[Constants]::AzSKAlertsVersionTagName=$this.AlertPolicyObj.Version}, $true)

                            $resultMessages += [MessageData]::new("All alerts have been removed from the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                            if($DeleteActionGroup)
                            {
                                Remove-AzResource -ResourceType "Microsoft.Insights/actiongroups" -ResourceGroupName $rgName -Name $([Constants]::AlertActionGroupName) -Force
                                Remove-AzResource -ResourceType "Microsoft.Insights/actiongroups" -ResourceGroupName $rgName -Name $([Constants]::CriticalAlertActionGroupName) -ErrorAction SilentlyContinue -Force 
                                $resultMessages += [MessageData]::new("Action Group have been removed from the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                            }
                                
                        }
                        elseif($errorCount -eq $enabledAlerts.Count)
                        {
                            $resultMessages += [MessageData]::new("No alerts have been removed from the subscription due to error occurred. Please add the alerts manually.`r`n" + [Constants]::SingleDashLine, [MessageType]::Error);
                        }
                        else
                        {
                            $resultMessages += [MessageData]::new("$errorCount/$($enabledAlerts.Count) alert(s) have not been removed from the subscription. Please remove the alerts manually.", [MessageType]::Error);
                            $resultMessages += [MessageData]::new("$($enabledAlerts.Count - $errorCount)/$($enabledAlerts.Count) alert(s) have been removed from the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                        }
                        $messages += $resultMessages;
                        $this.PublishCustomMessage($resultMessages);

                    }
                }
                else
                {
                    $this.PublishCustomMessage("No alerts have been found that matches the specified tags. Tags:[$([string]::Join(",", $this.FilterTags))].", [MessageType]::Warning);
                }
            }
            else
            {
                $this.PublishCustomMessage("No alerts found in the alert policy file", [MessageType]::Warning);
            }
        }
        else
        {
            $this.PublishCustomMessage("No configured alerts found in the subscription. Resource group not found: $rgName", [MessageType]::Warning);
        }
        return $messages;
    }    

    [MessageData[]] SetAlerts([string] $targetResourceGroup, [string] $securityContactEmails,[string] $SecurityPhoneNumbers, [string] $alertResourceGroupLocation, [PSObject] $emailReceivers, [PSObject] $smsReceivers, [string] $curWebhookUri)
    {
        $actionGroupResourceId = $null
        $criticalAlertActionGroupResourceId=$null
        $existingWebhookUri=$null
        [MessageData[]] $messages = @();
        $webhookUri = $curWebhookUri;
        #There is a possibility that if the Alerts are reconfigured, the existing webhook uri will get overwritten with the new setup. Need to take the current backup
        if([string]::IsNullOrWhiteSpace($webhookUri))
        {
            $webhookUri = $this.ComputeAlertRunbookWebhookUri("Alert");
        }        

        if(-not [string]::IsNullOrWhiteSpace($SecurityPhoneNumbers) -or $null -ne $smsReceivers)
        {
            if(-not $this.FilterTags.Contains("SMS"))
            {
                $this.FilterTags += "SMS"
            }
        }

        if($this.Force -or -not ([ResourceGroupHelper]::IsLatestVersionConfiguredOnSub($this.AlertPolicyObj.Version,[Constants]::AzSKAlertsVersionTagName,"Alerts")))
        {
            $allEmails = @();
            # Parameter validation
            if([string]::IsNullOrWhiteSpace($securityContactEmails))
            {
                #If security contact emails is blank check if old V1 alerts are configured and get email from alert resource
                #Check if V2 alert action group is present and assign existing action group resource Id
                $actionGroupResourceId = "";
                $curActionGroup = $this.GetAlertActionGroup($this.ResourceGroup, [Constants]::AlertActionGroupName)
                if($curActionGroup)
                {
                    $actionGroupResourceId = $curActionGroup.ResourceId
                }

                $criticalAlertActionGroupResourceId = "";
                $curCriticalActionGroup = $this.GetAlertActionGroup($this.ResourceGroup, [Constants]::CriticalAlertActionGroupName)
                if($curCriticalActionGroup)
                {
                    $criticalAlertActionGroupResourceId = $curCriticalActionGroup.ResourceId
                }

                if(($curActionGroup | Measure-Object).Count -eq 0 -and $null -eq $emailReceivers)
                {
                    $this.PublishCustomMessage("'SecurityContactEmails' is required to configure alerts. Please set up alerts with cmdlet Set-AzSKAlerts. Run 'Get-Help Set-AzSKAlerts -full' for more help.", [MessageType]::Error);
                    return $null;
                }            
            }
            else
            {
                $allEmails += $this.ConvertToStringArray($securityContactEmails);
                #Validate emails
                if((($allEmails | Measure-Object).Count -gt 0))
                {
                    $invalidEmailList = [Helpers]::ValidateEmailList($allEmails)
                    if(($invalidEmailList| Measure-Object).Count -gt 0)
                    {
                         $this.PublishCustomMessage(("Please enter valid security contact email id(s): "+ [string]::Join(",", $invalidEmailList)) , [MessageType]::Error);
                         return $null
                    }            
                }
            }


            if(-not [string]::IsNullOrWhiteSpace($alertResourceGroupLocation))
            {
                $this.ResourceGroupLocation = $alertResourceGroupLocation;
            }

        
            if(($this.Policy | Measure-Object).Count -ne 0)
            {
                $alertList = $this.GetApplicableAlerts();
                if($alertList -ne 0)
                {
                    $criticalAlerts = $alertList 
                    $startMessage = [MessageData]::new("Processing AzSK alerts. Total alert groups: $($criticalAlerts.Count)");
                    $messages += $startMessage;
                    $this.PublishCustomMessage($startMessage);
                    $this.PublishCustomMessage("Note: Configuring alerts can take about 4-5 min...", [MessageType]::Warning);                

                    $disabledAlerts = $criticalAlerts | Where-Object { -not $_.Enabled };
                    if(($disabledAlerts | Measure-Object).Count -ne 0)
                    {
                        $disabledMessage = "Found alerts which are disabled. This is intentional. Total disabled alerts: $($disabledAlerts.Count)";
                        $messages += [MessageData]::new($disabledMessage, $disabledAlerts);                    
                    }

                    $enabledAlerts = @();
                    $enabledAlerts += $criticalAlerts | Where-Object { $_.Enabled };
                    if($enabledAlerts.Count -ne 0)
                    {
                        $messages += [MessageData]::new([Constants]::SingleDashLine + "`r`nAdding following alerts to the subscription. Total alerts: $($enabledAlerts.Count)", $enabledAlerts);                                            

                        # Check if Resource Group exists
                        $existingRG = Get-AzResourceGroup -Name $this.ResourceGroup -ErrorAction SilentlyContinue
                        if(-not $existingRG)
                        {
                            [ResourceGroupHelper]::NewAzSKResourceGroup($this.ResourceGroup,$this.ResourceGroupLocation,$this.GetCurrentModuleVersion())                        
                        }
                        $messages += [MessageData]::new("All the alerts registered by this script will be placed in a resource group named: $($this.ResourceGroup)");
                        $isTargetResourceGroup = -not [string]::IsNullOrWhiteSpace($targetResourceGroup);
                        #if email address are provided, Create/edit the action group. This will not support append functionality.
                        if(($allEmails | Measure-Object).Count -gt 0 -or $null -ne $emailReceivers)
                        {
                            $actionGroupResourceId = $this.SetupAlertActionGroup($allEmails,$SecurityPhoneNumbers,$emailReceivers)                        
                            if([string]::IsNullOrWhiteSpace($actionGroupResourceId))
                            {
                                $this.PublishCustomMessage("Not able to create alert action group. Validate detailed log for more details.", [MessageType]::Error);
                                return $messages
                            }
                            if(-not [string]::IsNullOrWhiteSpace($SecurityPhoneNumbers) -or $null -ne $smsReceivers)
                            {
                                $criticalAlertActionGroupResourceId = $this.SetupAlertActionGroup($SecurityPhoneNumbers, $smsReceivers)
                                if([string]::IsNullOrWhiteSpace($criticalAlertActionGroupResourceId))
                                {
                                     $this.PublishCustomMessage("Some SMS-based alerts could not be created. This may be because the phone number specified was not in the expected format. E.g., +1-425-882-8080.If you would like to receive SMS alerts, please rerun this command with the correct phone number format.", [MessageType]::Warning);
                                }
                            }
                        }
                        try
                        {
                            $criticalAlertList = @()
                            $alertArm =  $this.LoadServerConfigFile("Subscription.AlertARM.json");
                            $alert = ($alertArm.resources | Select-Object -First 1).PSObject.Copy()
                            $enabledAlerts | ForEach-Object {
                                $alertObj =  [Helpers]::DeepCopy($alert)
                                $alertObj.name = $_.Name
                                $alertObj.properties.description = $_.Description                                
                                $alertObj.properties.condition.allOf[2].anyOf =@()
                                $_.OperationNameList | ForEach-Object {
                                    $alertObj.properties.condition.allOf[2].anyOf += @{ field = "operationName"; equals =$_ }
                                }
                                if($isTargetResourceGroup)
                                {
                                    $alertObj.properties.condition.allOf += @{field= "resourceGroup";equals= $targetResourceGroup}    
                                }
                                
                                if($_.Name -eq "AzSK_Critical_Alert")
                                {
                                    if(-not [string]::IsNullOrWhiteSpace($criticalAlertActionGroupResourceId))
                                     {
                                      $alertObj.properties.actions.actionGroups[0].actionGroupId = $criticalAlertActionGroupResourceId
                                      $criticalAlertList += $alertObj
                                    }            
                                }
                                else
                                {
                                  $alertObj.properties.actions.actionGroups[0].actionGroupId = $actionGroupResourceId
                                  $criticalAlertList += $alertObj
                                }
                                
                                #$criticalAlertList += $alertObj
                            }
                            $alertArm.resources = $criticalAlertList 
                            $armTemplatePath = [Constants]::AzSKTempFolderPath + "Subscription.AlertARM.json";
                            $alertArm | ConvertTo-Json -Depth 100  | New-Item $armTemplatePath -Force | Out-Null
                            $alertDeployment = New-AzResourceGroupDeployment -Name "AzSKAlertsDeployment" -ResourceGroupName $this.ResourceGroup -TemplateFile $armTemplatePath  -ErrorAction Stop                            
                            Remove-Item $armTemplatePath  -ErrorAction SilentlyContinue
                        }
                        catch
                        {
                            $messages += [MessageData]::new("Error while deploying alerts to the subscription", $_, [MessageType]::Error);                            
                        }            

                        [MessageData[]] $resultMessages = @();
                        #Logic to validate if Alerts are configured.
                        $configuredAlerts = @();        
                        $configuredAlerts = Get-AzResource -ResourceType "Microsoft.Insights/activityLogAlerts" -ResourceGroupName  $this.ResourceGroup 
                        $actualConfiguredAlertsCount = ($configuredAlerts | Measure-Object).Count
                        $notConfiguredAlertsCount = $enabledAlerts.Count - $actualConfiguredAlertsCount
                        if( $actualConfiguredAlertsCount -ge  $enabledAlerts.Count)
                        {
                            #setting the tag at AzSKRG
                            $azskRGName = [ConfigurationManager]::GetAzSKConfigData().AzSKRGName;
                            [ResourceGroupHelper]::SetResourceGroupTags($azskRGName,@{[Constants]::AzSKAlertsVersionTagName=$this.AlertPolicyObj.Version}, $false)

                            #After successfully setting V2 alerts, clean V1 alert rules
                                            
                            $resultMessages += [MessageData]::new("All AzSK alerts have been configured successfully.`r`n", [MessageType]::Update);
                            #$this.UpdateActionGroupWebhookUri($existingWebhookUri);
                        
                        }                    
                        else
                        {
                            $resultMessages += [MessageData]::new("$notConfiguredAlertsCount/$($enabledAlerts.Count) alert group(s) have not been added to the subscription. Please rerun the command after resolving any errors from the log.", [MessageType]::Error);
                            $resultMessages += [MessageData]::new("$actualConfiguredAlertsCount/$($enabledAlerts.Count) alert group(s) have been added to the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                        }
                    
                        $messages += $resultMessages;
                        $this.PublishCustomMessage($resultMessages);
                    }
                }
                else
                {
                    $this.PublishCustomMessage("No alerts have been found that matches the specified tags. Tags:[$([string]::Join(",", $this.FilterTags))].", [MessageType]::Warning);
                }
            }
            else
            {
                $this.PublishCustomMessage("No alerts found in the alert policy file", [MessageType]::Warning);
            }
        }
        $this.UpdateActionGroupWebhookUri($webhookUri,"Alert");
        return $messages;
    }

    [MessageData[]] SetAlerts([string] $actionGroupResourceId)
    {
        $existingWebhookUri=$null
        [MessageData[]] $messages = @();

        $webhookUri = $this.ComputeAlertRunbookWebhookUri("ResourceCreation");
        

        if(($this.Policy | Measure-Object).Count -ne 0)
        {
                $alertList = $this.GetApplicableAlerts();
                if($alertList -ne 0)
                {
                    $criticalAlerts = $alertList 
                    $startMessage = [MessageData]::new("Processing AzSK alerts. Total alert groups: $($criticalAlerts.Count)");
                    $messages += $startMessage;
                    $this.PublishCustomMessage($startMessage);
                    $this.PublishCustomMessage("Note: Configuring alerts can take about 4-5 min...", [MessageType]::Warning);                

                    $disabledAlerts = $criticalAlerts | Where-Object { -not $_.Enabled };
                    if(($disabledAlerts | Measure-Object).Count -ne 0)
                    {
                        $disabledMessage = "Found alerts which are disabled. This is intentional. Total disabled alerts: $($disabledAlerts.Count)";
                        $messages += [MessageData]::new($disabledMessage, $disabledAlerts);                    
                    }

                    $enabledAlerts = @();
                    $enabledAlerts += $criticalAlerts | Where-Object { $_.Enabled };
                    if($enabledAlerts.Count -ne 0)
                    {
                        $messages += [MessageData]::new([Constants]::SingleDashLine + "`r`nAdding following alerts to the subscription. Total alerts: $($enabledAlerts.Count)", $enabledAlerts);                                            

                        # Check if Resource Group exists
                        $existingRG = Get-AzResourceGroup -Name $this.ResourceGroup -ErrorAction SilentlyContinue
                        if(-not $existingRG)
                        {
                            [ResourceGroupHelper]::NewAzSKResourceGroup($this.ResourceGroup,$this.ResourceGroupLocation,$this.GetCurrentModuleVersion())                        
                        }
                        $messages += [MessageData]::new("All the alerts registered by this script will be placed in a resource group named: $($this.ResourceGroup)");


                        try
                        {
                            $criticalAlertList = @()
                            $alertArm =  $this.LoadServerConfigFile("Subscription.AlertARM.json");
                            $alert = ($alertArm.resources | Select-Object -First 1).PSObject.Copy()
                            $enabledAlerts | ForEach-Object {
                                $alertObj =  [Helpers]::DeepCopy($alert)
                                $alertObj.name = $_.Name
                                $alertObj.properties.description = $_.Description                                
                                $alertObj.properties.condition.allOf[2].anyOf =@()
                                $_.OperationNameList | ForEach-Object {
                                    $alertObj.properties.condition.allOf[2].anyOf += @{ field = "operationName"; equals =$_ }
                                }
                                
                                $alertObj.properties.actions.actionGroups[0].actionGroupId = $actionGroupResourceId
                                $criticalAlertList += $alertObj
                                
                                
                                #$criticalAlertList += $alertObj
                            }
                            $alertArm.resources = $criticalAlertList 
                            $armTemplatePath = [Constants]::AzSKTempFolderPath + "Subscription.AlertARM.json";
                            $alertArm | ConvertTo-Json -Depth 100  | New-Item $armTemplatePath -Force | Out-Null
                            $alertDeployment = New-AzResourceGroupDeployment -Name "AzSKAlertsDeployment" -ResourceGroupName $this.ResourceGroup -TemplateFile $armTemplatePath  -ErrorAction Stop                            
                            Remove-Item $armTemplatePath  -ErrorAction SilentlyContinue
                        }
                        catch
                        {
                            $messages += [MessageData]::new("Error while deploying alerts to the subscription", $_, [MessageType]::Error);                            
                        }            

                        [MessageData[]] $resultMessages = @();
                        #Logic to validate if Alerts are configured.
                        $configuredAlerts = @();        
                        $configuredAlerts = Get-AzResource -ResourceType "Microsoft.Insights/activityLogAlerts" -ResourceGroupName  $this.ResourceGroup 
                        $actualConfiguredAlertsCount = ($configuredAlerts | Measure-Object).Count
                        $notConfiguredAlertsCount = $enabledAlerts.Count - $actualConfiguredAlertsCount
                        if( $actualConfiguredAlertsCount -ge  $enabledAlerts.Count)
                        {
                            #setting the tag at AzSKRG
                            $azskRGName = [ConfigurationManager]::GetAzSKConfigData().AzSKRGName;
                            [ResourceGroupHelper]::SetResourceGroupTags($azskRGName,@{[Constants]::AzSKAlertsVersionTagName=$this.AlertPolicyObj.Version}, $false)

                            #After successfully setting V2 alerts, clean V1 alert rules
                                            
                            $resultMessages += [MessageData]::new("All AzSK alerts have been configured successfully.`r`n", [MessageType]::Update);
                            #$this.UpdateActionGroupWebhookUri($existingWebhookUri);
                        
                        }                    
                        else
                        {
                            $resultMessages += [MessageData]::new("$notConfiguredAlertsCount/$($enabledAlerts.Count) alert group(s) have not been added to the subscription. Please rerun the command after resolving any errors from the log.", [MessageType]::Error);
                            $resultMessages += [MessageData]::new("$actualConfiguredAlertsCount/$($enabledAlerts.Count) alert group(s) have been added to the subscription successfully`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
                        }
                    
                        $messages += $resultMessages;
                        $this.PublishCustomMessage($resultMessages);
                    }
                }
                else
                {
                    $this.PublishCustomMessage("No alerts have been found that matches the specified tags. Tags:[$([string]::Join(",", $this.FilterTags))].", [MessageType]::Warning);
                }
            }
        else
        {
            $this.PublishCustomMessage("No alerts found in the alert policy file", [MessageType]::Warning);
        }
        
        $this.UpdateActionGroupWebhookUri($webhookUri,"ResourceCreation");    

        return $messages;
    }

    [MessageData[]] SetAlerts()
    {
        [MessageData[]] $messages = @();
    
        return $this.SetAlerts($null,$null,$null,$null,$null,$null,$null);
        
        
    }

    [MessageData[]] SetAlerts([string] $targetResourceGroup, [string] $securityContactEmails, [string] $SecurityPhoneNumbers, [string] $alertResourceGroupLocation)
    {
        return $this.SetAlerts($targetResourceGroup,$securityContactEmails,$SecurityPhoneNumbers,$alertResourceGroupLocation, $null, $null, $null);
    }

    [MessageData[]] SetAlerts([PSObject] $emailReceivers, [PSObject] $smsReceivers, [string] $webhookUri)
    {
        return $this.SetAlerts($null, $null ,$null, $null, $emailReceivers, $smsReceivers, $webhookUri);
    }

    hidden [string] SetupAlertActionGroup([string[]] $securityContactEmails,[string] $SecurityPhoneNumbers, [PSObject] $CurEmailReceivers)
    {
        $actionGroupResourceId = $null
        try{
            #Get ARM template for action group
            $actionGroupArm = $this.LoadServerConfigFile("Subscription.AlertActionGroup.json");
            $actionGroupArmResource = $actionGroupArm.resources | Where-Object { $_.Name -eq $([Constants]::AlertActionGroupName) } 
            $emailReceivers = $actionGroupArmResource.properties.emailReceivers | Select-Object -first 1
            if(($securityContactEmails | Measure-Object).Count -gt 0)
            {
                $emailReceiversList = @();
                $Counter = 1;
                $securityContactEmails | ForEach-Object {
                    $email = $emailReceivers.PsObject.Copy()
                    $email.name = "SecurityContactEmail$Counter"
                    $email.emailAddress = $_
                    $emailReceiversList += $email  
                    $Counter+=1
                }
                $actionGroupArmResource.properties.emailReceivers = $emailReceiversList
            }
            elseif(($CurEmailReceivers | Measure-Object).Count -gt 0)
            {
                $emailReceiversList = @();
                $CurEmailReceivers | ForEach-Object {
                    $email = $emailReceivers.PsObject.Copy()
                    $email.name = $_.name
                    $email.emailAddress = $_.emailAddress
                    $emailReceiversList += $email  
                }
                $actionGroupArmResource.properties.emailReceivers = $emailReceiversList;
            }

            $actionGroupArmResource.properties.PSObject.Properties.Remove('smsReceivers')        
            $armTemplatePath =[Constants]::AzSKTempFolderPath + "Subscription.AlertActionGroup.json"
            $actionGroupArm | ConvertTo-Json -Depth 100  | New-Item $armTemplatePath -Force
            $actionGroupResource = New-AzResourceGroupDeployment -Name "AzSKAlertActionGroupDeployment" -ResourceGroupName $this.ResourceGroup -TemplateFile $armTemplatePath  -ErrorAction Stop
            $actionGroupId = $actionGroupResource.Outputs | Where-Object actionGroupId 
            $actionGroupResourceId = $actionGroupId.Values | Select-Object -ExpandProperty Value                      
            Remove-Item $armTemplatePath  -ErrorAction SilentlyContinue
        }
        catch
        {            
            $this.PublishException($_);
        }
        
        return     $actionGroupResourceId
    }
    hidden [string] SetupAlertActionGroup([string] $SecurityPhoneNumbers, [PSObject] $CurSMSReceivers)
    {
        $actionGroupResourceId = $null
        try{
            #Get ARM template for action group
            $actionGroupArm = $this.LoadServerConfigFile("Subscription.AlertActionGroup.json");
            $actionGroupArmResource = $actionGroupArm.resources | Where-Object { $_.Name -eq $([Constants]::AlertActionGroupName) } 
            $actionGroupArmResourceOutput = $actionGroupArm.outputs.actionGroupId
            $actionGroupArmResource.name="AzSKCriticalAlertActionGroup"
            $actionGroupArmResourceOutput.value = $actionGroupArmResourceOutput.value.Replace($([Constants]::AlertActionGroupName),$([Constants]::CriticalAlertActionGroupName));
            $actionGroupArmResource.properties.PSObject.Properties.Remove('emailReceivers')
            if(-not [string]::IsNullOrWhiteSpace($SecurityPhoneNumbers))
            {
                $smsReceivers = $actionGroupArmResource.properties.smsReceivers | Select-Object -first 1
                $allPhoneNumbers = @();        
                $allPhoneNumbers += $this.ConvertToStringArray($SecurityPhoneNumbers);
                $Counter = 1;
                $smsReceiversList = @();
                    foreach($allPhoneNumber in $allPhoneNumbers ) {
                    $phoneNumberDetails = $smsReceivers.PsObject.Copy()
                    $phoneNumberDetails.name = "SecurityPhoneNumber$Counter"
                    $startIndex=$allPhoneNumber.indexof("-")  
                    if($startIndex -ne -1)
                    {
                     $countryCode=$allPhoneNumber.substring(0,$startIndex) -replace '[^0-9]', ''
                     $phoneNumberDetails.countrycode= $countryCode
                    }
                    else
                    {
                     return $actionGroupResourceId
                    }
                    $phoneNumber=$allPhoneNumber.substring($startIndex+1) -replace '[^0-9]', ''
                    $phoneNumberDetails.phoneNumber = $phoneNumber
                    $smsReceiversList += $phoneNumberDetails                
                    $Counter+=1        
                }
                $actionGroupArmResource.properties.smsReceivers = $smsReceiversList
            }
            elseif($null -ne $CurSMSReceivers)
            {
                $smsReceivers = $actionGroupArmResource.properties.smsReceivers | Select-Object -first 1
                $smsReceiversList = @();
                $CurSMSReceivers | ForEach-Object {
                    $smsReceiver = $smsReceivers.PsObject.Copy()
                    $smsReceiver.name = $_.name
                    $smsReceiver.countrycode = $_.countrycode
                    $smsReceiver.phoneNumber = $_.phoneNumber
                    $smsReceiversList += $smsReceiver  
                }
                $actionGroupArmResource.properties.smsReceivers = $smsReceiversList;
            }
            else
            {
              return $actionGroupResourceId
            }
            $armTemplatePath =[Constants]::AzSKTempFolderPath + "Subscription.AlertActionGroup.json"
            $actionGroupArm | ConvertTo-Json -Depth 100  | New-Item $armTemplatePath -Force
            $actionGroupResource = New-AzResourceGroupDeployment -Name "AzSKAlertActionGroupDeployment" -ResourceGroupName $this.ResourceGroup -TemplateFile $armTemplatePath  -ErrorAction Stop
            $actionGroupId = $actionGroupResource.Outputs | Where-Object actionGroupId 
            $actionGroupResourceId = $actionGroupId.Values | Select-Object -ExpandProperty Value                      
            Remove-Item $armTemplatePath  -ErrorAction SilentlyContinue
        }
        catch
        {    
            #Eating up this error while action group is not setup we are showing user friendly message
            #$this.PublishException($_);
        }
        
        return     $actionGroupResourceId
    }

    hidden [string] SetupAlertActionGroup()
    {
        $actionGroupResourceId = $null
        try{
            #Get ARM template for action group
            $actionGroupArm = $this.LoadServerConfigFile("Subscription.AlertActionGroup.json");
            $actionGroupArmResource = $actionGroupArm.resources | Where-Object { $_.Name -eq $([Constants]::AlertActionGroupName) } 
            $actionGroupArmResourceOutput = $actionGroupArm.outputs.actionGroupId
            $actionGroupArmResource.name=[Constants]::ResourceDeploymentActionGroupName
            $actionGroupArmResourceOutput.value = $actionGroupArmResourceOutput.value.Replace($([Constants]::AlertActionGroupName),$([Constants]::ResourceDeploymentActionGroupName));
            $actionGroupArmResource.properties.PSObject.Properties.Remove('emailReceivers')
            $actionGroupArmResource.properties.PSObject.Properties.Remove('smsReceivers')
            
            $armTemplatePath =[Constants]::AzSKTempFolderPath + "Subscription.AlertActionGroup.json"
            $actionGroupArm | ConvertTo-Json -Depth 100  | New-Item $armTemplatePath -Force
            $actionGroupResource = New-AzResourceGroupDeployment -Name "AzSKAlertActionGroupDeployment" -ResourceGroupName $this.ResourceGroup -TemplateFile $armTemplatePath  -ErrorAction Stop
            $actionGroupId = $actionGroupResource.Outputs | Where-Object actionGroupId 
            $actionGroupResourceId = $actionGroupId.Values | Select-Object -ExpandProperty Value                      
            Remove-Item $armTemplatePath  -ErrorAction SilentlyContinue
        }
        catch
        {    
            #Eating up this error while action group is not setup we are showing user friendly message
            #$this.PublishException($_);
        }
        
        return     $actionGroupResourceId
    }

    hidden [MessageData[]] RemoveAllResourceGroupLocks()
    {
        $messages = @();
        #Remove Resource Lock on Resource Group if any
        $locks = @();
        $locks += Get-AzResourceLock -ResourceGroupName $this.V1AlertRGName
        if($locks.Count -ne 0)
        {
            $messages += [MessageData]::new("Removing following existing resource group locks so that old alert RG can be removed.", $locks);

            $locks | ForEach-Object {
                Remove-AzResourceLock -LockId $_.LockId -Force | Out-Null
            }
            Start-Sleep -Seconds 60
        }
        return $messages;
    }

    hidden [string] GetV1AlertSecurityEmailContact()
    {
        #Validate if V1(Old) Alert RG present
        $existingRG = Get-AzResourceGroup -Name $this.V1AlertRGName -ErrorAction SilentlyContinue
        $emailList  = [string]::Empty
        if($existingRG)
        {
            #Check if V1 alert resource is present
            $configuredAlerts = Get-AzResource -ResourceGroup $this.V1AlertRGName -ResourceType 'microsoft.insights/alertrules' 
            if(($configuredAlerts | Measure-Object).Count -gt 0)
            {
                #Validate if command exists. As this command will soon get deprecated from AzureRM
                if (Get-Command "Get-AzAlertRule" -errorAction SilentlyContinue)
                {
                    $alertResourceDetails = $configuredAlerts | Select-Object -First 1
                    $alertRuleDetails = Get-AzAlertRule -ResourceGroup $this.V1AlertRGName -Name $alertResourceDetails.Name -WarningAction SilentlyContinue
                    if($alertRuleDetails)
                    {
                        $emailList = [string]::Join(",",($alertRuleDetails.Actions | Select-Object @{N='EmailList';E={$_.CustomEmails}} ).EmailList)
                            
                    }                        
                }                  
            }            
        }
        return $emailList     
    }

    hidden [PSObject] GetAlertActionGroup($rgName, $actionGroupName)
    {
        #Validate if Alert RG present
        $existingRG = Get-AzResourceGroup -Name $rgName -ErrorAction SilentlyContinue
        if($existingRG)
        {
            $AGRSource = Get-AzResource -ResourceType  microsoft.insights/actiongroups -ResourceGroupName $rgName -Name $actionGroupName
            return $AGRSource;
        }
        else
        {
            return $null
        }    
    }
    hidden [string] ComputeAlertRunbookWebhookUri([string] $type)
    {
        $ActionGroup = ""
        if($type -eq "Alert")
        {
            $ActionGroup = [Constants]::AlertActionGroupName
        }
        elseif($type -eq "ResourceCreation")
        {
            $ActionGroup = [Constants]::ResourceDeploymentActionGroupName
        }

        $actionGroupResource = $this.GetAlertActionGroup($this.ResourceGroup, $ActionGroup);
        $existingWebhookUri = $null;
        if($null -eq $actionGroupResource)
        {                      
            $existingWebhookUri=$null;
        }
        else
        { 
              $existingActionGrp= Get-AzResource -ResourceId $actionGroupResource.ResourceId
              if(($existingActionGrp.Properties.webhookReceivers | Measure-Object).Count -gt 0)
              {
                $webhookReceivers=$existingActionGrp.Properties.webhookReceivers | Select-Object -first 1
                $existingWebhookUri=$webhookReceivers.serviceUri;
              }
              else
              {
                $existingWebhookUri=$null;
              }
        }
        return $existingWebhookUri
    }


    hidden [string] UpdateActionGroupWebhookUri([string] $existingWebhookUri,[string] $type)
    {
     #pass webhook uri if exist n actiongrp(update SS cmd) or create a new webhook uri(install CA ,update CA)
        $ActionGroup = ""
        if($type -eq "Alert")
        {
            $ActionGroup = [Constants]::AlertActionGroupName
            $Alertname = "WebHookForMonitoringAlerts"
        }
        elseif($type -eq "ResourceCreation")
        {
            $ActionGroup = [Constants]::ResourceDeploymentActionGroupName
            $Alertname = "WebHookForResourceCreationAlerts"
        }
        else
        {
            $Alertname = ""
        }
        $actionGroupResourceId = $this.GetAlertActionGroup($this.ResourceGroup, $ActionGroup)
        $runBookResourceID= $this.GetAlertRunBookResourceId($type)
        #check for empty string
        if($null -ne $actionGroupResourceId -and (-not [string]::IsNullOrWhiteSpace($runBookResourceID)))
        {
            $existingActionGrp= Get-AzResource -ResourceId $actionGroupResourceId.ResourceId
            $webhookReceiversList = @();
            if(-not [string]::IsNullOrWhiteSpace($existingWebhookUri))
            {
            $webhookUri=$existingWebhookUri;
            }
            else
            {
            $webhookUri=$this.GetAlertRunBookWebHookUri($type);
            }    
            $props = @{
                    name = $Alertname
                    serviceUri=$webhookUri
                    }
            $object = new-object psobject -Property $props
            $webhookReceiversList += $object 
            $existingActionGrp.Properties.webhookReceivers=$webhookReceiversList
            $existingActionGrp | Set-AzResource -Force

        }
        return [string]::Empty
    }
    hidden [string] RemoveActionGroupWebhookUri()
    {
        $type = "Alert"
      $actionGroupResourceId = $this.GetAlertActionGroup($this.ResourceGroup, [Constants]::AlertActionGroupName)
      $runBookResourceID= $this.GetAlertRunBookResourceId($type)

      if($null -ne $actionGroupResourceId -and (-not [string]::IsNullOrWhiteSpace($runBookResourceID)))
      {
       try
       {
         $existingActionGrp= Get-AzResource -ResourceId $actionGroupResourceId.ResourceId
         $webhookReceiversList = @();
         $existingActionGrp.Properties.webhookReceivers=$webhookReceiversList
         $existingActionGrp | Set-AzResource -Force
         #Remove Webhook from Automation Runbook as well
         Remove-AzAutomationWebhook -Name $this.AutomationWebhookName -ResourceGroup $this.ResourceGroup -AutomationAccountName $this.AutomationAccountName -ErrorAction SilentlyContinue
       }
       catch
       {
        # It will retry, no need to break execution
       }
      }
      return [string]::Empty
    }
    hidden [string] GetAlertRunbookResourceId([string] $type)
    {
        #Validate if Alert RG present $this.ResourceGroup
        $existingRG = Get-AzResourceGroup -Name $this.ResourceGroup -ErrorAction SilentlyContinue
        if($existingRG)
        {
            $RunbookNamebyType = ""
            if($type -eq "Alert")
            {
                $RunbookNamebyType = $this.RunbookName
            }
            elseif($type -eq "ResourceCreation")
            {
                $RunbookNamebyType = $this.Alert_ResourceCreation_Runbook
            }
           $resourceName= $RunbookNamebyType
             $AlertRunBook = Get-AzResource -ResourceType  "Microsoft.Automation/automationAccounts/runbooks" -ResourceGroupName $this.ResourceGroup -Name $resourceName
            if($AlertRunBook )
            {
                return $AlertRunBook.ResourceId
            }
            else
            {
                return [string]::Empty
            }            
        }
        else
        {
            return [string]::Empty
        }    
    }
    hidden [string] GetAlertRunBookWebHookUri([string] $type)
    {    
        try
        {
        $RunbookNamebyType = ""
        $Alertname = ""
        if($type -eq "Alert")
        {
            $RunbookNamebyType = $this.RunbookName
            $Alertname = "WebHookForMonitoringAlerts"
        }
        elseif($type -eq "ResourceCreation")
        {
            $RunbookNamebyType = $this.Alert_ResourceCreation_Runbook
            $Alertname = "WebHookForResourceCreationAlerts"
        }

        $webhookExpiryDate=(Get-Date).AddDays($this.WebhookExpiryInDays)
        Remove-AzAutomationWebhook -Name $Alertname -ResourceGroup $this.ResourceGroup -AutomationAccountName $this.AutomationAccountName -ErrorAction SilentlyContinue
        $Webhook = New-AzAutomationWebhook -Name $Alertname -IsEnabled $True -ExpiryTime $webhookExpiryDate -RunbookName $RunbookNamebyType -ResourceGroup $this.ResourceGroup -AutomationAccountName $this.AutomationAccountName -Force
        $NewWebHookUri=$Webhook.WebhookURI
        return $NewWebHookUri;        
        }
        catch{
          $this.PublishException($_)
          return [string]::Empty
        }
    }
}