public/New-AzureMonitorDeployment.ps1

function New-AzureMonitorDeployment {
    <#
    .DESCRIPTION
    Utilized for adding in custom azure metric alerts tied to a webhook for alert notification. The function by default will only replace any missing alerts on each eligible resource based on an alert name match.
    If the ReplaceAll switch is used, all alerts will be removed from all resource objects in the Azure subscription and eligible resources will have their metric alerts replaced with the input payload.
     
    .PARAMETER webHookURI
    The specific API endpoint where the notification will be sent.
     
    .PARAMETER AlertJSONPath
    Specific file path to the JSON alert payload.
     
    .PARAMETER CustomerName
    String utilized for customer name tagging of the created webhook.
     
    .PARAMETER ResourceClass
    The specific resource classes that will be considered for alert metrics.
    Default values are Class A, Class B, and Class C.
     
    .PARAMETER ReplaceAll
    Utilized to remove all azure metric alerts from all resources in the subscription before attempting to add metrics to eligible resources based on input JSON.
     
    .EXAMPLE
    New-AzureMonitorDeployment -WebHookURI "https://contoso.com/api" -AlertJSONPath ".\alertPayload.json" -CustomerName "contoso"
    #>

    [CmdletBinding()]
    param (
        # The URI of the webHook endpoint.
        [Parameter(Mandatory=$true)]
        [string]
        $WebHookURI,
        # Exact path to the JSON file containing the alert specific data.
        [Parameter(Mandatory=$true)]
        [ValidateScript({ Test-Path -Path $_ })]
        [string]
        $AlertJSONPath,
        # Customer name string to be used as a resource tag.
        [Parameter(Mandatory=$true)]
        [string]
        $CustomerName,
        # Resource class values that are valid for metric creation. Default values are Class A, Class B, Class C.
        [Parameter(Mandatory=$false)]
        [string[]]
        $ResourceClass = @('Class A', 'Class B', 'Class C'),
        # Utilized for replacing all alerts for all resources in scope of the functionality with the input JSON alert data.
        [Parameter(Mandatory=$false)]
        [switch]
        $ReplaceAll
    )
    process {
        # Imports the azure metric alert JSON data and converts it to a pscustomobject.

        try {
            $alertImport = Get-Content -Path $alertJSONPath -Raw -ErrorAction Stop
            $alertObjects = ($alertImport | ConvertFrom-Json -ErrorAction Stop).alerts
        }
        catch {
            throw "Failed to successfully import and convert alert JSON data due to the following exception, alert deployment cannot proceed.`r`n$($_.Exception)"
            exit 1
        }

        # If the replaceAll switch is used during function invocation. All resource objects within the subscription will have their custom alert metrics removed before attempting to add the specified JSON input alerts.

        if ($replaceAll) {
            # Compile a list of all resource group objects within the Azure subscription
            try {
                [array]$resourceGroups = Get-AzureRmResourceGroup -Verbose -ErrorAction Stop
            }
            catch {
                throw "Failed to return any valid resourceGroups from the current Azure subscription, alert deployment cannot proceed!`r`n$($_.Exception)"
                exit 1
            }

            foreach ($rg in $resourceGroups) {
                # Attempt to obtain all the alert objects for a specific resource group.

                try {
                    [array]$alerts = Get-AzureRmAlertRule -ResourceGroupName $rg.ResourceGroupname -Verbose -ErrorAction Stop
                }
                catch {
                    Write-Warning "Failed to return any alerts from resource group $($rg.ResourceGroupName) due to the following exception. No further removal logic will be applied to this resource group.`r`n$($_.Exception)"
                    continue
                }

                #If the alerts array returned has a count greater than or equal to 1, removal attempts will be made.

                if ($alerts.count -ge 1) {
                        $alerts | ForEach-Object {
                            try {
                                Remove-AzureRmAlertRule -ResourceGroupName $rg.ResourceGroupName -Name $_.Name -ErrorAction Stop -Verbose | Out-Null
                            }
                            catch {
                                Write-Warning "Failed to remove alert $($_.Name) from resource group $($rg.ResourceGroupName) due to the following exception.`r`n$($_.Exception)"
                            }
                        }
                    }
                else {
                    Write-Warning "No alerts were found within $($rg.ResourceGroupName), skipping removal logic for this resource group!"
                }
            }
        }

        # Identify unique alert resource classes from input JSON.
        [array]$uniqueInputAlertClasses = $alertObjects.alertClass | Select-Object -Unique

        foreach ($alertClass in $uniqueInputAlertClasses) {
            # Attempt to find all azure resources that match each input resource type class.

            try {
                [array]$resources = Find-AzureRmResource -ResourceType $alertClass -Verbose -ErrorAction Stop
            }
            catch {
                Write-Warning "Failed to return any alerts for alert class $($alertClass) due to the following exception.`r`n$($_.Exception)"
                continue
            }

            if ($resources.count -ge 1) {
                # Temporarily compile a list of all input Azure alerts to be added to the resources of the same class type.
                [array]$tempAlertObjects = $alertObjects.Where({ $_.alertClass -eq $alertClass })

                foreach ($resource in $resources) {
                    # An evaluation is made to determine if the resource has a valid Resource Class tag. Only resources that contain a valid resource class tag are eligible for metric alert logic.

                    if (($null -ne $resource.tags.'Resource Class') -and ($resourceClass -contains $resource.tags.'Resource Class')) {
                        # An array of the current resource alerts is instantiated. For each eligible alert applicable to the resource an evaluation is made to determine if the alert already exists for the resource.
                        # A final alert name is constructed based on the alert name and the resource name concatenated together.

                        [array]$resourceAlerts = Get-AzureRMAlertRule -ResourceGroupName $resource.ResourceGroupName -TargetResourceId $resource.ResourceId
                        foreach ($alertObject in $tempAlertObjects) {
                            $finalAlertName = "$($resource.Name)" + ' - ' + "$($alertObject.Name)"
                            if ($resourceAlerts.Name -notcontains $finalAlertName) {
                                # Attempt to determine the webhook severity based on the alert priority value and the resource class tag.

                                try {
                                    $webHookSeverity = Get-AzureRMWebHookSeverity -AlertPriority $alertObject.priority -ResourceClass $resource.tags.'Resource Class' -ErrorAction Stop
                                }
                                catch {
                                    Write-Warning "Failed to return webhook severity value due to the following exception.`r`n$($_.Exception)"
                                    [array]$errorResult += New-Object psobject -Property @{ResourceName=$($resource.Name);Status='Failed';Exception=$($_.Exception.Message)}
                                    continue
                                }
                                #Attempt to register a new Azure alert rule webhook object to be used in the metric creation. The rule will have the severity and customer name tags added as well.

                                try {
                                    $webHookObject = New-AzureRmAlertRuleWebhook -ServiceUri $webHookURI -Property @{'severity' = $($webHookSeverity);'CustomerName' = $customerName} -Verbose -ErrorAction Stop
                                }
                                catch {
                                    Write-Warning "Failed to create Azure webhook due to the following exception.`r`n$($_.Exception)"
                                    [array]$errorResult += New-Object psobject -Property @{ResourceName=$($resource.Name);Status='Failed';Exception=$($_.Exception.Message)}
                                    continue
                                }
                                # Build an alert splat based on both the resource object and the alert object properties to be used in the metric alert creation.

                                $newAlertParams = @{
                                    WindowSize = $alertObject.windowSize;
                                    Operator   = $alertObject.Operator;
                                    Threshold  = $alertObject.Threshold;
                                    TargetResourceID = $resource.ResourceId;
                                    MetricName = $alertObject.metricName;
                                    TimeAggregationOperator = $alertObject.timeAggregationOperator;
                                    Description = $alertObject.Description;
                                    Name = $finalAlertName;
                                    Action = $webHookObject;
                                    Location = $resource.Location;
                                    ResourceGroup = $resource.resourceGroupName;
                                    ErrorAction = 'Stop'
                                }
                                #Attempt to create a new Azure metric alert rule.

                                try {
                                    Add-AzureRmMetricAlertRule @newAlertParams -Verbose | Out-Null
                                }
                                catch {
                                    Write-Warning "Failed to successfully create alert $($finalAlertName) on resource $($resource.ResourceId) due to the following exception.`r`n$($_.Exception)"
                                    [array]$errorResult += New-Object psobject -Property @{ResourceName=$($resource.Name);Status='Failed';Exception=$($_.Exception.Message)}
                                    continue
                                }
                            }
                        }
                    }
                    else {
                        Write-Warning "Resource $($resource.Name) missing Resource Class tag or tag has invalid value, alert metric logic will not proceed for this resource."
                        [array]$errorResult += New-Object psobject -Property @{ResourceName=$($resource.Name);Status='Failed';Exception="Resource Class tag either invalid or non-existent for resource."}
                    }
                }
            }
            else {
                Write-Warning "No resources of type $($alertClass) were found in this Azure subscription, skipping further tagging processing for this class type"
            }
        }
        if ($errorResult.count -ge 1) {
            return $errorResult
        }
    }
}