Private/Notify.ps1

#Requires -Version 5.1

function Get-SMBeatNotifyStatePath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    Join-Path $DataRoot 'notify.state.json'
}

function Write-SMBeatLog {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot,
        [Parameter(Mandatory = $true)]
        [string]$Message,
        [ValidateSet('INFO', 'WARN', 'ERROR')]
        [string]$Level = 'INFO'
    )

    $line = '{0} {1} {2}{3}' -f (ConvertTo-SMBeatUtcString -DateTime (Get-SMBeatUtcNow)), $Level, $Message, [Environment]::NewLine
    Add-SMBeatTextFile -Path (Get-SMBeatLogPath -DataRoot $DataRoot) -Text $line
}

function Test-SMBeatNotifyConfigured {
    param($Config)

    if ($null -eq $Config) {
        return $false
    }
    if (-not $Config.PSObject.Properties['MailTo'] -or [string]::IsNullOrWhiteSpace([string]$Config.MailTo)) {
        return $false
    }
    if (-not $Config.PSObject.Properties['SmtpServer'] -or [string]::IsNullOrWhiteSpace([string]$Config.SmtpServer)) {
        return $false
    }
    $true
}

function Get-SMBeatNoticeSubject {
    param(
        [string]$Kind,
        [string]$HostName
    )

    if ([string]::IsNullOrWhiteSpace($HostName)) {
        $HostName = $env:COMPUTERNAME
    }

    switch ($Kind) {
        'started' { return ('SMBeat {0} collector started' -f $HostName) }
        'stopped' { return ('SMBeat {0} collector stopped' -f $HostName) }
        default { return ('SMBeat {0} collector error' -f $HostName) }
    }
}

function Test-SMBeatNotifyRateLimit {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot,
        [Parameter(Mandatory = $true)]
        [string]$Key,
        [int]$QuietSec = 900
    )

    if ($QuietSec -le 0) {
        return $false
    }

    $path = Get-SMBeatNotifyStatePath -DataRoot $DataRoot
    $json = Read-SMBeatTextFile -Path $path
    if ($null -eq $json) {
        return $false
    }

    $state = $null
    try {
        $state = ConvertFrom-SMBeatJson -Json $json
    }
    catch {
        return $false
    }

    if (-not $state -or -not $state.PSObject.Properties['Key'] -or [string]$state.Key -ne $Key) {
        return $false
    }
    if (-not $state.PSObject.Properties['Utc'] -or -not $state.Utc) {
        return $false
    }

    $last = ConvertFrom-SMBeatUtcString -Value ([string]$state.Utc)
    $age = (Get-SMBeatUtcNow) - $last
    $age.TotalSeconds -lt $QuietSec
}

function Save-SMBeatNotifyState {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot,
        [Parameter(Mandatory = $true)]
        [string]$Key
    )

    $obj = [PSCustomObject]@{
        Key = $Key
        Utc = (ConvertTo-SMBeatUtcString -DateTime (Get-SMBeatUtcNow))
    }
    Write-SMBeatTextFile -Path (Get-SMBeatNotifyStatePath -DataRoot $DataRoot) -Text (ConvertTo-SMBeatJson -InputObject $obj)
}

function Send-SMBeatMailMessage {
    param(
        [Parameter(Mandatory = $true)]
        [string]$To,
        [Parameter(Mandatory = $true)]
        [string]$SmtpServer,
        [string]$From,
        [Parameter(Mandatory = $true)]
        [string]$Subject,
        [Parameter(Mandatory = $true)]
        [string]$Text
    )

    if ([string]::IsNullOrWhiteSpace($From)) {
        $From = $To
    }

    Send-MailMessage -To $To -From $From -Subject $Subject -Body $Text -SmtpServer $SmtpServer -ErrorAction Stop
}

function Send-SMBeatNotice {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot,
        [ValidateSet('started', 'stopped', 'error')]
        [string]$Kind = 'error',
        [Parameter(Mandatory = $true)]
        [string]$Message,
        $Config
    )

    $level = 'INFO'
    if ($Kind -eq 'error') {
        $level = 'ERROR'
    }
    elseif ($Kind -eq 'stopped') {
        $level = 'WARN'
    }
    $key = '{0}|{1}' -f $Kind, $Message
    $quiet = 900
    if ($Kind -eq 'started' -or $Kind -eq 'stopped') {
        $quiet = 60
    }
    $limited = Test-SMBeatNotifyRateLimit -DataRoot $DataRoot -Key $key -QuietSec $quiet
    if ($Kind -eq 'error' -and $limited) {
        return
    }

    Write-SMBeatLog -DataRoot $DataRoot -Message $Message -Level $level

    if ($null -eq $Config) {
        $Config = Read-SMBeatConfig -DataRoot $DataRoot
    }
    if (-not (Test-SMBeatNotifyConfigured -Config $Config)) {
        if ($Kind -eq 'error') {
            Save-SMBeatNotifyState -DataRoot $DataRoot -Key $key
        }
        return
    }
    if ($limited) {
        Write-SMBeatLog -DataRoot $DataRoot -Message ('Mail skipped (quiet): {0}' -f $Kind) -Level 'INFO'
        return
    }

    $subject = Get-SMBeatNoticeSubject -Kind $Kind -HostName $env:COMPUTERNAME
    $text = @(
        $Message
        ''
        ('Host: {0}' -f $env:COMPUTERNAME)
        ('Kind: {0}' -f $Kind)
        ('Utc: {0}' -f (ConvertTo-SMBeatUtcString -DateTime (Get-SMBeatUtcNow)))
        ('Log: {0}' -f (Get-SMBeatLogPath -DataRoot $DataRoot))
    ) -join [Environment]::NewLine

    $to = [string]$Config.MailTo
    $smtp = [string]$Config.SmtpServer
    $from = ''
    if ($Config.PSObject.Properties['MailFrom'] -and $Config.MailFrom) {
        $from = [string]$Config.MailFrom
    }

    try {
        Send-SMBeatMailMessage -To $to -SmtpServer $smtp -From $from -Subject $subject -Text $text
        Save-SMBeatNotifyState -DataRoot $DataRoot -Key $key
        Write-SMBeatLog -DataRoot $DataRoot -Message ('Mail sent: {0}' -f $Kind) -Level 'INFO'
    }
    catch {
        Write-SMBeatLog -DataRoot $DataRoot -Message ('Mail failed: {0}' -f $_.Exception.Message) -Level 'WARN'
    }
}