Private/Notify.ps1
|
#Requires -Version 5.1 function Get-SMBeatNotifyStatePath { param( [Parameter(Mandatory = $true)] [string]$DataRoot ) Join-Path $DataRoot 'notify.state.json' } function Add-SMBeatCollectorEvent { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [Parameter(Mandatory = $true)] [string]$Kind, [Parameter(Mandatory = $true)] [string]$Message ) try { $obj = [PSCustomObject]@{ ts = (ConvertTo-SMBeatUtcString -DateTime (Get-SMBeatUtcNow)) kind = $Kind message = $Message host = $env:COMPUTERNAME } $line = '{0}{1}' -f (ConvertTo-Json -InputObject $obj -Depth 4 -Compress), [Environment]::NewLine $path = Get-SMBeatEventsPath -DataRoot $DataRoot Add-SMBeatTextFile -Path $path -Text $line if ((Test-Path -LiteralPath $path) -and ((Get-Item -LiteralPath $path).Length -gt 1048576)) { $kept = New-Object System.Collections.Generic.List[string] foreach ($old in [System.IO.File]::ReadLines($path, $script:SMBeatUtf8NoBom)) { if (-not [string]::IsNullOrWhiteSpace($old)) { [void]$kept.Add($old) } } if ($kept.Count -gt 1500) { $start = $kept.Count - 1500 $text = ($kept.GetRange($start, 1500) -join [Environment]::NewLine) + [Environment]::NewLine Write-SMBeatTextFile -Path $path -Text $text } } } catch { } } function Read-SMBeatCollectorEvents { param( [Parameter(Mandatory = $true)] [string]$DataRoot, [datetime]$StartUtc, [datetime]$EndUtc ) $path = Get-SMBeatEventsPath -DataRoot $DataRoot if (-not (Test-Path -LiteralPath $path)) { return @() } $list = New-Object System.Collections.Generic.List[object] foreach ($line in [System.IO.File]::ReadLines($path, $script:SMBeatUtf8NoBom)) { if ([string]::IsNullOrWhiteSpace($line)) { continue } try { $obj = ConvertFrom-SMBeatJson -Json $line } catch { continue } $ts = [datetime]::MinValue if ($obj.PSObject.Properties['ts'] -and $obj.ts) { $ts = ConvertFrom-SMBeatUtcString -Value ([string]$obj.ts) } if ($ts -eq [datetime]::MinValue) { continue } if ($ts -lt $StartUtc -or $ts -ge $EndUtc) { continue } $kind = '' $message = '' $hostName = '' if ($obj.PSObject.Properties['kind'] -and $obj.kind) { $kind = [string]$obj.kind } if ($obj.PSObject.Properties['message'] -and $obj.message) { $message = [string]$obj.message } if ($obj.PSObject.Properties['host'] -and $obj.host) { $hostName = [string]$obj.host } $list.Add([PSCustomObject]@{ Utc = $ts Kind = $kind Message = $message Host = $hostName }) | Out-Null } $list.ToArray() } 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) { 'registered' { return ('SMBeat {0} collector registered' -f $HostName) } '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('registered', '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 'registered' -or $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 Add-SMBeatCollectorEvent -DataRoot $DataRoot -Kind $Kind -Message $Message 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' } } |