Functions/Raise-SdtAlert.ps1

function Raise-SdtAlert
{
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()]
        [string]$Subject,
        [Parameter(Mandatory=$true)]
        [string]$Body,
        [Parameter(Mandatory=$false)]
        [string[]]$ServersAffected = @(),
        [Parameter(Mandatory=$false)][ValidateSet('Critical', 'High', 'Medium', 'Low', 'Error')]
        $Severity = 'High',
        [Parameter(Mandatory=$false)]
        [string[]]$To = @($SdtDBAGroupMailId),
        [Parameter(Mandatory=$false)]
        [string[]]$Attachments,
        [Parameter(Mandatory=$false)]
        [Switch]$BodyAsHtml,
        [Parameter(Mandatory=$false)]
        [string]$SmtpServer = $SdtSmtpServer,
        [Parameter(Mandatory=$false)]
        [int]$Port = $SdtSmtpServerPort,
        [Parameter(Mandatory=$false)][ValidateSet('Normal', 'High', 'Low')]
        $Priority = 'Normal',
        [Parameter(Mandatory=$false)]
        [int]$DelayMinutes = 15,
        [Switch]$ClearAlert
    )

    # Set Initial Variables
    $startTime = Get-Date
    $dtmm = $startTime.ToString('yyyy-MM-dd HH.mm.ss')
    $script = $MyInvocation.MyCommand.Name
    if([String]::IsNullOrEmpty($Script)) {
        $Script = 'Raise-SdtAlert'
    }

    $isCustomError = $false
    try
    {
        # Declare local variables
        $alert = $true
        if ($ClearAlert) {
            $alert = $false
        }

        $isErrorAlert = $false
        if($Severity -eq 'Error') {
            $Severity = 'High'
            $isErrorAlert = $true
        }

        $alertTime = Get-Date
        $alertTimeUTC = (Get-Date).ToUniversalTime()
        $alertTimeString = $alertTime.ToString('yyyy-MM-dd HH.mm.ss')
        
        $lastOccurredDateUTC = $alertTimeUTC
        $lastNotifiedDateUTC = $alertTimeUTC
        $newOccurredDateUTC = $alertTimeUTC
        $newNotifiedDateUTC = $alertTimeUTC
        
        #Write-Debug "Inside Raise-SdtAlert.ps1"

        # Setup mail profiles
        Write-Verbose "Setup mail profiles in `$mailParams"
        if($SdtSmtpServer -eq 'smtp.gmail.com') {
            $mailParams = @{ From = $SdtAlertEmailAddress; To = $To; SmtpServer = $SdtSmtpServer; Port = $SdtSmtpServerPort; UseSsl = $SdtUseSsl; Credential = $SdtSmtpGmailCredential; BodyAsHtml=$BodyAsHtml; DeliveryNotificationOption = @('OnSuccess', 'OnFailure');}
        } else {
            $mailParams = @{ From = $SdtAlertEmailAddress; To = $To; SmtpServer = $SdtSmtpServer; Port = $SdtSmtpServerPort; UseSsl = $SdtUseSsl; BodyAsHtml=$BodyAsHtml; DeliveryNotificationOption = @('OnSuccess', 'OnFailure');}
        }

        # Get current alert details from Inventory
        Write-Verbose "Get current alert details from Inventory for AlertKey"
        $currentAlert = @()
        $currentAlert += Invoke-DbaQuery -SqlInstance $SdtInventoryInstance -Database $SdtInventoryDatabase `
                    -Query "select * from $SdtAlertTable a with (nolock) where alert_key = '$Subject' and state in ('active','suppressed') and severity = '$Severity' and email_to = '$($To -join ';')'";

        #1/0;

        $currentAlert | Select id, alert_key, email_to, state, severity, last_occurred_date_utc, last_notified_date_utc | ft -autosize | Write-Verbose

        # if alert is not active and history is found, then clear history
        Write-Verbose "Evaluate => (`$ClearAlert -and `$currentAlert.Count -gt 0)"
        if($ClearAlert -and $currentAlert.Count -gt 0)
        {
            Write-Verbose "Alert is inactive, but history is found."
            Write-Verbose "Clearing mail notification.."
            Send-MailMessage @mailParams -Subject "[CLEARED] - [Id#$($currentAlert.id)] - [$(if($isErrorAlert){'ERROR'}else{$Severity})] - $Subject" -Body $Body -Priority $Priority
        
            Write-Verbose "Marking cleared in alert table.."
            $alertUpdateSql = @"
    set nocount on;
    update a
    set [state] = 'Cleared', last_occurred_date_utc = '$($newOccurredDateUTC.ToString('yyyy-MM-dd HH:mm:ss.fff'))'
    from $SdtAlertTable a --with (nolock)
    where alert_key = '$Subject' and state in ('active','suppressed')
        and severity = '$Severity' and email_to = '$($To -join ';')'
"@

            Invoke-DbaQuery -SqlInstance $SdtInventoryInstance -Database $SdtInventoryDatabase -Query $alertUpdateSql -EnableException
        }

        # if alert is active but not found in history, then send mail notification and add entry into alert table
        Write-Verbose "Evaluate => ( (-not `$ClearAlert) -and `$currentAlert.Count -eq 0) "
        if( (-not $ClearAlert) -and $currentAlert.Count -eq 0) 
        {
            $alert = $true
            Write-Verbose "No existing alert found for '$Subject'"
            Write-Verbose "Creating alert in alert table.."
            $alertUpdateSql = @"
    set nocount on;
    insert $SdtAlertTable (alert_key, email_to, severity, servers_affected)
    select '$Subject', '$($To -join ';')', '$Severity', $( if($ServersAffected.Count -gt 0){"'"+($ServersAffected -join '; ')+"'"}else{'null'} );
 
    select SCOPE_IDENTITY() as id;
"@

            $generatedAlertId = 0;
            $generatedAlertId = Invoke-DbaQuery -SqlInstance $SdtInventoryInstance -Database $SdtInventoryDatabase -Query $alertUpdateSql -EnableException | Select-Object -ExpandProperty id;

            Write-Verbose "Sending mail notification.."
            if([String]::IsNullOrEmpty($Attachments)) {
                Send-MailMessage @mailParams -Subject "[ACTIVE] - [Id#$generatedAlertId] - [$(if($isErrorAlert){'ERROR'}else{$Severity})] - $Subject" -Body $Body -Priority $Priority
            }
            else {
                Send-MailMessage @mailParams -Subject "[ACTIVE] - [Id#$generatedAlertId] - [$(if($isErrorAlert){'ERROR'}else{$Severity})] - $Subject" -Body $Body -Attachments $Attachments
            }
        }

        # if alert is active, and present in history. Then update history
        Write-Verbose "Evaluate => ( (-not $ClearAlert) -and $currentAlert.Count -ne 0 ) "
        if( (-not $ClearAlert) -and $currentAlert.Count -ne 0 )
        {
            Write-Verbose "Alert found for '$Subject' in alert table."
            $lastOccurredDateUTC = $currentAlert[0].last_occurred_date_utc
            $lastNotifiedDateUTC = $currentAlert[0].last_notified_date_utc        

            # if alert is out of $DelayMinutes
            if ($lastNotifiedDateUTC -le $alertTimeUTC.AddMinutes(-$DelayMinutes)) {
                $alert = $true
            }
            else { # no alert please
                $alert = $false
                $newNotifiedDateUTC = $lastNotifiedDateUTC
                "{0} {1,-10} {2}" -f "($((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')))","(INFO)","Raise-SdtAlert => Last alert was sent @ $((Get-SdtLocalTime $lastNotifiedDateUTC).ToString('yyyy-MM-dd HH.mm.ss'))" | Write-Output
                "{0} {1,-10} {2}" -f "($((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')))","(INFO)","Raise-SdtAlert => Last alert is within `$DelayMinutes ($DelayMinutes). So skipping current alert." | Write-Output
            }

            if( ($currentAlert[0].state -eq 'Suppressed') -and ($alertTimeUTC -ge $currentAlert[0].suppress_start_date_utc -and $alertTimeUTC -le $currentAlert[0].suppress_end_date_utc) ) {
                "{0} {1,-10} {2}" -f "($((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')))","(INFO)","Alert '$Subject' is in suppressed state." | Write-Output
                $alert = $false
                $newNotifiedDateUTC = $lastNotifiedDateUTC
            }

            Write-Verbose "Updating alert in alert table.."
            $alertUpdateSql = @"
    set nocount on;
    update a
    set last_occurred_date_utc = '$($newOccurredDateUTC.ToString('yyyy-MM-dd HH:mm:ss.fff'))'
        $(if($alert){",last_notified_date_utc = '$($newNotifiedDateUTC.ToString('yyyy-MM-dd HH:mm:ss.fff'))', notification_counts += 1"})
    from $SdtAlertTable a with (nolock)
    where alert_key = '$Subject' and state in ('active','suppressed')
        and severity = '$Severity' and email_to = '$($To -join ';')'
"@

            Invoke-DbaQuery -SqlInstance $SdtInventoryInstance -Database $SdtInventoryDatabase -Query $alertUpdateSql -EnableException

            if($alert)
            {
                Write-Verbose "Sending mail notification.."
                if([String]::IsNullOrEmpty($Attachments)) {
                    Send-MailMessage @mailParams -Subject "[ACTIVE] - [Id#$($currentAlert.id)] - [$(if($isErrorAlert){'ERROR'}else{$Severity})] - $Subject" -Body $Body -Priority $Priority 
                }
                else {
                    Send-MailMessage @mailParams -Subject "[ACTIVE] - [Id#$($currentAlert.id)] - [$(if($isErrorAlert){'ERROR'}else{$Severity})] - $Subject" -Body $Body -Attachments $Attachments -Priority $Priority
                }
            }
        }
    }
    catch {
        $errMessage = $_;
        "{0} {1,-10} {2}" -f "($((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')))","(ERROR)","Something went wrong. Inside catch block of '$script'." | Write-Output
        $isCustomError = $true
        $_ | Write-Warning
    }
    finally {
        if($isCustomError) {
            throw $errMessage
        }
    }
<#
.SYNOPSIS
    Send Alert on Email, Slack, or MS Teams.
.DESCRIPTION
    This function helps to send alert message with attachment using Mail, Slack or Microsoft Teams
.PARAMETER Subject
    Alert Subject. Would be used as Subject for Email.
.PARAMETER Body
    Text of alert body
.PARAMETER AlertKey
    String that uniquely identifies the Alert. Based on alert key, existing alert is found & cleared when required.
.PARAMETER ServersAffected
    List of servers that are impacted in this Alert.
.PARAMETER Severity
    Severity of the alert issue. Possible values are Low, Medium, High, Critical
.PARAMETER To
    Receiver of alert. Could be comma separated list of email addresses, slack channel or team channel name.
.PARAMETER Attachments
    Comma separated list of documents to be sent with email as attachment.
.PARAMETER BodyAsHtml
    Switch when alert need to be send as html body.
.PARAMETER SmtpServer
    SMTP Server name
.PARAMETER Port
    SMTP Server port
.PARAMETER Priority
    Email priority. Possible values are Normal, High, Low. Default is Normal.
.PARAMETER ClearAlert
    When this switch is used, alert matching by Alert Key would be cleared.
.EXAMPLE
    Raise-SdtAlert -To $SdtDBAMailId -Subject 'Raise-SdtAlert' -Body "Testing Email using SQLDBATools PowerShell module"
       
    Send a normal email alert
.EXAMPLE
    Raise-SdtAlert -To $SdtDBAMailId -Subject 'Raise-SdtAlert' -Body 'Testing Email using <span style="color:blue;">SQLDBATools</span> PowerShell module' -BodyAsHtml -Priority High
       
    Send a high priority email alert with html body
.LINK
    https://github.com/imajaydwivedi/SQLDBATools
#>

}