Last active
March 22, 2024 08:29
Star
You must be signed in to star a gist
[Send Custom HTML Notifications] Recently, we changed the way PRTG handles notification emails and simplified the approach so there is only one email template and the option to deliver plain text emails. For most of our customers this will be sufficient. But what about customers that have multiple clients and need customized notifications, langu…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Requires -Version 4 | |
<# | |
___ ___ _____ ___ | |
| _ \ _ \_ _/ __| | |
| _/ / | || (_ | | |
|_| |_|_\ |_| \___| | |
HTMLMailNotifier | |
With this, you can send personalized HTML mails using templates and PRTGs EXE notification | |
#> | |
param( | |
## From | |
[string] $From = "", | |
## Sensor ID | |
[int] $SensorID = 0, | |
## The recipient of the mail message | |
[string] $Recipients = "", | |
## Set this if you want to use a PRTG group as recipient | |
[string] $PrtgGroup = "", | |
## The subjecty of the message | |
[string] $Subject = "", | |
## The template folder | |
[string] $TemplateFolder = "", | |
## The template we'll use | |
[string] $Template = "", | |
## Placeholder set | |
$Placeholders = @(), | |
## Use HTML or text based template (this will use the .htm instead of the .txt file for the template) | |
$HTML = $true, | |
## The priority of the mail | |
[ValidateSet("Low","Normal","High")][string]$priority = "High", | |
## Only use primary contact - if false, all mail contacts will receive the notification | |
[switch] $primaryOnly = $true, | |
## For the initial password setup, call it with -SetupPasswords | |
[switch] $SetupPasswords = $false | |
) | |
#region configuration | |
[string] $ConfigurationFilePath = ((Get-ItemProperty -Path "hklm:SOFTWARE\Wow6432Node\Paessler\PRTG Network Monitor\Server\Core" -Name "Datapath").DataPath) + "PRTG Configuration.dat" | |
[xml] $configuration = New-Object -TypeName XML; | |
$configuration.Load($ConfigurationFilePath) | |
## server configuration | |
[hashtable]$global:servers = @{ | |
smtpFrom = $From | |
smtpHost = "" | |
smtpPort = 123 | |
smtpUser = "" | |
smtpSsl = $true | |
smtpBackupFrom = $From | |
smtpBackupHost = "" | |
smtpBackupPort = 0 | |
smtpBackupUser = "" | |
smtpBackupSsl = "" | |
prtgHost = "" | |
prtgPort = 80 | |
prtgProtocol = "http" | |
prtgUsername = "" | |
prtgPasshash = "" | |
} | |
## mail configuration | |
[System.Collections.ArrayList]$global:recipientList = @(); | |
[string] $priority = "high" | |
## template configuration | |
[string] $templateBaseDir = "C:\Program Files (x86)\PRTG Network Monitor\webroot\mailtemplates\custom" | |
[string] $templateDefaultLogo = "C:\Program Files (x86)\PRTG Network Monitor\webroot\images\prtglogo.png" | |
[string] $Global:graphGUID = [guid]::NewGuid() | |
[string] $iconFolder = "C:\Program Files (x86)\PRTG Network Monitor\webroot\icons" | |
[string] $tempFolder = "C:\temp"; | |
[string] $global:stateIcon = ""; | |
## miscallenious | |
[hashtable]$directories = @{ | |
"templateBaseDir" = "C:\Program Files (x86)\PRTG Network Monitor\webroot\mailtemplates\custom"; | |
"tempFolder" = "C:\temp"; | |
"iconFolder" = "C:\Program Files (x86)\PRTG Network Monitor\webroot\icons"; | |
"templatePath" = ""; } | |
########################################################################### | |
# /!\ DON'T CHANGE ANYTHING BELOW - Except you know what you're doing /!\ # | |
########################################################################### | |
[hashtable] $stateColors = @{ | |
"1" = "#808282"; #unknown | |
"3" = "#b4cc38"; #up | |
"4" = "#ffcb05"; #warning | |
"5" = "#d71920"; #down | |
"8" = "#447fc1"; #paused-dependency | |
"9" = "#447fc1"; #paused-schedule | |
"10" = "#f99d1c"; #unusual | |
"11" = "#447fc1"; #paused-license | |
"12" = "#447fc1"; #paused-until | |
"13" = "#e77579"; #acknowledged | |
"14" = "#d71920"; #down-partial | |
} | |
[hashtable]$stateIcons = @{ | |
"1" = "led_grey.png"; #unknown | |
"3" = "led_green.png"; #up | |
"4" = "led_yellow.png"; #warning | |
"5" = "led_red.png"; #down | |
"8" = "led_blue.png"; #paused-dependency | |
"9" = "led_blue.png"; #paused-schedule | |
"10" = "led_orange.png"; #unusual | |
"11" = "led_blue.png"; #paused-license | |
"12" = "led_blue.png"; #paused-until | |
"13" = "led_redok.png"; #acknowledged | |
"14" = "led_redgreen.png";#down-partial | |
} | |
## debugging | |
[switch] $verbose = $true; | |
#endregion | |
#region function library | |
## show messages, nicely formatted. | |
function Console-ShowMessage([string]$type,$message){ | |
if($verbose){ | |
Write-Host ("[{0}] [" -f (Get-Date)) -NoNewline; | |
switch ($type){ | |
"done" { Write-Host "done" -ForegroundColor Green -NoNewline;} | |
"info" { Write-Host "info" -ForegroundColor DarkCyan -NoNewline; } | |
"warn" { Write-Host "warn" -ForegroundColor DarkYellow -NoNewline; } | |
"fail" { Write-Host "fail" -ForegroundColor Red -NoNewline; } | |
default{ Write-Host $type -NoNewline; } | |
} | |
Write-Host ("]`t{0}{1}" -f $message,$Global:blank) | |
} | |
} | |
## check the password files for completeness | |
function Check-PasswordFiles(){ | |
$checklist = @("Primary","Secondary") | |
foreach($server in $checklist){ | |
$fileCount = (Get-ChildItem "C:\Program Files (x86)\PRTG Network Monitor\Notifications\EXE\*" -Include "*$($server).key","*$($server).pass").Count | |
if(!($fileCount -eq 2)){ | |
Console-ShowMessage "fail" "The credential set for the $($server) server is missing or incomplete." | |
Passwords Set $server | |
} | |
} | |
if($SetupPasswords){ Console-ShowMessage "info" "Credentials created successfully."; exit 0; } | |
if((Get-ChildItem "C:\Program Files (x86)\PRTG Network Monitor\Notifications\EXE\*" -Include "*.key","*.pass").Count -eq 4){ | |
Console-ShowMessage "done" "All credentials found." | |
} | |
} | |
## check the mail server availability | |
function Check-MailServers(){ | |
begin { Console-ShowMessage "info" "Checking mail server availability..." } | |
process { | |
## lets create a new TCP client first | |
$tcpClient = New-Object System.Net.Sockets.TCPClient | |
try{ | |
$tcpClient.Connect($global:servers.smtpHost,$global:servers.smtpPort) | |
Console-ShowMessage "info" "Using primary mail server" | |
} | |
catch{ | |
try { | |
$tcpClient.Connect($global:servers.smtpBackupHost,$global:servers.smtpBackupPort) | |
Console-ShowMessage "warn" "Primary mail server not responding, using fallback server" | |
$global:servers.smtpHost = $global:servers.smtpBackupHost | |
$global:servers.smtpPort = $global:servers.smtpBackupPort | |
$global:servers.smtpSsl = $global:servers.smtpBackupSsl | |
$global:servers.smtpUser = $global:servers.smtpBackupUser | |
} | |
catch { | |
Console-ShowMessage "fail" "None of the configured mailservers is responding, please check them for availability." | |
#exit 1; | |
} | |
} | |
finally { $tcpClient.Close(); } | |
} | |
} | |
function ValidateEmail{ | |
param([string]$address) | |
($address -as [System.Net.Mail.MailAddress]).Address -eq $address -and $address -ne $null | |
} | |
## store the passwords | |
function Passwords([ValidateSet("Get","Set")][string]$action, [ValidateSet("Primary","Secondary")][string]$Server = ""){ | |
switch($Server) | |
{ | |
"primary" { | |
$passFile = "C:\Program Files (x86)\PRTG Network Monitor\Notifications\EXE\HTMLNotifyPrimary.pass"; | |
$keyFile = "C:\Program Files (x86)\PRTG Network Monitor\Notifications\EXE\HTMLNotifyPrimary.key"; | |
} | |
"secondary" { | |
$passFile = "C:\Program Files (x86)\PRTG Network Monitor\Notifications\EXE\HTMLNotifySecondary.pass" | |
$keyFile = "C:\Program Files (x86)\PRTG Network Monitor\Notifications\EXE\HTMLNotifySecondary.key"; | |
} | |
} | |
if($action -eq "set"){ | |
$Key = New-Object Byte[] 32 | |
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key) | |
$Key | out-file $keyFile | |
Read-Host "Enter Password for the $($Server) server" -AsSecureString | ConvertFrom-SecureString -key $Key | Out-File $passFile | |
} | |
if($action -eq "get"){ | |
$Key = Get-Content $KeyFile | |
$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $global:servers.smtpUser, (Get-Content $passFile | ConvertTo-SecureString -Key $key) | |
return $Credentials; | |
} | |
} | |
## set up the template paths | |
function Set-TemplatePaths(){ | |
begin { Console-ShowMessage "info" "Setting up template paths..." } | |
process { | |
## set up the paths... | |
if($templateFolder.Length -ne 0) | |
{ $directories.templatePath = [string]::Format("{0}\{1}\{2}.htm",$templateBaseDir,$templateFolder,$Template) } | |
else | |
{ $directories.templatePath = [string]::Format("{0}\{1}.htm",$templateBaseDir,$Template) } | |
## Check the paths for existance | |
$directories.Keys | % { | |
if(!(Test-Path $directories.Item($_))) | |
{ Write-Host "Template or directory not found. Please make sure all folders and templates exist!" -ForegroundColor Red; exit 2; } | |
} | |
## if there's no logo, use the default | |
if(!(Test-Path -Path ([string]::Format("{0}\{1}\logo.png",$templateBaseDir,$templateFolder)))) | |
{ | |
$global:templateLogo = $templateDefaultLogo | |
Console-ShowMessage "info" "No customized logo found, using the specified default logo..." | |
} | |
else | |
{ | |
Console-ShowMessage "info" "Customized logo found." | |
$global:templateLogo = [string]::Format("{0}\{1}\logo.png",$templateBaseDir,$templateFolder) | |
} | |
} | |
} | |
## this function will return the group ID of the given group | |
function Get-PrtgGroup( [string]$prtgGroup ){ | |
if($prtgGroup.Length -ne 0){ | |
## retrieve the group id of the corresponding PRTG user group | |
$group = $configuration.SelectSingleNode([string]::Format('//usergroup[@id][data/name[normalize-space(text())="{0}"]]',$prtgGroup)) | |
## return the id, otherwise error if you found nothing | |
if($group -ne $null) | |
{ return $group.id.Trim(); } | |
else | |
{ Console-ShowMessage -type "fail" "The specified user group was not found. Please make sure that the name is correct." } | |
} | |
} | |
## this will retrieve the single users from PRTG and create the recipientList | |
function Get-PrtgUsers([string]$prtgGroup){ | |
begin { Console-ShowMessage -type "info" "Retrieving recipients..." } | |
process { | |
if($prtgGroup.Length -ne 0){ | |
Console-ShowMessage -type "info" "Retrieving users of PRTG group $($prtgGroup)" | |
## lets retrieve the ID of the corresponding group first. | |
$groupId = (Get-PrtgGroup -prtgGroup $prtgGroup) | |
## now lets get all active users within that group | |
$userList = $configuration.SelectNodes([string]::Format('//user[@id][data/primarygroup = {0}][data/active = 1]',$groupId)) | |
$availableUsers = Foreach($user in $userList){ | |
## we only want the primary contacts if PrimaryOnly is true. | |
if($PrimaryOnly) | |
{ $contacts = $user.SelectNodes('//user[@id='+$user.id+']/contacts/contact[data/status = 1][data/contacttype = 0][@id = -100]') } | |
else | |
{ $contacts = $user.SelectNodes('//user[@id='+$user.id+']/contacts/contact[data/status = 1][data/contacttype = 0]') } | |
$availableContacts = foreach($contact in $contacts) | |
{ $recipientList.Add($contact.data.recipient.Trim()) } | |
} | |
## create the final recipient list and remove duplicates | |
$recipients = $recipientList + $recipients.Trim().Split(",") | Select -Unique | |
} | |
else | |
{ | |
$recipients = $recipients.Trim().Split(",") | Select -Unique | |
} | |
$correctedRecipients = @(); | |
# remove all recipients that are not valid | |
foreach($recipient in $recipients){ | |
if(ValidateEmail($recipient)) | |
{ $correctedRecipients += $recipient } | |
} | |
} | |
end { | |
# stop it if we don't have any recipients | |
if($correctedRecipients.Length -eq 0) { Console-ShowMessage -type "fail" "No recipients found. Please check the group name and the recipient list for errors. Exiting."; exit 0; } | |
Console-ShowMessage -type "done" "Found $($userList.count) users in group $($prtgGroup), sending to $($correctedRecipients.count) contacts in total." | |
return $correctedRecipients; | |
} | |
} | |
## this function will retrieve the latest sensor informations | |
function Get-SensorLiveInfo([int]$SensorId){ | |
begin { Console-ShowMessage -type "info" "Retrieving information for sensor #$($SensorID)" } | |
process { | |
## build the graph URL | |
$params = @($global:servers.prtgProtocol,$global:servers.prtgHost,$global:servers.prtgPort,$SensorId,$global:servers.prtgUsername,$global:servers.prtgPasshash) | |
$url = [string]::Format("{0}://{1}:{2}/api/getsensordetails.json?id={3}&username={4}&passhash={5}",$params); | |
## download the graph and store it with the current guid | |
$wc = New-Object System.Net.WebClient | |
$sensorInfo = ($wc.DownloadString($url)) | ConvertFrom-Json; | |
} | |
end { return $SensorInfo } | |
} | |
## retrieves the live graph from the given sensor id | |
function Get-SensorLiveGraph([int]$id){ | |
begin { Console-ShowMessage -type "info" "Retrieving current live graph for sensor #$($SensorID)" } | |
process { | |
## build the graph URL | |
$params = @($global:servers.prtgProtocol,$global:servers.prtgHost,$global:servers.prtgPort,600,280,$SensorId,$global:servers.prtgUsername,$global:servers.prtgPasshash) | |
$url = [string]::Format("{0}://{1}:{2}/chart.png?type=graph&width={3}&height={4}&graphid=0&id={5}&username={6}&passhash={7}",$params); | |
## download the graph and store it with the current guid | |
$wc = New-Object System.Net.WebClient | |
$wc.DownloadFile($url, [string]::Format("{0}\{1}.png",$directories.tempFolder,$Global:GraphGUID)) | |
} | |
end { Console-ShowMessage -type "done" "Graph downloaded and stored as $($tempFolder)\$($Global:GraphGUID).png" } | |
} | |
## sets up the template and replaces the placeholders accordingly. | |
function Get-Template(){ | |
begin { Console-ShowMessage "info" "Preparing notification mail" } | |
process { | |
## retrieve current sensor information | |
$SensorInfo = (Get-SensorLiveInfo($SensorID)) | |
## set the status icon | |
$global:stateIcon = $stateIcons[$SensorInfo.sensordata.statusid] | |
## retrieve the template of the specified content | |
[string]$body = (Get-Content $directories.templatePath) -join "`n" | |
## check if the amount of placeholders does match | |
try | |
{$body = [string]::Format($body,$placeholders) } | |
catch | |
{ Console-ShowMessage "warn" "The amount of placeholders doesn't match with the template. Please make sure that they're equal." } | |
[hashtable]$placeholderTable = @{ | |
"miscLOGO" = [string]::Format("<img src='{0}' />",(Split-Path -Path $global:templateLogo -Leaf)) | |
"sensorNAME" = $SensorInfo.sensordata.name; | |
"sensorTYPE" = $SensorInfo.sensordata.sensortype; | |
"sensorLIVEGRAPH" = [string]::Format("<img src='{0}.png' />",$Global:GraphGUID); | |
"sensorSTATECOLOR" = $stateColors[$SensorInfo.sensordata.statusid]; | |
"sensorSTATEICON" = [string]::Format("<img src='{0}' />",$global:stateIcon) | |
"sensorINTERVAL" = $SensorInfo.sensordata.interval; | |
"sensorPARENTGROUP" = $SensorInfo.sensordata.parentgroupname; | |
"sensorPARENTDEVICE" = $SensorInfo.sensordata.parentdevicename; | |
"sensorPARENTDEVICEID" = $SensorInfo.sensordata.parentdeviceid; | |
"sensorLASTVALUE" = $SensorInfo.sensordata.lastvalue; | |
"sensorLASTMESSAGE" = $SensorInfo.sensordata.lastmessage; | |
"sensorSTATUSTEXT" = $SensorInfo.sensordata.statustext; | |
"sensorSTATUSID" = $SensorInfo.sensordata.statusid; | |
"sensorLASTUP" = $SensorInfo.sensordata.lastup; | |
"sensorLASTDOWN" = $SensorInfo.sensordata.lastdown; | |
"sensorLASTCHECK" = $SensorInfo.sensordata.lastcheck; | |
"sensorUPTIME" = $SensorInfo.sensordata.uptime; | |
"sensorUPTIMETIME" = $SensorInfo.sensordata.uptimetime; | |
"sensorDOWNTIME" = $SensorInfo.sensordata.downtime; | |
"sensorDOWNTIMETIME" = $SensorInfo.sensordata.downtimetime; | |
"sensorUPDOWNTOTAL" = $SensorInfo.sensordata.uptimetotal; | |
"sensorUPDOWNSINCE" = $SensorInfo.sensordata.updownsince; | |
"sensorURL" = [string]::Format("{0}://{1}:{2}/sensor.htm?id={3}",$global:servers.prtgProtocol,$global:servers.prtgHost,$global:servers.prtgPort,$SensorID); | |
"deviceURL" = [string]::Format("{0}://{1}:{2}/device.htm?id={3}",$global:servers.prtgProtocol,$global:servers.prtgHost,$global:servers.prtgPort,$SensorInfo.sensordata.parentdeviceid); | |
} | |
## replace all existing placeholders within the template | |
foreach ($placeholder in $placeholderTable.Keys) | |
{ | |
$body = ($body -replace $placeholder, $placeholderTable.$placeholder) | |
$subject = ($subject -replace $placeholder, $placeholderTable.$placeholder) | |
} | |
[hashtable]$mail = @{ | |
Body = $body; | |
Subject = $subject; | |
}; | |
} | |
end { return $mail } | |
} | |
## this function will format the mail and send it to the given recipient(s) | |
function Send-NotificationMail([hashtable]$mail){ | |
## There will always be a logo and a livegraph available for every template. | |
## Use logo,livegraph,probeicon,breadcrumb as img src to embed them, like this: | |
## See the following placeholders to insert them in your mail | |
[string[]]$images = @( | |
$global:templateLogo, | |
[string]::Format("{0}\{1}.png",$directories.tempFolder,$Global:GraphGUID), | |
[string]::Format("{0}\{1}",$directories.iconFolder,$global:stateIcon) | |
) | |
$mailparameters = @{ | |
SmtpServer = $global:servers.smtpHost; | |
Port = $global:servers.smtpPort; | |
Credential = (Passwords -Action Get -Server Primary); | |
From = $global:servers.smtpFrom; | |
To = Get-PrtgUsers($prtgGroup); | |
Body = $mail.Body; | |
Subject = $mail.Subject; | |
Attachments = $images; | |
BodyAsHtml = $HTML; | |
UseSsl = $global:servers.smtpSsl; | |
Priority = $priority; | |
} | |
try{ | |
Send-MailMessage @mailparameters | |
Console-ShowMessage "info" "Recipient(s) should be notified now." | |
} | |
catch { Console-ShowMessage "fail" "The notification could not be sent." } | |
} | |
#endregion | |
function Main(){ | |
begin{ | |
Check-PasswordFiles; | |
Check-MailServers; | |
Set-TemplatePaths; | |
} | |
process{ | |
Get-SensorLiveGraph; | |
Send-NotificationMail -mail (Get-Template) | |
} | |
end{ | |
Console-ShowMessage "info" "Removing live graph image. Exiting." | |
Remove-Item ([string]::Format("{0}\{1}.png",$tempFolder,$Global:graphGUID)) | |
} | |
} | |
Main; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fixed a bug where the server variable was not read correctly