Public/New-AzSentinelAlertRule.ps1

#requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'}
#requires -version 6.2

function New-AzSentinelAlertRule {
    <#
    .SYNOPSIS
    Create Azure Sentinal Alert Rules
    .DESCRIPTION
    Use this function creates Azure Sentinal Alert rules from provided CMDLET
    .PARAMETER SubscriptionId
    Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used
    .PARAMETER WorkspaceName
    Enter the Workspace name
    .PARAMETER Kind
    The alert rule kind
    .PARAMETER DisplayName
    The display name for alerts created by this alert rule.
    .PARAMETER Description
    The description of the alert rule.
    .PARAMETER Severity
    Enter the Severity, valid values: Medium", "High", "Low", "Informational"
    .PARAMETER Enabled
    Determines whether this alert rule is enabled or disabled.
    .PARAMETER Query
    The query that creates alerts for this rule.
    .PARAMETER QueryFrequency
    Enter the query frequency, example: 5H, 5M, 5D (H stands for Hour, M stands for Minute and D stands for Day)
    .PARAMETER QueryPeriod
    Enter the query period, exmaple: 5H, 5M, 5D (H stands for Hour, M stands for Minute and D stands for Day)
    .PARAMETER TriggerOperator
    Select the triggert Operator, valid values are: "GreaterThan", "FewerThan", "EqualTo", "NotEqualTo"
    .PARAMETER TriggerThreshold
    Enter the trigger treshold
    .PARAMETER SuppressionDuration
    Enter the suppression duration, example: 5H, 5M, 5D (H stands for Hour, M stands for Minute and D stands for Day)
    .PARAMETER SuppressionEnabled
    Set $true to enable Suppression or $false to disable Suppression
    .PARAMETER Tactics
    Enter the Tactics, valid values: "InitialAccess", "Persistence", "Execution", "PrivilegeEscalation", "DefenseEvasion", "CredentialAccess", "LateralMovement", "Discovery", "Collection", "Exfiltration", "CommandAndControl", "Impact"
    .PARAMETER PlaybookName
    Enter the Logic App name that you want to configure as playbook trigger
    .PARAMETER CreateIncident
    Create incidents from alerts triggered by this analytics rule
    .PARAMETER GroupingConfigurationEnabled
    Group related alerts, triggered by this analytics rule, into incidents
    .PARAMETER ReopenClosedIncident
    Re-open closed matching incidents
    .PARAMETER LookbackDuration
    Limit the group to alerts created within the selected time frame
    .PARAMETER EntitiesMatchingMethod
    Group alerts triggered by this analytics rule into a single incident by
    .PARAMETER GroupByEntities
    Grouping alerts into a single incident if the selected entities match:
    .PARAMETER AggregationKind
    Configure how rule query results are grouped into alerts
    .PARAMETER AlertRuleTemplateName
    The Name of the alert rule template used to create this rule
    .PARAMETER ProductFilter
    The alerts' productName on which the cases will be generated
    .PARAMETER SeveritiesFilter
    The alerts' severities on which the cases will be generated
    .PARAMETER DisplayNamesFilter
    The alerts' displayNames on which the cases will be generated
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -DisplayName "" -Description "" -Severity -Enabled $true -Query '' -QueryFrequency "" -QueryPeriod "" -TriggerOperator -TriggerThreshold -SuppressionDuration "" -SuppressionEnabled $false -Tactics @("","") -PlaybookName ""
    Example on how to create a scheduled rule
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -Kind Fusion -DisplayName "Advanced Multistage Attack Detection" -Enabled $true -AlertRuleTemplateName "f71aba3d-28fb-450b-b192-4e76a83015c8"
    Example on how to create a Fusion rule
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -Kind MLBehaviorAnalytics -DisplayName "(Preview) Anomalous SSH Login Detection" -Enabled $true -AlertRuleTemplateName "fa118b98-de46-4e94-87f9-8e6d5060b60b"
    Example on how to create a MLBehaviorAnalytics rule
    .EXAMPLE
    New-AzSentinelAlertRule -WorkspaceName "" -Kind MicrosoftSecurityIncidentCreation -DisplayName "" -Description "" -Enabled $true -ProductFilter "" -SeveritiesFilter "","" -DisplayNamesFilter ""
    Example on how to create a MicrosoftSecurityIncidentCreation rule
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = "Sub")]
        [ValidateNotNullOrEmpty()]
        [string]$SubscriptionId,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$WorkspaceName,

        [Parameter(Mandatory = $false)]
        [Kind]$Kind = 'Scheduled',

        [Parameter(Mandatory = $false)]
        [string]$DisplayName,

        [Parameter(Mandatory = $false)]
        [string]$Description,

        [Parameter(Mandatory = $false)]
        [Severity]$Severity,

        [Parameter(Mandatory = $false)]
        [bool]$Enabled,

        [Parameter(Mandatory = $false)]
        [string]$Query,

        [Parameter(Mandatory = $false)]
        [string]$QueryFrequency,

        [parameter(Mandatory = $false)]
        [string]$QueryPeriod,

        [Parameter(Mandatory = $false)]
        [TriggerOperator]$TriggerOperator,

        [Parameter(Mandatory = $false)]
        [Int]$TriggerThreshold,

        [Parameter(Mandatory = $false)]
        [string]$SuppressionDuration,

        [Parameter(Mandatory = $false)]
        [bool]$SuppressionEnabled,

        [Parameter(Mandatory = $false)]
        #[Tactics[]] $Tactics,
        [string[]]$Tactics,

        [Parameter(Mandatory = $false)]
        [string[]]$PlaybookName = '',

        [Parameter(Mandatory = $false)]
        [bool]$CreateIncident,

        [Parameter(Mandatory = $false)]
        [bool]$GroupingConfigurationEnabled,

        [Parameter(Mandatory = $false)]
        [bool]$ReopenClosedIncident,

        [Parameter(Mandatory = $false)]
        [string]$LookbackDuration,

        [Parameter(Mandatory = $false)]
        [MatchingMethod]$EntitiesMatchingMethod,

        [Parameter(Mandatory = $false)]
        #[groupByEntities[]]$GroupByEntities,
        [string[]]$GroupByEntities,

        [Parameter(Mandatory = $false)]
        [AggregationKind]$AggregationKind,

        #Fusion & MLBehaviorAnalytics & Scheduled
        [Parameter(Mandatory = $false)]
        [string]$AlertRuleTemplateName,

        #MicrosoftSecurityIncidentCreation
        [Parameter(Mandatory = $false)]
        [string]$ProductFilter,

        [Parameter(Mandatory = $false)]
        [Severity[]]$SeveritiesFilter,

        [Parameter(Mandatory = $false)]
        [string]$DisplayNamesFilter
    )

    begin {
        precheck
    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            Sub {
                $arguments = @{
                    WorkspaceName  = $WorkspaceName
                    SubscriptionId = $SubscriptionId
                }
            }
            default {
                $arguments = @{
                    WorkspaceName = $WorkspaceName
                }
            }
        }

        $item = @{ }

        Write-Verbose -Message "Creating new rule: $($DisplayName)"

        try {
            $content = Get-AzSentinelAlertRule @arguments -RuleName $DisplayName -ErrorAction Stop
        }
        catch {
            Write-Error $_.Exception.Message
            break
        }

        if ($content) {
            Write-Verbose -Message "Rule $($DisplayName) exists in Azure Sentinel"

            $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force
            $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.eTag -Force
            $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force

            $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview"
        }
        else {
            Write-Verbose -Message "Rule $($DisplayName) doesn't exists in Azure Sentinel"

            $guid = (New-Guid).Guid

            $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force
            $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force
            $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force

            $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview"
        }

        if ($Kind -eq 'Scheduled') {

            try {
                $groupingConfiguration = [GroupingConfiguration]::new(
                    $GroupingConfigurationEnabled,
                    $ReopenClosedIncident,
                    $LookbackDuration,
                    $EntitiesMatchingMethod,
                    $GroupByEntities
                )

                $incidentConfiguration = [IncidentConfiguration]::new(
                    $CreateIncident,
                    $groupingConfiguration
                )

                if (($AlertRuleTemplateName -and ! $content) -or $content.AlertRuleTemplateName) {
                    if ($content.AlertRuleTemplateName){
                        <#
                            If alertRule is already created with a TemplateName then Always use template name from existing rule.
                            You can't attach existing scheduled rule to another templatename or remove the link to the template
                        #>

                        $AlertRuleTemplateName = $content.AlertRuleTemplateName
                    }
                    $bodyAlertProp = [ScheduledAlertProp]::new(
                        $item.name,
                        $DisplayName,
                        $Description,
                        $Severity,
                        $Enabled,
                        $Query,
                        $QueryFrequency,
                        $QueryPeriod,
                        $TriggerOperator,
                        $TriggerThreshold,
                        $SuppressionDuration,
                        $SuppressionEnabled,
                        $Tactics,
                        $PlaybookName,
                        $incidentConfiguration,
                        $AggregationKind,
                        $AlertRuleTemplateName
                    )
                } else {
                    $bodyAlertProp = [ScheduledAlertProp]::new(
                        $item.name,
                        $DisplayName,
                        $Description,
                        $Severity,
                        $Enabled,
                        $Query,
                        $QueryFrequency,
                        $QueryPeriod,
                        $TriggerOperator,
                        $TriggerThreshold,
                        $SuppressionDuration,
                        $SuppressionEnabled,
                        $Tactics,
                        $PlaybookName,
                        $incidentConfiguration,
                        $AggregationKind
                    )
                }

                $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Scheduled')
            }
            catch {
                Write-Error "Unable to initiate class with error: $($_.Exception.Message)" -ErrorAction Stop
            }

            if ($content) {
                if ($PlaybookName -or $content.playbookName) {
                    $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name)
                }
                else {
                    $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, PlaybookName) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name, PlaybookName)
                }

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                    if (($compareResult | Where-Object PropertyName -eq "playbookName").DiffValue) {
                        foreach ($playbook in ($body.Properties.PlaybookName)) {
                            $PlaybookResult = New-AzSentinelAlertRuleAction @arguments -PlayBookName $playbook -RuleId $($body.Name) -confirm:$false
                            $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                        }
                    }
                    elseif (($compareResult | Where-Object PropertyName -eq "playbookName").RefValue) {
                        $PlaybookResult = Remove-AzSentinelAlertRuleAction @arguments -RuleId $($body.Name) -Confirm:$false
                        $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                    }
                    else {
                        #nothing
                    }

                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties

                    return $return
                }
                catch {
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties

                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue

                    return $return
                }
            }
            else {
                Write-Verbose "Creating new rule: $($DisplayName)"

                try {
                    $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                    if (($body.Properties.PlaybookName)) {
                        foreach ($playbook in ($body.Properties.PlaybookName)) {
                            New-AzSentinelAlertRuleAction @arguments -PlayBookName $playbook -RuleId $($body.Name) -confirm:$false
                            $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force
                        }
                    }

                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties
                    return $return
                }
                catch {
                    $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                    $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force
                    $return += $body.Properties
                    return $return

                    Write-Verbose $_
                    Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
                }
            }
        }

        if ($Kind -eq 'Fusion') {

            $bodyAlertProp = [Fusion]::new(
                $Enabled,
                $AlertRuleTemplateName
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Fusion')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force
                $return += $body.Properties

                return $return
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force
                $return += $body.Properties

                return $return

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        if ($Kind -eq 'MLBehaviorAnalytics') {

            $bodyAlertProp = [MLBehaviorAnalytics]::new(
                $Enabled,
                $AlertRuleTemplateName
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MLBehaviorAnalytics')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force
                $return += $body.Properties

                return $return
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force
                $return += $body.Properties

                return $return

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }

        if ($Kind -eq 'MicrosoftSecurityIncidentCreation') {

            $bodyAlertProp = [MicrosoftSecurityIncidentCreation]::new(
                $DisplayName,
                $Description,
                $Enabled,
                $ProductFilter,
                $SeveritiesFilter,
                $DisplayNamesFilter
            )

            $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MicrosoftSecurityIncidentCreation')

            try {
                $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings)

                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force
                $return += $body.Properties

                return $return
            }
            catch {
                $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force
                $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force
                $return += $body.Properties

                return $return

                Write-Verbose $_
                Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue
            }
        }
    }
}