# This PowerShell script can be used as a starting template for enabling
# automated remediation for alerts coming from LogicMonitor.
# In LogicMonitor, you can use the External Alerting feature to pass all alerts
# (or for a specific group of resources) to this script.
# ----
# To use this script:
# 1. Drop this script onto your Collector server under the Collector's agent/lib directory.
# 2. In your LogicMonitor portal go to Settings, then click External Alerting.
# 3. Click the Add button.
# 4. Set the 'Groups' field as needed to limit the actions to a specific group of resources.
# 5. Choose the appropriate Collector in the 'Collector' field.
# 6. Set 'Delivery Mechanism' to \"Script\"
# 7. Enter \"alert_central.ps1\" in the 'Script' field.
# 8. Paste the following into the 'Script Command Line' field:
# \"##ALERTID##\" \"##ALERTSTATUS##\" \"##LEVEL##\" \"##HOSTNAME##\" \"##DSNAME##\" \"##INSTANCE##\" \"##DATAPOINT##\" \"##VALUE##\" \"##ALERTDETAILURL##\" \"##DPDESCRIPTION##\"
# 9. Click Save.
Param ($alertID = \"\", $alertStatus = \"\", $severity = \"\", $hostName = \"\", $dsName = \"\", $instance = \"\", $datapoint = \"\", $metricValue = \"\", $alertURL = \"\", $dpDescription = \"\")
###--- SET THE FOLLOWING VARIABLES AS APPROPRIATE ---###
# LogicMonitor API account information - the API user will need \"Acknowledge\" permissions...
$accessId = ''
$accessKey = ''
$company = ''
# OPTIONAL: Set a filename in the following variable if you want specific alerts logged. (example: \"C:\\lm_alert_central.log\")...
$logFile = \"\"
########################################################
# Function for logging the alert to a local text file if one was specified in the $logFile variable above...
Function LogWrite ($logstring = \"\")
{
\tif ($logFile -ne \"\") {
\t\tAdd-content $logFile -value $logstring
\t}
}
# Function for attaching a note to the alert...
function AddNoteToAlert ($alertID = \"\", $note = \"\")
{
\t# Encode the note...
\t$encodedNote = $note | ConvertTo-Json
\t<# API and URL request details #>
\t$httpVerb = 'POST'
\t$resourcePath = '/alert/alerts/' + $alertID + '/note'
\t$url = 'https://' + $company + '.logicmonitor.com/santaba/rest' + $resourcePath
\t$data = '{\"ackComment\":' + $encodedNote + '}'
\t<# Get current time in milliseconds #>
\t$epoch = [Math]::Round((New-TimeSpan -start (Get-Date -Date \"1/1/1970\") -end (Get-Date).ToUniversalTime()).TotalMilliseconds)
\t<# Concatenate General Request Details #>
\t$requestVars_00 = $httpVerb + $epoch + $data + $resourcePath
\t<# Construct Signature #>
\t$hmac = New-Object System.Security.Cryptography.HMACSHA256
\t$hmac.Key = [Text.Encoding]::UTF8.GetBytes($accessKey)
\t$signatureBytes = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($requestVars_00))
\t$signatureHex = [System.BitConverter]::ToString($signatureBytes) -replace '-'
\t$signature = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($signatureHex.ToLower()))
\t<# Construct Headers #>
\t$auth = 'LMv1 ' + $accessId + ':' + $signature + ':' + $epoch
\t$headers = New-Object \"System.Collections.Generic.Dictionary[[String],[String]]\"
\t$headers.Add(\"Authorization\",$auth)
\t$headers.Add(\"Content-Type\",'application/json')
\t<# Make Request to add collector#>
\t$response = Invoke-RestMethod -Uri $url -Method $httpVerb -Body $data -Header $headers
\t# Write-Host \"API call response: $response\"
}
# Placeholder variable for capturing any note we want to attach back to the alert...
$alertNote = \"\"
# --------------------
### CUSTOMIZE THE FOLLOWING AS NEEDED TO HANDLE SPECIFIC ALERTS FROM LOGICMONITOR...
# Actions to take if the alert is new or re-opened (note: status will be \"active\" or \"clear\")...
if ($alertStatus -eq 'active') {
\t# Perform actions based on the type of alert...
\tif ($dsName -eq 'HTTPS-' -and $datapoint -eq 'CantConnect') {
\t\t# Insert action here to take if there's a website error.
\t\t# Attach a note to the LogicMonitor alert...
\t\t$alertNote = \"Action X performed on this alert\"
\t} elseif ($dsName -eq 'Ping' -and $datapoint -eq 'PingLossPercent') {
\t\t# Insert action to take if a device becomes unpingable.
\t\t$job = ping -c 4 $hostName
\t\t# Restore line feeds to the output...
\t\t$job = [string]::join(\"`n\", $job)
\t\t# Add ping results as a note on the alert...
\t\t$alertNote = \"Ping results: $job\"
\t}
}
# --------------------
# Update the LogicMonitor alert if 'alertNote' is true...
if ($alertNote -ne \"\") {
\tAddNoteToAlert $alertID $alertNote
\t# Optionally log the alert (if a filename is given in the $logFile variable)...
\tLogWrite \"$alertID, $alertStatus, $severity, $hostName, $dsName, $instance, $datapoint, $metricValue, $alertURL, $dpDescription\"
}
","body@stripHtml({\"removeProcessingText\":true,\"removeSpoilerMarkup\":true,\"removeTocMarkup\":true,\"truncateLength\":200})@stringLength":"203","postTime":"2023-03-10T09:33:56.000-08:00","lastPublishTime":"2023-03-10T09:33:56.000-08:00","images":{"__typename":"AssociatedImageConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"attachments":{"__typename":"AttachmentConnection","pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null},"edges":[]},"solution":true,"metrics":{"__typename":"MessageMetrics","views":0},"placeholder":false,"originalMessageForPlaceholder":null,"videos":{"__typename":"VideoConnection","edges":[],"totalCount":0,"pageInfo":{"__typename":"PageInfo","hasNextPage":false,"endCursor":null,"hasPreviousPage":false,"startCursor":null}},"isEscalated":null,"entityType":"FORUM_REPLY","eventPath":"category:lm-pulse/community:ajkuw86572board:community-feedback/message:11620/message:11634","customFields":[],"readOnly":false,"editFrozen":false,"body@stringLength":"5550","rawBody":"
Until we can locate the original article, I’ll post it’s PowerShell script here. The key instructions are in the comments at the top of the script.
# ----
# This PowerShell script can be used as a starting template for enabling
# automated remediation for alerts coming from LogicMonitor.
# In LogicMonitor, you can use the External Alerting feature to pass all alerts
# (or for a specific group of resources) to this script.
# ----
# To use this script:
# 1. Drop this script onto your Collector server under the Collector's agent/lib directory.
# 2. In your LogicMonitor portal go to Settings, then click External Alerting.
# 3. Click the Add button.
# 4. Set the 'Groups' field as needed to limit the actions to a specific group of resources.
# 5. Choose the appropriate Collector in the 'Collector' field.
# 6. Set 'Delivery Mechanism' to \"Script\"
# 7. Enter \"alert_central.ps1\" in the 'Script' field.
# 8. Paste the following into the 'Script Command Line' field:
# \"##ALERTID##\" \"##ALERTSTATUS##\" \"##LEVEL##\" \"##HOSTNAME##\" \"##DSNAME##\" \"##INSTANCE##\" \"##DATAPOINT##\" \"##VALUE##\" \"##ALERTDETAILURL##\" \"##DPDESCRIPTION##\"
# 9. Click Save.
Param ($alertID = \"\", $alertStatus = \"\", $severity = \"\", $hostName = \"\", $dsName = \"\", $instance = \"\", $datapoint = \"\", $metricValue = \"\", $alertURL = \"\", $dpDescription = \"\")
###--- SET THE FOLLOWING VARIABLES AS APPROPRIATE ---###
# LogicMonitor API account information - the API user will need \"Acknowledge\" permissions...
$accessId = ''
$accessKey = ''
$company = ''
# OPTIONAL: Set a filename in the following variable if you want specific alerts logged. (example: \"C:\\lm_alert_central.log\")...
$logFile = \"\"
########################################################
# Function for logging the alert to a local text file if one was specified in the $logFile variable above...
Function LogWrite ($logstring = \"\")
{
\tif ($logFile -ne \"\") {
\t\tAdd-content $logFile -value $logstring
\t}
}
# Function for attaching a note to the alert...
function AddNoteToAlert ($alertID = \"\", $note = \"\")
{
\t# Encode the note...
\t$encodedNote = $note | ConvertTo-Json
\t<# API and URL request details #>
\t$httpVerb = 'POST'
\t$resourcePath = '/alert/alerts/' + $alertID + '/note'
\t$url = 'https://' + $company + '.logicmonitor.com/santaba/rest' + $resourcePath
\t$data = '{\"ackComment\":' + $encodedNote + '}'
\t<# Get current time in milliseconds #>
\t$epoch = [Math]::Round((New-TimeSpan -start (Get-Date -Date \"1/1/1970\") -end (Get-Date).ToUniversalTime()).TotalMilliseconds)
\t<# Concatenate General Request Details #>
\t$requestVars_00 = $httpVerb + $epoch + $data + $resourcePath
\t<# Construct Signature #>
\t$hmac = New-Object System.Security.Cryptography.HMACSHA256
\t$hmac.Key = [Text.Encoding]::UTF8.GetBytes($accessKey)
\t$signatureBytes = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($requestVars_00))
\t$signatureHex = [System.BitConverter]::ToString($signatureBytes) -replace '-'
\t$signature = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($signatureHex.ToLower()))
\t<# Construct Headers #>
\t$auth = 'LMv1 ' + $accessId + ':' + $signature + ':' + $epoch
\t$headers = New-Object \"System.Collections.Generic.Dictionary[[String],[String]]\"
\t$headers.Add(\"Authorization\",$auth)
\t$headers.Add(\"Content-Type\",'application/json')
\t<# Make Request to add collector#>
\t$response = Invoke-RestMethod -Uri $url -Method $httpVerb -Body $data -Header $headers
\t# Write-Host \"API call response: $response\"
}
# Placeholder variable for capturing any note we want to attach back to the alert...
$alertNote = \"\"
# --------------------
### CUSTOMIZE THE FOLLOWING AS NEEDED TO HANDLE SPECIFIC ALERTS FROM LOGICMONITOR...
# Actions to take if the alert is new or re-opened (note: status will be \"active\" or \"clear\")...
if ($alertStatus -eq 'active') {
\t# Perform actions based on the type of alert...
\tif ($dsName -eq 'HTTPS-' -and $datapoint -eq 'CantConnect') {
\t\t# Insert action here to take if there's a website error.
\t\t# Attach a note to the LogicMonitor alert...
\t\t$alertNote = \"Action X performed on this alert\"
\t} elseif ($dsName -eq 'Ping' -and $datapoint -eq 'PingLossPercent') {
\t\t# Insert action to take if a device becomes unpingable.
\t\t$job = ping -c 4 $hostName
\t\t# Restore line feeds to the output...
\t\t$job = [string]::join(\"`n\", $job)
\t\t# Add ping results as a note on the alert...
\t\t$alertNote = \"Ping results: $job\"
\t}
}
# --------------------
# Update the LogicMonitor alert if 'alertNote' is true...
if ($alertNote -ne \"\") {
\tAddNoteToAlert $alertID $alertNote
\t# Optionally log the alert (if a filename is given in the $logFile variable)...
\tLogWrite \"$alertID, $alertStatus, $severity, $hostName, $dsName, $instance, $datapoint, $metricValue, $alertURL, $dpDescription\"
}
","kudosSumWeight":2,"repliesCount":0,"timeToRead":3,"currentRevision":{"__ref":"Revision:revision:11634_1"},"latestVersion":null,"visibilityScope":"PUBLIC","messagePolicies":{"__typename":"MessagePolicies","canModerateSpamMessage":{"__typename":"PolicyResult","failureReason":{"__typename":"FailureReason","message":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","key":"error.lithium.policies.feature.moderation_spam.action.moderate_entity.allowed.accessDenied","args":[]}}}},"ModerationData:moderation_data:11638":{"__typename":"ModerationData","id":"moderation_data:11638","status":"APPROVED","rejectReason":null,"isReportedAbuse":false,"rejectUser":null,"rejectTime":null,"rejectActorType":null},"Revision:revision:11638_1":{"__typename":"Revision","id":"revision:11638_1","lastEditTime":"2023-03-10T14:01:07.000-08:00"},"QueryVariables:ReplyList:message:11638:1":{"__typename":"QueryVariables","id":"ReplyList:message:11638:1","value":{"id":"message:11638","first":10,"sorts":{"kudosSumWeight":{"direction":"DESC","order":0},"postTime":{"direction":"ASC","order":1}},"repliesFirst":3,"repliesFirstDepthThree":1,"repliesSorts":{"kudosSumWeight":{"direction":"DESC","order":0},"postTime":{"direction":"ASC","order":1}},"useAvatar":true,"useAuthorLogin":true,"useAuthorRank":true,"useBody":true,"useKudosCount":true,"useTimeToRead":false,"useMedia":false,"useReadOnlyIcon":false,"useRepliesCount":true,"useSearchSnippet":false,"useAcceptedSolutionButton":true,"useSolvedBadge":false,"useAttachments":false,"attachmentsFirst":5,"useTags":false,"useNodeAncestors":false,"useUserHoverCard":false,"useNodeHoverCard":false,"useModerationStatus":true,"usePreviewSubjectModal":false,"useMessageStatus":true}},"CachedAsset:text:en_US-shared/client/components/users/UserAvatar-1743036179325":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/users/UserAvatar-1743036179325","value":{"altText":"{login}'s avatar","altTextGeneric":"User's avatar"},"localOverride":false},"CachedAsset:text:en_US-components/messages/AcceptedSolutionButton-1743036179325":{"__typename":"CachedAsset","id":"text:en_US-components/messages/AcceptedSolutionButton-1743036179325","value":{"accept":"Mark as Solution","accepted":"Marked as Solution","errorHeader":"Error!","errorAdd":"There was an error marking as solution.","errorRemove":"There was an error unmarking as solution.","solved":"Solved","topicAlreadySolvedErrorTitle":"Solution Already Exists","topicAlreadySolvedErrorDesc":"Refresh the browser to view the existing solution"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/ranks/UserRankLabel-1743036179325":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/ranks/UserRankLabel-1743036179325","value":{"altTitle":"Icon for {rankName} rank"},"localOverride":false},"CachedAsset:text:en_US-components/messages/ThreadedReplyList-1743036179325":{"__typename":"CachedAsset","id":"text:en_US-components/messages/ThreadedReplyList-1743036179325","value":{"title":"{count, plural, one{# Reply} other{# Replies}}","title@board:BLOG":"{count, plural, one{# Comment} other{# Comments}}","title@board:TKB":"{count, plural, one{# Comment} other{# Comments}}","title@board:IDEA":"{count, plural, one{# Comment} other{# Comments}}","title@board:OCCASION":"{count, plural, one{# Comment} other{# Comments}}","noRepliesTitle":"No Replies","noRepliesTitle@board:BLOG":"No Comments","noRepliesTitle@board:TKB":"No Comments","noRepliesTitle@board:IDEA":"No Comments","noRepliesTitle@board:OCCASION":"No Comments","noRepliesDescription":"Be the first to reply","noRepliesDescription@board:BLOG":"Be the first to comment","noRepliesDescription@board:TKB":"Be the first to comment","noRepliesDescription@board:IDEA":"Be the first to comment","noRepliesDescription@board:OCCASION":"Be the first to comment","messageReadOnlyAlert:BLOG":"Comments have been turned off for this post","messageReadOnlyAlert:TKB":"Comments have been turned off for this article","messageReadOnlyAlert:IDEA":"Comments have been turned off for this idea","messageReadOnlyAlert:FORUM":"Replies have been turned off for this discussion","messageReadOnlyAlert:OCCASION":"Comments have been turned off for this event"},"localOverride":false},"CachedAsset:text:en_US-shared/client/components/nodes/NodeIcon-1743036179325":{"__typename":"CachedAsset","id":"text:en_US-shared/client/components/nodes/NodeIcon-1743036179325","value":{"contentType":"Content Type {style, select, FORUM {Forum} BLOG {Blog} TKB {Knowledge Base} IDEA {Ideas} OCCASION {Events} other {}} icon"},"localOverride":false},"Revision:revision:11634_1":{"__typename":"Revision","id":"revision:11634_1","lastEditTime":"2023-03-10T09:33:56.000-08:00"},"CachedAsset:text:en_US-components/tags/TagView/TagViewChip-1743036179325":{"__typename":"CachedAsset","id":"text:en_US-components/tags/TagView/TagViewChip-1743036179325","value":{"tagLabelName":"Tag name {tagName}"},"localOverride":false}}}},"page":"/forums/ForumMessagePage/ForumMessagePage","query":{"boardId":"community-feedback","messageSubject":"can-not-find-old-post","messageId":"11620","replyId":"11638"},"buildId":"q_bLpq2mflH0BeZigxpj6","runtimeConfig":{"buildInformationVisible":false,"logLevelApp":"info","logLevelMetrics":"info","openTelemetryClientEnabled":false,"openTelemetryConfigName":"logicmonitor","openTelemetryServiceVersion":"25.2.0","openTelemetryUniverse":"prod","openTelemetryCollector":"http://localhost:4318","openTelemetryRouteChangeAllowedTime":"5000","apolloDevToolsEnabled":false,"inboxMuteWipFeatureEnabled":false},"isFallback":false,"isExperimentalCompile":false,"dynamicIds":["./components/seo/QAPageSchema/QAPageSchema.tsx","./components/community/Navbar/NavbarWidget.tsx","./components/community/Breadcrumb/BreadcrumbWidget.tsx","./components/messages/TopicWithThreadedReplyListWidget/TopicWithThreadedReplyListWidget.tsx","./components/messages/RelatedContentWidget/RelatedContentWidget.tsx","./components/messages/MessageListForNodeByRecentActivityWidget/MessageListForNodeByRecentActivityWidget.tsx","./components/community/FooterWidget/FooterWidget.tsx","./components/messages/MessageView/MessageViewStandard/MessageViewStandard.tsx","./components/community/FooterWidgetHelpLink/FooterWidgetHelpLink.tsx","./components/community/KhorosLogo/KhorosLogo.tsx","../shared/client/components/common/List/UnstyledList/UnstyledList.tsx","./components/messages/MessageView/MessageView.tsx","./components/messages/MessageView/MessageViewInline/MessageViewInline.tsx","../shared/client/components/common/List/ListGroup/ListGroup.tsx","../shared/client/components/common/Pager/PagerLoadMore/PagerLoadMore.tsx","../shared/client/components/common/List/UnwrappedList/UnwrappedList.tsx","./components/tags/TagView/TagView.tsx","./components/tags/TagView/TagViewChip/TagViewChip.tsx"],"appGip":true,"scriptLoader":[]}