Enhanced Alerts
Provide a custom field with additional alert information at Organization and Device level.
Last updated
Provide a custom field with additional alert information at Organization and Device level.
Last updated
This script will populate a custom field on Organizations and Devices. It will show a prioritized list of alerts sorted by Severity, Priority then Created Date. It will also display a small graph detailing when the specific alert has been triggered on the device in the last 30 days.
It will also populate a view across organizations of Critical and Major severity items into the Global Overview company.
Make sure the Getting Started guide has been followed
Core RMM
Once per hour
None
Device Alert Summary
deviceAlertSummary
WYSIWYG
Technician: Read Only Automations: None
API: Read/Write
Organization, Location, Device
Displays the Alert Summary
$Start = Get-Date
$NinjaOneInstance = Ninja-Property-Get ninjaoneInstance
$NinjaOneClientID = Ninja-Property-Get ninjaoneClientId
$NinjaOneClientSecret = Ninja-Property-Get ninjaoneClientSecret
$OverviewCompany = 'Global Overview'
$SummaryField = 'deviceAlertSummary'
function Get-AlertsTable ($Alerts, $MaxChars, $CountAlerts) {
[System.Collections.Generic.List[string]]$ParsedTable = @()
if (($CountAlerts | Measure-Object).count -gt 0) {
[System.Collections.Generic.List[PSCustomObject]]$WidgetData = @()
$WidgetData.add([PSCustomObject]@{
Value = '<i class="fas fa-circle-xmark"></i> ' + $(($CountAlerts | Where-Object { $_.Severity -eq 'CRITICAL' } | Measure-Object).count)
Description = 'Critical'
Colour = '#D53948'
})
$WidgetData.add([PSCustomObject]@{
Value = '<i class="fas fa-triangle-exclamation"></i> ' + $(($CountAlerts | Where-Object { $_.Severity -eq 'MAJOR' } | Measure-Object).count)
Description = 'Major'
Colour = '#FAC905'
})
$WidgetData.add([PSCustomObject]@{
Value = '<i class="fas fa-circle-exclamation"></i> ' + $(($CountAlerts | Where-Object { $_.Severity -eq 'MODERATE' } | Measure-Object).count)
Description = 'Moderate'
Colour = '#337AB7'
})
$WidgetData.add([PSCustomObject]@{
Value = '<i class="fas fa-circle-exclamation"></i> ' + $(($CountAlerts | Where-Object { $_.Severity -eq 'MINOR' } | Measure-Object).count)
Description = 'Minor'
Colour = '#949597'
})
$WidgetData.add([PSCustomObject]@{
Value = '<i class="fas fa-circle-info"></i> ' + $(($CountAlerts | Where-Object { $_.Severity -eq 'NONE' } | Measure-Object).count)
Description = 'None'
Colour = '#949597'
})
$WidgetHTML = (Get-NinjaOneWidgetCard -Data $WidgetData -SmallCols 3 -MedCols 3 -LargeCols 5 -XLCols 5 -NoCard)
$ParsedTable.add($WidgetHTML)
$ParsedTable.add('<table>')
$ParsedTable.add('<tr><th>Created</th><th></th><th>Device</th><th>Organization</th><th style="white-space: nowrap;">Severity</th><th style="white-space: nowrap;">Priority</th><th style="white-space: nowrap;">Last 30 Days</th><th>Message</th></tr>')
foreach ($ParsedAlert in $Alerts) {
$HTML = '<tr class="' + $ParsedAlert.RowClass + '">' +
'<td style="white-space: nowrap;">' + ($ParsedAlert.Created).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss") + '</td>' +
'<td style="white-space: nowrap;"><i style="color: ' + $ParsedAlert.OnlineColour + ';" class="' + $ParsedAlert.OnlineIcon + '"></i></td>' +
'<td style="white-space: nowrap;"><a href="https://' + $NinjaOneInstance + '/#/deviceDashboard/' + $ParsedAlert.DeviceID + '/overview">' + $ParsedAlert.Device + '</a></td>' +
'<td style="white-space: nowrap;"><a href="https://' + $NinjaOneInstance + '/#/customerDashboard/' + $ParsedAlert.OrgID + '/overview">' + $ParsedAlert.OrgName + '</a></td>' +
'<td style="white-space: nowrap;"><i style="color: ' + $ParsedAlert.SeverityColour + ';" class="' + $ParsedAlert.SeverityIcon + '"></i> ' + (Get-Culture).TextInfo.ToTitleCase($ParsedAlert.Severity.ToLower()) + '</td>' +
'<td style="white-space: nowrap;"><i style="color: ' + $ParsedAlert.PiorityColour + ';" class="' + $ParsedAlert.PiorityIcon + '"></i> ' + (Get-Culture).TextInfo.ToTitleCase($ParsedAlert.Piority.ToLower()) + '</td>' +
'<td style="white-space: nowrap;">' + $ParsedAlert.Last30Days + '</td>' +
'<td>' + ($ParsedAlert.Message).Substring(0, [Math]::Min(($ParsedAlert.Message).Length, $MaxChars)) + '</td>' + '</tr>'
$ParsedTable.add($HTML)
}
$ParsedTable.add('</table>')
} else {
[System.Collections.Generic.List[PSCustomObject]]$WidgetData = @()
$WidgetData.add([PSCustomObject]@{
Value = '<i class="fas fa-circle-check"></i>'
Description = 'No Alerts'
Colour = '#26a644'
})
$WidgetHTML = (Get-NinjaOneWidgetCard -Data $WidgetData -SmallCols 3 -MedCols 3 -LargeCols 5 -XLCols 5 -NoCard)
$ParsedTable.add($WidgetHTML)
}
Return $ParsedTable
}
try {
$moduleName = "NinjaOneDocs"
if (-not (Get-Module -ListAvailable -Name $moduleName)) {
$Null = Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name $moduleName -Force -AllowClobber
} else {
$latestVersion = (Find-Module -Name $moduleName).Version
$installedVersion = (Get-Module -ListAvailable -Name $moduleName).Version | Sort-Object -Descending | Select-Object -First 1
if ($installedVersion -ne $latestVersion) {
Update-Module -Name $moduleName -Force
}
}
Import-Module $moduleName
# Fix for PSCustomObjects being broken in 7.4.0
$ExecutionContext.SessionState.LanguageMode = 'FullLanguage'
Connect-NinjaOne -NinjaOneInstance $NinjaOneInstance -NinjaOneClientID $NinjaOneClientID -NinjaOneClientSecret $NinjaOneClientSecret
Write-Output "$(Get-Date): Fetching Core Data"
$Alerts = Invoke-NinjaOneRequest -Method GET -Path 'alerts' -Paginate
$Devices = Invoke-NinjaOneRequest -Method GET -Path 'devices' -Paginate
$Organizations = Invoke-NinjaOneRequest -Method GET -Path 'organizations' -Paginate
$Locations = Invoke-NinjaOneRequest -Method GET -Path 'locations' -Paginate
$CurrentAlerts = (Invoke-NinjaOneRequest -Method GET -Path 'queries/scoped-custom-fields' -QueryParams "fields=$SummaryField" -Paginate).results
Write-Output "$(Get-Date): Fetching Activities"
$31DaysAgo = Get-NinjaOneTime -Date ((Get-Date).adddays(-31)) -Seconds
[System.Collections.Generic.List[PSCustomObject]]$Activities = (Invoke-NinjaOneRequest -Method GET -Path 'activities' -QueryParams "status=TRIGGERED&pageSize=1000&after=$31DaysAgo").activities
$Count = ($Activities.id | measure-object -Minimum).minimum
$PageSize = 1000
$Found = $False
do {
$Result = Invoke-NinjaOneRequest -Method GET -Path 'activities' -QueryParams "status=TRIGGERED&pageSize=$($PageSize)&olderThan=$($Count)&after=$31DaysAgo"
if (($Result.Activities | Measure-Object).count -gt 0) {
$Activities.AddRange([System.Collections.Generic.List[PSCustomObject]]$Result.Activities)
$Count = ($Result.Activities.id | measure-object -Minimum).Minimum
$Measurement = $($Result.Activities.id | measure-object -Minimum -Maximum)
Write-Host "Min: $($Measurement.Minimum) Max: $($Measurement.Maximum)"
} else {
$Found = $True
}
} while ($Found -eq $False)
[System.Collections.Generic.List[PSCustomObject]]$ParsedAlerts = @()
Write-Output "$(Get-Date): Processing Organizations"
foreach ($Org in $Organizations) {
Write-Host "$(Get-Date): Processing $($Org.name)"
$OrgDevices = $Devices | where-object { $_.organizationId -eq $Org.id }
$OrgAlerts = $Alerts | Where-Object { $_.deviceId -in $OrgDevices.id }
Foreach ($Alert in $OrgAlerts) {
$CurrentActivity = $Activities | Where-Object { $_.seriesUid -eq $Alert.uid }
if (($CurrentActivity | Measure-Object).count -ne 1) {
$AssociatedTriggers = $Null
$CurrentActivity = (Invoke-NinjaOneRequest -Method GET -Path 'activities' -QueryParams "status=TRIGGERED&seriesUid=$($Alert.uid)").Activities
}
$AssociatedTriggers = $Activities | Where-Object { $_.sourceConfigUid -eq $Alert.sourceConfigUid -and $_.deviceId -eq $Alert.deviceId }
$AlertDevice = $Devices | Where-Object { $_.id -eq $Alert.deviceId }
$AlertLocation = $Locations | Where-Object { $_.id -eq $AlertDevice.locationId }
if ($AlertDevice.offline -eq $True) {
$OnlineColour = '#949597'
$OnlineIcon = 'fas fa-plug'
} else {
$OnlineColour = '#26a644'
$OnlineIcon = 'fas fa-plug'
}
Switch ($CurrentActivity.severity) {
'CRITICAL' { $SeverityIcon = 'fas fa-circle-xmark'; $SeverityColour = '#D53948'; $SeverityScore = 5; $RowClass = 'danger' }
'MAJOR' { $SeverityIcon = 'fas fa-triangle-exclamation'; $SeverityColour = '#FAC905'; $SeverityScore = 4; $RowClass = 'warning' }
'MODERATE' { $SeverityIcon = 'fas fa-circle-exclamation'; $SeverityColour = '#337AB7 '; $SeverityScore = 3; $RowClass = 'other' }
'MINOR' { $SeverityIcon = 'fas fa-circle-exclamation'; $SeverityColour = '#949597'; $SeverityScore = 2; $RowClass = 'unknown' }
'NONE' { $SeverityIcon = 'fas fa-circle-info'; $SeverityColour = '#949597'; $SeverityScore = 1; $RowClass = '' }
default { $SeverityIcon = 'fas fa-circle-info'; $SeverityColour = '#949597'; $SeverityScore = 1; $RowClass = '' }
}
Switch ($CurrentActivity.priority) {
'HIGH' { $PiorityIcon = 'fas fa-circle-arrow-up'; $PiorityColour = '#D53948'; $PiorityScore = 5 }
'MEDIUM' { $PiorityIcon = 'fas fa-circle-arrow-right'; $PiorityColour = '#FAC905'; $PiorityScore = 4 }
'LOW' { $PiorityIcon = 'fas fa-circle-arrow-down'; $PiorityColour = '#337AB7'; $PiorityScore = 3 }
'NONE' { $PiorityIcon = 'fas fa-circle-info'; $PiorityColour = '#949597'; $PiorityScore = 2 }
default { $PiorityIcon = 'fas fa-circle-info'; $PiorityColour = '#949597'; $PiorityScore = 2 }
}
$TotalCount = ($AssociatedTriggers | Measure-Object).count
$Last30DaysAlerts = $AssociatedTriggers | Where-Object { $_.activityTime -gt (Get-NinjaOneTime -Date (Get-Date).AddDays(-30) -Seconds) } | Sort-Object activityTime
# Get the current date
$today = Get-Date
# Initialize variables to track consecutive days and status
$consecutiveDays = 0
$previousStatus = $null
$HTMLHistory = ''
# Loop through the last 30 days
for ($i = 0; $i -le 30; $i++) {
# Calculate the date to check
$dateToCheck = $today.AddDays(-$i)
# Check if any alerts were created on this date
$alertsOnThisDay = $Last30DaysAlerts | Where-Object { (Get-TimeFromNinjaOne -Date ($_.activityTime) -Seconds).Date -eq $dateToCheck.Date }
$currentStatus = if ($alertsOnThisDay.Count -gt 0) { "#D53948" } else { "#cccccc" }
# Check if the status changed or it's the last iteration
if ($currentStatus -ne $previousStatus -or $i -eq 30) {
if ($consecutiveDays -gt 0) {
# Calculate width of the span
$width = $consecutiveDays * 3 # Example width calculation
$color = if ($previousStatus -eq "#D53948") { "#D53948" } else { "#cccccc" }
$HTMLHistory = "<div style='background-color: $color; width: ${width}px;'></div>" + $HTMLHistory
}
# Reset for the new status
$consecutiveDays = 0
}
# Increment the day count and update the previous status
$consecutiveDays++
$previousStatus = $currentStatus
}
# End of HTML output
$HTMLHistory = '<div style="display: flex; height: 20px;">' + $HTMLHistory + '</div>'
$ParsedAlerts.add([PSCustomObject]@{
Created = Get-TimeFromNinjaOne -Date $Alert.createTime -seconds
Updated = Get-TimeFromNinjaOne -Date $Alert.updateTime -seconds
Device = $AlertDevice.systemName
DeviceID = $AlertDevice.id
OnlineIcon = $OnlineIcon
OnlineColour = $OnlineColour
OrgName = $Org.name
OrgID = $Org.id
LocName = $AlertLocation.name
LocID = $AlertLocation.id
Message = $Alert.message
Severity = if ($CurrentActivity.severity) { $CurrentActivity.severity } else { 'None' }
Piority = if ($CurrentActivity.priority) { $CurrentActivity.priority } else { 'None' }
SeverityIcon = $SeverityIcon
SeverityColour = $SeverityColour
SeverityScore = $SeverityScore
PiorityIcon = $PiorityIcon
PiorityColour = $PiorityColour
PiorityScore = $PiorityScore
RowClass = $RowClass
TotalCount = $TotalCount
Last30Days = $HTMLHistory
})
}
$OrgAlertsTable = ($ParsedAlerts | Where-object { $_.OrgID -eq $Org.id } | Sort-Object SeverityScore, PiorityScore, Created -Descending)
$ParsedTable = Get-AlertsTable -Alerts $OrgAlertsTable -CountAlerts $OrgAlertsTable -MaxChars 300
$OrgHTML = "$($ParsedTable -join '')"
$CurrentAlert = $CurrentAlerts | Where-Object { $_.scope -eq 'ORGANIZATION' -and $_.entityId -eq $Org.id }
if (($CurrentAlert.fields."$SummaryField".html -replace '<[^>]+>', '') -ne ($OrgHTML -replace '<[^>]+>', '')) {
$OrgUpdate = [PSCustomObject]@{
"$SummaryField" = @{'html' = $OrgHTML }
}
$Null = Invoke-NinjaOneRequest -Method PATCH -Path "organization/$($Org.id)/custom-fields" -InputObject $OrgUpdate
}
}
Write-Output "$(Get-Date): Generating Global View"
# Set Global View
$OverviewMatch = $Organizations | Where-Object { $_.name -eq $OverviewCompany }
if (($OverviewMatch | Measure-Object).count -eq 1) {
$ParsedTable = Get-AlertsTable -Alerts ($ParsedAlerts | Sort-Object SeverityScore, PiorityScore, Message, Created -Descending | select-object -first 100) -MaxChars 100 -CountAlerts $ParsedAlerts
$GlobalHTML = "$($ParsedTable -join '')"
$CurrentAlert = $CurrentAlerts | Where-Object { $_.scope -eq 'ORGANIZATION' -and $_.entityId -eq $OverviewMatch.id }
if (($CurrentAlert.fields."$SummaryField".html -replace '<[^>]+>', '') -ne ($GlobalHTML -replace '<[^>]+>', '')) {
$OrgUpdate = [PSCustomObject]@{
"$SummaryField" = @{'html' = $GlobalHTML }
}
$Null = Invoke-NinjaOneRequest -Method PATCH -Path "organization/$($OverviewMatch.id)/custom-fields" -InputObject $OrgUpdate
}
}
Write-Output "$(Get-Date): Processing Devices"
# Set Each Device
Foreach ($UpdateDevice in $Devices) {
$DeviceAlerts = ($ParsedAlerts | Where-object { $_.DeviceID -eq $UpdateDevice.id } | Sort-Object SeverityScore, PiorityScore, Created -Descending)
$ParsedTable = Get-AlertsTable -MaxChars 300 -Alerts $DeviceAlerts -CountAlerts $DeviceAlerts
$DeviceHTML = "$($ParsedTable -join '')"
$CurrentAlert = $CurrentAlerts | Where-Object { $_.scope -eq 'NODE' -and $_.entityId -eq $UpdateDevice.id }
if (($CurrentAlert.fields."$SummaryField".html -replace '<[^>]+>', '') -ne ($DeviceHTML -replace '<[^>]+>', '')) {
$DeviceUpdate = [PSCustomObject]@{
"$SummaryField" = @{'html' = $DeviceHTML }
}
$Null = Invoke-NinjaOneRequest -Method PATCH -Path "device/$($UpdateDevice.id)/custom-fields" -InputObject $DeviceUpdate
}
}
Write-Output "$(Get-Date): Complete Total Runtime: $((New-TimeSpan -Start $Start -End (Get-Date)).TotalSeconds) seconds"
} catch {
Write-Output "Failed to Generate Documentation: $_"
exit 1
}