diff --git a/docs/cwrmm/custom-fields/ticket-mgmt-webhook-url.md b/docs/cwrmm/custom-fields/ticket-mgmt-webhook-url.md new file mode 100644 index 000000000..4093815f4 --- /dev/null +++ b/docs/cwrmm/custom-fields/ticket-mgmt-webhook-url.md @@ -0,0 +1,52 @@ +--- +id: '8e55deb6-bef8-4501-9e64-7b25e7fcd1ab' +slug: /8e55deb6-bef8-4501-9e64-7b25e7fcd1ab +title: 'Ticket_Mgmt_Webhook_Url' +title_meta: 'Ticket_Mgmt_Webhook_Url' +keywords: ['trigger', 'ticketing', 'ticket-management', 'webhook', 'workflow', 'automation', 'monitors'] +description: 'Webhook URL for the ConnectWise workflow that manages ticket creation and closure.' +tags: ['api', 'automation', 'ticketing'] +draft: false +unlisted: false +last_update: + date: 2026-07-23 +--- + +## Summary + +Webhook URL for the [ConnectWise workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) that manages ticket creation and closure. + +## Dependencies + +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) + +## Custom Field Setup Location + +**Custom Fields Path:** SETTINGS ➞ Custom Fields + +## Details + +| Name | Description | Level | Type | Help Text | Default Value | Editable | +|---|---|---|---|---|---|---| +| Ticket_Mgmt_Webhook_Url | Webhook URL for the ConnectWise workflow that manages ticket creation and closure. | `Company` | `Text Box` | Paste the Webhook URL from the ticket management workflow here. | `https://webhook.myconnectwise.net/...` | `No` | + +**Set the workflow's webhook URL as this field's Default Value:** + +This custom field is the **shared storage location** for the webhook URL generated by the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow. That URL does **not** exist until you create a webhook instance inside the workflow's [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7), and because webhook instances are environment‑specific they **cannot be migrated** — so the value has to be typed in here by hand as part of the workflow setup. + +When you finish [creating the webhook instance](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) (*Automation ➞ Workflows ➞ open the workflow ➞ click the Trigger node ➞ **New Webhook Instance +** ➞ copy the generated URL*), paste that URL into the **Default Value** of this custom field and **save** the field. The `https://webhook.myconnectwise.net/...` shown in the table above is only a **placeholder / example** — replace it with the real URL copied from your trigger instance. + +The monitor scripts (Enhanced Drive Space, CPU, Memory, etc.) read this default value to discover where to POST their `Create` / `Close` / `Comment` payloads. That is exactly why the field is defined at the **Company** level with **Editable = No**: it represents a single, global webhook endpoint — not a per‑device value — and locking it stops anyone from accidentally overriding it on an individual record. + +**If the Default Value is left blank or still set to the placeholder, the monitor scripts have no target to send to and alert ticketing will silently fail for every device.** After saving, always double‑check that the stored default value is an exact, character‑for‑character match of the URL shown in the workflow's trigger instance. + +## Completed Custom Field + +![Image1](../../../static/img/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab/image1.webp) + +## Changelog + +### 2026-06-24 + +- Initial version of the document diff --git a/docs/cwrmm/groups/enhanced-drive-space-monitoring.md b/docs/cwrmm/groups/enhanced-drive-space-monitoring.md index 6393b0f98..a8f101c1f 100644 --- a/docs/cwrmm/groups/enhanced-drive-space-monitoring.md +++ b/docs/cwrmm/groups/enhanced-drive-space-monitoring.md @@ -33,7 +33,7 @@ Target group for the Enhanced Drive Space Monitoring automation. Includes all se ## Group Summary - **Group Name:** `Enhanced Drive Space Monitoring` -- **Category:** `Monitoring` +- **Category:** `Monitoring` - **Description:** `Target group for the Enhanced Drive Space Monitoring automation. Includes all servers and workstations explicitly opted-in through hierarchical custom fields.` ![Image1](../../../static/img/docs/475ce8e8-458e-4901-bdfc-18e79f62c549/image1.webp) diff --git a/docs/cwrmm/monitors/cpu-threshold-violation-monitoring-workflow.md b/docs/cwrmm/monitors/cpu-threshold-violation-monitoring-workflow.md new file mode 100644 index 000000000..f9edbfa93 --- /dev/null +++ b/docs/cwrmm/monitors/cpu-threshold-violation-monitoring-workflow.md @@ -0,0 +1,542 @@ +--- +id: '92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9' +slug: /92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9 +title: 'CPU Threshold Violation Monitoring [Workflow]' +title_meta: 'CPU Threshold Violation Monitoring [Workflow]' +keywords: ['cpu', 'monitoring', 'windows', 'alerts', 'thresholds', 'performance', 'workflow', 'trigger'] +description: 'Checks local CPU usage against sustained high percentage thresholds. Alerts when CPU usage remains above the limit for the configured duration. Resolves once CPU usage drops below the low threshold. Uses "CWRMM Ticket Management for Monitors" workflow for ticket management.' +tags: ['performance', 'monitoring', 'windows'] +draft: false +unlisted: false +last_update: + date: 2026-07-23 +--- + +## Summary + +The **CPU Threshold Violation Monitoring [Workflow]** monitor continuously checks local CPU usage on Windows endpoints against the sustained‑usage thresholds defined in the configuration file generated by the [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) task. It does **not** read custom fields directly; it relies on the pre‑built JSON configuration — which also carries the ticketing webhook URL (`TicketWebhookUrl`) — to know when to alert, when to consider the condition resolved, and where to send ticket actions. + +This is the **workflow‑based** variant of the monitor. The original [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) set uses the monitor's built‑in ticketing: it raises one ticket per incident and auto‑resolves it on recovery (via the monitor set's automatic resolution rule), but the ticket subject and body are monitor‑generated and not customizable, and a comment is appended on every detection while the alert persists, making the ticket bulky. This variant instead manages the ticket lifecycle through the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow: it tracks ticket state locally and fires webhooks to **create** a clean ticket when a sustained high‑CPU condition is confirmed and **close** it when usage recovers. Like the original, it creates a single ticket per incident and closes it automatically on recovery — but with a controlled subject and body (including the top CPU‑consuming processes), **no per‑detection comment spam**, and a standardized, workflow‑managed lifecycle. + +### How It Works + +1. **Configuration File** + At each scheduled run (every 15 minutes, as configured), the monitor reads `C:\ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json`. This file contains: + - **HighThreshold** – the CPU usage percentage that starts the timer. + - **LowThreshold** – the CPU percentage the usage must stay above to keep the timer running (and below which the timer resets). + - **UsageMins** – how many minutes the CPU must remain above the low threshold (after first exceeding the high threshold) before an alert is raised. + - **TicketWebhookUrl** – the workflow webhook URL that ticket actions are POSTed to. + If the file is missing or cannot be parsed, the monitor returns a failure message and skips the run without changing any state or firing any webhooks. + +2. **CPU Sampling** + The monitor samples the `\Processor Information(_Total)\% Processor Time` counter once per second for 10 seconds and averages the result to get a stable current CPU percentage. If counter sampling fails or returns no data, the monitor returns early **without modifying any ticket state** — a safeguard so a transient counter failure never accidentally closes an open ticket. + +3. **Two‑Threshold Hysteresis with a Marker File** + To distinguish a brief spike from a genuinely sustained condition, the monitor uses two thresholds and a small marker file (`Test-CPUUsage.flag`) that records when the current event began: + - **No marker file:** the active threshold is the **HighThreshold**. When CPU usage first reaches or exceeds it, the marker file is created and the timer starts. + - **Marker file present:** the active threshold drops to the **LowThreshold**. As long as CPU usage stays at or above the low threshold, the marker remains and the elapsed time (measured from the marker's creation time) keeps accumulating. + - **Usage drops below the active threshold:** the marker file is deleted and the timer resets immediately, so the event must begin again from the high threshold. + - **Elapsed time reaches `UsageMins`:** the sustained condition is confirmed and an alert is built for this run. + +4. **Sustained‑Breach Alert Content** + Once the sustained condition is confirmed, the monitor builds a ticket subject (`CPU Threshold Violation - CPU - - Percent`) and a body that records how long ago the spike began, the low threshold the CPU has stayed above, the current CPU percentage, and the **top five CPU‑consuming processes** (normalized across all processor cores). If PowerShell appears among those processes, the command lines of the running `powershell.exe` instances (excluding the monitor's own process) are appended to assist investigation. The CPU is then marked as "detected" for this run. + +5. **Ticket State Machine (duplicate prevention)** + This is the core of the variant. The monitor maintains three local state files alongside the config: + - `CPU_To_Create_Ticket.json` + - `CPU_With_Existing_Ticket.json` + - `CPU_To_Close_Ticket.json` + + Because the monitor tracks a single component (`CPU`), there is **at most one open CPU ticket per device**. For that component, it applies the following transitions each run: + + | CPU condition this run | Previous state | Transition | Webhook | + | --- | --- | --- | --- | + | Sustained breach detected | not tracked | Added to **Create** | `Create` fires | + | Sustained breach detected | Create | Moved to **Existing** | none | + | Sustained breach detected | Existing | Kept in **Existing** | none | + | Recovered (below low threshold) | Create or Existing | Moved to **Close** (subject kept, body replaced with a recovery message) | `Close` fires | + | Recovered | Close | Cleared from **Close** | none | + | No active event | not tracked | No record | none | + + Because a confirmed breach fires `Create` only on the run it first appears and is then promoted to **Existing**, a sustained high‑CPU event produces **exactly one ticket**, no matter how many 15‑minute runs occur while it persists. On recovery, `Close` fires once and the record is then cleared, so it never fires twice. + +6. **Webhook Execution** + When the component is newly in the **Create** list, the monitor POSTs `Action = Create`; when it is newly in the **Close** list, it POSTs `Action = Close`. Each payload carries `Action`, `TicketSubject`, `TicketBody`, and `DeviceId` (read from the endpoint's registry). The webhook URL is the `TicketWebhookUrl` value from the config file. The [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow receives the payload and performs the actual ticket creation or closure in ConnectWise. This monitor uses only the `Create` and `Close` actions; it does not send `Comment`. + +7. **Reporting and Alert Signal** + If a `Create` or `Close` webhook fired, the monitor returns `Action detected - N component(s) for ticket creation and M component(s) for ticket closure`; otherwise it returns `CPU monitoring state evaluated successfully`. The monitor's success criteria is configured as *Script Output contains `Action detected`*, so CW RMM only flags the monitor on runs where a ticket action actually occurred. Because RMM's own ticketing is set to **Do not Generate Ticket**, **Escalate ticket on script failure = Disabled**, and **Automatically Resolve = Disabled** (closure is handled by the workflow's `Close` webhook, not by RMM), that flag is purely informational — the real ticket lives in ConnectWise and is owned by the workflow, not by RMM. + +8. **Failure Handling** + - Missing/invalid config → run skipped, no state change, no webhooks. + - CPU counter sampling failure → early return that preserves existing ticket state. + - Webhook failure → logged, the run is reported as `Failure`, but processing continues. + - Note: state files are written **before** the webhooks fire, so a failed `Create` or `Close` is **not** automatically retried on the next run — the CPU condition must change state again (re‑breach or re‑recover) for a new webhook to be sent. + +### Scenario – Alert Triggered (Create) + +A server's CPU spikes to 98% at 10:00 AM. The configured thresholds are HighThreshold = 95%, LowThreshold = 90%, UsageMins = 30 minutes. Because 98% exceeds the high threshold, the marker file is created and the timer starts. The CPU stays above 90%, so the marker persists. On the first 15‑minute run after 30 minutes have elapsed (≈10:30 AM), the sustained condition is confirmed: the CPU is added to the **Create** list and a `Create` webhook fires. The workflow creates a ticket such as *"CPU Threshold Violation - CPU - SERVER01 - 95 Percent"* whose body lists the current usage and the top five CPU‑consuming processes. + +### Scenario – Sustained Breach (No Duplicate) + +The CPU remains above 90% on the following runs. The marker file still exists and the elapsed time still exceeds 30 minutes, so the CPU is "detected" again each run — but it is now found in the **Create** list (then **Existing**), so it is promoted to **Existing** and **no webhook fires**. Only the single original ticket exists for the device, regardless of how long the condition persists — no duplicates, no repeated comments. + +### Scenario – Timer Reset (No Alert) + +The server spikes to 98% and the marker file is created, but after 20 minutes the CPU drops to 85%. Because 85% is below the low threshold (the active threshold while the marker exists), the marker file is deleted and the timer resets before 30 minutes ever elapse. The CPU is never "detected," so no webhook fires and no ticket is created. A future spike must exceed the high threshold again to start a new event. + +### Scenario – Automatic Resolution (Close) + +After a ticket has been created and the CPU is in **Existing**, usage eventually drops to 10% and stays low. The marker file is removed and the CPU is no longer detected, so it is moved to the **Close** list — keeping the original ticket subject (so the workflow can match the open ticket) but replacing the body with *"CPU usage has recovered and is now below the threshold. The ticket can be closed."* A `Close` webhook fires and the workflow closes the ticket. On the following run the record is cleared from **Close**, so no further webhook is sent. + +This design produces clean, workflow‑managed tickets — one per incident per device, no comment spam, and self‑closing on recovery — without the monitor‑generated formatting and per‑detection comments of the built‑in path, and without any native RMM ticketing tasks. It does depend on a valid `TicketWebhookUrl` in the configuration file (set via the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field) and on the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow being installed and reachable; if the URL is missing or invalid, webhooks fail and tickets will not be created or closed. + +## Dependencies + +- [Group: CPU Threshold Violation Monitoring](/docs/006889e2-8977-4957-9c4d-7381bdbea9a0) +- [Task: CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) +- [Solution: CPU Threshold Violation Monitoring](/docs/49b06af7-af3b-4aaa-a90c-8efb28a65c9e) + +## Monitor Setup Location + +**Monitors Path:** `ENDPOINTS` ➞ `Alerts` ➞ `Monitors` + +## Monitor Summary + +- **Name:** `CPU Threshold Violation Monitoring [Workflow]` +- **Description:** `Checks local CPU usage against sustained high percentage thresholds. Alerts when CPU usage remains above the limit for the configured duration. Resolves once CPU usage drops below the low threshold. Uses "CWRMM Ticket Management for Monitors" workflow for ticket management.` +- **Type:** `Script` +- **Severity:** `Others` +- **Family:** `Desktop Health` + +![Image1](../../../static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image1.webp) + +## Targeted Resources + +- **Target Type:** `Device Groups` +- **Group Name:** `CPU Threshold Violation Monitoring` + +![Image2](../../../static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image2.webp) + +## Conditions + +- **Run script on:** `Schedule` +- **Repeat every:** `15` `Minute(s)` +- **Script Language:** `PowerShell` +- **Use Generative AI Assist for script creation:** `False` + +- **PowerShell Script Editor:** + +```PowerShell +<# +.SYNOPSIS + Monitors CPU usage against configured thresholds and manages ConnectWise ticket creation and closure via webhooks. + +.DESCRIPTION + This script is executed periodically by a CW RMM monitor set. It reads CPU threshold values and the webhook URL + from the JSON configuration file generated by the CPU Threshold Violation Monitoring Configuration Writer task. + + The monitoring logic evaluates sustained CPU usage. It uses a marker file to track how long CPU has remained + above the low threshold after spiking above the high threshold. + + To manage the ticket lifecycle cleanly without requiring separate CW RMM ticketing tasks, the script implements + a state machine using three local JSON files. It tracks whether a ticket needs to be created, already exists, + or requires closure. Upon detecting state changes, the script fires HTTP POST webhooks containing the ticket + subject, body, action (Create or Close), and device ID to a ConnectWise workflow. + +.FUNCTIONALITY + 1. Loads the JSON configuration file. If missing or invalid, outputs a warning and returns. + 2. Samples CPU performance counters over a 10-second window. Exits early if sampling fails to preserve existing ticket states. + 3. Compares current CPU usage against an active threshold (High or Low depending on hysteresis state). + 4. If CPU is sustained above the threshold for the configured minutes, it builds an alert detailing the top 5 consuming processes. + 5. Manages state files to track ticket actions: + - If CPU is detected and not in Create/Existing, it is added to Create. + - If CPU is detected and already in Create, it is moved to Existing. + - If CPU is detected and already in Existing, no action is taken. + - If CPU is no longer detected and is in Create/Existing, it is moved to Close. + - If CPU is no longer detected and is in Close, it is removed from Close. + 6. Fires webhook triggers for newly created or closed tickets, sending the Action, TicketSubject, TicketBody, and DeviceId. + +.PARAMETER None + This script does not accept parameters. + +.INPUTS + None. You cannot pipe objects to this script. + +.OUTPUTS + System.String. Returns a success or failure summary of the monitoring state and webhook execution. + +.NOTES + Script Name : CPU Threshold Violation Monitor [Workflow] + Configuration : $env:ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json + Marker File : $env:ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.flag + + Paths updated by this script: + - $env:ProgramData\_Automation\Script\Test-CPUUsage\CPU_To_Create_Ticket.json + - $env:ProgramData\_Automation\Script\Test-CPUUsage\CPU_With_Existing_Ticket.json + - $env:ProgramData\_Automation\Script\Test-CPUUsage\CPU_To_Close_Ticket.json + + ConnectWise Workflow Details: + - Workflow Name : CWRMM Ticket Management for Monitors + - Custom Trigger Name : CWRMM Ticket Management for Monitors + - Webhook Trigger Name : CWRMM Ticket Management for Monitors (custom trigger is used as Webhook Trigger) + - Webhook Trigger Instance Name : CWRMM Ticket Management for Monitors + +.EXAMPLE + .\Monitor-CPUUsage.ps1 + + Runs the CPU usage monitoring check, updates the ticket state JSON files, and fires the required ConnectWise webhooks. +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param() + +#region globals +$ProgressPreference = 'SilentlyContinue' +$WarningPreference = 'SilentlyContinue' +$InformationPreference = 'Continue' +$ConfirmPreference = 'None' +#endregion + +#region variables +$projectName = 'Test-CPUUsage' +$workingDirectory = '{0}\_Automation\Script\{1}' -f $env:ProgramData, $projectName +$configPath = '{0}\{1}.json' -f $workingDirectory, $projectName +$markerFile = '{0}\{1}.flag' -f $workingDirectory, $projectName + +$createTicketPath = '{0}\CPU_To_Create_Ticket.json' -f $workingDirectory +$existingTicketPath = '{0}\CPU_With_Existing_Ticket.json' -f $workingDirectory +$closeTicketPath = '{0}\CPU_To_Close_Ticket.json' -f $workingDirectory + +$deviceId = 'Unknown' +try { + $regPath = 'HKLM:\SOFTWARE\WOW6432Node\ITSPlatform' + if (Test-Path -Path $regPath) { + $regItem = Get-ItemProperty -Path $regPath -ErrorAction Stop + if ($null -ne $regItem.privateendpointid) { + $deviceId = $regItem.privateendpointid + } + } +} catch { + Write-Information -MessageData ('[WARNING] Failed to retrieve Device ID from registry. Error: {0}' -f $Error[0].Exception.Message) +} +#endregion + +#region functions +function Read-JsonArray { + <# + .SYNOPSIS + Reads a JSON file and returns its contents as an array. + .DESCRIPTION + Reads a JSON file from the specified path. If the file does not exist or is empty, an empty array is returned. + .PARAMETER Path + The full path to the JSON file to read. + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The full path to the JSON file to read.')] + [string]$Path + ) + + #region main + if (-not (Test-Path -Path $Path)) { + return @() + } + + try { + $raw = Get-Content -Path $Path -Raw -Encoding UTF8 -ErrorAction Stop + if ([string]::IsNullOrWhiteSpace($raw)) { + return @() + } + $data = $raw | ConvertFrom-Json -ErrorAction Stop + if ($null -eq $data) { + return @() + } + return @($data) + } catch { + Write-Information -MessageData ('[WARNING] Failed to parse JSON from {0}. Error: {1}' -f $Path, $Error[0].Exception.Message) + return @() + } + #endregion +} + +function Write-JsonArray { + <# + .SYNOPSIS + Writes an array of objects to a JSON file. + .DESCRIPTION + Serializes the provided data array to JSON and writes it to the specified file path. If the data array is empty, an empty JSON array is written without a BOM. + .PARAMETER Path + The full path to the JSON file to write. + .PARAMETER Data + The array of objects to serialize. + #> + [CmdletBinding(SupportsShouldProcess = $true)] + [OutputType([System.Void])] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The full path to the JSON file to write.')] + [string]$Path, + + [Parameter(Mandatory = $true, HelpMessage = 'The array of objects to serialize.')] + [object]$Data + ) + + #region variables + $utf8NoBomEncoding = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false + #endregion + + #region main + if ($PSCmdlet.ShouldProcess($Path, 'Write JSON Array')) { + if (-not $Data -or $Data.Count -eq 0) { + [System.IO.File]::WriteAllLines($Path, '[]', $utf8NoBomEncoding) + return + } + + $json = $Data | ConvertTo-Json -Depth 3 -ErrorAction Stop + if ($Data.Count -eq 1) { + $json = '[{0}]' -f $json + } + + try { + [System.IO.File]::WriteAllLines($Path, $json, $utf8NoBomEncoding) + } catch { + throw ('Failed to write JSON array to {0}. Error: {1}' -f $Path, $Error[0].Exception.Message) + } + } + #endregion +} +#endregion + +#region config import +if (-not (Test-Path -Path $configPath)) { + return 'Failure: CPU monitoring configuration file not found. Skipping check.' +} + +try { + $rawJson = Get-Content -Path $configPath -Raw -Encoding UTF8 -ErrorAction Stop + $config = $rawJson | ConvertFrom-Json -ErrorAction Stop +} catch { + return ('Failure: Failed to read or parse the configuration file. Error: {0}' -f $Error[0].Exception.Message) +} + +[int]$highThreshold = $config.HighThreshold +[int]$lowThreshold = $config.LowThreshold +[int]$usageMinutes = $config.UsageMins +$webhookUrl = $config.TicketWebhookUrl +#endregion + +#region monitoring +$detectedComponents = @() + +if (Test-Path -Path $markerFile) { + [int]$activeThreshold = $lowThreshold +} else { + [int]$activeThreshold = $highThreshold +} + +$cpuSamples = Get-Counter -Counter '\Processor Information(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 10 -ErrorAction SilentlyContinue +if (-not $cpuSamples) { + return 'Failure: Unable to retrieve CPU performance counter data. Skipping check to preserve existing ticket state.' +} + +$currentCpu = ($cpuSamples.CounterSamples | Measure-Object -Property CookedValue -Average).Average + +if ($currentCpu -ge $activeThreshold) { + if (-not (Test-Path -Path $markerFile)) { + $null = New-Item -Path $markerFile -ItemType 'File' -Force -ErrorAction SilentlyContinue + } + + $markerCreationTime = (Get-Item -Path $markerFile -ErrorAction SilentlyContinue).CreationTime + $elapsedMinutes = [Math]::Round((New-TimeSpan -Start $markerCreationTime -End (Get-Date)).TotalMinutes) + + if ($elapsedMinutes -ge $usageMinutes) { + $processSamples = Get-Counter -Counter '\Process(*)\% Processor Time' -ErrorAction SilentlyContinue + $topProcessesString = '' + + if ($processSamples) { + $topProcesses = $processSamples.CounterSamples | + Where-Object -FilterScript { $_.InstanceName -notin '_total', 'idle' } | + Group-Object -Property InstanceName | + ForEach-Object -Process { + [PSCustomObject]@{ + Process = $_.Name + CPUUsage = [Math]::Round(($_.Group | Measure-Object -Property CookedValue -Sum).Sum / [System.Environment]::ProcessorCount, 2) + } + } | + Sort-Object -Property CPUUsage -Descending | + Select-Object -First 5 + + $topProcessesString = ($topProcesses | Format-Table -AutoSize | Out-String).TrimEnd() + } else { + $topProcessesString = '(Unable to collect per-process CPU data)' + } + + $ticketSubject = 'CPU Threshold Violation - CPU - {0} - {1} Percent' -f $env:COMPUTERNAME, $highThreshold + $ticketBody = 'The CPU usage spiked above {0}% {1} minutes ago and has remained consistently above {2}% since then.{3}{3}Current CPU Usage: {4}%{3}{3}Top 5 Processes utilizing CPU:{3}{5}' -f $highThreshold, $elapsedMinutes, $lowThreshold, [Environment]::NewLine, [Math]::Round($currentCpu, 2), $topProcessesString + + if ($topProcessesString -match 'powershell') { + $ticketBody = '{0}{1}{1}PowerShell Process Command Line:{1}' -f $ticketBody, [Environment]::NewLine + try { + $psProcesses = Get-CimInstance -ClassName 'Win32_Process' -Filter 'Name = ''powershell.exe''' -ErrorAction SilentlyContinue | + Where-Object -FilterScript { $_.ProcessId -ne $pid } + $psLines = $psProcesses.CommandLine -join [Environment]::NewLine + $ticketBody = '{0}{1}' -f $ticketBody, $psLines + } catch { + $ticketBody = '{0}(Could not retrieve PowerShell command lines)' -f $ticketBody + } + } + + $detectedComponents += [PSCustomObject]@{ + Component = 'CPU' + TicketSubject = $ticketSubject + TicketBody = $ticketBody + } + } +} else { + if (Test-Path -Path $markerFile) { + Remove-Item -Path $markerFile -Force -ErrorAction SilentlyContinue + } +} +#endregion + +#region state machine management +$createList = Read-JsonArray -Path $createTicketPath +$existingList = Read-JsonArray -Path $existingTicketPath +$closeList = Read-JsonArray -Path $closeTicketPath + +$allComponents = @() +$allComponents += $detectedComponents.Component +$allComponents += $createList.Component +$allComponents += $existingList.Component +$allComponents += $closeList.Component +$allComponents = $allComponents | + Where-Object -FilterScript { $null -ne $_ } | + Sort-Object -Unique + +$newCreateList = [System.Collections.ArrayList]@() +$newExistingList = [System.Collections.ArrayList]@() +$newCloseList = [System.Collections.ArrayList]@() + +foreach ($component in $allComponents) { + $detectedObj = $detectedComponents | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + $oldCreateObj = $createList | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + $oldExistingObj = $existingList | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + $oldCloseObj = $closeList | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + if ($null -ne $detectedObj) { + if ($null -ne $oldExistingObj) { + $newExistingList.Add($oldExistingObj) | Out-Null + } elseif ($null -ne $oldCreateObj) { + $newExistingList.Add($oldCreateObj) | Out-Null + } else { + $newCreateList.Add($detectedObj) | Out-Null + } + } else { + if ($null -ne $oldCreateObj -or $null -ne $oldExistingObj) { + $objToClose = if ($null -ne $oldExistingObj) { $oldExistingObj } else { $oldCreateObj } + $objToClose.TicketBody = 'CPU usage has recovered and is now below the threshold. The ticket can be closed.' + $newCloseList.Add($objToClose) | Out-Null + } elseif ($null -ne $oldCloseObj) { + Write-Information -MessageData ('[INFO] CPU ticket was previously closed. Clearing from close list.') + } + } +} + +Write-JsonArray -Path $createTicketPath -Data $newCreateList +Write-JsonArray -Path $existingTicketPath -Data $newExistingList +Write-JsonArray -Path $closeTicketPath -Data $newCloseList +#endregion + +#region webhook execution +$webhookSuccess = $true + +if ($newCreateList.Count -gt 0) { + foreach ($createComponent in $newCreateList) { + $payload = [ordered]@{ + Action = 'Create' + TicketSubject = $createComponent.TicketSubject + TicketBody = $createComponent.TicketBody + DeviceId = $deviceId + } + $jsonPayload = $payload | ConvertTo-Json -Depth 2 + + try { + Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' -ErrorAction Stop | Out-Null + } catch { + $webhookSuccess = $false + Write-Information -MessageData ('[FAIL] Failed to trigger ConnectWise create workflow for ''{0}''. Error: {1}' -f $createComponent.TicketSubject, $Error[0].Exception.Message) + } + } +} + +if ($newCloseList.Count -gt 0) { + foreach ($closeComponent in $newCloseList) { + $payload = [ordered]@{ + Action = 'Close' + TicketSubject = $closeComponent.TicketSubject + TicketBody = $closeComponent.TicketBody + DeviceId = $deviceId + } + $jsonPayload = $payload | ConvertTo-Json -Depth 2 + + try { + Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' -ErrorAction Stop | Out-Null + } catch { + $webhookSuccess = $false + Write-Information -MessageData ('[FAIL] Failed to trigger ConnectWise close workflow for ''{0}''. Error: {1}' -f $closeComponent.TicketSubject, $Error[0].Exception.Message) + } + } +} +#endregion + +if ($newCreateList.Count -gt 0 -or $newCloseList.Count -gt 0) { + $status = if ($webhookSuccess) { 'Success' } else { 'Failure' } + return ('{0}: Action detected - {1} component(s) for ticket creation and {2} component(s) for ticket closure.' -f $status, $newCreateList.Count, $newCloseList.Count) +} + +return 'Success: CPU monitoring state evaluated successfully.' +#endregion +``` + +- **Criteria:** `Contains` +- **Operator:** `AND` +- **Script Output:** `Action detected` +- **Escalate ticket on script failure:** `Disabled` +- **Add Automation:** `` + +![Image3](../../../static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image3.webp) + +## Ticket Resolution + +- **Automatically Resolve:** `Disabled` + +![Image4](../../../static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image4.webp) + +## Monitor Output + +**Output:** `Do not Generate Ticket` + +![Image5](../../../static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image5.webp) + +## Completed Monitor + +![Image6](../../../static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image6.webp) + +## Changelog + +### 2026-07-23 + +- Initial version of the document diff --git a/docs/cwrmm/monitors/enhanced-drive-space-monitoring-workflow.md b/docs/cwrmm/monitors/enhanced-drive-space-monitoring-workflow.md new file mode 100644 index 000000000..c60986813 --- /dev/null +++ b/docs/cwrmm/monitors/enhanced-drive-space-monitoring-workflow.md @@ -0,0 +1,552 @@ +--- +id: '2c9dd7bc-f5aa-48c2-be76-1348e13cda07' +slug: /2c9dd7bc-f5aa-48c2-be76-1348e13cda07 +title: 'Enhanced Drive Space Monitoring [Workflow]' +title_meta: 'Enhanced Drive Space Monitoring [Workflow]' +keywords: ['monitoring', 'drive', 'space', 'thresholds', 'tickets', 'workflow', 'trigger'] +description: 'Checks local drive free space against capacity-based thresholds. Alerts when a drive falls below its limit. Resolves once free space returns above the threshold. Uses "CWRMM Ticket Management for Monitors" workflow for ticket management.' +tags: ['disk', 'monitoring', 'windows'] +draft: false +unlisted: false +last_update: + date: 2026-07-23 +--- + +## Summary + +The **Enhanced Drive Space Monitoring [Workflow]** monitor continuously checks the free space of local drives on Windows endpoints against the thresholds and drive lists defined in the configuration file generated by the [Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) task. It does **not** read custom fields directly; it relies on the pre‑built JSON configuration — which also carries the ticketing webhook URL (`TicketWebhookUrl`) — to know which drives to monitor, what limits to enforce, and where to send ticket actions. + +This is the **workflow‑based** variant of the monitor. Unlike the original [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) set, which relies on the monitor's built‑in ticketing, this variant manages the entire ticket lifecycle itself: it tracks ticket state locally and fires webhooks to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow to **create** a ticket when a drive first breaches its threshold and **close** it when the drive recovers. The result is a clean ticket with a controlled subject and body, **no per‑detection comment spam**, and **automatic closure on recovery** — with no duplicate tickets and no manual intervention. + +### How It Works + +1. **Configuration File** + At each scheduled run (hourly, as configured), the monitor reads: + `C:\ProgramData\_Automation\Script\EnhancedDriveSpaceMonitoring\EnhancedDriveSpaceMonitoring.json`. + This file contains: + - **Thresholds** – an array of capacity buckets (16–300 GB, 300–1024 GB, 1024–4096 GB, 4096+ GB), each with a numeric limit and a unit (%, MB, or GB). + - **IncludeDrives** – which drive letters to monitor (`All`, `None`, or a sequence like `CDEF`). + - **ExcludeDrives** – which drive letters to ignore (`None`, `All`, or specific letters like `Z`). + - **TicketWebhookUrl** – the workflow webhook URL that ticket actions are POSTed to. + If the file is missing or cannot be parsed, the monitor returns a failure message and skips the run without changing any state or firing any webhooks. + +2. **Drive Discovery and Filtering** + The monitor enumerates all local fixed drives (`Win32_LogicalDisk`, `DriveType=3`) and filters them by the **IncludeDrives** / **ExcludeDrives** settings. If drive enumeration fails or returns nothing, the monitor returns early **without modifying any ticket state** — a safeguard so a transient WMI hiccup never accidentally closes open tickets. + +3. **Capacity Bucket Assignment** + Each drive's total capacity is calculated and assigned to a bucket (16–300 GB, 300–1024 GB, 1024–4096 GB, or 4096+ GB). Drives smaller than 16 GB, or with no readable size, are skipped. + +4. **Threshold Comparison** + The monitor looks up the threshold for the drive's bucket; a threshold of `0` disables that bucket. It compares current free space (converted to the configured unit) against the limit. For every drive that falls below its threshold, it builds a ticket subject (`Enhanced Drive Space Monitoring - - - `) and a body detailing total, used, and free space plus the breached threshold, and marks the drive as "detected low" for this run. + +5. **Ticket State Machine (duplicate prevention)** + This is the core of the variant. The monitor maintains three local state files alongside the config: + - `Drives_To_Create_Ticket.json` + - `Drives_With_Existing_Ticket.json` + - `Drives_To_Close_Ticket.json` + + For every drive letter (detected this run plus any letter already tracked), it applies the following transitions: + + | Drive this run | Previous state | Transition | Webhook | + | --- | --- | --- | --- | + | Low | not tracked | Added to **Create** | `Create` fires | + | Low | Create | Moved to **Existing** | none | + | Low | Existing | Kept in **Existing** | none | + | Healthy | Create or Existing | Moved to **Close** (subject kept, body replaced with a recovery message) | `Close` fires | + | Healthy | Close | Cleared from **Close** | none | + | Healthy | not tracked | No record | none | + + Because a freshly detected drive fires `Create` only on the run it first appears and is then promoted to **Existing**, a sustained breach (e.g., a drive sitting at 95% full for hours) produces **exactly one ticket**, no matter how many hourly runs occur. On recovery, `Close` fires once and the record is then cleared, so it never fires twice. + +6. **Webhook Execution** + For each drive newly in the **Create** list, the monitor POSTs `Action = Create`; for each drive newly in the **Close** list, it POSTs `Action = Close`. Each payload carries `Action`, `TicketSubject`, `TicketBody`, and `DeviceId` (read from the endpoint's registry). The webhook URL is the `TicketWebhookUrl` value from the config file. The [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow receives the payload and performs the actual ticket creation or closure in ConnectWise. This monitor uses only the `Create` and `Close` actions; it does not send `Comment`. + +7. **Reporting and Alert Signal** + If any `Create` or `Close` webhook fired, the monitor returns `Action detected - N drive(s) for ticket creation and M drive(s) for ticket closure`; otherwise it returns `Drive space monitoring state evaluated successfully`. The monitor's success criteria is configured as *Script Output contains `Action detected`*, so CW RMM only flags the monitor on runs where a ticket action actually occurred. Because RMM's own ticketing is set to **Do not Generate Ticket**, **Escalate ticket on script failure = Disabled**, and **Automatically Resolve = Disabled**, that flag is purely informational — the real ticket lives in ConnectWise and is owned by the workflow, not by RMM. + +8. **Failure Handling** + - Missing/invalid config → run skipped, no state change, no webhooks. + - Drive enumeration failure → early return that preserves existing ticket state. + - Webhook failure → logged, the run is reported as `Failure`, but remaining drives are still processed. + - Note: state files are written **before** the webhooks fire, so a failed `Create` or `Close` is **not** automatically retried on the next run — the drive must change state again (re‑breach or re‑recover) for a new webhook to be sent. + +### Scenario – Alert Triggered (Create) + +A server has a 500 GB D: drive (bucket 300–1024 GB) with a `30GB` threshold. Free space drops to 22 GB. On the next run the drive is detected low for the first time, so it is added to the **Create** list and a `Create` webhook fires. The workflow creates a ticket such as *"Enhanced Drive Space Monitoring - D - SERVER01 - 30 GB"* with the total/used/free/threshold details in the body. + +### Scenario – Sustained Breach (No Duplicate) + +An hour later the D: drive is still at 22 GB. It is now found in the **Create** list, so it is promoted to **Existing** and **no webhook fires**. It remains in **Existing** on every subsequent low run, so only the single original ticket exists — no duplicates, no repeated comments. + +### Scenario – Healthy (No Action) + +A drive that has 45 GB free against a 30 GB threshold is not detected as low. If it has no tracked state, nothing happens: no webhook fires, the output is *"Drive space monitoring state evaluated successfully,"* and CW RMM raises no alert. + +### Scenario – Automatic Resolution (Close) + +After a technician frees up space, the D: drive rises to 60 GB. It is no longer low but is still in **Existing**, so it is moved to the **Close** list — keeping the original ticket subject (so the workflow can match the open ticket) but replacing the body with *"Drive space has recovered and is now above the threshold. The ticket can be closed."* A `Close` webhook fires and the workflow closes the ticket. On the following run the record is cleared from **Close**, so no further webhook is sent. + +This design produces clean, workflow‑managed tickets — one per incident, no comment spam, and self‑closing on recovery — without any native RMM ticketing tasks. It does depend on a valid `TicketWebhookUrl` in the configuration file (set via the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field) and on the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow being installed and reachable; if the URL is missing or invalid, webhooks fail and tickets will not be created or closed. + +## Dependencies + +- [Group: Enhanced Drive Space Monitoring](/docs/475ce8e8-458e-4901-bdfc-18e79f62c549) +- [Task: Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) +- [Solution: Enhanced Drive Space Monitoring](/docs/e9cf4ff0-4413-447b-97dd-b8b2abd59597) + +## Monitor Setup Location + +**Monitors Path:** `ENDPOINTS` ➞ `Alerts` ➞ `Monitors` + +## Monitor Summary + +- **Name:** `Enhanced Drive Space Monitoring [Workflow]` +- **Description:** `Checks local drive free space against capacity-based thresholds. Alerts when a drive falls below its limit. Resolves once free space returns above the threshold. Uses "CWRMM Ticket Management for Monitors" workflow for ticket management.` +- **Type:** `Script` +- **Severity:** `Others` +- **Family:** `Disk Space` + +![Image1](../../../static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image1.webp) + +## Targeted Resources + +- **Target Type:** `Device Groups` +- **Group Name:** `Enhanced Drive Space Monitoring` + +![Image2](../../../static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image2.webp) + +## Conditions + +- **Run script on:** `Schedule` +- **Repeat every:** `1` `Hour(s)` +- **Script Language:** `PowerShell` +- **Use Generative AI Assist for script creation:** `False` + +- **PowerShell Script Editor:** + +```PowerShell +<# +.SYNOPSIS + Monitors local drive free space against configured thresholds and manages ConnectWise ticket creation and closure via webhooks. + +.DESCRIPTION + This script is executed periodically by a CW RMM monitor set. It reads drive space thresholds, inclusion/exclusion lists, and the webhook URL from the JSON configuration file generated by the Enhanced Drive Space Monitoring Configuration Writer task. + + The monitoring logic evaluates local fixed drives (DriveType=3), filters them based on configuration, assigns them to capacity buckets (16-300 GB, 300-1024 GB, 1024-4096 GB, 4096+ GB), and compares free space against configured thresholds. + + To manage the ticket lifecycle cleanly without requiring separate CW RMM ticketing tasks, the script implements a state machine using three local JSON files. It tracks which drives require new tickets, which have existing tickets, and which have recovered and require ticket closure. Upon detecting state changes, the script fires HTTP POST webhooks containing the ticket subject, body, action (Create or Close), and device ID to a ConnectWise workflow. + +.FUNCTIONALITY + 1. Loads the JSON configuration file. If missing or invalid, outputs a warning and returns. + 2. Enumerates all local fixed drives (DriveType=3). Exits early if drive enumeration fails to preserve existing ticket states. + 3. Filters drives based on the IncludeDrives and ExcludeDrives lists. + 4. Assigns each drive to a capacity bucket (16-300 GB, 300-1024 GB, 1024-4096 GB, 4096+ GB). + 5. Compares free space against the bucket threshold, skipping buckets where the threshold is 0. + 6. Manages state files to track ticket actions: + - If a drive is detected and not in Create/Existing, it is added to Create. + - If a drive is detected and already in Create, it is moved to Existing. + - If a drive is detected and already in Existing, no action is taken. + - If a drive is no longer detected and is in Create/Existing, it is moved to Close. + - If a drive is no longer detected and is in Close, it is removed from Close. + 7. Fires webhook triggers for newly created or closed tickets, sending the Action, TicketSubject, TicketBody, and DeviceId. + +.PARAMETER None + This script does not accept parameters. + +.INPUTS + None. You cannot pipe objects to this script. + +.OUTPUTS + System.String. Returns a success or failure summary of the monitoring state and webhook execution. + +.NOTES + Script Name : Enhanced Drive Space Monitoring [Workflow] + Configuration : $env:ProgramData\_Automation\Script\EnhancedDriveSpaceMonitoring\EnhancedDriveSpaceMonitoring.json + + Paths updated by this script: + - $env:ProgramData\_Automation\Script\EnhancedDriveSpaceMonitoring\Drives_To_Create_Ticket.json + - $env:ProgramData\_Automation\Script\EnhancedDriveSpaceMonitoring\Drives_With_Existing_Ticket.json + - $env:ProgramData\_Automation\Script\EnhancedDriveSpaceMonitoring\Drives_To_Close_Ticket.json + + ConnectWise Workflow Details: + - Workflow Name : CWRMM Ticket Management for Monitors + - Custom Trigger Name : CWRMM Ticket Management for Monitors + - Webhook Trigger Name : CWRMM Ticket Management for Monitors (custom trigger is used as Webhook Trigger) + - Webhook Trigger Instance Name : CWRMM Ticket Management for Monitors + +.EXAMPLE + .\Monitor-EnhancedDriveSpace.ps1 + + Runs the drive space monitoring check, updates the ticket state JSON files, and fires the required ConnectWise webhooks. +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param() + +#region globals +$ProgressPreference = 'SilentlyContinue' +$WarningPreference = 'SilentlyContinue' +$InformationPreference = 'Continue' +#endregion + +#region variables +$projectName = 'EnhancedDriveSpaceMonitoring' +$workingDirectory = '{0}\_Automation\Script\{1}' -f $env:ProgramData, $projectName +$configPath = '{0}\{1}.json' -f $workingDirectory, $projectName + +$createTicketPath = '{0}\Drives_To_Create_Ticket.json' -f $workingDirectory +$existingTicketPath = '{0}\Drives_With_Existing_Ticket.json' -f $workingDirectory +$closeTicketPath = '{0}\Drives_To_Close_Ticket.json' -f $workingDirectory + +$deviceId = 'Unknown' +try { + $regPath = 'HKLM:\SOFTWARE\WOW6432Node\ITSPlatform' + if (Test-Path -Path $regPath) { + $regItem = Get-ItemProperty -Path $regPath -ErrorAction Stop + if ($null -ne $regItem.privateendpointid) { + $deviceId = $regItem.privateendpointid + } + } +} catch { + Write-Information -MessageData ('[WARNING] Failed to retrieve Device ID from registry. Error: {0}' -f $Error[0].Exception.Message) +} +#endregion + +#region functions +function Read-JsonArray { + <# + .SYNOPSIS + Reads a JSON file and returns its contents as an array. + .DESCRIPTION + Reads a JSON file from the specified path. If the file does not exist or is empty, an empty array is returned. + .PARAMETER Path + The full path to the JSON file to read. + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The full path to the JSON file to read.')] + [string]$Path + ) + + #region main + if (-not (Test-Path -Path $Path)) { + return @() + } + + try { + $raw = Get-Content -Path $Path -Raw -Encoding UTF8 -ErrorAction Stop + if ([string]::IsNullOrWhiteSpace($raw)) { + return @() + } + $data = $raw | ConvertFrom-Json -ErrorAction Stop + if ($null -eq $data) { + return @() + } + return @($data) + } catch { + Write-Information -MessageData ('[WARNING] Failed to parse JSON from {0}. Error: {1}' -f $Path, $Error[0].Exception.Message) + return @() + } + #endregion +} + +function Write-JsonArray { + <# + .SYNOPSIS + Writes an array of objects to a JSON file. + .DESCRIPTION + Serializes the provided data array to JSON and writes it to the specified file path. If the data array is empty, an empty JSON array is written without a BOM. + .PARAMETER Path + The full path to the JSON file to write. + .PARAMETER Data + The array of objects to serialize. + #> + [CmdletBinding(SupportsShouldProcess = $true)] + [OutputType([System.Void])] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The full path to the JSON file to write.')] + [string]$Path, + + [Parameter(Mandatory = $true, HelpMessage = 'The array of objects to serialize.')] + [object]$Data + ) + + #region variables + $utf8NoBomEncoding = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false + #endregion + + #region main + if ($PSCmdlet.ShouldProcess($Path, 'Write JSON Array')) { + if (-not $Data -or $Data.Count -eq 0) { + [System.IO.File]::WriteAllLines($Path, '[]', $utf8NoBomEncoding) + return + } + + $json = $Data | ConvertTo-Json -Depth 3 -ErrorAction Stop + if ($Data.Count -eq 1) { + $json = '[{0}]' -f $json + } + + try { + [System.IO.File]::WriteAllLines($Path, $json, $utf8NoBomEncoding) + } catch { + throw ('Failed to write JSON array to {0}. Error: {1}' -f $Path, $Error[0].Exception.Message) + } + } + #endregion +} +#endregion + +#region config import +if (-not (Test-Path -Path $configPath)) { + return 'Failure: Drive space configuration file not found. Skipping check.' +} + +try { + $rawJson = Get-Content -Path $configPath -Raw -Encoding UTF8 -ErrorAction Stop + $config = $rawJson | ConvertFrom-Json -ErrorAction Stop +} catch { + return ('Failure: Failed to read or parse the configuration file. Error: {0}' -f $Error[0].Exception.Message) +} + +$includeDrives = $config.IncludeDrives +$excludeDrives = $config.ExcludeDrives +$thresholds = $config.Thresholds +$webhookUrl = $config.TicketWebhookUrl +#endregion + +#region monitoring +$detectedDrives = @() +$drives = @(Get-CimInstance -ClassName 'Win32_LogicalDisk' -Filter 'DriveType=3' -ErrorAction SilentlyContinue) + +if ($drives.Count -eq 0) { + return 'Failure: Unable to retrieve drive information. Skipping check to preserve existing ticket state.' +} + +foreach ($drive in $drives) { + $letter = $drive.DeviceID.Substring(0, 1) + + if ($includeDrives -ne 'All' -and $includeDrives -notmatch $letter) { + continue + } + if ($excludeDrives -ne 'None' -and $excludeDrives -match $letter) { + continue + } + + $totalBytes = $drive.Size + if ($null -eq $totalBytes -or $totalBytes -eq 0) { + continue + } + + $totalGB = [Math]::Round($totalBytes / 1GB, 2) + $freeBytes = $drive.FreeSpace + $freeGB = [Math]::Round($freeBytes / 1GB, 2) + $freeMB = [Math]::Round($freeBytes / 1MB, 2) + $freePct = [Math]::Round(($freeBytes / $totalBytes) * 100, 2) + $usedBytes = $totalBytes - $freeBytes + $usedGB = [Math]::Round(($totalGB - $freeGB), 2) + + $bucket = $null + if ($totalGB -ge 16 -and $totalGB -le 300) { + $bucket = '16To300' + } elseif ($totalGB -gt 300 -and $totalGB -le 1024) { + $bucket = '300To1024' + } elseif ($totalGB -gt 1024 -and $totalGB -le 4096) { + $bucket = '1024To4096' + } elseif ($totalGB -gt 4096) { + $bucket = '4096Plus' + } + + if ($null -eq $bucket) { + continue + } + + $thresholdEntry = $thresholds | Where-Object -FilterScript { $_.Bucket -eq $bucket } + if ($null -eq $thresholdEntry) { + continue + } + + [int]$thresholdValue = $thresholdEntry.Value + $thresholdUnit = $thresholdEntry.Unit + + if ($thresholdValue -eq 0) { + continue + } + + $isLow = $false + $usedFormatted = '' + $freeFormatted = '' + $thresholdFormatted = '' + + if ($thresholdUnit -eq 'Percent') { + if ($freePct -lt $thresholdValue) { $isLow = $true } + $usedFormatted = '{0} %' -f [Math]::Round(($usedBytes / $totalBytes) * 100, 2) + $freeFormatted = '{0} %' -f $freePct + $thresholdFormatted = '{0} %' -f $thresholdValue + } elseif ($thresholdUnit -eq 'MB') { + if ($freeMB -lt $thresholdValue) { $isLow = $true } + $usedFormatted = '{0} MB' -f [Math]::Round($usedBytes / 1MB, 2) + $freeFormatted = '{0} MB' -f $freeMB + $thresholdFormatted = '{0} MB' -f $thresholdValue + } elseif ($thresholdUnit -eq 'GB') { + if ($freeGB -lt $thresholdValue) { $isLow = $true } + $usedFormatted = '{0} GB' -f $usedGB + $freeFormatted = '{0} GB' -f $freeGB + $thresholdFormatted = '{0} GB' -f $thresholdValue + } + + if ($isLow) { + $ticketSubject = 'Enhanced Drive Space Monitoring - {0} - {1} - {2}' -f $letter, $env:COMPUTERNAME, $($thresholdFormatted -replace '%', 'Percent') + $ticketBody = 'Drive {0} has breached the free space threshold.{1}{1}Total Space: {2} GB{1}Used Space: {3}{1}Free Space: {4}{1}Threshold: {5}' -f $letter, [Environment]::NewLine, $totalGB, $usedFormatted, $freeFormatted, $thresholdFormatted + + $detectedDrives += [PSCustomObject]@{ + Letter = $letter + TicketSubject = $ticketSubject + TicketBody = $ticketBody + } + } +} +#endregion + +#region state machine management +$createList = Read-JsonArray -Path $createTicketPath +$existingList = Read-JsonArray -Path $existingTicketPath +$closeList = Read-JsonArray -Path $closeTicketPath + +$allLetters = @() +$allLetters += $detectedDrives.Letter +$allLetters += $createList.Letter +$allLetters += $existingList.Letter +$allLetters += $closeList.Letter +$allLetters = $allLetters | + Where-Object -FilterScript { $null -ne $_ } | + Sort-Object -Unique + +$newCreateList = [System.Collections.ArrayList]@() +$newExistingList = [System.Collections.ArrayList]@() +$newCloseList = [System.Collections.ArrayList]@() + +foreach ($letter in $allLetters) { + $detectedObj = $detectedDrives | + Where-Object -FilterScript { $_.Letter -eq $letter } | + Select-Object -First 1 + + $oldCreateObj = $createList | + Where-Object -FilterScript { $_.Letter -eq $letter } | + Select-Object -First 1 + + $oldExistingObj = $existingList | + Where-Object -FilterScript { $_.Letter -eq $letter } | + Select-Object -First 1 + + $oldCloseObj = $closeList | + Where-Object -FilterScript { $_.Letter -eq $letter } | + Select-Object -First 1 + + if ($null -ne $detectedObj) { + if ($null -ne $oldExistingObj) { + $newExistingList.Add($oldExistingObj) | Out-Null + } elseif ($null -ne $oldCreateObj) { + $newExistingList.Add($oldCreateObj) | Out-Null + } else { + $newCreateList.Add($detectedObj) | Out-Null + } + } else { + if ($null -ne $oldCreateObj -or $null -ne $oldExistingObj) { + $objToClose = if ($null -ne $oldExistingObj) { $oldExistingObj } else { $oldCreateObj } + $objToClose.TicketBody = 'Drive space has recovered and is now above the threshold. The ticket can be closed.' + $newCloseList.Add($objToClose) | Out-Null + } elseif ($null -ne $oldCloseObj) { + Write-Information -MessageData ('[INFO] Drive {0} ticket was previously closed. Clearing from close list.' -f $letter) + } + } +} + +Write-JsonArray -Path $createTicketPath -Data $newCreateList +Write-JsonArray -Path $existingTicketPath -Data $newExistingList +Write-JsonArray -Path $closeTicketPath -Data $newCloseList +#endregion + +#region webhook execution +$webhookSuccess = $true + +if ($newCreateList.Count -gt 0) { + foreach ($createDrive in $newCreateList) { + $payload = [ordered]@{ + Action = 'Create' + TicketSubject = $createDrive.TicketSubject + TicketBody = $createDrive.TicketBody + DeviceId = $deviceId + } + $jsonPayload = $payload | ConvertTo-Json -Depth 2 + + try { + Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' -ErrorAction Stop | Out-Null + } catch { + $webhookSuccess = $false + Write-Information -MessageData ('[FAIL] Failed to trigger ConnectWise create workflow for ''{0}''. Error: {1}' -f $createDrive.TicketSubject, $Error[0].Exception.Message) + } + } +} + +if ($newCloseList.Count -gt 0) { + foreach ($closeDrive in $newCloseList) { + $payload = [ordered]@{ + Action = 'Close' + TicketSubject = $closeDrive.TicketSubject + TicketBody = $closeDrive.TicketBody + DeviceId = $deviceId + } + $jsonPayload = $payload | ConvertTo-Json -Depth 2 + + try { + Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' -ErrorAction Stop | Out-Null + } catch { + $webhookSuccess = $false + Write-Information -MessageData ('[FAIL] Failed to trigger ConnectWise close workflow for ''{0}''. Error: {1}' -f $closeDrive.TicketSubject, $Error[0].Exception.Message) + } + } +} +#endregion + +if ($newCreateList.Count -gt 0 -or $newCloseList.Count -gt 0) { + $status = if ($webhookSuccess) { 'Success' } else { 'Failure' } + return ('{0}: Action detected - {1} drive(s) for ticket creation and {2} drive(s) for ticket closure.' -f $status, $newCreateList.Count, $newCloseList.Count) +} + +return 'Success: Drive space monitoring state evaluated successfully.' +#endregion +``` + +- **Criteria:** `Contains` +- **Operator:** `AND` +- **Script Output:** `Action detected` +- **Escalate ticket on script failure:** `Disabled` +- **Add Automation:** `` + +![Image3](../../../static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image3.webp) + +## Ticket Resolution + +- **Automatically Resolve:** `Disabled` + +![Image4](../../../static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image4.webp) + +## Monitor Output + +**Output:** `Do not Generate Ticket` + +![Image5](../../../static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image5.webp) + +## Completed Monitor + +![Image6](../../../static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image6.webp) + +## Changelog + +### 2026-07-23 + +- Initial version of the document diff --git a/docs/cwrmm/monitors/memory-threshold-violation-monitoring-workflow.md b/docs/cwrmm/monitors/memory-threshold-violation-monitoring-workflow.md new file mode 100644 index 000000000..f4e458afa --- /dev/null +++ b/docs/cwrmm/monitors/memory-threshold-violation-monitoring-workflow.md @@ -0,0 +1,550 @@ +--- +id: '029c88f9-1655-4919-8c25-873ad4b53ca0' +slug: /029c88f9-1655-4919-8c25-873ad4b53ca0 +title: 'Memory Threshold Violation Monitoring [Workflow]' +title_meta: 'Memory Threshold Violation Monitoring [Workflow]' +keywords: ['memory', 'monitoring', 'windows', 'alerts', 'thresholds', 'performance', 'workflow', 'trigger'] +description: 'Checks local Memory usage against sustained high percentage thresholds. Alerts when Memory usage remains above the limit for the configured duration. Resolves once Memory usage drops below the low threshold. Uses "CWRMM Ticket Management for Monitors" workflow for ticket management.' +tags: ['performance', 'monitoring', 'windows'] +draft: false +unlisted: false +last_update: + date: 2026-07-23 +--- + +## Summary + +The **Memory Threshold Violation Monitoring [Workflow]** monitor continuously checks local memory usage (specifically the `% Committed Bytes In Use` counter) on Windows endpoints against the sustained‑usage thresholds defined in the configuration file generated by the [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) task. It does **not** read custom fields directly; it relies on the pre‑built JSON configuration — which also carries the ticketing webhook URL (`TicketWebhookUrl`) — to know when to alert, when to consider the condition resolved, and where to send ticket actions. + +This is the **workflow‑based** variant of the monitor. The original [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) set uses the monitor's built‑in ticketing: it raises one ticket per incident and auto‑resolves it on recovery (via the monitor set's automatic resolution rule), but the ticket subject and body are monitor‑generated and not customizable, and a comment is appended on every detection while the alert persists, making the ticket bulky. This variant instead manages the ticket lifecycle through the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow: it tracks ticket state locally and fires webhooks to **create** a clean ticket when a sustained high‑memory condition is confirmed and **close** it when usage recovers. Like the original, it creates a single ticket per incident and closes it automatically on recovery — but with a controlled subject and body (including the top memory‑consuming processes), **no per‑detection comment spam**, and a standardized, workflow‑managed lifecycle. + +### How It Works + +1. **Configuration File** + At each scheduled run (every 15 minutes, as configured), the monitor reads `C:\ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json`. This file contains: + - **HighThreshold** – the memory usage percentage that starts the timer. + - **LowThreshold** – the memory percentage the usage must stay above to keep the timer running (and below which the timer resets). + - **UsageMins** – how many minutes the memory must remain above the low threshold (after first exceeding the high threshold) before an alert is raised. + - **TicketWebhookUrl** – the workflow webhook URL that ticket actions are POSTed to. + If the file is missing or cannot be parsed, the monitor returns a failure message and skips the run without changing any state or firing any webhooks. + +2. **Memory Sampling** + The monitor samples the `\Memory\% Committed Bytes In Use` counter once per second for 10 seconds and averages the result to get a stable current memory percentage. If counter sampling fails or returns no data, the monitor returns early **without modifying any ticket state** — a safeguard so a transient counter failure never accidentally closes an open ticket. + +3. **Two‑Threshold Hysteresis with a Marker File** + To distinguish a brief spike from a genuinely sustained condition, the monitor uses two thresholds and a small marker file (`Test-MemoryUsage.flag`) that records when the current event began: + - **No marker file:** the active threshold is the **HighThreshold**. When memory usage first reaches or exceeds it, the marker file is created and the timer starts. + - **Marker file present:** the active threshold drops to the **LowThreshold**. As long as memory usage stays at or above the low threshold, the marker remains and the elapsed time (measured from the marker's creation time) keeps accumulating. + - **Usage drops below the active threshold:** the marker file is deleted and the timer resets immediately, so the event must begin again from the high threshold. + - **Elapsed time reaches `UsageMins`:** the sustained condition is confirmed and an alert is built for this run. + +4. **Sustained‑Breach Alert Content** + Once the sustained condition is confirmed, the monitor builds a ticket subject (`Memory Threshold Violation - Memory - - Percent`) and a body that records how long ago the spike began, the low threshold the memory has stayed above, the current memory percentage, and the **top five memory‑consuming processes** (each process's working set expressed as a percentage of total physical memory). If PowerShell appears among those processes, the command lines of the running `powershell.exe` instances (excluding the monitor's own process) are appended to assist investigation. The memory component is then marked as "detected" for this run. + +5. **Ticket State Machine (duplicate prevention)** + This is the core of the variant. The monitor maintains three local state files alongside the config: + - `Memory_To_Create_Ticket.json` + - `Memory_With_Existing_Ticket.json` + - `Memory_To_Close_Ticket.json` + + Because the monitor tracks a single component (`Memory`), there is **at most one open memory ticket per device**. For that component, it applies the following transitions each run: + + | Memory condition this run | Previous state | Transition | Webhook | + | --- | --- | --- | --- | + | Sustained breach detected | not tracked | Added to **Create** | `Create` fires | + | Sustained breach detected | Create | Moved to **Existing** | none | + | Sustained breach detected | Existing | Kept in **Existing** | none | + | Recovered (below low threshold) | Create or Existing | Moved to **Close** (subject kept, body replaced with a recovery message) | `Close` fires | + | Recovered | Close | Cleared from **Close** | none | + | No active event | not tracked | No record | none | + + Because a confirmed breach fires `Create` only on the run it first appears and is then promoted to **Existing**, a sustained high‑memory event produces **exactly one ticket**, no matter how many 15‑minute runs occur while it persists. On recovery, `Close` fires once and the record is then cleared, so it never fires twice. + +6. **Webhook Execution** + When the component is newly in the **Create** list, the monitor POSTs `Action = Create`; when it is newly in the **Close** list, it POSTs `Action = Close`. Each payload carries `Action`, `TicketSubject`, `TicketBody`, and `DeviceId` (read from the endpoint's registry). The webhook URL is the `TicketWebhookUrl` value from the config file. The [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow receives the payload and performs the actual ticket creation or closure in ConnectWise. This monitor uses only the `Create` and `Close` actions; it does not send `Comment`. + +7. **Reporting and Alert Signal** + If a `Create` or `Close` webhook fired, the monitor returns `Action detected - N component(s) for ticket creation and M component(s) for ticket closure`; otherwise it returns `Memory monitoring state evaluated successfully`. The monitor's success criteria is configured as *Script Output contains `Action detected`*, so CW RMM only flags the monitor on runs where a ticket action actually occurred. Because RMM's own ticketing is set to **Do not Generate Ticket**, **Escalate ticket on script failure = Disabled**, and **Automatically Resolve = Disabled** (closure is handled by the workflow's `Close` webhook, not by RMM), that flag is purely informational — the real ticket lives in ConnectWise and is owned by the workflow, not by RMM. + +8. **Failure Handling** + - Missing/invalid config → run skipped, no state change, no webhooks. + - Memory counter sampling failure → early return that preserves existing ticket state. + - Webhook failure → logged, the run is reported as `Failure`, but processing continues. + - Note: state files are written **before** the webhooks fire, so a failed `Create` or `Close` is **not** automatically retried on the next run — the memory condition must change state again (re‑breach or re‑recover) for a new webhook to be sent. + +### Scenario – Alert Triggered (Create) + +A server's memory usage spikes to 98% at 10:00 AM. The configured thresholds are HighThreshold = 95%, LowThreshold = 90%, UsageMins = 30 minutes. Because 98% exceeds the high threshold, the marker file is created and the timer starts. The memory stays above 90%, so the marker persists. On the first 15‑minute run after 30 minutes have elapsed (≈10:30 AM), the sustained condition is confirmed: the memory component is added to the **Create** list and a `Create` webhook fires. The workflow creates a ticket such as *"Memory Threshold Violation - Memory - SERVER01 - 95 Percent"* whose body lists the current usage and the top five memory‑consuming processes. + +### Scenario – Sustained Breach (No Duplicate) + +The memory remains above 90% on the following runs. The marker file still exists and the elapsed time still exceeds 30 minutes, so the memory component is "detected" again each run — but it is now found in the **Create** list (then **Existing**), so it is promoted to **Existing** and **no webhook fires**. Only the single original ticket exists for the device, regardless of how long the condition persists — no duplicates, no repeated comments. + +### Scenario – Timer Reset (No Alert) + +The server spikes to 98% and the marker file is created, but after 20 minutes the memory drops to 85%. Because 85% is below the low threshold (the active threshold while the marker exists), the marker file is deleted and the timer resets before 30 minutes ever elapse. The memory component is never "detected," so no webhook fires and no ticket is created. A future spike must exceed the high threshold again to start a new event. + +### Scenario – Automatic Resolution (Close) + +After a ticket has been created and the memory component is in **Existing**, usage eventually drops to 40% and stays low. The marker file is removed and the memory component is no longer detected, so it is moved to the **Close** list — keeping the original ticket subject (so the workflow can match the open ticket) but replacing the body with *"Memory usage has recovered and is now below the threshold. The ticket can be closed."* A `Close` webhook fires and the workflow closes the ticket. On the following run the record is cleared from **Close**, so no further webhook is sent. + +This design produces clean, workflow‑managed tickets — one per incident per device, no comment spam, and self‑closing on recovery — without the monitor‑generated formatting and per‑detection comments of the built‑in path, and without any native RMM ticketing tasks. It does depend on a valid `TicketWebhookUrl` in the configuration file (set via the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field) and on the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow being installed and reachable; if the URL is missing or invalid, webhooks fail and tickets will not be created or closed. + +## Dependencies + +- [Group: Memory Threshold Violation Monitoring](/docs/183946ab-f199-4b68-b92a-6dab5ae19d24) +- [Task: Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) +- [Solution: Memory Threshold Violation Monitoring](/docs/cda6ee21-e70f-45c3-868c-1800d4aa26d7) + +## Monitor Setup Location + +**Monitors Path:** `ENDPOINTS` ➞ `Alerts` ➞ `Monitors` + +## Monitor Summary + +- **Name:** `Memory Threshold Violation Monitoring [Workflow]` +- **Description:** `Checks local Memory usage against sustained high percentage thresholds. Alerts when Memory usage remains above the limit for the configured duration. Resolves once Memory usage drops below the low threshold. Uses "CWRMM Ticket Management for Monitors" workflow for ticket management.` +- **Type:** `Script` +- **Severity:** `Others` +- **Family:** `Desktop Health` + +![Image1](../../../static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image1.webp) + +## Targeted Resources + +- **Target Type:** `Device Groups` +- **Group Name:** `Memory Threshold Violation Monitoring` + +![Image2](../../../static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image2.webp) + +## Conditions + +- **Run script on:** `Schedule` +- **Repeat every:** `15` `Minute(s)` +- **Script Language:** `PowerShell` +- **Use Generative AI Assist for script creation:** `False` + +- **PowerShell Script Editor:** + +```PowerShell +<# +.SYNOPSIS + Monitors Memory usage against configured thresholds and manages ConnectWise ticket creation and closure via webhooks. + +.DESCRIPTION + This script is executed periodically by a CW RMM monitor set. It reads Memory threshold values and the webhook URL + from the JSON configuration file generated by the Memory Threshold Violation Monitoring Configuration Writer task. + + The monitoring logic evaluates sustained Memory usage. It uses a marker file to track how long Memory has remained + above the low threshold after spiking above the high threshold. + + To manage the ticket lifecycle cleanly without requiring separate CW RMM ticketing tasks, the script implements + a state machine using three local JSON files. It tracks whether a ticket needs to be created, already exists, + or requires closure. Upon detecting state changes, the script fires HTTP POST webhooks containing the ticket + subject, body, action (Create or Close), and device ID to a ConnectWise workflow. + +.FUNCTIONALITY + 1. Loads the JSON configuration file. If missing or invalid, outputs a warning and returns. + 2. Samples Memory performance counters over a 10-second window. Exits early if sampling fails to preserve existing ticket states. + 3. Compares current Memory usage against an active threshold (High or Low depending on hysteresis state). + 4. If Memory is sustained above the threshold for the configured minutes, it builds an alert detailing the top 5 consuming processes. + 5. Manages state files to track ticket actions: + - If Memory is detected and not in Create/Existing, it is added to Create. + - If Memory is detected and already in Create, it is moved to Existing. + - If Memory is detected and already in Existing, no action is taken. + - If Memory is no longer detected and is in Create/Existing, it is moved to Close. + - If Memory is no longer detected and is in Close, it is removed from Close. + 6. Fires webhook triggers for newly created or closed tickets, sending the Action, TicketSubject, TicketBody, and DeviceId. + +.PARAMETER None + This script does not accept parameters. + +.INPUTS + None. You cannot pipe objects to this script. + +.OUTPUTS + System.String. Returns a success or failure summary of the monitoring state and webhook execution. + +.NOTES + Script Name : Memory Threshold Violation Monitor [Workflow] + Configuration : $env:ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json + Marker File : $env:ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.flag + + Paths updated by this script: + - $env:ProgramData\_Automation\Script\Test-MemoryUsage\Memory_To_Create_Ticket.json + - $env:ProgramData\_Automation\Script\Test-MemoryUsage\Memory_With_Existing_Ticket.json + - $env:ProgramData\_Automation\Script\Test-MemoryUsage\Memory_To_Close_Ticket.json + + ConnectWise Workflow Details: + - Workflow Name : CWRMM Ticket Management for Monitors + - Custom Trigger Name : CWRMM Ticket Management for Monitors + - Webhook Trigger Name : CWRMM Ticket Management for Monitors (custom trigger is used as Webhook Trigger) + - Webhook Trigger Instance Name : CWRMM Ticket Management for Monitors + +.EXAMPLE + .\Monitor-MemoryUsage.ps1 + + Runs the Memory usage monitoring check, updates the ticket state JSON files, and fires the required ConnectWise webhooks. +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param() + +#region globals +$ProgressPreference = 'SilentlyContinue' +$WarningPreference = 'SilentlyContinue' +$InformationPreference = 'Continue' +$ConfirmPreference = 'None' +#endregion + +#region variables +$projectName = 'Test-MemoryUsage' +$workingDirectory = '{0}\_Automation\Script\{1}' -f $env:ProgramData, $projectName +$configPath = '{0}\{1}.json' -f $workingDirectory, $projectName +$markerFile = '{0}\{1}.flag' -f $workingDirectory, $projectName + +$createTicketPath = '{0}\Memory_To_Create_Ticket.json' -f $workingDirectory +$existingTicketPath = '{0}\Memory_With_Existing_Ticket.json' -f $workingDirectory +$closeTicketPath = '{0}\Memory_To_Close_Ticket.json' -f $workingDirectory + +$deviceId = 'Unknown' +try { + $regPath = 'HKLM:\SOFTWARE\WOW6432Node\ITSPlatform' + if (Test-Path -Path $regPath) { + $regItem = Get-ItemProperty -Path $regPath -ErrorAction Stop + if ($null -ne $regItem.privateendpointid) { + $deviceId = $regItem.privateendpointid + } + } +} catch { + Write-Information -MessageData ('[WARNING] Failed to retrieve Device ID from registry. Error: {0}' -f $Error[0].Exception.Message) +} +#endregion + +#region functions +function Read-JsonArray { + <# + .SYNOPSIS + Reads a JSON file and returns its contents as an array. + .DESCRIPTION + Reads a JSON file from the specified path. If the file does not exist or is empty, an empty array is returned. + .PARAMETER Path + The full path to the JSON file to read. + #> + [CmdletBinding()] + [OutputType([System.Object[]])] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The full path to the JSON file to read.')] + [string]$Path + ) + + #region main + if (-not (Test-Path -Path $Path)) { + return @() + } + + try { + $raw = Get-Content -Path $Path -Raw -Encoding UTF8 -ErrorAction Stop + if ([string]::IsNullOrWhiteSpace($raw)) { + return @() + } + $data = $raw | ConvertFrom-Json -ErrorAction Stop + if ($null -eq $data) { + return @() + } + return @($data) + } catch { + Write-Information -MessageData ('[WARNING] Failed to parse JSON from {0}. Error: {1}' -f $Path, $Error[0].Exception.Message) + return @() + } + #endregion +} + +function Write-JsonArray { + <# + .SYNOPSIS + Writes an array of objects to a JSON file. + .DESCRIPTION + Serializes the provided data array to JSON and writes it to the specified file path. If the data array is empty, an empty JSON array is written without a BOM. + .PARAMETER Path + The full path to the JSON file to write. + .PARAMETER Data + The array of objects to serialize. + #> + [CmdletBinding(SupportsShouldProcess = $true)] + [OutputType([System.Void])] + param ( + [Parameter(Mandatory = $true, HelpMessage = 'The full path to the JSON file to write.')] + [string]$Path, + + [Parameter(Mandatory = $true, HelpMessage = 'The array of objects to serialize.')] + [object]$Data + ) + + #region variables + $utf8NoBomEncoding = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false + #endregion + + #region main + if ($PSCmdlet.ShouldProcess($Path, 'Write JSON Array')) { + if (-not $Data -or $Data.Count -eq 0) { + [System.IO.File]::WriteAllLines($Path, '[]', $utf8NoBomEncoding) + return + } + + $json = $Data | ConvertTo-Json -Depth 3 -ErrorAction Stop + if ($Data.Count -eq 1) { + $json = '[{0}]' -f $json + } + + try { + [System.IO.File]::WriteAllLines($Path, $json, $utf8NoBomEncoding) + } catch { + throw ('Failed to write JSON array to {0}. Error: {1}' -f $Path, $Error[0].Exception.Message) + } + } + #endregion +} +#endregion + +#region config import +if (-not (Test-Path -Path $configPath)) { + return 'Failure: Memory monitoring configuration file not found. Skipping check.' +} + +try { + $rawJson = Get-Content -Path $configPath -Raw -Encoding UTF8 -ErrorAction Stop + $config = $rawJson | ConvertFrom-Json -ErrorAction Stop +} catch { + return ('Failure: Failed to read or parse the configuration file. Error: {0}' -f $Error[0].Exception.Message) +} + +[int]$highThreshold = $config.HighThreshold +[int]$lowThreshold = $config.LowThreshold +[int]$usageMinutes = $config.UsageMins +$webhookUrl = $config.TicketWebhookUrl +#endregion + +#region monitoring +$detectedComponents = @() + +if (Test-Path -Path $markerFile) { + [int]$activeThreshold = $lowThreshold +} else { + [int]$activeThreshold = $highThreshold +} + +$memorySamples = Get-Counter -Counter '\Memory\% Committed Bytes In Use' -SampleInterval 1 -MaxSamples 10 -ErrorAction SilentlyContinue +if (-not $memorySamples) { + return 'Failure: Unable to retrieve Memory performance counter data. Skipping check to preserve existing ticket state.' +} + +$currentMemory = ($memorySamples.CounterSamples | Measure-Object -Property CookedValue -Average).Average + +if ($currentMemory -ge $activeThreshold) { + if (-not (Test-Path -Path $markerFile)) { + $null = New-Item -Path $markerFile -ItemType 'File' -Force -ErrorAction SilentlyContinue + } + + $markerCreationTime = (Get-Item -Path $markerFile -ErrorAction SilentlyContinue).CreationTime + $elapsedMinutes = [Math]::Round((New-TimeSpan -Start $markerCreationTime -End (Get-Date)).TotalMinutes) + + if ($elapsedMinutes -ge $usageMinutes) { + $totalPhysicalMemory = (Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction SilentlyContinue).TotalPhysicalMemory + + if (-not $totalPhysicalMemory -or $totalPhysicalMemory -eq 0) { + $totalPhysicalMemory = 1 + } + + $topProcessesString = '' + try { + $topProcesses = Get-Process -ErrorAction SilentlyContinue | + Select-Object -Property Name, WorkingSet64 | + Group-Object -Property Name | + ForEach-Object -Process { + [PSCustomObject]@{ + Process = $_.Name + MemoryUsage = [Math]::Round(100 * (($_.Group | Measure-Object -Property WorkingSet64 -Sum).Sum / $totalPhysicalMemory), 2) + } + } | + Sort-Object -Property MemoryUsage -Descending | + Select-Object -First 5 + + if ($topProcesses) { + $topProcessesString = ($topProcesses | Format-Table -AutoSize | Out-String).TrimEnd() + } else { + $topProcessesString = '(No processes found)' + } + } catch { + $topProcessesString = '(Unable to collect per-process Memory data)' + } + + $ticketSubject = 'Memory Threshold Violation - Memory - {0} - {1} Percent' -f $env:COMPUTERNAME, $highThreshold + $ticketBody = 'The Memory usage spiked above {0}% {1} minutes ago and has remained consistently above {2}% since then.{3}{3}Current Memory Usage: {4}%{3}{3}Top 5 Processes utilizing Memory:{3}{5}' -f $highThreshold, $elapsedMinutes, $lowThreshold, [Environment]::NewLine, [Math]::Round($currentMemory, 2), $topProcessesString + + if ($topProcessesString -match 'powershell') { + $ticketBody = '{0}{1}{1}PowerShell Process Command Line:{1}' -f $ticketBody, [Environment]::NewLine + try { + $psProcesses = Get-CimInstance -ClassName 'Win32_Process' -Filter 'Name = ''powershell.exe''' -ErrorAction SilentlyContinue | + Where-Object -FilterScript { $_.ProcessId -ne $pid } + $psLines = $psProcesses.CommandLine -join [Environment]::NewLine + $ticketBody = '{0}{1}' -f $ticketBody, $psLines + } catch { + $ticketBody = '{0}(Could not retrieve PowerShell command lines)' -f $ticketBody + } + } + + $detectedComponents += [PSCustomObject]@{ + Component = 'Memory' + TicketSubject = $ticketSubject + TicketBody = $ticketBody + } + } +} else { + if (Test-Path -Path $markerFile) { + Remove-Item -Path $markerFile -Force -ErrorAction SilentlyContinue + } +} +#endregion + +#region state machine management +$createList = Read-JsonArray -Path $createTicketPath +$existingList = Read-JsonArray -Path $existingTicketPath +$closeList = Read-JsonArray -Path $closeTicketPath + +$allComponents = @() +$allComponents += $detectedComponents.Component +$allComponents += $createList.Component +$allComponents += $existingList.Component +$allComponents += $closeList.Component +$allComponents = $allComponents | + Where-Object -FilterScript { $null -ne $_ } | + Sort-Object -Unique + +$newCreateList = [System.Collections.ArrayList]@() +$newExistingList = [System.Collections.ArrayList]@() +$newCloseList = [System.Collections.ArrayList]@() + +foreach ($component in $allComponents) { + $detectedObj = $detectedComponents | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + $oldCreateObj = $createList | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + $oldExistingObj = $existingList | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + $oldCloseObj = $closeList | + Where-Object -FilterScript { $_.Component -eq $component } | + Select-Object -First 1 + + if ($null -ne $detectedObj) { + if ($null -ne $oldExistingObj) { + $newExistingList.Add($oldExistingObj) | Out-Null + } elseif ($null -ne $oldCreateObj) { + $newExistingList.Add($oldCreateObj) | Out-Null + } else { + $newCreateList.Add($detectedObj) | Out-Null + } + } else { + if ($null -ne $oldCreateObj -or $null -ne $oldExistingObj) { + $objToClose = if ($null -ne $oldExistingObj) { $oldExistingObj } else { $oldCreateObj } + $objToClose.TicketBody = 'Memory usage has recovered and is now below the threshold. The ticket can be closed.' + $newCloseList.Add($objToClose) | Out-Null + } elseif ($null -ne $oldCloseObj) { + Write-Information -MessageData ('[INFO] Memory ticket was previously closed. Clearing from close list.') + } + } +} + +Write-JsonArray -Path $createTicketPath -Data $newCreateList +Write-JsonArray -Path $existingTicketPath -Data $newExistingList +Write-JsonArray -Path $closeTicketPath -Data $newCloseList +#endregion + +#region webhook execution +$webhookSuccess = $true + +if ($newCreateList.Count -gt 0) { + foreach ($createComponent in $newCreateList) { + $payload = [ordered]@{ + Action = 'Create' + TicketSubject = $createComponent.TicketSubject + TicketBody = $createComponent.TicketBody + DeviceId = $deviceId + } + $jsonPayload = $payload | ConvertTo-Json -Depth 2 + + try { + Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' -ErrorAction Stop | Out-Null + } catch { + $webhookSuccess = $false + Write-Information -MessageData ('[FAIL] Failed to trigger ConnectWise create workflow for ''{0}''. Error: {1}' -f $createComponent.TicketSubject, $Error[0].Exception.Message) + } + } +} + +if ($newCloseList.Count -gt 0) { + foreach ($closeComponent in $newCloseList) { + $payload = [ordered]@{ + Action = 'Close' + TicketSubject = $closeComponent.TicketSubject + TicketBody = $closeComponent.TicketBody + DeviceId = $deviceId + } + $jsonPayload = $payload | ConvertTo-Json -Depth 2 + + try { + Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' -ErrorAction Stop | Out-Null + } catch { + $webhookSuccess = $false + Write-Information -MessageData ('[FAIL] Failed to trigger ConnectWise close workflow for ''{0}''. Error: {1}' -f $closeComponent.TicketSubject, $Error[0].Exception.Message) + } + } +} +#endregion + +if ($newCreateList.Count -gt 0 -or $newCloseList.Count -gt 0) { + $status = if ($webhookSuccess) { 'Success' } else { 'Failure' } + return ('{0}: Action detected - {1} component(s) for ticket creation and {2} component(s) for ticket closure.' -f $status, $newCreateList.Count, $newCloseList.Count) +} + +return 'Success: Memory monitoring state evaluated successfully.' +#endregion +``` + +- **Criteria:** `Contains` +- **Operator:** `AND` +- **Script Output:** `Action detected` +- **Escalate ticket on script failure:** `Disabled` +- **Add Automation:** `` + +![Image3](../../../static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image3.webp) + +## Ticket Resolution + +- **Automatically Resolve:** `Disabled` + +![Image4](../../../static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image4.webp) + +## Monitor Output + +**Output:** `Do not Generate Ticket` + +![Image5](../../../static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image5.webp) + +## Completed Monitor + +![Image6](../../../static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image6.webp) + +## Changelog + +### 2026-07-23 + +- Initial version of the document diff --git a/docs/cwrmm/tasks/cpu-threshold-violation-monitoring-configuration-writer.md b/docs/cwrmm/tasks/cpu-threshold-violation-monitoring-configuration-writer.md index 0754c1f96..e38cbd9a0 100644 --- a/docs/cwrmm/tasks/cpu-threshold-violation-monitoring-configuration-writer.md +++ b/docs/cwrmm/tasks/cpu-threshold-violation-monitoring-configuration-writer.md @@ -9,20 +9,20 @@ tags: ['performance', 'monitoring', 'windows'] draft: false unlisted: false last_update: - date: 2026-07-03 + date: 2026-07-23 --- ## Summary -The CPU Threshold Violation is a preparation task that builds the local monitoring configuration used by the [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) monitor set. It does **not** perform any monitoring itself. Instead, it reads the threshold values you define in ConnectWise RMM custom fields and writes a simple JSON file that the monitor set reads every time it runs. +The CPU Threshold Violation Monitoring Configuration Writer is a preparation task that builds the local monitoring configuration consumed by the CPU Threshold Violation Monitoring monitor set(s). It does **not** perform any monitoring itself. Instead, it reads the threshold values you define in ConnectWise RMM custom fields — together with the ConnectWise ticketing webhook URL — and writes a simple JSON file that the deployed monitor set reads every time it runs. Two monitor‑set variants share this file: the original [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) set, which uses the monitor's built‑in ticketing, and the optional [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) set, which delegates ticketing to the ConnectWise workflow. The webhook URL written into the file is consumed **only** by the [Workflow] variant; the original set ignores it. ### How it works 1. **Custom Fields Evaluation** - The script reads the CPU monitoring thresholds from custom fields at the Company, Site, and Endpoint levels. It follows a strict priority order: **Endpoint → Site → Company**. If a value is set at the Endpoint level, that value is used. If not, the Site level is checked, then the Company level. If no value is set at any level, a sensible default is applied. + The script reads the CPU monitoring thresholds from custom fields at the Company, Site, and Endpoint levels. It follows a strict priority order: **Endpoint → Site → Company**. If a value is set at the Endpoint level, that value is used. If not, the Site level is checked, then the Company level. If no value is set at any level, a sensible default is applied. In the same pass it also reads the company-level [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) field, which holds the webhook URL of the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). Unlike the threshold fields, this URL is read at the **Company level only** — there is no Site/Endpoint override and no `_Svr` / `_Wks` server/workstation split — because it represents a single, global webhook endpoint shared by every device. The value is stored purely so the [Workflow] monitor‑set variant can reach the workflow; the original monitor set never reads it. 2. **Server & Workstation Separation** - The script automatically detects whether the endpoint is a Windows Server or Workstation and applies the correct set of Company/Site fields (`_Svr` or `_Wks` suffix). This allows you to set different thresholds for servers and workstations without duplicating scripts. + The script automatically detects whether the endpoint is a Windows Server or Workstation and applies the correct set of Company/Site fields (`_Svr` or `_Wks` suffix) for the thresholds. (The webhook URL is unaffected by this detection — it is the same company-level value on both.) This allows you to set different thresholds for servers and workstations without duplicating scripts. 3. **Configuration File Generation** Once the final values are resolved, the script writes a JSON configuration file to the endpoint: @@ -31,14 +31,17 @@ The CPU Threshold Violation is a preparation task that builds the local monitori C:\ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json ``` - The file contains three numbers: + The file contains: - **HighThreshold** – CPU percentage that, once exceeded, starts the timer. - **LowThreshold** – CPU percentage that resets the timer if usage drops below it. - **UsageMins** – the number of minutes the CPU must stay above the low threshold (after the initial spike above the high threshold) before an alert is triggered. + - **TicketWebhookUrl** – the ConnectWise ticketing webhook URL resolved from `Ticket_Mgmt_Webhook_Url`, written verbatim so the [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) monitor set can POST `Create` / `Close` / `Comment` payloads to the workflow. The original monitor set ignores this key. If the company field is empty or its RMM token fails to resolve, a hard-coded placeholder (`https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL`) is written instead. + +> In the samples below, `https://webhook.myconnectwise.net/...` is documentation shorthand for the real instance URL you paste into the company field; the script writes whatever string is stored there verbatim, with no validation of the URL itself. ### Sample Scenario 1: Using Default Values -No custom fields are configured at any level. The script runs on a server and uses the built‑in defaults for servers: High = 95, Low = 90, Minutes = 30. +No *threshold* custom fields are configured at any level. The script runs on a server and uses the built‑in defaults for servers: High = 95, Low = 90, Minutes = 30. The company-level `Ticket_Mgmt_Webhook_Url` field is assumed to be set, because it is a mandatory prerequisite for the [Workflow] monitor‑set variant (see the Custom Fields notes). The resulting configuration file would be: @@ -46,7 +49,8 @@ The resulting configuration file would be: { "HighThreshold": 95, "LowThreshold": 90, - "UsageMins": 30 + "UsageMins": 30, + "TicketWebhookUrl": "https://webhook.myconnectwise.net/..." } ``` @@ -58,13 +62,14 @@ An administrator wants a tighter threshold for a critical finance server. At the - `CTVM_LowThreshold` = `90` - `CTVM_UsageMins` = `15` -The script runs and, because the Endpoint fields take priority over any Company or Site fields, the configuration file becomes: +The script runs and, because the Endpoint fields take priority over any Company or Site fields, the threshold values are overridden, but `TicketWebhookUrl` is unaffected — it always reflects the single company-level field, never the per-level overrides, and is consumed only by the [Workflow] monitor‑set variant. The configuration file becomes: ```json { "HighThreshold": 98, "LowThreshold": 90, - "UsageMins": 15 + "UsageMins": 15, + "TicketWebhookUrl": "https://webhook.myconnectwise.net/..." } ``` @@ -72,9 +77,12 @@ On all other servers where no Endpoint-level fields are set, the script falls ba ### Ticketing & Alerting Behavior -- A separate [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) monitor set reads the configuration file and periodically checks the CPU usage. -- An alert is triggered only when the CPU first exceeds the **high threshold** and then **continues to stay above the low threshold** for the number of minutes specified in `UsageMins`. -- When the CPU drops below the low threshold, the condition clears. Because CW RMM does not support auto‑closing these alerts, any generated tickets must be closed manually by a technician after the issue is resolved. +- The deployed monitor set reads the configuration file and periodically checks the CPU usage. Which set is deployed is a per‑partner choice — only one of the two should be active for a given device. +- An alert is triggered only when the CPU first exceeds the **high threshold** and then **continues to stay above the low threshold** for the number of minutes specified in `UsageMins`. When the CPU drops below the low threshold, the condition clears. +- **Original [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) set:** generates alerts and tickets using the monitor's *built‑in* ticketing. It reads only the thresholds from the config file and **ignores `TicketWebhookUrl`**. It raises one ticket per incident and auto‑resolves it on recovery via the monitor set's automatic resolution rule, but the ticket subject/body are monitor‑generated (not customizable) and a comment is appended on every detection while the alert persists. For this variant the webhook URL is irrelevant and the company field may be left blank. +- **Optional [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) set:** evaluates CPU usage the same way, but instead of the built‑in ticketing it reads `TicketWebhookUrl` from the config file and POSTs `Create` / `Close` / `Comment` payloads to the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). The monitor script fires a `Close` payload when the condition clears, so the workflow **auto‑closes the ticket on recovery** — overcoming the manual‑close limitation of the built‑in path — while also producing clean tickets with no comment spam. **This is the only variant for which the webhook URL is required.** +- The configuration writer itself never contacts the webhook; it only stores the URL for the [Workflow] monitor set to use. +- The configuration file is updated once per day (or manually) by this task, so any changes to custom fields — including the webhook URL — take effect on the next scheduled run. ## Sample Run @@ -82,6 +90,7 @@ On all other servers where no Endpoint-level fields are set, the script falls ba ## Dependencies +- [Custom Field: Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) - [Custom Field: CTVM_Enable](/docs/aa6be36d-3653-4f68-b9fe-5bdb7c7f5c20) - [Custom Field: CTVM_Enable_Svr](/docs/5f3cb7ce-6d25-4199-9434-574fb2ed6542) - [Custom Field: CTVM_Enable_Svr_Site](/docs/f991ac6d-10ed-4957-8cb7-72b08d01f4d3) @@ -103,6 +112,8 @@ On all other servers where no Endpoint-level fields are set, the script falls ba - [Custom Field: CTVM_UsageMins_Wks](/docs/df1733e0-701e-4d73-9826-404f1921a1db) - [Custom Field: CTVM_UsageMins_Wks_Site](/docs/3abfb2fc-2278-46d5-beb9-e26fa4c20a6f) - [Group: CPU Threshold Violation Monitoring](/docs/006889e2-8977-4957-9c4d-7381bdbea9a0) +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) - [Solution: CPU Threshold Violation Monitoring](/docs/49b06af7-af3b-4aaa-a90c-8efb28a65c9e) ## Custom Fields @@ -127,10 +138,26 @@ The following table lists all custom fields used by the to determine the CPU mon | [CTVM_UsageMins_Wks_Site](/docs/3abfb2fc-2278-46d5-beb9-e26fa4c20a6f) | `20`, `10` | Site | Text Box | – | Site‑level override for workstations. Overrides Company; overridden by Endpoint. | | [CTVM_UsageMins](/docs/3f442fad-1a4a-4793-91f3-46ee9b16e956) | `5`, `10` | Endpoint | Text Box | – | Endpoint‑level sustained minutes. Overrides all higher levels (applies to both OS types). | ---- - ![Image2](../../../static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image2.webp) +In addition to the threshold fields listed above, this task also reads the following company-level field and embeds its value into the configuration file as `TicketWebhookUrl`: + +| Name | Example | Level | Type | Default Value | Description | +| --- | --- | --- | --- | --- | --- | +| [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) | `https://webhook./` | Company | Text Box | `https://webhook.myconnectwise.net/...` | Company-level webhook URL of the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57); written into the config JSON as `TicketWebhookUrl`. Read at the Company level only and consumed only by the [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) monitor set — see notes below. Name must match exactly. | + +**Only required for the [Workflow] monitor-set variant:** +`TicketWebhookUrl` exists in the config file for the benefit of the optional [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) monitor set, which is the only component that reads it. If a partner deploys the original [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) set (built-in ticketing) instead, this field is unused end-to-end — the original monitor set ignores the key, so the company field may be left blank and the silent-failure consequence described below does not apply. The mandatory-prerequisite and silent-failure warnings that follow therefore apply **only** when the [Workflow] monitor set is in use. + +**Company-level only — no hierarchical override and no server/workstation split:** +Unlike every `CTVM_*` threshold field above, `Ticket_Mgmt_Webhook_Url` exists only at the Company level and has no `_Svr` / `_Wks` variants. The script reads it once (Row 16) and the OS-detection block does not touch it, so servers and workstations — and every Site and Endpoint — share the exact same webhook URL. This is intentional: the URL points to a single, environment-wide webhook instance, not to a per-device or per-class value. + +**Mandatory prerequisite for the [Workflow] variant — silent-failure risk if left blank:** +When the [Workflow] monitor set is deployed, the value written to `TicketWebhookUrl` must be the real workflow URL — whatever string is stored as the field's Default Value, copied verbatim from the [workflow's trigger webhook instance](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). The `https://webhook.myconnectwise.net/...` shown in the table is only a placeholder / example and must be replaced with the real URL during workflow setup. If the field is empty, missing, or its RMM token fails to resolve, the script does **not** error out — Row 16 is configured with **Continue on Failure = `True`** — it silently writes the hard-coded fallback `https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL` into the JSON instead. The configuration task will still report success, but the [Workflow] monitor set will then have no valid endpoint to POST to, and **ticket creation/closure will silently fail for every device**. After saving the field, always confirm the URL stored in the company field is a character-for-character match of the URL shown in the workflow's trigger instance, then re-run this task (or wait for the next daily run) so the new URL is written into the config file. + +**No URL validation is performed:** +This task only stores the string; it does not check that the URL is reachable or well-formed. Connectivity, authentication, and payload handling are the responsibility of the [Workflow] monitor set and the [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) on the receiving end. + ## Task Setup Path - **Tasks Path:** `AUTOMATION` ➞ `Tasks` @@ -141,7 +168,14 @@ The following table lists all custom fields used by the to determine the CPU mon ### **Description** - **Name:** `CPU Threshold Violation Monitoring Configuration Writer` -- **Description:** `Generates a JSON configuration file for CPU threshold monitoring using hierarchical custom fields. The actual monitoring is performed by an external monitor set that reads this file.` +- **Description:** + +```plainText +Generates a JSON configuration file for CPU threshold monitoring using hierarchical custom fields. The actual monitoring is performed by an external monitor set that reads this file. +Defaults = High: 95%, Low: 90%, Minutes: 30 +Output File = %ProgramData%\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json +``` + - **Category:** `Monitoring` ![Image3](../../../static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image3.webp) @@ -298,7 +332,17 @@ The following table lists all custom fields used by the to determine the CPU mon ![Image18](../../../static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image18.webp) -#### **Row 16 Function: PowerShell script** +#### **Row 16 Function: Set Pre-defined Variable ( @Ticket_Mgmt_Webhook_Url@ = Ticket_Mgmt_Webhook_Url )** + +- **Notes:** `Ticket_Mgmt_Webhook_Url` +- **Continue on Failure:** `True` +- **Operating System:** `Windows` +- **Variable Name:** `Ticket_Mgmt_Webhook_Url` +- **Custom Field:** `Ticket_Mgmt_Webhook_Url (STRING - COMPANY)` + +![Image26](../../../static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image26.webp) + +#### **Row 17 Function: PowerShell script** - **Notes:** `` - **Use Generative AI Assist for script creation:** `False` @@ -314,16 +358,18 @@ The following table lists all custom fields used by the to determine the CPU mon Generates a JSON configuration file for CPU threshold violation monitoring based on CW RMM hierarchical custom fields. .DESCRIPTION - This script resolves the CPU monitoring thresholds (high %, low %, sustained minutes) from hierarchically defined - custom fields in CW RMM, then writes a local configuration file that a separate monitor set reads to perform - the actual CPU usage monitoring and alerting. + This script resolves the CPU monitoring thresholds (high %, low %, sustained minutes), and the + ConnectWise ticketing webhook URL from hierarchically defined custom fields in CW RMM, then writes + a local configuration file that a separate monitor set reads to perform the actual + CPU usage monitoring and alerting. Workflow: 1. Reads custom fields at Endpoint, Site, and Company levels using the CTVM_ naming convention. - 2. Detects the operating system type (Server vs. Workstation) to select the correct field suffixes (_Svr / _Wks). - 3. Applies hierarchical override logic: Endpoint > Site > Company, falling back to hard-coded defaults if nothing is set. - 4. Validates that the high threshold is greater than the low threshold. - 5. Creates a JSON configuration file under C:\ProgramData\_Automation\Script\\. + 2. Reads the client-level Ticket_Mgmt_Webhook_Url custom field for ConnectWise workflow integration. + 3. Detects the operating system type (Server vs. Workstation) to select the correct field suffixes (_Svr / _Wks). + 4. Applies hierarchical override logic: Endpoint > Site > Company, falling back to hard-coded defaults if nothing is set. + 5. Validates that the high threshold is greater than the low threshold. + 6. Creates a JSON configuration file under C:\ProgramData\_Automation\Script\\. The actual monitoring is performed by a separate monitor set that reads this configuration file. This script itself does NOT perform any CPU monitoring. @@ -356,10 +402,18 @@ The following table lists all custom fields used by the to determine the CPU mon | CTVM_UsageMins_Wks_Site | Site‑level override for the alert threshold in minutes on workstations. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | | CTVM_UsageMins | Endpoint‑level override for the alert threshold in minutes. Overrides all higher levels. | Endpoint | Text Box | | | Enter a number. Applies regardless of OS type. | | Yes | + --------------------------------------------------------------------- + Ticketing Configuration Field + --------------------------------------------------------------------- + +| Name | Description | Level | Type | Help Text | Default Value | Editable | +|---|---|---|---|---|---|---| +| Ticket_Mgmt_Webhook_Url | Webhook URL for the ConnectWise workflow that manages ticket creation and closure. | Company | Text Box | Paste the Webhook URL from the ticket management workflow here. | https://webhook.myconnectwise.net/... | No | + ------------------------------------------------------------------------ Enablement Field Usage (outside this script) ------------------------------------------------------------------------ - The CTVM_Enable* fields are NOT evaluated by this. They are used solely by + The CTVM_Enable* fields are NOT evaluated by this configuration writer. They are used solely by CW RMM automation groups (or monitors) to determine whether this script should be executed on a given endpoint. When the resolved enablement value is 'Enable', the automation should run this script to generate the configuration file; otherwise, it should be skipped. @@ -370,13 +424,19 @@ The following table lists all custom fields used by the to determine the CPU mon Company (CTVM_*_Svr / CTVM_*_Wks) provides the baseline default. .NOTES - ScriptName = CPU Threshold Violation Monitoring + ScriptName = CPU Threshold Violation Monitoring Configuration Writer Description = Generates a JSON configuration file for CPU threshold monitoring using hierarchical custom fields. The actual monitoring is performed by an external monitor set that reads this file. Defaults: Servers – High: 95%, Low: 90%, Minutes: 30 Workstations – High: 90%, Low: 85%, Minutes: 30 + Output File = %ProgramData%\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json + + Important: Ensure you add the Webhook URL as the default value to the Ticket_Mgmt_Webhook_Url + custom field in CW RMM before initiating this solution. Without this, the monitor script will + not be able to trigger the ConnectWise workflows to create or close tickets. + .OUTPUTS On success, writes the configuration file and returns $true. On failure (e.g., invalid thresholds, inaccessible path), throws a terminating error. @@ -414,6 +474,10 @@ $clientLevelLowThreshold_Svr = '@CTVM_LowThreshold_Svr@' $clientLevelLowThreshold_Wks = '@CTVM_LowThreshold_Wks@' $clientLevelUsageMins_Svr = '@CTVM_UsageMins_Svr@' $clientLevelUsageMins_Wks = '@CTVM_UsageMins_Wks@' + + +# Company/Client Level (Ticketing Configuration) +$clientLevelTicketMgmtWebhookUrl = '@Ticket_Mgmt_Webhook_Url@' #endregion #region os detection & variable mapping @@ -524,6 +588,14 @@ if ($highThreshold -le $lowThreshold) { } #endregion +# Ticketing Webhook URL +$ticketMgmtWebhookUrl = if (-not [string]::IsNullOrEmpty($clientLevelTicketMgmtWebhookUrl) -and $clientLevelTicketMgmtWebhookUrl -notmatch 'clientLevelTicketMgmtWebhookUrl') { + $clientLevelTicketMgmtWebhookUrl +} else { + 'https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL' +} +#endregion + #region working directory if (-not (Test-Path -Path $workingDirectory)) { try { @@ -536,9 +608,10 @@ if (-not (Test-Path -Path $workingDirectory)) { #region config file $config = @{ - HighThreshold = $highThreshold - LowThreshold = $lowThreshold - UsageMins = $usageMins + HighThreshold = $highThreshold + LowThreshold = $lowThreshold + UsageMins = $usageMins + TicketWebhookUrl = $ticketMgmtWebhookUrl } try { $config | ConvertTo-Json -Depth 3 | Set-Content -Path $configFilePath -Force -Encoding 'UTF8' -ErrorAction Stop @@ -552,7 +625,7 @@ return ('Configuration file ''{0}'' written successfully.{1}{1}Configuration:{1} ![Image19](../../../static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image19.webp) -#### **Row 17 Function: Script Log** +#### **Row 18 Function: Script Log** - **Notes:** `` - **Continue on Failure:** `False` @@ -605,6 +678,10 @@ return ('Configuration file ''{0}'' written successfully.{1}{1}Configuration:{1} ## Changelog +### 2026-07-23 + +- **Task Update:** The task now reads the company-level `Ticket_Mgmt_Webhook_Url` custom field and writes it into the configuration file as `TicketWebhookUrl`, enabling the optional `[Workflow]` monitor-set variant to trigger the ConnectWise ticketing workflow. + ### 2026-07-03 - Initial version of the document diff --git a/docs/cwrmm/tasks/enhanced-drive-space-monitoring.md b/docs/cwrmm/tasks/enhanced-drive-space-monitoring-configuration-writer.md similarity index 78% rename from docs/cwrmm/tasks/enhanced-drive-space-monitoring.md rename to docs/cwrmm/tasks/enhanced-drive-space-monitoring-configuration-writer.md index a9f51476d..071bb914b 100644 --- a/docs/cwrmm/tasks/enhanced-drive-space-monitoring.md +++ b/docs/cwrmm/tasks/enhanced-drive-space-monitoring-configuration-writer.md @@ -9,20 +9,20 @@ tags: ['disk', 'monitoring', 'windows'] draft: false unlisted: false last_update: - date: 2026-07-02 + date: 2026-07-23 --- ## Summary -The Enhanced Drive Space Monitoring Configuration Writer is a preparation task that builds the local monitoring configuration used by the [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) monitor set. It does **not** perform any drive space monitoring itself. Instead, it reads the threshold values you define in ConnectWise RMM custom fields and writes a structured JSON file that the monitor set reads every time it runs. +The Enhanced Drive Space Monitoring Configuration Writer is a preparation task that builds the local monitoring configuration consumed by the Enhanced Drive Space Monitoring monitor set(s). It does **not** perform any drive space monitoring itself. Instead, it reads the threshold values you define in ConnectWise RMM custom fields — together with the ConnectWise ticketing webhook URL — and writes a structured JSON file that the deployed monitor set reads every time it runs. Two monitor‑set variants share this file: the original [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) set, which uses the monitor's built‑in ticketing, and the optional [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) set, which delegates ticketing to the ConnectWise workflow. The webhook URL written into the file is consumed **only** by the [Workflow] variant; the original set ignores it. ### How it works 1. **Custom Fields Evaluation** - The script reads drive space thresholds (per capacity bucket) and drive inclusion/exclusion lists from custom fields at the Company, Site, and Endpoint levels. It follows a strict priority order: **Endpoint → Site → Company**. If a value is set at the Endpoint level, that value is used. If not, the Site level is checked, then the Company level. If no value is set at any level, sensible defaults are applied. + The script reads drive space thresholds (per capacity bucket) and drive inclusion/exclusion lists from custom fields at the Company, Site, and Endpoint levels. It follows a strict priority order: **Endpoint → Site → Company**. If a value is set at the Endpoint level, that value is used. If not, the Site level is checked, then the Company level. If no value is set at any level, sensible defaults are applied. In the same pass it also reads the company-level [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) field, which holds the webhook URL of the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). Unlike the threshold and drive-list fields, this URL is read at the **Company level only** — there is no Site/Endpoint override and no `_Svr` / `_Wks` server/workstation split — because it represents a single, global webhook endpoint shared by every device. The value is stored purely so the [Workflow] monitor‑set variant can reach the workflow; the original monitor set never reads it. 2. **Server & Workstation Separation** - The script automatically detects whether the endpoint is a Windows Server or Workstation and applies the correct set of Company/Site fields (`_Svr` or `_Wks` suffix). This allows you to set different thresholds for servers and workstations without duplicating scripts. + The script automatically detects whether the endpoint is a Windows Server or Workstation and applies the correct set of Company/Site fields (`_Svr` or `_Wks` suffix) for the thresholds and drive lists. (The webhook URL is unaffected by this detection — it is the same company-level value on both.) This allows you to set different thresholds for servers and workstations without duplicating scripts. 3. **Configuration File Generation** Once the final values are resolved, the script writes a JSON configuration file to the endpoint: @@ -35,10 +35,13 @@ The Enhanced Drive Space Monitoring Configuration Writer is a preparation task t - **Thresholds** – an array of capacity buckets (16–300 GB, 300–1024 GB, 1024–4096 GB, 4096+ GB), each with a numeric value and unit (% , MB, or GB). - **IncludeDrives** – a string specifying which drive letters to monitor (`All`, `None`, or a string like `CDEF`). - **ExcludeDrives** – a string specifying which drive letters to ignore (`None`, `All`, or letters like `Z`). + - **TicketWebhookUrl** – the ConnectWise ticketing webhook URL resolved from `Ticket_Mgmt_Webhook_Url`, written verbatim so the [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) monitor set can POST `Create` / `Close` / `Comment` payloads to the workflow. The original monitor set ignores this key. If the company field is empty or its RMM token fails to resolve, a hard-coded placeholder (`https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL`) is written instead. + +> In the samples below, `https://webhook.myconnectwise.net/...` is documentation shorthand for the real instance URL you paste into the company field; the script writes whatever string is stored there verbatim, with no validation of the URL itself. ### Sample Scenario 1: Using Default Values -No custom fields are configured at any level. The script runs on a server and uses the built‑in defaults: 10% for 16–300 GB drives, 30 GB for 300–1024 GB drives, 50 GB for 1024–4096 GB drives, 100 GB for 4096+ GB drives, include all drives, exclude none. +No *threshold or drive-list* custom fields are configured at any level, so the script uses the built‑in defaults for those: 10% for 16–300 GB drives, 30 GB for 300–1024 GB drives, 50 GB for 1024–4096 GB drives, 100 GB for 4096+ GB drives, include all drives, exclude none. The company-level `Ticket_Mgmt_Webhook_Url` field is assumed to be set, because it is a mandatory prerequisite for the [Workflow] monitor‑set variant (see the Custom Fields notes). The resulting configuration file would be: @@ -51,7 +54,8 @@ The resulting configuration file would be: { "Bucket": "4096Plus", "Value": 100, "Unit": "GB" } ], "IncludeDrives": "All", - "ExcludeDrives": "None" + "ExcludeDrives": "None", + "TicketWebhookUrl": "https://webhook.myconnectwise.net/..." } ``` @@ -64,7 +68,7 @@ An administrator wants to monitor only the `C` and `D` drives on a critical serv - `EDSM_DrivesToInclude` = `CD` - `EDSM_DrivesToExclude` = `None` -The configuration file becomes: +The threshold and drive-list values are overridden, but `TicketWebhookUrl` is unaffected — it always reflects the single company-level field, never the per-level overrides, and is consumed only by the [Workflow] monitor‑set variant. The configuration file becomes: ```json { @@ -75,15 +79,18 @@ The configuration file becomes: { "Bucket": "4096Plus", "Value": 100, "Unit": "GB" } ], "IncludeDrives": "CD", - "ExcludeDrives": "None" + "ExcludeDrives": "None", + "TicketWebhookUrl": "https://webhook.myconnectwise.net/..." } ``` ### Ticketing & Alerting Behavior -- A separate [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) monitor set reads the configuration file and periodically checks the drive space. -- Alerts and tickets are generated by the monitor set when a drive falls below its assigned threshold. -- The configuration file is updated once per day (or manually) by this task, so any changes to custom fields take effect on the next scheduled run. +- The deployed monitor set reads the configuration file and periodically checks the drive space. Which set is deployed is a per‑partner choice — only one of the two should be active for a given device. +- **Original [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) set:** generates alerts and tickets using the monitor's *built‑in* ticketing. It reads only the thresholds and drive lists from the config file and **ignores `TicketWebhookUrl`**. Tickets produced this way carry the monitor's default subject/body and append a comment on every detection. For this variant the webhook URL is irrelevant and the company field may be left blank. +- **Optional [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) set:** evaluates drive space the same way, but instead of the built‑in ticketing it reads `TicketWebhookUrl` from the config file and POSTs `Create` / `Close` / `Comment` payloads to the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), producing clean tickets that auto‑close on recovery with no comment spam. **This is the only variant for which the webhook URL is required.** +- The configuration writer itself never contacts the webhook; it only stores the URL for the [Workflow] monitor set to use. +- The configuration file is updated once per day (or manually) by this task, so any changes to custom fields — including the webhook URL — take effect on the next scheduled run. ## Sample Run @@ -91,6 +98,7 @@ The configuration file becomes: ## Dependencies +- [Custom Field: Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) - [Custom Field: EDSM_16To300Threshold_Svr](/docs/b6af6e72-388a-49e8-8cd1-658d240b8813) - [Custom Field: EDSM_16To300Threshold_Wks](/docs/8bc872f6-4810-4414-9532-ddec748df9ea) - [Custom Field: EDSM_16To300Threshold_Svr](/docs/84777e7f-983e-4a36-a61b-248f7a83aacf) @@ -122,6 +130,8 @@ The configuration file becomes: - [Custom Field: EDSM_DrivesToExclude_Wks](/docs/5bdbc620-2b0f-4217-9009-78ebdfebbda1) - [Custom Field: EDSM_DrivesToExclude](/docs/10713e2f-1457-4e60-8903-232032cc033e) - [Group: Enhanced Drive Space Monitoring](/docs/475ce8e8-458e-4901-bdfc-18e79f62c549) +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) - [Solution: Enhanced Drive Space Monitoring](/docs/e9cf4ff0-4413-447b-97dd-b8b2abd59597) ## Custom Fields @@ -161,6 +171,24 @@ The configuration file becomes: ![Image41](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image41.webp) +In addition to the threshold and drive-list fields listed above, this task also reads the following company-level field and embeds its value into the configuration file as `TicketWebhookUrl`: + +| Name | Example | Level | Type | Default Value | Description | +| --- | --- | --- | --- | --- | --- | +| [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) | `https://webhook./` | `Company` | `Text Box` | `https://webhook.myconnectwise.net/...` | Company-level webhook URL of the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57); written into the config JSON as `TicketWebhookUrl`. Read at the Company level only and consumed only by the [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) monitor set — see notes below. Name must match exactly. | + +**Only required for the [Workflow] monitor-set variant:** +`TicketWebhookUrl` exists in the config file for the benefit of the optional [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) monitor set, which is the only component that reads it. If a partner deploys the original [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) set (built-in ticketing) instead, this field is unused end-to-end — the original monitor set ignores the key, so the company field may be left blank and the silent-failure consequence described below does not apply. The mandatory-prerequisite and silent-failure warnings that follow therefore apply **only** when the [Workflow] monitor set is in use. + +**Company-level only — no hierarchical override and no server/workstation split:** +Unlike every `EDSM_*` field above, `Ticket_Mgmt_Webhook_Url` exists only at the Company level and has no `_Svr` / `_Wks` variants. The script reads it once (Row 31) and the OS-detection block does not touch it, so servers and workstations — and every Site and Endpoint — share the exact same webhook URL. This is intentional: the URL points to a single, environment-wide webhook instance, not to a per-device or per-class value. + +**Mandatory prerequisite for the [Workflow] variant — silent-failure risk if left blank:** +When the [Workflow] monitor set is deployed, the value written to `TicketWebhookUrl` must be the real workflow URL — whatever string is stored as the field's Default Value, copied verbatim from the [workflow's trigger webhook instance](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). The `https://webhook.myconnectwise.net/...` shown in the table is only a placeholder / example and must be replaced with the real URL during workflow setup. If the field is empty, missing, or its RMM token fails to resolve, the script does **not** error out — Row 31 is configured with **Continue on Failure = `True`** — it silently writes the hard-coded fallback `https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL` into the JSON instead. The configuration task will still report success, but the [Workflow] monitor set will then have no valid endpoint to POST to, and **ticket creation/closure will silently fail for every device**. After saving the field, always confirm the URL stored in the company field is a character-for-character match of the URL shown in the workflow's trigger instance, then re-run this task (or wait for the next daily run) so the new URL is written into the config file. + +**No URL validation is performed:** +This task only stores the string; it does not check that the URL is reachable or well-formed. Connectivity, authentication, and payload handling are the responsibility of the [Workflow] monitor set and the [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) on the receiving end. + ## Task Setup Path - **Tasks Path:** `AUTOMATION` ➞ `Tasks` @@ -485,7 +513,17 @@ Output File = %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\Enha ![Image33](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image33.webp) -#### Row 31 Function: PowerShell script +### Row 31 Function: Set Pre-defined Variable ( @clientLevelTicketMgmtWebhookUrl@ = Ticket_Mgmt_Webhook_Url ) + +- **Notes:** `clientLevelTicketMgmtWebhookUrl` +- **Continue on Failure:** `True` +- **Operating System:** `Windows` +- **Variable Name:** `clientLevelTicketMgmtWebhookUrl` +- **Custom Field:** `Ticket_Mgmt_Webhook_Url (STRING - COMPANY)` + +![Image46](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image46.webp) + +#### Row 32 Function: PowerShell script - **Notes:** `` - **Use Generative AI Assist for script creation:** `False` @@ -502,16 +540,18 @@ Output File = %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\Enha Does NOT perform any monitoring itself. .DESCRIPTION - This script resolves drive space thresholds (per capacity bucket) and drive inclusion/exclusion lists from - hierarchically defined custom fields in CW RMM, then writes a local configuration file. - A separate monitor set reads this file to perform the actual drive space checks and alerting. + This script resolves drive space thresholds (per capacity bucket), drive inclusion/exclusion lists, and the + ConnectWise ticketing webhook URL from hierarchically defined custom fields in CW RMM, then writes a local + configuration file. A separate monitor set reads this file to perform the actual drive space checks, + alerting, and ticket management. The workflow is: 1. Reads custom fields at Endpoint, Site, and Company levels using the EDSM_ naming convention. - 2. Detects the operating system type (Server vs. Workstation) to select the correct field suffixes (_Svr / _Wks). - 3. Applies hierarchical override logic: Endpoint > Site > Company, falling back to hard‑coded defaults - (10% for 16‑300 GB, 30 GB for 300‑1024 GB, 50 GB for 1024‑4096 GB, 100 GB for 4096+ GB). - 4. Writes a JSON configuration file under %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\. + 2. Reads the client-level Ticket_Mgmt_Webhook_Url custom field for ConnectWise workflow integration. + 3. Detects the operating system type (Server vs. Workstation) to select the correct field suffixes (_Svr / _Wks). + 4. Applies hierarchical override logic: Endpoint > Site > Company, falling back to hard-coded defaults + (10% for 16-300 GB, 30 GB for 300-1024 GB, 50 GB for 1024-4096 GB, 100 GB for 4096+ GB). + 5. Writes a JSON configuration file under %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\. The actual monitoring is performed by an external monitor set that reads this configuration file. This script itself does NOT check any drives or produce alerts. @@ -525,34 +565,42 @@ Output File = %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\Enha |---|---|---|---|---|---|---| | EDSM_16To300Threshold_Svr | Company baseline limit for 16-300 GB drives on Servers. | Company | Text Box | Enter X%, XMB, or XGB (e.g., 10%). Set to 0 to disable. | 10% | Yes | | EDSM_16To300Threshold_Wks | Company baseline limit for 16-300 GB drives on Workstations. | Company | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | 10% | Yes | -| EDSM_16To300Threshold_Svr | Site‑level limit for 16-300 GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_16To300Threshold_Wks | Site‑level limit for 16-300 GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_16To300Threshold | Endpoint‑level limit for 16-300 GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_16To300Threshold_Svr | Site-level limit for 16-300 GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_16To300Threshold_Wks | Site-level limit for 16-300 GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_16To300Threshold | Endpoint-level limit for 16-300 GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | | EDSM_300To1024Threshold_Svr | Company baseline limit for 300-1024 GB drives on Servers. | Company | Text Box | Enter X%, XMB, or XGB (e.g., 30GB). Set to 0 to disable. | 30GB | Yes | | EDSM_300To1024Threshold_Wks | Company baseline limit for 300-1024 GB drives on Workstations. | Company | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | 30GB | Yes | -| EDSM_300To1024Threshold_Svr | Site‑level limit for 300-1024 GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_300To1024Threshold_Wks | Site‑level limit for 300-1024 GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_300To1024Threshold | Endpoint‑level limit for 300-1024 GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_300To1024Threshold_Svr | Site-level limit for 300-1024 GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_300To1024Threshold_Wks | Site-level limit for 300-1024 GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_300To1024Threshold | Endpoint-level limit for 300-1024 GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | | EDSM_1024To4096Threshold_Svr | Company baseline limit for 1024-4096 GB drives on Servers. | Company | Text Box | Enter X%, XMB, or XGB (e.g., 50GB). Set to 0 to disable. | 50GB | Yes | | EDSM_1024To4096Threshold_Wks | Company baseline limit for 1024-4096 GB drives on Workstations. | Company | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | 50GB | Yes | -| EDSM_1024To4096Threshold_Svr | Site‑level limit for 1024-4096 GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_1024To4096Threshold_Wks | Site‑level limit for 1024-4096 GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_1024To4096Threshold | Endpoint‑level limit for 1024-4096 GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_1024To4096Threshold_Svr | Site-level limit for 1024-4096 GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_1024To4096Threshold_Wks | Site-level limit for 1024-4096 GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_1024To4096Threshold | Endpoint-level limit for 1024-4096 GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | | EDSM_4096PlusThreshold_Svr | Company baseline limit for 4096+ GB drives on Servers. | Company | Text Box | Enter X%, XMB, or XGB (e.g., 100GB). Set to 0 to disable. | 100GB | Yes | | EDSM_4096PlusThreshold_Wks | Company baseline limit for 4096+ GB drives on Workstations. | Company | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | 100GB | Yes | -| EDSM_4096PlusThreshold_Svr | Site‑level limit for 4096+ GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_4096PlusThreshold_Wks | Site‑level limit for 4096+ GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | -| EDSM_4096PlusThreshold | Endpoint‑level limit for 4096+ GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_4096PlusThreshold_Svr | Site-level limit for 4096+ GB drives on Servers. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_4096PlusThreshold_Wks | Site-level limit for 4096+ GB drives on Workstations. | Site | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | +| EDSM_4096PlusThreshold | Endpoint-level limit for 4096+ GB drives. | Endpoint | Text Box | Enter X%, XMB, or XGB. Set to 0 to disable. | | Yes | | EDSM_DrivesToInclude_Svr | Company baseline server drive letters to monitor. | Company | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | | EDSM_DrivesToInclude_Wks | Company baseline workstation drive letters to monitor. | Company | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | -| EDSM_DrivesToInclude_Svr | Site‑level server drive letters to monitor. | Site | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | -| EDSM_DrivesToInclude_Wks | Site‑level workstation drive letters to monitor. | Site | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | -| EDSM_DrivesToInclude | Endpoint‑level drive letters to monitor. | Endpoint | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | +| EDSM_DrivesToInclude_Svr | Site-level server drive letters to monitor. | Site | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | +| EDSM_DrivesToInclude_Wks | Site-level workstation drive letters to monitor. | Site | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | +| EDSM_DrivesToInclude | Endpoint-level drive letters to monitor. | Endpoint | Text Box | Enter sequential letters (e.g., CDEF), All, or None. | | Yes | | EDSM_DrivesToExclude_Svr | Company baseline server drive letters to ignore. | Company | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | | EDSM_DrivesToExclude_Wks | Company baseline workstation drive letters to ignore. | Company | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | -| EDSM_DrivesToExclude_Svr | Site‑level server drive letters to ignore. | Site | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | -| EDSM_DrivesToExclude_Wks | Site‑level workstation drive letters to ignore. | Site | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | -| EDSM_DrivesToExclude | Endpoint‑level drive letters to ignore. | Endpoint | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | +| EDSM_DrivesToExclude_Svr | Site-level server drive letters to ignore. | Site | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | +| EDSM_DrivesToExclude_Wks | Site-level workstation drive letters to ignore. | Site | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | +| EDSM_DrivesToExclude | Endpoint-level drive letters to ignore. | Endpoint | Text Box | Enter sequential letters (e.g., Z), None, or All. | | Yes | + + --------------------------------------------------------------------- + Ticketing Configuration Field + --------------------------------------------------------------------- + +| Name | Description | Level | Type | Help Text | Default Value | Editable | +|---|---|---|---|---|---|---| +| Ticket_Mgmt_Webhook_Url | Webhook URL for the ConnectWise workflow that manages ticket creation and closure. | Company | Text Box | Paste the Webhook URL from the ticket management workflow here. | https://webhook.myconnectwise.net/... | No | --------------------------------------------------------------------- Enablement Fields (not used by this script) @@ -572,9 +620,13 @@ Output File = %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\Enha ScriptName = Enhanced Drive Space Monitoring Configuration Writer Description = Resolves hierarchical custom field thresholds and writes a JSON config file for the external drive space monitor set. - Defaults = 10% (16‑300 GB), 30 GB (300‑1024 GB), 50 GB (1024‑4096 GB), 100 GB (4096+ GB) + Defaults = 10% (16-300 GB), 30 GB (300-1024 GB), 50 GB (1024-4096 GB), 100 GB (4096+ GB) Output File = %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\EnhancedDriveSpaceMonitoring.json + Important: Ensure you add the Webhook URL as the default value to the Ticket_Mgmt_Webhook_Url + custom field in CW RMM before initiating this solution. Without this, the monitor script will + not be able to trigger the ConnectWise workflows to create or close tickets. + .OUTPUTS Writes the JSON configuration file. Returns nothing on success. Throws a terminating error if the working directory or config file cannot be written. @@ -627,6 +679,9 @@ $clientLevelDrivesToInclude_Svr = '@clientLevelDrivesToInclude_Svr@' $clientLevelDrivesToInclude_Wks = '@clientLevelDrivesToInclude_Wks@' $clientLevelDrivesToExclude_Svr = '@clientLevelDrivesToExclude_Svr@' $clientLevelDrivesToExclude_Wks = '@clientLevelDrivesToExclude_Wks@' + +# Company/Client Level (Ticketing Configuration) +$clientLevelTicketMgmtWebhookUrl = '@clientLevelTicketMgmtWebhookUrl@' #endregion #region os detection & variable mapping @@ -663,7 +718,7 @@ if ($isServer) { #endregion #region set thresholds based on rmm variables (hierarchical override) -# 16‑300 GB bucket +# 16-300 GB bucket [int]$16To300Threshold = if (-not [string]::IsNullOrEmpty($computerLevel16To300Threshold) -and $computerLevel16To300Threshold -notmatch 'computerLevel16To300Threshold' -and $computerLevel16To300Threshold -match '^([1-9]\d?%|[1-9]\d*[MG]B)$') { [regex]::Match($computerLevel16To300Threshold, '[1-9]\d*').Value } elseif (-not [string]::IsNullOrEmpty($locationLevel16To300Threshold) -and $locationLevel16To300Threshold -notmatch 'locationLevel16To300Threshold' -and $locationLevel16To300Threshold -match '^([1-9]\d?%|[1-9]\d*[MG]B)$') { @@ -696,7 +751,7 @@ $16To300Unit = if ($computerLevel16To300Threshold -match '[1-9]\d?%') { 'Percent' } -# 300‑1024 GB bucket +# 300-1024 GB bucket [int]$300To1024Threshold = if (-not [string]::IsNullOrEmpty($computerLevel300to1024Threshold) -and $computerLevel300to1024Threshold -notmatch 'computerLevel300to1024Threshold' -and $computerLevel300to1024Threshold -match '^([1-9]\d?%|[1-9]\d*[MG]B)$') { [regex]::Match($computerLevel300to1024Threshold, '[1-9]\d*').Value } elseif (-not [string]::IsNullOrEmpty($locationLevel300to1024Threshold) -and $locationLevel300to1024Threshold -notmatch 'locationLevel300to1024Threshold' -and $locationLevel300to1024Threshold -match '^([1-9]\d?%|[1-9]\d*[MG]B)$') { @@ -729,7 +784,7 @@ $300To1024Unit = if ($computerLevel300to1024Threshold -match '[1-9]\d?%') { 'GB' } -# 1024‑4096 GB bucket +# 1024-4096 GB bucket [int]$1024To4096Threshold = if (-not [string]::IsNullOrEmpty($computerLevel1024to4096Threshold) -and $computerLevel1024to4096Threshold -notmatch 'computerLevel1024to4096Threshold' -and $computerLevel1024to4096Threshold -match '^([1-9]\d?%|[1-9]\d*[MG]B)$') { [regex]::Match($computerLevel1024to4096Threshold, '[1-9]\d*').Value } elseif (-not [string]::IsNullOrEmpty($locationLevel1024to4096Threshold) -and $locationLevel1024to4096Threshold -notmatch 'locationLevel1024to4096Threshold' -and $locationLevel1024to4096Threshold -match '^([1-9]\d?%|[1-9]\d*[MG]B)$') { @@ -815,12 +870,19 @@ $excludedDrives = if (-not [string]::IsNullOrEmpty($computerLevelDrivesToExclude } else { 'None' } + +# Ticketing Webhook URL +$ticketMgmtWebhookUrl = if (-not [string]::IsNullOrEmpty($clientLevelTicketMgmtWebhookUrl) -and $clientLevelTicketMgmtWebhookUrl -notmatch 'clientLevelTicketMgmtWebhookUrl') { + $clientLevelTicketMgmtWebhookUrl +} else { + 'https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL' +} #endregion #region working directory if (-not (Test-Path -Path $workingDirectory)) { try { - New-Item -Path $workingDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null + New-Item -Path $workingDirectory -ItemType 'Directory' -Force -ErrorAction Stop | Out-Null } catch { throw ('Failed to create the working directory {2}{0}{2}. Error: {1}' -f $workingDirectory, $Error[0].Exception.Message, [char]34) } @@ -829,7 +891,7 @@ if (-not (Test-Path -Path $workingDirectory)) { #region config file $config = [ordered]@{ - Thresholds = @( + Thresholds = @( [ordered]@{ Bucket = '16To300' Value = $16To300Threshold @@ -851,8 +913,9 @@ $config = [ordered]@{ Unit = $4096PlusUnit } ) - IncludeDrives = $includedDrives - ExcludeDrives = $excludedDrives + IncludeDrives = $includedDrives + ExcludeDrives = $excludedDrives + TicketWebhookUrl = $ticketMgmtWebhookUrl } try { @@ -867,7 +930,7 @@ return ('Configuration file ''{0}'' written successfully.{1}{1}Configuration:{1} ![Image34](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image34.webp) -#### Row 32 Function: Script Log +#### Row 33 Function: Script Log - **Notes:** `` - **Continue on Failure:** `False` @@ -878,7 +941,7 @@ return ('Configuration file ''{0}'' written successfully.{1}{1}Configuration:{1} ## Completed Task -![Image39](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image39.webp) +![Image45](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image45.webp) ## Output @@ -923,10 +986,14 @@ Output File = %ProgramData%\_Automation\Script\EnhancedDriveSpaceMonitoring\Enha ### Completed Scheduled Task -![Image45](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image45.webp) +![Image39](../../../static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image39.webp) ## Changelog +### 2026-07-23 + +- **Task Update:** The task now reads the company-level `Ticket_Mgmt_Webhook_Url` custom field and writes it into the configuration file as `TicketWebhookUrl`, enabling the optional `[Workflow]` monitor-set variant to trigger the ConnectWise ticketing workflow. + ### 2026-07-02 - **Architecture Change:** The task has been converted from a monitoring script that directly generated tickets to a **configuration writer**. It now only resolves custom field thresholds and writes a JSON configuration file (`EnhancedDriveSpaceMonitoring.json`). The actual drive space evaluation and ticketing are handled by a dedicated monitor set that reads this file. This decoupling allows more frequent health checks (via the monitor) while keeping policy management centralized in custom fields. diff --git a/docs/cwrmm/tasks/memory-threshold-violation-monitoring-configuration-writer.md b/docs/cwrmm/tasks/memory-threshold-violation-monitoring-configuration-writer.md index cf78d195a..fa2e97b33 100644 --- a/docs/cwrmm/tasks/memory-threshold-violation-monitoring-configuration-writer.md +++ b/docs/cwrmm/tasks/memory-threshold-violation-monitoring-configuration-writer.md @@ -9,20 +9,20 @@ tags: ['performance', 'monitoring', 'windows'] draft: false unlisted: false last_update: - date: 2026-07-15 + date: 2026-07-23 --- ## Summary -The Memory Threshold Violation Monitoring Configuration Writer is a preparation task that builds the local monitoring configuration used by the [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) monitor set. It does **not** perform any monitoring itself. Instead, it reads the threshold values you define in ConnectWise RMM custom fields and writes a simple JSON file that the monitor set reads every time it runs. +The Memory Threshold Violation Monitoring Configuration Writer is a preparation task that builds the local monitoring configuration consumed by the Memory Threshold Violation Monitoring monitor set(s). It does **not** perform any monitoring itself. Instead, it reads the threshold values you define in ConnectWise RMM custom fields — together with the ConnectWise ticketing webhook URL — and writes a simple JSON file that the deployed monitor set reads every time it runs. Two monitor‑set variants share this file: the original [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) set, which uses the monitor's built‑in ticketing, and the optional [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) set, which delegates ticketing to the ConnectWise workflow. The webhook URL written into the file is consumed **only** by the [Workflow] variant; the original set ignores it. ### How it works 1. **Custom Fields Evaluation** - The script reads the Memory monitoring thresholds from custom fields at the Company, Site, and Endpoint levels. It follows a strict priority order: **Endpoint → Site → Company**. If a value is set at the Endpoint level, that value is used. If not, the Site level is checked, then the Company level. If no value is set at any level, a sensible default is applied. + The script reads the memory monitoring thresholds from custom fields at the Company, Site, and Endpoint levels. It follows a strict priority order: **Endpoint → Site → Company**. If a value is set at the Endpoint level, that value is used. If not, the Site level is checked, then the Company level. If no value is set at any level, a sensible default is applied. In the same pass it also reads the company-level [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) field, which holds the webhook URL of the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). Unlike the threshold fields, this URL is read at the **Company level only** — there is no Site/Endpoint override and no `_Svr` / `_Wks` server/workstation split — because it represents a single, global webhook endpoint shared by every device. The value is stored purely so the [Workflow] monitor‑set variant can reach the workflow; the original monitor set never reads it. 2. **Server & Workstation Separation** - The script automatically detects whether the endpoint is a Windows Server or Workstation and applies the correct set of Company/Site fields (`_Svr` or `_Wks` suffix). This allows you to set different thresholds for servers and workstations without duplicating scripts. + The script automatically detects whether the endpoint is a Windows Server or Workstation and applies the correct set of Company/Site fields (`_Svr` or `_Wks` suffix) for the thresholds. (The webhook URL is unaffected by this detection — it is the same company-level value on both.) This allows you to set different thresholds for servers and workstations without duplicating scripts. 3. **Configuration File Generation** Once the final values are resolved, the script writes a JSON configuration file to the endpoint: @@ -31,14 +31,17 @@ The Memory Threshold Violation Monitoring Configuration Writer is a preparation C:\ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json ``` - The file contains three numbers: - - **HighThreshold** – Memory percentage that, once exceeded, starts the timer. - - **LowThreshold** – Memory percentage that resets the timer if usage drops below it. - - **UsageMins** – the number of minutes the Memory must stay above the low threshold (after the initial spike above the high threshold) before an alert is triggered. + The file contains: + - **HighThreshold** – memory percentage that, once exceeded, starts the timer. + - **LowThreshold** – memory percentage that resets the timer if usage drops below it. + - **UsageMins** – the number of minutes the memory must stay above the low threshold (after the initial spike above the high threshold) before an alert is triggered. + - **TicketWebhookUrl** – the ConnectWise ticketing webhook URL resolved from `Ticket_Mgmt_Webhook_Url`, written verbatim so the [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) monitor set can POST `Create` / `Close` / `Comment` payloads to the workflow. The original monitor set ignores this key. If the company field is empty or its RMM token fails to resolve, a hard-coded placeholder (`https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL`) is written instead. + +> In the samples below, `https://webhook.myconnectwise.net/...` is documentation shorthand for the real instance URL you paste into the company field; the script writes whatever string is stored there verbatim, with no validation of the URL itself. ### Sample Scenario 1: Using Default Values -No custom fields are configured at any level. The script runs on a server and uses the built‑in defaults for servers: High = 95, Low = 90, Minutes = 30. +No *threshold* custom fields are configured at any level. The script runs on a server and uses the built‑in defaults for servers: High = 95, Low = 90, Minutes = 30. The company-level `Ticket_Mgmt_Webhook_Url` field is assumed to be set, because it is a mandatory prerequisite for the [Workflow] monitor‑set variant (see the Custom Fields notes). The resulting configuration file would be: @@ -46,7 +49,8 @@ The resulting configuration file would be: { "HighThreshold": 95, "LowThreshold": 90, - "UsageMins": 30 + "UsageMins": 30, + "TicketWebhookUrl": "https://webhook.myconnectwise.net/..." } ``` @@ -58,13 +62,14 @@ An administrator wants a tighter threshold for a critical database server. At th - `MTVM_LowThreshold` = `90` - `MTVM_UsageMins` = `15` -The script runs and, because the Endpoint fields take priority over any Company or Site fields, the configuration file becomes: +The script runs and, because the Endpoint fields take priority over any Company or Site fields, the threshold values are overridden, but `TicketWebhookUrl` is unaffected — it always reflects the single company-level field, never the per-level overrides, and is consumed only by the [Workflow] monitor‑set variant. The configuration file becomes: ```json { "HighThreshold": 98, "LowThreshold": 90, - "UsageMins": 15 + "UsageMins": 15, + "TicketWebhookUrl": "https://webhook.myconnectwise.net/..." } ``` @@ -72,9 +77,12 @@ On all other servers where no Endpoint-level fields are set, the script falls ba ### Ticketing & Alerting Behavior -- A separate [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) monitor set reads the configuration file and periodically checks the Memory usage. -- An alert is triggered only when the Memory first exceeds the **high threshold** and then **continues to stay above the low threshold** for the number of minutes specified in `UsageMins`. -- When the Memory drops below the low threshold, the condition clears. Because CW RMM does not support auto‑closing these alerts, any generated tickets must be closed manually by a technician after the issue is resolved. +- The deployed monitor set reads the configuration file and periodically checks the memory usage. Which set is deployed is a per‑partner choice — only one of the two should be active for a given device. +- An alert is triggered only when the memory first exceeds the **high threshold** and then **continues to stay above the low threshold** for the number of minutes specified in `UsageMins`. When the memory drops below the low threshold, the condition clears. +- **Original [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) set:** generates alerts and tickets using the monitor's *built‑in* ticketing. It reads only the thresholds from the config file and **ignores `TicketWebhookUrl`**. It raises one ticket per incident and auto‑resolves it on recovery via the monitor set's automatic resolution rule, but the ticket subject/body are monitor‑generated (not customizable) and a comment is appended on every detection while the alert persists. For this variant the webhook URL is irrelevant and the company field may be left blank. +- **Optional [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) set:** evaluates memory usage the same way, but instead of the built‑in ticketing it reads `TicketWebhookUrl` from the config file and POSTs `Create` / `Close` / `Comment` payloads to the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). The monitor script fires a `Close` payload when the condition clears, so the workflow **auto‑closes the ticket on recovery** — overcoming the manual‑close limitation of the built‑in path — while also producing clean tickets with no comment spam. **This is the only variant for which the webhook URL is required.** +- The configuration writer itself never contacts the webhook; it only stores the URL for the [Workflow] monitor set to use. +- The configuration file is updated once per day (or manually) by this task, so any changes to custom fields — including the webhook URL — take effect on the next scheduled run. ## Sample Run @@ -82,6 +90,7 @@ On all other servers where no Endpoint-level fields are set, the script falls ba ## Dependencies +- [Custom Field: Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) - [Custom Field: MTVM_Enable_Svr](/docs/2fae464f-4fe6-4c42-9957-664365c25fe0) - [Custom Field: MTVM_Enable_Wks](/docs/f00277da-77b2-4f63-9f06-438b5f3800c8) - [Custom Field: MTVM_Enable_Svr_Site](/docs/46461e0d-19a9-415f-998f-fc9b6d2a6112) @@ -103,6 +112,8 @@ On all other servers where no Endpoint-level fields are set, the script falls ba - [Custom Field: MTVM_UsageMins_Wks_Site](/docs/bb20ad1a-a6f2-4d6d-9193-ef4051adeba4) - [Custom Field: MTVM_UsageMins](/docs/ac69275d-6200-4bc0-a449-0778149615f0) - [Group: Memory Threshold Violation Monitoring](/docs/183946ab-f199-4b68-b92a-6dab5ae19d24) +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) - [Solution: Memory Threshold Violation Monitoring](/docs/cda6ee21-e70f-45c3-868c-1800d4aa26d7) ## Custom Fields @@ -127,10 +138,26 @@ The following table lists all custom fields used by the script to determine the | [MTVM_UsageMins_Wks_Site](/docs/bb20ad1a-a6f2-4d6d-9193-ef4051adeba4) | `20`, `10` | Site | Text Box | – | Site‑level override for workstations. Overrides Company; overridden by Endpoint. | | [MTVM_UsageMins](/docs/ac69275d-6200-4bc0-a449-0778149615f0) | `5`, `10` | Endpoint | Text Box | – | Endpoint‑level sustained minutes. Overrides all higher levels (applies to both OS types). | ---- - ![Image2](../../../static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image2.webp) +In addition to the threshold fields listed above, this task also reads the following company-level field and embeds its value into the configuration file as `TicketWebhookUrl`: + +| Name | Example | Level | Type | Default Value | Description | +| --- | --- | --- | --- | --- | --- | +| [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) | `https://webhook./` | Company | Text Box | `https://webhook.myconnectwise.net/...` | Company-level webhook URL of the [CWRMM ticketing workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57); written into the config JSON as `TicketWebhookUrl`. Read at the Company level only and consumed only by the [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) monitor set — see notes below. Name must match exactly. | + +**Only required for the [Workflow] monitor-set variant:** +`TicketWebhookUrl` exists in the config file for the benefit of the optional [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) monitor set, which is the only component that reads it. If a partner deploys the original [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) set (built-in ticketing) instead, this field is unused end-to-end — the original monitor set ignores the key, so the company field may be left blank and the silent-failure consequence described below does not apply. The mandatory-prerequisite and silent-failure warnings that follow therefore apply **only** when the [Workflow] monitor set is in use. + +**Company-level only — no hierarchical override and no server/workstation split:** +Unlike every `MTVM_*` threshold field above, `Ticket_Mgmt_Webhook_Url` exists only at the Company level and has no `_Svr` / `_Wks` variants. The script reads it once (Row 16) and the OS-detection block does not touch it, so servers and workstations — and every Site and Endpoint — share the exact same webhook URL. This is intentional: the URL points to a single, environment-wide webhook instance, not to a per-device or per-class value. + +**Mandatory prerequisite for the [Workflow] variant — silent-failure risk if left blank:** +When the [Workflow] monitor set is deployed, the value written to `TicketWebhookUrl` must be the real workflow URL — whatever string is stored as the field's Default Value, copied verbatim from the [workflow's trigger webhook instance](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57). The `https://webhook.myconnectwise.net/...` shown in the table is only a placeholder / example and must be replaced with the real URL during workflow setup. If the field is empty, missing, or its RMM token fails to resolve, the script does **not** error out — Row 16 is configured with **Continue on Failure = `True`** — it silently writes the hard-coded fallback `https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL` into the JSON instead. The configuration task will still report success, but the [Workflow] monitor set will then have no valid endpoint to POST to, and **ticket creation/closure will silently fail for every device**. After saving the field, always confirm the URL stored in the company field is a character-for-character match of the URL shown in the workflow's trigger instance, then re-run this task (or wait for the next daily run) so the new URL is written into the config file. + +**No URL validation is performed:** +This task only stores the string; it does not check that the URL is reachable or well-formed. Connectivity, authentication, and payload handling are the responsibility of the [Workflow] monitor set and the [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) on the receiving end. + ## Task Setup Path - **Tasks Path:** `AUTOMATION` ➞ `Tasks` @@ -141,7 +168,14 @@ The following table lists all custom fields used by the script to determine the ### **Description** - **Name:** `Memory Threshold Violation Monitoring Configuration Writer` -- **Description:** `Generates a JSON configuration file for Memory threshold monitoring using hierarchical custom fields. The actual monitoring is performed by an external monitor set that reads this file.` +- **Description:** + +```PlainText +Generates a JSON configuration file for Memory threshold monitoring using hierarchical custom fields. The actual monitoring is performed by an external monitor set that reads this file. +Defaults = High: 95%, Low: 90%, Minutes: 30 +Output File = %ProgramData%\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json +``` + - **Category:** `Monitoring` ![Image3](../../../static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image3.webp) @@ -298,7 +332,17 @@ The following table lists all custom fields used by the script to determine the ![Image18](../../../static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image18.webp) -#### **Row 16 Function: PowerShell script** +#### **Row 16 Function: Set Pre-defined Variable ( @Ticket_Mgmt_Webhook_Url@ = Ticket_Mgmt_Webhook_Url )** + +- **Notes:** `Ticket_Mgmt_Webhook_Url` +- **Continue on Failure:** `True` +- **Operating System:** `Windows` +- **Variable Name:** `Ticket_Mgmt_Webhook_Url` +- **Custom Field:** `Ticket_Mgmt_Webhook_Url (STRING - COMPANY)` + +![Image26](../../../static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image26.webp) + +#### **Row 17 Function: PowerShell script** - **Notes:** `` - **Use Generative AI Assist for script creation:** `False` @@ -309,12 +353,279 @@ The following table lists all custom fields used by the script to determine the - **PowerShell Script Editor:** ```PowerShell +<# +.SYNOPSIS + Generates a JSON configuration file for Memory threshold violation monitoring based on CW RMM hierarchical custom fields. + +.DESCRIPTION + This script resolves the Memory monitoring thresholds (high %, low %, sustained minutes), and the + ConnectWise ticketing webhook URL from hierarchically defined custom fields in CW RMM, then writes + a local configuration file that a separate monitor set reads to perform the actual Memory usage + monitoring and alerting. + + Workflow: + 1. Reads custom fields at Endpoint, Site, and Company levels using the MTVM_ naming convention. + 2. Reads the client-level Ticket_Mgmt_Webhook_Url custom field for ConnectWise workflow integration. + 3. Detects the operating system type (Server vs. Workstation) to select the correct field suffixes (_Svr / _Wks). + 4. Applies hierarchical override logic: Endpoint > Site > Company, falling back to hard-coded defaults if nothing is set. + 5. Validates that the high threshold is greater than the low threshold. + 6. Creates a JSON configuration file under C:\ProgramData\_Automation\Script\\. + + The actual monitoring is performed by a separate monitor set that reads this configuration file. This script + itself does NOT perform any Memory monitoring. + +.COMPONENT + ------------------------------------------------------------------------ + CW RMM Task Custom Field Mapping + ------------------------------------------------------------------------ + +| Name | Description | Level | Type | Option Type | Options | Help Text | Default Value | Editable | +|---|---|---|---|---|---|---|---|---| +| MTVM_Enable_Svr | Enables/disables Memory monitoring for servers at the Company level. (Used only by automation groups; not evaluated by this script.) | Company | Dropdown | String | Enable, Disable | Select Enable to turn on monitoring, Disable to exclude. | Disable | Yes | +| MTVM_Enable_Wks | Enables/disables Memory monitoring for workstations at the Company level. (Used only by automation groups.) | Company | Dropdown | String | Enable, Disable | Select Enable to turn on monitoring, Disable to exclude. | Disable | Yes | +| MTVM_Enable_Svr_Site | Enables/disables Memory monitoring for servers at the Site level. (Used only by automation groups.) | Site | Dropdown | String | Enable, Disable | Select Enable to turn on monitoring, Disable to exclude. | | Yes | +| MTVM_Enable_Wks_Site | Enables/disables Memory monitoring for workstations at the Site level. (Used only by automation groups.) | Site | Dropdown | String | Enable, Disable | Select Enable to turn on monitoring, Disable to exclude. | | Yes | +| MTVM_Enable | Enables/disables Memory monitoring at the Endpoint level. (Used only by automation groups.) | Endpoint | Dropdown | String | Enable, Disable | Select Enable to turn on monitoring, Disable to exclude. | | Yes | +| MTVM_HighThreshold_Svr | Company baseline for the high Memory % that starts the timer on servers. | Company | Text Box | | | Enter a number (e.g., 95). Must be higher than the low threshold. | 95 | Yes | +| MTVM_HighThreshold_Wks | Company baseline for the high Memory % that starts the timer on workstations. | Company | Text Box | | | Enter a number (e.g., 90). | 90 | Yes | +| MTVM_HighThreshold_Svr_Site | Site‑level override for the high Memory % on servers. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | +| MTVM_HighThreshold_Wks_Site | Site‑level override for the high Memory % on workstations. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | +| MTVM_HighThreshold | Endpoint‑level override for the high Memory %. Overrides all higher levels. | Endpoint | Text Box | | | Enter a number. Applies regardless of OS type. | | Yes | +| MTVM_LowThreshold_Svr | Company baseline for the low Memory % that resets the timer on servers. | Company | Text Box | | | Enter a number (e.g., 90). Must be lower than the high threshold. | 90 | Yes | +| MTVM_LowThreshold_Wks | Company baseline for the low Memory % that resets the timer on workstations. | Company | Text Box | | | Enter a number (e.g., 85). | 85 | Yes | +| MTVM_LowThreshold_Svr_Site | Site‑level override for the low Memory % on servers. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | +| MTVM_LowThreshold_Wks_Site | Site‑level override for the low Memory % on workstations. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | +| MTVM_LowThreshold | Endpoint‑level override for the low Memory %. Overrides all higher levels. | Endpoint | Text Box | | | Enter a number. Applies regardless of OS type. | | Yes | +| MTVM_UsageMins_Svr | Company baseline for the minutes of sustained high Memory before an alert on servers. | Company | Text Box | | | Enter the number of minutes (e.g., 30). | 30 | Yes | +| MTVM_UsageMins_Wks | Company baseline for the minutes of sustained high Memory before an alert on workstations. | Company | Text Box | | | Enter the number of minutes (e.g., 30). | 30 | Yes | +| MTVM_UsageMins_Svr_Site | Site‑level override for the alert threshold in minutes on servers. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | +| MTVM_UsageMins_Wks_Site | Site‑level override for the alert threshold in minutes on workstations. | Site | Text Box | | | Enter a number. Overrides the Company value. | | Yes | +| MTVM_UsageMins | Endpoint‑level override for the alert threshold in minutes. Overrides all higher levels. | Endpoint | Text Box | | | Enter a number. Applies regardless of OS type. | | Yes | + + --------------------------------------------------------------------- + Ticketing Configuration Field + --------------------------------------------------------------------- + +| Name | Description | Level | Type | Help Text | Default Value | Editable | +|---|---|---|---|---|---|---| +| Ticket_Mgmt_Webhook_Url | Webhook URL for the ConnectWise workflow that manages ticket creation and closure. | Company | Text Box | Paste the Webhook URL from the ticket management workflow here. | https://webhook.myconnectwise.net/... | No | + + ------------------------------------------------------------------------ + Enablement Field Usage (outside this script) + ------------------------------------------------------------------------ + The MTVM_Enable* fields are NOT evaluated by this configuration writer. They are used solely by + CW RMM automation groups (or monitors) to determine whether this script should be executed on a + given endpoint. When the resolved enablement value is 'Enable', the automation should run this + script to generate the configuration file; otherwise, it should be skipped. + + Hierarchical Precedence (for thresholds): + Endpoint (MTVM_* without Svr/Wks suffix) overrides Site and Company. + Site (MTVM_*_Svr_Site / MTVM_*_Wks_Site) overrides Company. + Company (MTVM_*_Svr / MTVM_*_Wks) provides the baseline default. + +.NOTES + ScriptName = Memory Threshold Violation Monitoring Configuration Writer + Description = Generates a JSON configuration file for Memory threshold monitoring using hierarchical custom fields. + The actual monitoring is performed by an external monitor set that reads this file. + Defaults: + Servers – High: 95%, Low: 90%, Minutes: 30 + Workstations – High: 90%, Low: 85%, Minutes: 30 + + Output File = %ProgramData%\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json + + Important: Ensure you add the Webhook URL as the default value to the Ticket_Mgmt_Webhook_Url + custom field in CW RMM before initiating this solution. Without this, the monitor script will + not be able to trigger the ConnectWise workflows to create or close tickets. + +.OUTPUTS + On success, writes the configuration file and returns $true. + On failure (e.g., invalid thresholds, inaccessible path), throws a terminating error. +#> + +#region globals +$ProgressPreference = 'SilentlyContinue' +$WarningPreference = 'SilentlyContinue' +#endregion + +#region variables +$projectName = 'Test-MemoryUsage' +$workingDirectory = '{0}\_Automation\Script\{1}' -f $env:ProgramData, $projectName +$configFilePath = '{0}\{1}.json' -f $workingDirectory, $projectName +#endregion + +#region rmm variables +# Endpoint Level (Base Variables) +$computerLevelHighThreshold = '@MTVM_HighThreshold@' +$computerLevelLowThreshold = '@MTVM_LowThreshold@' +$computerLevelUsageMins = '@MTVM_UsageMins@' + +# Site/Location Level (Server & Workstation splits) +$locationLevelHighThreshold_Svr = '@MTVM_HighThreshold_Svr_Site@' +$locationLevelHighThreshold_Wks = '@MTVM_HighThreshold_Wks_Site@' +$locationLevelLowThreshold_Svr = '@MTVM_LowThreshold_Svr_Site@' +$locationLevelLowThreshold_Wks = '@MTVM_LowThreshold_Wks_Site@' +$locationLevelUsageMins_Svr = '@MTVM_UsageMins_Svr_Site@' +$locationLevelUsageMins_Wks = '@MTVM_UsageMins_Wks_Site@' + +# Company/Client Level (Server & Workstation splits) +$clientLevelHighThreshold_Svr = '@MTVM_HighThreshold_Svr@' +$clientLevelHighThreshold_Wks = '@MTVM_HighThreshold_Wks@' +$clientLevelLowThreshold_Svr = '@MTVM_LowThreshold_Svr@' +$clientLevelLowThreshold_Wks = '@MTVM_LowThreshold_Wks@' +$clientLevelUsageMins_Svr = '@MTVM_UsageMins_Svr@' +$clientLevelUsageMins_Wks = '@MTVM_UsageMins_Wks@' + + +# Company/Client Level (Ticketing Configuration) +$clientLevelTicketMgmtWebhookUrl = '@Ticket_Mgmt_Webhook_Url@' +#endregion + +#region os detection & variable mapping +$osInfo = Get-CimInstance -ClassName 'Win32_OperatingSystem' -ErrorAction SilentlyContinue +$isServer = $osInfo.ProductType -ne 1 + +if ($isServer) { + # Use the server‑specific Site & Company variables + $locationLevelHighThreshold = $locationLevelHighThreshold_Svr + $locationLevelLowThreshold = $locationLevelLowThreshold_Svr + $locationLevelUsageMins = $locationLevelUsageMins_Svr + + $clientLevelHighThreshold = $clientLevelHighThreshold_Svr + $clientLevelLowThreshold = $clientLevelLowThreshold_Svr + $clientLevelUsageMins = $clientLevelUsageMins_Svr + + # Hard defaults for servers if nothing is configured + $defaultHighThreshold = 95 + $defaultLowThreshold = 90 + $defaultUsageMins = 30 +} else { + # Use the workstation‑specific Site & Company variables + $locationLevelHighThreshold = $locationLevelHighThreshold_Wks + $locationLevelLowThreshold = $locationLevelLowThreshold_Wks + $locationLevelUsageMins = $locationLevelUsageMins_Wks + + $clientLevelHighThreshold = $clientLevelHighThreshold_Wks + $clientLevelLowThreshold = $clientLevelLowThreshold_Wks + $clientLevelUsageMins = $clientLevelUsageMins_Wks + + # Hard defaults for workstations if nothing is configured + $defaultHighThreshold = 90 + $defaultLowThreshold = 85 + $defaultUsageMins = 30 +} +#endregion + +#region set thresholds based on rmm variables (hierarchical override) +[int]$highThreshold = if ( + -not [string]::IsNullOrEmpty($computerLevelHighThreshold) -and + $computerLevelHighThreshold -notmatch 'computerLevelHighThreshold' -and + $computerLevelHighThreshold -match '^\d+$' +) { + [int]$computerLevelHighThreshold +} elseif ( + -not [string]::IsNullOrEmpty($locationLevelHighThreshold) -and + $locationLevelHighThreshold -notmatch 'locationLevelHighThreshold' -and + $locationLevelHighThreshold -match '^\d+$' +) { + [int]$locationLevelHighThreshold +} elseif ( + -not [string]::IsNullOrEmpty($clientLevelHighThreshold) -and + $clientLevelHighThreshold -notmatch 'clientLevelHighThreshold' -and + $clientLevelHighThreshold -match '^\d+$' +) { + [int]$clientLevelHighThreshold +} else { + $defaultHighThreshold +} +[int]$lowThreshold = if ( + -not [string]::IsNullOrEmpty($computerLevelLowThreshold) -and + $computerLevelLowThreshold -notmatch 'computerLevelLowThreshold' -and + $computerLevelLowThreshold -match '^\d+$' +) { + [int]$computerLevelLowThreshold +} elseif ( + -not [string]::IsNullOrEmpty($locationLevelLowThreshold) -and + $locationLevelLowThreshold -notmatch 'locationLevelLowThreshold' -and + $locationLevelLowThreshold -match '^\d+$' +) { + [int]$locationLevelLowThreshold +} elseif ( + -not [string]::IsNullOrEmpty($clientLevelLowThreshold) -and + $clientLevelLowThreshold -notmatch 'clientLevelLowThreshold' -and + $clientLevelLowThreshold -match '^\d+$' +) { + [int]$clientLevelLowThreshold +} else { + $defaultLowThreshold +} + +[int]$usageMins = if ( + -not [string]::IsNullOrEmpty($computerLevelUsageMins) -and + $computerLevelUsageMins -notmatch 'computerLevelUsageMins' -and + $computerLevelUsageMins -match '^\d+$' +) { + [int]$computerLevelUsageMins +} elseif ( + -not [string]::IsNullOrEmpty($locationLevelUsageMins) -and + $locationLevelUsageMins -notmatch 'locationLevelUsageMins' -and + $locationLevelUsageMins -match '^\d+$' +) { + [int]$locationLevelUsageMins +} elseif ( + -not [string]::IsNullOrEmpty($clientLevelUsageMins) -and + $clientLevelUsageMins -notmatch 'clientLevelUsageMins' -and + $clientLevelUsageMins -match '^\d+$' +) { + [int]$clientLevelUsageMins +} else { + $defaultUsageMins +} + +# Sanity check +if ($highThreshold -le $lowThreshold) { + throw ('Configuration error: High threshold ({0}%) must be greater than low threshold ({1}%).' -f $highThreshold, $lowThreshold) +} +#endregion + +# Ticketing Webhook URL +$ticketMgmtWebhookUrl = if (-not [string]::IsNullOrEmpty($clientLevelTicketMgmtWebhookUrl) -and $clientLevelTicketMgmtWebhookUrl -notmatch 'clientLevelTicketMgmtWebhookUrl') { + $clientLevelTicketMgmtWebhookUrl +} else { + 'https://webhook.myconnectwise.net/REPLACE_WITH_YOUR_DEFAULT_WEBHOOK_URL' +} +#endregion + +#region working directory +if (-not (Test-Path -Path $workingDirectory)) { + try { + New-Item -Path $workingDirectory -ItemType 'Directory' -Force -ErrorAction Stop | Out-Null + } catch { + throw ('Failed to create the working directory {2}{0}{2}. Error: {1}' -f $workingDirectory, $Error[0].Exception.Message, [char]34) + } +} +#endregion + +#region config file +$config = @{ + HighThreshold = $highThreshold + LowThreshold = $lowThreshold + UsageMins = $usageMins + TicketWebhookUrl = $ticketMgmtWebhookUrl +} +try { + $config | ConvertTo-Json -Depth 3 | Set-Content -Path $configFilePath -Force -Encoding 'UTF8' -ErrorAction Stop +} catch { + throw ('Failed to write the configuration file {2}{0}{2}. Error: {1}' -f $configFilePath, $Error[0].Exception.Message, [char]34) +} + +return ('Configuration file ''{0}'' written successfully.{1}{1}Configuration:{1}{2}' -f $configFilePath, [System.Environment]::NewLine, ($config | Out-String)) +#endregion ``` ![Image19](../../../static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image19.webp) -#### **Row 17 Function: Script Log** +#### **Row 18 Function: Script Log** - **Notes:** `` - **Continue on Failure:** `False` @@ -367,6 +678,10 @@ The following table lists all custom fields used by the script to determine the ## Changelog +### 2026-07-23 + +- **Task Update:** The task now reads the company-level `Ticket_Mgmt_Webhook_Url` custom field and writes it into the configuration file as `TicketWebhookUrl`, enabling the optional `[Workflow]` monitor-set variant to trigger the ConnectWise ticketing workflow. + ### 2026-07-15 - Initial version of the document diff --git a/docs/cwrmm/triggers/_category_.json b/docs/cwrmm/triggers/_category_.json new file mode 100644 index 000000000..ab593219b --- /dev/null +++ b/docs/cwrmm/triggers/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "CW RMM Custom Triggers" +} diff --git a/docs/cwrmm/triggers/_template.md b/docs/cwrmm/triggers/_template.md new file mode 100644 index 000000000..e880d5f19 --- /dev/null +++ b/docs/cwrmm/triggers/_template.md @@ -0,0 +1,48 @@ +--- +id: 'New GUID' +slug: /New GUID +title: 'Filename' +title_meta: 'Filename' +keywords: [] +description: '' +tags: [] +draft: true +unlisted: false +last_update: + date: 2025-08-01 +--- + +## Summary + +## Details + +| Trigger Name | Description | Tags | Secure user access | +| ------------ | ----------- | ---- | ------------------ | + +## Request Body + +These are the fields that will be collected in order for the custom workflow action to get executed. + +### Payload Schema + +| Field Type | Setup option | Field Caption | +| ---------- | ------------ | ------------- | + +### Data + +| Json Path Name | Display Name | Data Format | Default Value | List Options | Visibility | +| -------------- | ------------ | ----------- | ------------- | ------------ | ---------- | + +## Usage Example + +## Dependencies + +## Trigger Setup Path + +- **Tasks Path:** `Automation` ➞ `Workflows` ➞ `Custom Triggers` + +## Trigger Creation + +Install the corresponding workflow from the `ProVal - Content` Community to automatically install this trigger as well. + +## Changelog diff --git a/docs/cwrmm/triggers/cw-rmm-ticket-management-for-monitors.md b/docs/cwrmm/triggers/cw-rmm-ticket-management-for-monitors.md new file mode 100644 index 000000000..c27606d6d --- /dev/null +++ b/docs/cwrmm/triggers/cw-rmm-ticket-management-for-monitors.md @@ -0,0 +1,92 @@ +--- +id: '05c811e6-c6d0-4652-b4b6-2aa83f9605c7' +slug: /05c811e6-c6d0-4652-b4b6-2aa83f9605c7 +title: 'CWRMM Ticket Management for Monitors' +title_meta: 'CWRMM Ticket Management for Monitors' +keywords: ['trigger', 'ticketing', 'ticket-management', 'webhook', 'workflow', 'automation', 'monitors'] +description: 'Automates ConnectWise ticket lifecycle management directly from CW RMM monitors via webhook. Receives JSON payloads to Create, Close, or Comment on tickets, eliminating the need for separate RMM ticketing tasks and ensuring clean, duplicate-free alerting.' +tags: ['api', 'automation', 'ticketing'] +draft: false +unlisted: false +last_update: + date: 2026-07-23 +--- + +## Summary + +The **CWRMM Ticket Management for Monitors** trigger serves as a dedicated webhook endpoint designed to bridge CW RMM monitoring scripts and ConnectWise ticketing workflows. + +Instead of relying on native RMM ticketing tasks, monitoring scripts (such as Enhanced Drive Space, CPU Threshold, and Memory Threshold monitors) evaluate device telemetry locally and send a structured JSON payload to this trigger only when a ticket state change is required. This allows monitors to implement a robust local state machine, tracking which alerts already have open tickets and firing webhooks exclusively to create new tickets or close resolved ones. + +**Key Benefits:** + +- **Prevents Ticket Duplication:** By offloading ticket tracking to the monitor's local state files, it ensures that sustained alerts (e.g., a drive sitting at 95% full for hours) do not generate duplicate tickets on every monitor run. +- **Automated Resolution:** When an alert condition clears (e.g., CPU drops below the low threshold), the monitor automatically fires a `Close` action webhook, allowing ConnectWise to auto-resolve the ticket without technician intervention. +- **Decoupled Architecture:** Monitoring logic and ticketing actions are cleanly separated. Monitors focus purely on threshold evaluation and state management, while the ConnectWise workflow handles the API calls to Create, Close, or Comment on tickets based on the payload. + +Valid actions recognized by this trigger are **Create**, **Close**, and **Comment**. + +## Details + +| Trigger Name | Description | Tags | Secure user access | +| ------------ | ----------- | ---- | ------------------ | +| CWRMM Ticket Management For Monitors | Create and Close a Ticket based on Action from a monitor. Valid actions are Create, Close and Comment. | Ticketing | No Auth | + +## Request Body + +These are the fields that will be collected in order for the custom workflow action to get executed. + +### Payload Schema + +| Field Type | Setup option | Field Caption | +| ---------- | ------------ | ------------- | +| Json/complex data | | Payload Schema | + +### Data + +| Json Path Name | Display Name | Required | Data Format | Default Value | Valid Options | Visibility | +| -------------- | ------------ | ---------| ----------- | ------------- | ------------- | ---------- | +| Action | Action | True | String | | Create, Close, Comment | Shown | +| TicketBody | TicketBody | True | String | | | Shown | +| TicketSubject | TicketSubject | True | String | | | Shown | +| DeviceId | DeviceId | True | String | | | Shown | + +## Usage Example + +```PowerShell +$webhookUrl = 'https://webhook.xyz.net/sampleUrl' + +$action = 'Create' +$ticketSubject = 'A Sample Ticket for {0}' -f $env:ComputerName +$ticketBody = 'This ticket is created to test the functionality of the trigger' + +$deviceId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\ITSPlatform').privateendpointid + +$payload = [ordered]@{ + Action = $action + TicketSubject = $ticketSubject + TicketBody = $ticketBody + DeviceId = $deviceId +} +$jsonPayload = $payload | ConvertTo-Json -Depth 2 + +Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' +``` + +## Dependencies + +- [Workflow: CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) + +## Trigger Setup Path + +- **Tasks Path:** `Automation` ➞ `Workflows` ➞ `Custom Triggers` + +## Trigger Creation + +Install the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow from the `ProVal - Content` Community to automatically install this trigger as well. + +## Changelog + +### 2025-07-23 + +- Initial version of the document diff --git a/docs/cwrmm/workflows/_category_.json b/docs/cwrmm/workflows/_category_.json new file mode 100644 index 000000000..9e27c1344 --- /dev/null +++ b/docs/cwrmm/workflows/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "CW RMM Workflows" +} diff --git a/docs/cwrmm/workflows/_template.md b/docs/cwrmm/workflows/_template.md new file mode 100644 index 000000000..3b8e386a4 --- /dev/null +++ b/docs/cwrmm/workflows/_template.md @@ -0,0 +1,42 @@ +--- +id: 'New GUID' +slug: /New GUID +title: 'Filename' +title_meta: 'Filename' +keywords: [] +description: '' +tags: [] +draft: true +unlisted: false +last_update: + date: 2025-08-01 +--- + +## Summary + +## Details + +| Name | Description | Category tags | Trigger | +| ---- | ----------- | ------------- | ------- | + +## Dependencies + +## Workflow Setup Path + +- **Tasks Path:** `Automation` ➞ `Workflows` ➞ `All Workflows` + +## Usage Example + +## Component Used + +## Diagram + +> use [mermaid](https://mermaid.live/) to create a diagram + +## Workflow Creation + +Install the workflow from the `ProVal - Content` Community. + +## Completed Screenshot + +## Changelog diff --git a/docs/cwrmm/workflows/cw-rmm-ticket-management-for-monitors.md b/docs/cwrmm/workflows/cw-rmm-ticket-management-for-monitors.md new file mode 100644 index 000000000..9c5f58fb5 --- /dev/null +++ b/docs/cwrmm/workflows/cw-rmm-ticket-management-for-monitors.md @@ -0,0 +1,237 @@ +--- +id: '57daa951-2acc-4be7-a025-0d0ca729ef57' +slug: /57daa951-2acc-4be7-a025-0d0ca729ef57 +title: 'CWRMM Ticket Management for Monitors' +title_meta: 'CWRMM Ticket Management for Monitors' +keywords: ['trigger', 'ticketing', 'ticket-management', 'webhook', 'workflow', 'automation', 'monitors'] +description: 'Automates ConnectWise ticket creation, closure, and commenting based on webhook payloads from CW RMM monitors. This workflow handles device lookups, ticket state checks, and status updates to streamline alert management without requiring native RMM ticketing tasks.' +tags: ['api', 'automation', 'ticketing'] +draft: false +unlisted: false +last_update: + date: 2026-07-23 +--- + +## Summary + +The **CWRMM Ticket Management for Monitors** workflow automates the lifecycle of ConnectWise tickets based on JSON payloads received from a custom webhook trigger. Designed to work in tandem with advanced RMM monitoring scripts (such as Enhanced Drive Space, CPU, or Memory monitors), this workflow processes three primary actions: `Create`, `Close`, and `Comment`. + +**How it works:** + +1. A monitor script evaluates device telemetry and maintains a local state file. +2. If a threshold is breached or recovered, the script sends an HTTP POST request to the webhook URL containing the `Action`, `TicketSubject`, `TicketBody`, and `DeviceId`. +3. This workflow catches the webhook, looks up the endpoint, and executes the requested ticketing action. + +**Duplicate Ticket Prevention:** This workflow does not verify if an open ticket already exists before creating a new one. The RMM monitor script calling the workflow is solely responsible for intelligently managing state and firing the `Create` action only once per incident to prevent duplicate tickets. + +## Details + +| Name | Description | Category tags | Trigger | +| ---- | ----------- | ------------- | ------- | +| CWRMM Ticket Management for Monitors | Automates ticket creation, closure, and commenting for CW RMM monitors via webhook. | Ticketing | [CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) | + +## Dependencies + +- [Triggers: CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) +- [Custom Field: Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) + +## Workflow Setup Path + +- **Tasks Path:** `Automation` ➞ `Workflows` ➞ `All Workflows` + +## Usage Example + +```PowerShell +$webhookUrl = 'https://webhook.xyz.net/sampleUrl' + +$action = 'Create' +$ticketSubject = 'A Sample Ticket for {0}' -f $env:ComputerName +$ticketBody = 'This ticket is created to test the functionality of the trigger' + +$deviceId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\ITSPlatform').privateendpointid + +$payload = [ordered]@{ + Action = $action + TicketSubject = $ticketSubject + TicketBody = $ticketBody + DeviceId = $deviceId +} +$jsonPayload = $payload | ConvertTo-Json -Depth 2 + +Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $jsonPayload -ContentType 'application/json' +``` + +## Component Used + +*This workflow utilizes native ConnectWise RMM workflow actions, including:* + +- **Webhook Trigger:** Catches incoming JSON payloads. +- **Get a Endpoint by Criteria:** Resolves the `$deviceId` to an endpoint. +- **Set Variable:** Used to define a blank assignee variable (`NoOne`). +- **Condition:** Branches logic based on the `$action` payload value. +- **Create Ticket:** Generates a new ticket on the specified Service Board. +- **Get All Tickets By Criteria:** Fetches existing tickets matching the subject and device. +- **Loop:** Iterates through fetched tickets to apply notes or status updates. +- **Create Ticket Notes:** Appends the `$ticketBody` to an existing ticket. +- **Update Ticket Status:** Closes the ticket if the action is `Close`. + +## Diagram + +*End‑to‑end flow of the workflow. Every shape is annotated with what that node actually does, the arrows show execution order, and the edge labels (`toCreate`, `Catchall`, `isNotClosed`, `toClose`) mirror the configured condition branches. Colour coding matches the workflow builder (green = trigger, blue = action, magenta = variable, grey = condition, orange = loop container, green circle = start event, blue circle = branch/iteration stop).* + +```mermaid +%%{init: {"flowchart": {"htmlLabels": true, "curve": "basis", "rankSpacing": 65, "nodeSpacing": 45}}}%% +flowchart TD + + %% ---------- MAIN SPINE ---------- + T(["Trigger — CWRMM Ticket Management for Monitors
Webhook entry point; receives Action, TicketSubject, TicketBody, DeviceId"]) + EP["Action — Get A Endpoint By Criteria
Resolve DeviceId → CW RMM endpoint"] + VAR["Variable — NoOne
Blank assignee (single space) to avoid default assignment"] + CHK1{"Condition — actionCheck
Branch on payload Action value"} + + T --> EP + EP --> VAR + VAR --> CHK1 + + %% ---------- BRANCH: CREATE ---------- + CREATE["Action — Create Ticket
Create a new ticket on the Service Board for the endpoint"] + STOP1((■)) + CHK1 -- toCreate --> CREATE + CREATE --> STOP1 + + %% ---------- BRANCH: CLOSE / COMMENT ---------- + GET["Action — Get All Tickets By Criteria
Fetch existing tickets matching subject + device"] + CHK1 -- Catchall --> GET + + %% ---------- LOOP CONTAINER ---------- + subgraph LOOP ["Loop (For Each) — Close Open Tickets · iterate every ticket returned above"] + direction TD + LS((▶)) + CHK2{"Condition — isNotClosed
Is the current ticket still open?"} + NOTE["Action — Create Ticket Notes
Append TicketBody as an internal note / comment"] + CHK3{"Condition — actionCheck
Is Action = Close ?"} + UPDATE["Action — Update Ticket Status
Set the ticket status → Closed"] + STOP2((■)) + STOP3((■)) + STOP4((■)) + + LS --> CHK2 + CHK2 -- isNotClosed --> NOTE + NOTE --> CHK3 + CHK2 -- Catchall --> STOP2 + CHK3 -- toClose --> UPDATE + UPDATE --> STOP3 + CHK3 -- Catchall --> STOP4 + end + + GET --> LS + LOOP --> STOPF((■)) + + %% ---------- STYLING ---------- + classDef trigger fill:#16a34a,stroke:#0f7a37,color:#ffffff,stroke-width:2px; + classDef action fill:#2563eb,stroke:#1746b8,color:#ffffff,stroke-width:2px; + classDef variable fill:#be185d,stroke:#83103f,color:#ffffff,stroke-width:2px; + classDef condition fill:#6b7280,stroke:#3f4654,color:#ffffff,stroke-width:2px; + classDef startev fill:#22c55e,stroke:#15803d,color:#ffffff,stroke-width:2px; + classDef stop fill:#1e3a8a,stroke:#13265c,color:#ffffff,stroke-width:2px; + + class T trigger; + class EP,CREATE,GET,NOTE,UPDATE action; + class VAR variable; + class CHK1,CHK2,CHK3 condition; + class LS startev; + class STOP1,STOP2,STOP3,STOP4,STOPF stop; + + style LOOP fill:#fff7ed,stroke:#ea580c,stroke-width:2px,color:#111827; +``` + +### Legend + +- 🟩 **Trigger** – the webhook that catches the monitor payload (entry point). +- 🟦 **Action** – an API/operation step (endpoint lookup, ticket create, get tickets, add note, update status). +- 🟪 **Variable** – sets a value used later (`NoOne` blank assignee). +- ◆ **Condition** (grey diamond) – a branch point; outgoing arrows are labelled with the branch taken. +- 🟧 **Loop (For Each)** – orange container; the steps inside repeat once per ticket returned by *Get All Tickets By Criteria*. +- 🟢 **Start event** (▶) – beginning of a loop iteration. +- 🔵 **Stop** (■) – end of a branch or of the current loop iteration (the loop then advances to the next ticket). + +### How to read the two paths + +- **`Action = Create`** → `actionCheck` takes the **toCreate** branch → *Create Ticket* → stop. (No duplicate check is performed here; the calling monitor script is responsible for firing `Create` only once per incident.) +- **`Action = Close` or `Comment`** → `actionCheck` takes the **Catchall** branch → *Get All Tickets By Criteria* → enter the **loop**. For each ticket: if it is already closed, the iteration ends (`isNotClosed` → Catchall); if it is open, *Create Ticket Notes* appends the body, then the inner `actionCheck` either closes it (*Update Ticket Status*, when `Action = Close`) or simply ends the iteration (when `Action = Comment`, the note alone is the desired outcome). + +### Node Descriptions + +1. **Trigger (CWRMM Ticket Management for Monitors):** The entry point. A webhook instance must be created here to generate the URL used by the monitoring scripts. +2. **Get A Endpoint By Criteria:** Retrieves the endpoint details in CW RMM using the `$deviceId` provided in the webhook payload. +3. **NoOne (Set Variable):** Sets a blank variable (a single space). This is passed to the "Assigned To" field in the Create Ticket action to prevent the ticket from defaulting to the workflow creator or any specific user. +4. **actionCheck (Condition):** Evaluates the `$action` payload property. + - **If Action is 'Create':** Routes to the **Create Ticket** action. This generates a new ticket for the `$deviceId` using the `$ticketSubject` and `$ticketBody`. + - **Catchall (Close/Comment):** Routes to the **Get All Tickets By Criteria** action to fetch existing tickets for the device matching the `$ticketSubject`. +5. **Close Open Tickets (Loop):** Iterates through all tickets returned by the previous action. +6. **isNotClosed (Condition inside Loop):** Checks the status of the current ticket. + - **If Closed/Resolved:** The loop skips to the next ticket. + - **If Open:** Routes to **Create Ticket Notes**. +7. **Create Ticket Notes:** Adds the `$ticketBody` as an internal note or comment to the ticket. +8. **Check Action (Condition inside Loop):** Evaluates if `$action` equals 'Close'. + - **If 'Close':** Routes to **Update Ticket Status**, which changes the ticket status to Closed. + - **If 'Comment':** The loop moves to the next ticket, as the note has already been added. + +## Sample Ticket + +**Ticket Subject:** +Enhanced Drive Space Monitoring - C - SERVER01 - 10 Percent + +**Ticket Body:** +Drive C has breached the free space threshold. + +Total Space: 100 GB +Used Space: 90 % +Free Space: 10 % +Threshold: 10 % + +>*(If the action is Close, the body will append: "Drive space has recovered and is now above the threshold. The ticket can be closed.")* + +## Workflow Creation + +Install the workflow from the `ProVal - Content` Community. After installation, you must perform the following mandatory configuration steps: + +![Image1](../../../static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image1.webp) + +### 1. Create the Webhook Instance + +Webhook instances are environment-specific and cannot be migrated. You must create a new instance inside the trigger. + +![Image2](../../../static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image2.webp) + +1. Open the workflow and click on the **Trigger** node. +2. Under **Webhook Trigger**, click **New Webhook Instance +**. +3. Name it `CWRMM Ticket Management for Monitors`. +4. **Copy the URL** generated for this instance. +5. **Mandatory:** Take this URL and set it as the default value for the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field in CW RMM. Without this, your monitor scripts will not know where to send their payloads. + +### 2. Configure the Create Ticket Action + +The workflow installs with default options for ticket creation. You must update these to match your environment's Service Board and assignment rules. + +![Image3](../../../static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image3.webp) + +1. Open the **Create Ticket** action node. +2. Update the **Service Board** to the correct board for your alerts. +3. Update the **Assigned To** field as required by your environment (by default, it passes the `NoOne` variable to leave it unassigned). +4. Save the workflow. + +**Primary Note: User Permissions** +The workflow runs under the context of the user account that created it. Therefore, **the workflow must be created by a user account with access to all devices in the environment.** + +If the creating user does not have permission to a specific machine, the workflow will fail to create or update tickets for that device, and monitoring will silently fail for that endpoint. + +## Completed Screenshot + +![Image4](../../../static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image4.webp) + +## Changelog + +### 2025-07-23 + +- Initial version of the document diff --git a/docs/solutions/cpu-threshold-violation-monitoring-cw-rmm.md b/docs/solutions/cpu-threshold-violation-monitoring-cw-rmm.md index abb95dc2d..b6a2ae4e2 100644 --- a/docs/solutions/cpu-threshold-violation-monitoring-cw-rmm.md +++ b/docs/solutions/cpu-threshold-violation-monitoring-cw-rmm.md @@ -3,13 +3,13 @@ id: '49b06af7-af3b-4aaa-a90c-8efb28a65c9e' slug: /49b06af7-af3b-4aaa-a90c-8efb28a65c9e title: 'CPU Threshold Violation Monitoring' title_meta: 'CPU Threshold Violation Monitoring' -keywords: ['cpu', 'monitoring', 'windows', 'alerts', 'thresholds', 'performance'] -description: 'Intelligently monitors sustained high CPU usage using hierarchical custom thresholds with automatic ticket resolution.' +keywords: ['cpu', 'monitoring', 'windows', 'alerts', 'thresholds', 'performance', 'workflow', 'trigger'] +description: 'Intelligently monitors sustained high CPU usage using hierarchical custom thresholds. Offers a choice of built‑in or workflow‑based ticketing, both with automatic ticket resolution.' tags: ['performance', 'monitoring', 'windows'] draft: false unlisted: false last_update: - date: 2026-07-03 + date: 2026-07-23 --- ## Purpose @@ -18,25 +18,55 @@ The **CPU Threshold Violation Monitoring** solution continuously tracks CPU usag Unlike traditional monitoring that looks at a single fixed percentage, this solution uses a **two‑threshold logic**: -1. **High Threshold** – the CPU percentage that starts the timer. -2. **Low Threshold** – the percentage that resets the timer if the CPU falls below it. +1. **High Threshold** – the CPU percentage that starts the timer. +2. **Low Threshold** – the percentage that resets the timer if the CPU falls below it. 3. **Sustained Minutes** – the number of minutes the CPU must remain above the low threshold (after first exceeding the high threshold) before an alert is raised. The actual monitoring is performed by a lightweight monitor that reads a local JSON configuration file every 15 minutes. A separate daily configuration writer task pulls the threshold values from ConnectWise RMM custom fields and writes that file, allowing you to manage policies entirely through custom fields without touching scripts. +The solution ships with **two monitor sets** that detect sustained high CPU identically but handle ticketing differently. You choose the one that fits the partner — see [Ticketing Options](#ticketing-options). + ### Key Capabilities -- **Two‑Threshold Alerting with Automatic Reset:** Alert only on sustained high CPU. Brief spikes that drop back down reset the timer automatically. -- **Automatic Ticket Resolution:** Once CPU falls below the low threshold, the monitor clears and the ticket is automatically closed. -- **Hierarchical Policy Management:** Set Company‑wide defaults, override them at a Site, or set a unique value for a single Endpoint – all through custom fields. -- **OS‑Aware Targeting:** Servers and Workstations automatically use their respective threshold fields (`_Svr` / `_Wks`). -- **Configuration / Monitoring Decoupling:** Threshold changes take effect on the next daily (or manual) run of the configuration writer, without modifying the monitor itself. +- **Two‑Threshold Alerting with Automatic Reset** – Alert only on sustained high CPU. Brief spikes that drop back below the low threshold reset the timer automatically. +- **Choice of Ticketing Approach** – Use the monitor's **built‑in ticketing** for zero extra setup, or the optional **[Workflow] monitor** for clean, standardized tickets driven by the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow. See [Ticketing Options](#ticketing-options). +- **Automatic Ticket Resolution (both options)** – When CPU falls below the low threshold, the open ticket is closed automatically — by the monitor's auto‑resolution rule (built‑in) or by a `Close` webhook (workflow). No manual closure is required. +- **No Duplicate Tickets (both options)** – A persistent high‑CPU condition never spawns repeated tickets. The built‑in monitor comments on the existing ticket; the [Workflow] monitor tracks state locally so exactly one ticket exists per incident (with no comment spam). +- **Informative Tickets** – The alert includes how long ago the spike began, the current CPU usage, the top five CPU‑consuming processes, and the PowerShell command line when PowerShell is among them. The [Workflow] variant presents this with a clean, controlled subject and body. +- **Hierarchical Policy Management** – Set Company‑wide defaults, override them at a Site, or set a unique value for a single Endpoint – all through custom fields. +- **OS‑Aware Targeting** – Servers and Workstations automatically use their respective threshold fields (`_Svr` / `_Wks`). +- **Configuration / Monitoring Decoupling** – Threshold changes take effect on the next daily (or manual) run of the configuration writer, without modifying the monitor itself. ### Important Caveats & Behavior -- **Daily Configuration Update:** The configuration writer task runs once per day. If you change a custom field, the new thresholds will only be picked up after the next scheduled run. You can manually run the task on an endpoint to apply changes immediately. -- **Monitor Interval:** The monitor checks CPU usage every 15 minutes against the stored configuration file. -- **Auto‑Resolution:** Tickets generated by this solution will be closed automatically when the CPU usage drops below the low threshold – no manual intervention required. +- **Daily Configuration Update** – The configuration writer task runs once per day. If you change a custom field, the new thresholds will only be picked up after the next scheduled run. You can manually run the task on an endpoint to apply changes immediately. +- **Monitor Interval** – The monitor checks CPU usage every 15 minutes against the stored configuration file. +- **Ticketing Behavior Depends on the Monitor** – Both monitors auto‑close the ticket on recovery, but the mechanism and the comment handling differ. See [Ticketing Options](#ticketing-options) for the exact behavior of each. +- **One Monitor Per Device** – The two monitors are mutually exclusive ticketing approaches. Import and deploy **only one** of them to a given device; deploying both would create conflicting or duplicate tickets. +- **Workflow Option Coverage** – The [Workflow] monitor depends on a valid webhook URL and on the workflow running under its creator's permissions. If the creating user lacks access to a device, ticketing will silently fail for that device. The built‑in monitor has no such dependency and works on all machines. + +## Ticketing Options + +This solution provides **two monitor sets** that perform identical sustained‑CPU detection but handle ticketing differently. **Choose one** and import only that monitor — they are mutually exclusive per device. + +| | Built‑in Ticketing | Workflow Ticketing | +|---|---|---| +| **Monitor** | [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) | [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) | +| **How tickets are created** | CW RMM monitor's built‑in ticketing | Webhook → [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow | +| **Ticket subject** | Set by the monitor; not customizable | Clean and controlled (e.g., `CPU Threshold Violation - CPU - SERVER01 - 95 Percent`) | +| **Ticket body** | The monitor's alert message in its fixed format (spike time, current usage, top five processes) | A clean, controlled message: how long ago the spike began, the low threshold held above, current usage, and the top five processes (+ PowerShell command line if applicable) | +| **While the alert persists** | Adds a comment to the open ticket on each detection (bulky ticket) | One ticket per incident; no comment spam | +| **Resolution** | Auto‑resolves via the monitor set's automatic resolution rule | Auto‑closes via a `Close` webhook to the workflow | +| **Device coverage** | Works on all machines | Depends on the workflow + a valid webhook URL + the workflow creator's device permissions | +| **Extra components required** | None | [Trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7), [Workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field, and a webhook instance | +| **Best for** | Partners who want zero extra setup | Partners who want clean, standardized tickets with no comment spam | + +**How to choose:** + +- Pick **Built‑in** if you want the simplest deployment and don't mind monitor‑generated ticket formatting and per‑detection comments. +- Pick **Workflow** if you want clean, consistently formatted tickets without comment spam — and you can complete the one‑time workflow/webhook setup (and ensure the workflow is created by a user with access to all monitored devices). Both options auto‑close the ticket on recovery, so the decision comes down to ticket quality and setup versus universal coverage. + +> Whichever you choose, the **group**, the **configuration writer task**, the **threshold / sustained‑minutes / enablement custom fields**, and the **local config file** are shared and identical. Only the monitor — and, for the Workflow option, the trigger / workflow / webhook field — differ. ## Associated Content @@ -44,23 +74,38 @@ The actual monitoring is performed by a lightweight monitor that reads a local J | Name | Purpose | |---|---| -| [CPU Threshold Violation Monitoring](/docs/006889e2-8977-4957-9c4d-7381bdbea9a0) | Dynamic group that targets Windows endpoints based on the `CTVM_Enable` custom fields, automatically pulling in devices authorized for monitoring. | +| [CPU Threshold Violation Monitoring](/docs/006889e2-8977-4957-9c4d-7381bdbea9a0) | Dynamic group that targets Windows endpoints based on the `CTVM_Enable` custom fields, automatically pulling in devices authorized for monitoring. Shared by both ticketing options. | ### Task | Name | Purpose | |---|---| -| [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) | Runs daily to resolve hierarchical thresholds from custom fields and writes the JSON configuration file for the monitor. | +| [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) | Runs daily to resolve hierarchical thresholds from custom fields and writes the JSON configuration file for the monitor. Also writes the workflow webhook URL (`TicketWebhookUrl`) into the file when the `Ticket_Mgmt_Webhook_Url` field is populated. Shared by both ticketing options. | ### Monitor +> Choose **one** of the following monitors to import. They are mutually exclusive ticketing approaches for the same solution — do not deploy both to the same device. See [Ticketing Options](#ticketing-options) for help choosing. + +| Name | Ticketing | Purpose | +|---|---|---| +| [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) | Built‑in | Runs every 15 minutes, reading the local JSON configuration file. Evaluates sustained CPU usage via the two‑threshold logic and generates / resolves tickets using the monitor's built‑in ticketing. | +| [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) | Workflow | Runs every 15 minutes, reading the same JSON configuration file. Performs the same CPU evaluation but creates and closes tickets by firing webhooks to the CWRMM Ticket Management workflow, producing clean tickets with no comment spam. | + +### Trigger (Workflow option only) + | Name | Purpose | |---|---| -| [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) | Executes every 15 minutes, reading the local JSON configuration file. Checks CPU usage and generates or resolves tickets based on the two‑threshold logic. | +| [CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) | Webhook trigger that receives `Create` / `Close` / `Comment` payloads from the [Workflow] monitor and starts the ticketing workflow. Required only for the Workflow option. | + +### Workflow (Workflow option only) + +| Name | Purpose | +|---|---| +| [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) | Creates, closes, and comments on ConnectWise tickets based on the monitor's webhook payloads. Required only for the Workflow option. | ### Custom Fields: Enablement -These fields act as the “on/off switch” for the solution. They use a Dropdown (`Enable` or `Disable`) to dynamically target devices. +These fields act as the "on/off switch" for the solution. They use a Dropdown (`Enable` or `Disable`) to dynamically target devices. | Name | Level | Type | Purpose | |---|---|---|---| @@ -106,13 +151,21 @@ Enter the number of minutes the CPU must remain above the low threshold (after s | [CTVM_UsageMins_Wks_Site](/docs/3abfb2fc-2278-46d5-beb9-e26fa4c20a6f) | Site | Text Box | Site‑level override for workstations. | | [CTVM_UsageMins](/docs/3f442fad-1a4a-4793-91f3-46ee9b16e956) | Endpoint | Text Box | Endpoint‑level override (applies to both OS types). | +### Custom Fields: Ticketing (Workflow option only) + +Required only when you deploy the [Workflow] monitor. The configuration writer reads this field and writes its value into the config file as `TicketWebhookUrl`, which the [Workflow] monitor uses to reach the ticketing workflow. If you deploy the built‑in monitor only, you may skip this field — the configuration writer tolerates its absence. + +| Name | Level | Type | Purpose | +|---|---|---|---| +| [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) | Company | Text Box | Stores the workflow's webhook URL (copied from the trigger's webhook instance) as its Default Value. | + ## Implementation -Follow these steps in order to successfully deploy the CPU Threshold Violation Monitoring solution in your environment. +Follow these steps in order. Steps 1–3 and 6–8 are common to both ticketing options. **Steps 4–5** are where you choose and configure your ticketing approach — import **only** the monitor for the option you choose. -### Step 1: Create the Following Custom Fields +### Step 1: Create the Shared Custom Fields -Create all 20 custom fields listed below in ConnectWise RMM. These are required for the solution to function correctly. +Create all 20 shared custom fields listed below in ConnectWise RMM. These are required regardless of which ticketing option you choose. - [Custom Field: CTVM_Enable](/docs/aa6be36d-3653-4f68-b9fe-5bdb7c7f5c20) - [Custom Field: CTVM_Enable_Svr](/docs/5f3cb7ce-6d25-4199-9434-574fb2ed6542) @@ -141,29 +194,50 @@ Create the dynamic group that will automatically target the enabled machines. - [CPU Threshold Violation Monitoring](/docs/006889e2-8977-4957-9c4d-7381bdbea9a0) -### Step 3: Create the Task +### Step 3: Create the Configuration Writer Task Set up the configuration writer script that will run on the targeted endpoints. - [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) -### Step 4: Create the Monitor +> This task also writes the workflow webhook URL into the config file when the `Ticket_Mgmt_Webhook_Url` field is populated. If you choose the built‑in option and skip that field, the task still runs successfully (the relevant row is set to Continue on Failure) and simply writes a placeholder the built‑in monitor ignores. -Set up the monitor that will evaluate CPU usage against the configuration file. +### Step 4: Choose Your Ticketing Approach + +Decide whether the partner will use **Built‑in** or **Workflow** ticketing. See [Ticketing Options](#ticketing-options) for the comparison and guidance. Your choice determines which monitor you import in Step 5 and whether you need the workflow components. + +### Step 5: Create the Monitor (and Workflow Components, if applicable) + +#### Option A — Built‑in Ticketing + +Import the original monitor. No additional components are required. - [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) -### Step 5: Schedule the Task +#### Option B — Workflow Ticketing + +Import the [Workflow] monitor **and** set up the workflow components. Complete the following in order: + +1. Create the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field (Company, Text Box). +2. Install the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow from the `ProVal - Content` Community. This also installs the [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7). +3. In the workflow's **Trigger** node, create a new webhook instance named `CWRMM Ticket Management for Monitors` and **copy the generated URL**. (See the workflow document's *Create the Webhook Instance* section.) +4. Set that URL as the **Default Value** of the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field and save. Then run the Configuration Writer task once (or wait for the next daily run) so the config file is populated with the real URL before the [Workflow] monitor relies on it. +5. Configure the workflow's **Create Ticket** action (Service Board and assignment) to match your environment. (See the workflow document's *Configure the Create Ticket Action* section.) +6. Import the [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) monitor. + +> **Important — user permissions:** The workflow runs under the context of the user account that creates it. Create the workflow with a user that has access to **all** devices you intend to monitor; otherwise ticket creation/closure will silently fail for any device that user cannot access. See the workflow document for details. + +### Step 6: Schedule the Task Schedule the [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) task to run once per day against the [CPU Threshold Violation Monitoring](/docs/006889e2-8977-4957-9c4d-7381bdbea9a0) group, as described in the [Schedule Task](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a#schedule-task) section of the task's document. -The monitor is already configured to run every 15 minutes against the same group and requires no additional scheduling. +Whichever monitor you imported is already configured to run every 15 minutes against the same group and requires no additional scheduling. -### Step 6: Review and Set Threshold Custom Fields +### Step 7: Review and Set Threshold Custom Fields Review the default threshold values and set the threshold custom fields as required for your environments. You can set broad Company‑level baselines, and then configure specific Site‑level or Endpoint‑level overrides where tighter rules or exceptions are needed. -### Step 7: Review and Set Enablement Custom Fields +### Step 8: Review and Set Enablement Custom Fields Set the enablement custom fields to `Enable` for the client, location, or specific endpoint to turn the solution and monitoring on for them. Machines will not be monitored until they are explicitly enabled via these dropdown fields. @@ -171,17 +245,27 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: How does the CPU Threshold Violation monitor work? -> A local configuration file (JSON) is written daily by the configuration writer task. The monitor reads this file every 15 minutes, samples the CPU over 10 seconds, and applies a two‑threshold logic: +> A local configuration file (JSON) is written daily by the configuration writer task. The monitor reads this file every 15 minutes, samples the CPU over 10 seconds, and applies a two‑threshold logic: > -> - When CPU first exceeds the **high threshold**, a marker is created and the timer starts. -> - As long as CPU stays above the **low threshold**, the timer continues. -> - If the timer reaches the **sustained minutes**, an alert ticket is created. +> - When CPU first exceeds the **high threshold**, a marker is created and the timer starts. +> - As long as CPU stays above the **low threshold**, the timer continues. +> - If the timer reaches the **sustained minutes**, an alert is raised. > - If at any point CPU drops below the low threshold, the marker is deleted and the timer resets. +> +> The two available monitors detect sustained high CPU identically — they differ only in how they create and close tickets (see [Ticketing Options](#ticketing-options)). + +### Q: What's the difference between the two monitors, and which should I choose? + +> Both monitors read the same config file and detect sustained high CPU the same way, and both close the ticket automatically on recovery. The **built‑in** monitor ([CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b)) uses the monitor's native ticketing — simplest to deploy, but tickets carry monitor‑generated formatting and a comment is added on each detection. The **[Workflow]** monitor ([CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9)) creates clean, consistently formatted tickets via the [CWRMM workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) with no comment spam — at the cost of a one‑time workflow/webhook setup. See the [Ticketing Options](#ticketing-options) comparison table. + +### Q: Do I need to import both monitors? -### Q: What are the default thresholds if I don’t set any custom fields? +> No. The two monitors are mutually exclusive ticketing approaches for the same solution. Import and deploy **only one** per device. Deploying both would create conflicting or duplicate tickets. -> For **servers**: High = 95%, Low = 90%, Sustained Minutes = 30. -> For **workstations**: High = 90%, Low = 85%, Sustained Minutes = 30. +### Q: What are the default thresholds if I don't set any custom fields? + +> For **servers**: High = 95%, Low = 90%, Sustained Minutes = 30. +> For **workstations**: High = 90%, Low = 85%, Sustained Minutes = 30. > These defaults are used when no Company, Site, or Endpoint field is populated. ### Q: I changed a threshold custom field. When will the new values take effect? @@ -194,7 +278,34 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: Will the ticket close automatically when the CPU usage drops? -> Yes. The monitor runs the same script for resolution. When the CPU falls below the low threshold, the marker file is deleted and the script outputs nothing, which signals a healthy state. The monitor’s automatic resolution rule then closes the ticket. +> Yes, with either monitor. The built‑in monitor re‑runs the same script for resolution; when the CPU falls below the low threshold the marker file is deleted and the script outputs nothing, so the monitor's automatic resolution rule closes the ticket. The [Workflow] monitor fires a `Close` webhook on recovery and the workflow closes the ticket. Neither requires manual closure. + +### Q: Will the monitor create a new ticket every time it detects high CPU usage? + +> No. With the built‑in monitor, the first sustained detection creates a ticket and subsequent detections add a comment to that same ticket. With the [Workflow] monitor, a local state machine ensures exactly one ticket exists per incident and adds no comments. In both cases a new ticket is only created if the issue was resolved (CPU dropped below the low threshold) and later re‑occurs. + +### Q: Why does the Workflow option need a trigger, a workflow, and a custom field? + +> The [Workflow] monitor doesn't create tickets itself — it sends an HTTP POST (a webhook) to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow, which performs the actual ticket creation/closure in ConnectWise. The [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) provides the webhook endpoint, and the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field stores that endpoint's URL so the configuration writer can pass it to the monitor via the config file. + +### Q: The [Workflow] monitor isn't creating or closing tickets. What should I check? + +> Work through this checklist: +> +> 1. The [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field's Default Value is set to the **real** webhook instance URL (not the placeholder). +> 2. A webhook instance was created in the workflow's trigger and the URL was copied from it. +> 3. The [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) is installed and published, and its Create Ticket action is configured for a valid Service Board. +> 4. The Configuration Writer task was re‑run **after** the URL was set, so the config file contains the real URL. +> 5. The user who created the workflow has access to the affected device (see the next question). +> 6. The monitor's output for an action run — a failed webhook is logged there. + +### Q: Why does the [Workflow] monitor work on some machines but not others? + +> The workflow runs under the context of the user account that created it. If that user lacks permission to a specific device, the workflow cannot create or update tickets for it and ticketing silently fails for that endpoint. Create the workflow with a user that has access to all monitored devices. The built‑in monitor has no such dependency. + +### Q: Can I switch from built‑in to workflow ticketing (or vice versa) later? + +> Yes. Import the monitor for the new approach (and, for workflow, complete the trigger/workflow/webhook setup), then remove or disable the old monitor so both aren't active at once. Note that tickets already opened by the old approach won't be auto‑managed by the new one — close any stragglers manually. ### Q: Why isn't a specific device being monitored? @@ -214,7 +325,7 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: What does the monitor ticket contain? -> The ticket includes the time the CPU spiked above the high threshold, the current CPU usage, a list of the top five CPU‑consuming processes, and if applicable, the command line of any PowerShell process that is consuming CPU. +> The ticket body includes how long ago the CPU spiked above the high threshold, the current CPU usage, a list of the top five CPU‑consuming processes, and — if applicable — the command line of any PowerShell process that is consuming CPU. The [Workflow] monitor presents this with a clean, controlled subject (e.g., `CPU Threshold Violation - CPU - SERVER01 - 95 Percent`) and body; the built‑in monitor uses the monitor's default subject and formatting. ### Q: Can I disable monitoring for a single server temporarily without deleting custom fields? @@ -224,24 +335,27 @@ Set the enablement custom fields to `Enable` for the client, location, or specif > Manual changes to the JSON file will be overwritten the next time the configuration writer task runs. Always adjust thresholds through the custom fields to ensure consistency. -### Q: The monitor output says “CPU monitoring configuration file not found.” What should I do? +### Q: The monitor output says "CPU monitoring configuration file not found." What should I do? > This means the configuration writer task has not yet run on that endpoint. Run the [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) task manually or wait for the next daily schedule. -### Q: Will the monitor create a new ticket every time it detects high CPU usage? - -> No. The first time a sustained high‑CPU condition is detected, a ticket is created. On subsequent checks, if the same condition is still active and the original ticket is still open, the monitor will **add a comment** to the existing ticket instead of creating a duplicate. This prevents ticket clutter and keeps all related information in one place. A new ticket is not created until the issue is resolved (CPU drops below low threshold) and then re‑occurs. +### Q: The monitor isn't generating tickets, or the ticket status isn't updating. How can I check if the configuration file is correct? -### Q: The monitor isn’t generating tickets, or the ticket status isn’t updating. How can I check if the configuration file is correct? - -> Verify that the configuration file exists at `C:\ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json`. If the file is missing, run the [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) task manually on the endpoint. If the file exists but the thresholds seem wrong, check the custom fields at the Endpoint, Site, and Company levels; the task will use the first valid value it finds in that order. After updating fields, manually run the task again to refresh the file. +> Verify that the configuration file exists at `C:\ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.json`. If the file is missing, run the [CPU Threshold Violation Monitoring Configuration Writer](/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a) task manually on the endpoint. If the file exists but the thresholds seem wrong, check the custom fields at the Endpoint, Site, and Company levels; the task will use the first valid value it finds in that order. After updating fields, manually run the task again to refresh the file. (For the [Workflow] monitor, also confirm the `TicketWebhookUrl` value in the file is the real webhook URL — see the workflow troubleshooting question above.) ### Q: How can I tell if the monitor is actually running and evaluating CPU usage? -> Check the monitor’s run history in the ConnectWise RMM console. You can also look for the marker file `C:\ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.flag`. If it exists, a high‑CPU event is currently being tracked. If it does not exist, either no spike has occurred or the CPU has dropped below the low threshold and the timer has reset. +> Check the monitor's run history in the ConnectWise RMM console. You can also look for the marker file `C:\ProgramData\_Automation\Script\Test-CPUUsage\Test-CPUUsage.flag`. If it exists, a high‑CPU event is currently being tracked. If it does not exist, either no spike has occurred or the CPU has dropped below the low threshold and the timer has reset. ## Changelog +### 2026-07-23 + +- **New Optional Workflow Ticketing:** Added a second, optional monitor — [CPU Threshold Violation Monitoring [Workflow]](/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9) — that creates clean tickets and auto‑closes them on recovery by firing webhooks to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow, with no per‑detection comment spam. +- **New Components (Workflow option only):** Added the [CWRMM Ticket Management for Monitors trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) and [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), and the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field that stores the webhook URL. +- **Partner Choice:** The two monitors are mutually exclusive ticketing approaches. Added a [Ticketing Options](#ticketing-options) comparison and reworked Implementation so partners import **only** the monitor they choose; the group, task, and threshold/sustained‑minutes/enablement fields are shared. Both options auto‑close tickets on recovery; the [Workflow] option's advantages are clean tickets and no comment spam. +- The original built‑in monitor [CPU Threshold Violation Monitoring](/docs/b03e0a64-8e91-4d2b-b08a-d320713e295b) remains available unchanged. + ### 2026-07-03 - Initial version of the document diff --git a/docs/solutions/enhanced-drive-space-monitoring-cw-rmm.md b/docs/solutions/enhanced-drive-space-monitoring-cw-rmm.md index caccfab87..0e9766816 100644 --- a/docs/solutions/enhanced-drive-space-monitoring-cw-rmm.md +++ b/docs/solutions/enhanced-drive-space-monitoring-cw-rmm.md @@ -3,38 +3,65 @@ id: 'e9cf4ff0-4413-447b-97dd-b8b2abd59597' slug: /e9cf4ff0-4413-447b-97dd-b8b2abd59597 title: 'Enhanced Drive Space Monitoring' title_meta: 'Enhanced Drive Space Monitoring' -keywords: ['monitoring', 'drive', 'space', 'thresholds', 'tickets'] -description: 'Intelligently monitors local drive free space using capacity‑based hierarchical thresholds with automatic ticket resolution.' +keywords: ['monitoring', 'drive', 'space', 'thresholds', 'tickets', 'workflow', 'trigger'] +description: 'Intelligently monitors local drive free space using capacity‑based hierarchical thresholds. Offers a choice of built‑in or workflow‑based ticketing, both with automatic ticket resolution.' tags: ['disk', 'monitoring', 'windows'] draft: false unlisted: false last_update: - date: 2026-07-02 + date: 2026-07-23 --- ## Purpose The **Enhanced Drive Space Monitoring** solution provides an intelligent, highly customizable automated health check for local drive space across your managed Windows endpoints. -Instead of relying on a single, rigid “one‑size‑fits‑all” threshold, this solution dynamically calculates the total capacity of each logical drive and assigns it to a specific storage tier (e.g., 16–300 GB, 300–1024 GB). It then compares the available free space against flexible, hierarchical limits that you control, using percentages, megabytes, or gigabytes. +Instead of relying on a single, rigid "one‑size‑fits‑all" threshold, this solution dynamically calculates the total capacity of each logical drive and assigns it to a specific storage tier (e.g., 16–300 GB, 300–1024 GB). It then compares the available free space against flexible, hierarchical limits that you control, using percentages, megabytes, or gigabytes. The monitoring itself is performed by a lightweight **monitor set** that runs every hour, reading a local JSON configuration file. A separate daily **configuration writer** task pulls your custom field settings and updates that file, so you can adjust thresholds without ever editing a script. +The solution ships with **two monitor sets** that detect low drive space identically but handle ticketing differently. You choose the one that fits the partner — see [Ticketing Options](#ticketing-options). + ### Key Capabilities -- **Decoupled Configuration & Monitoring** – Thresholds are managed entirely through custom fields. The configuration writer runs once per day (or manually) to update the local config file; the monitor reads that file every hour to check drive health. -- **Automatic Ticket Resolution** – When drive space returns above its threshold, the monitor automatically resolves the ticket – no manual closure required. -- **Ticket Commenting, Not Duplication** – If a drive remains low on space, the monitor adds a comment to the existing open ticket instead of creating a new one, keeping all history in one place. -- **Hierarchical Policy Management** – Set Company‑wide baselines and override them for specific Sites or individual Endpoints. -- **OS‑Aware Targeting** – Servers and Workstations automatically use their respective threshold fields (`_Svr` / `_Wks`). +- **Decoupled Configuration & Monitoring** – Thresholds are managed entirely through custom fields. The configuration writer runs once per day (or manually) to update the local config file; the monitor reads that file every hour to check drive health. +- **Choice of Ticketing Approach** – Use the monitor's **built‑in ticketing** for zero extra setup, or the optional **[Workflow] monitor** for clean, standardized tickets driven by the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow. See [Ticketing Options](#ticketing-options). +- **Automatic Ticket Resolution (both options)** – When drive space returns above its threshold, the open ticket is closed automatically — by the monitor's auto‑resolve (built‑in) or by a `Close` webhook (workflow). No manual closure is required. +- **No Duplicate Tickets (both options)** – A persistent low‑space condition never spawns repeated tickets. The built‑in monitor comments on the existing ticket; the [Workflow] monitor tracks state locally so exactly one ticket exists per incident (with no comment spam). +- **Hierarchical Policy Management** – Set Company‑wide baselines and override them for specific Sites or individual Endpoints. +- **OS‑Aware Targeting** – Servers and Workstations automatically use their respective threshold fields (`_Svr` / `_Wks`). - **Smart Drive Targeting** – Explicitly include or exclude specific drive letters (e.g., `CDEF`, `Z`). ### Important Caveats & Behavior -- **Daily Configuration Refresh** – The configuration writer task runs once per day. If you change a custom field, the new thresholds take effect after the next scheduled run. You can run the writer manually to apply changes immediately. -- **Monitor Interval** – The monitor evaluates drive space every hour. At most, a newly low drive will be detected within one hour. -- **Auto‑Resolution** – Tickets are automatically closed once all drives meet or exceed their thresholds. -- **No Duplicate Tickets** – When a low‑space condition persists, the monitor comments on the existing ticket rather than creating a new one. +- **Daily Configuration Refresh** – The configuration writer task runs once per day. If you change a custom field, the new thresholds take effect after the next scheduled run. You can run the writer manually to apply changes immediately. +- **Monitor Interval** – The monitor evaluates drive space every hour. At most, a newly low drive will be detected within one hour. +- **Ticketing Behavior Depends on the Monitor** – Auto‑resolution and duplicate/comment handling differ between the two monitors. See [Ticketing Options](#ticketing-options) for the exact behavior of each. +- **One Monitor Per Device** – The two monitors are mutually exclusive ticketing approaches. Import and deploy **only one** of them to a given device; deploying both would create conflicting or duplicate tickets. +- **Workflow Option Coverage** – The [Workflow] monitor depends on a valid webhook URL and on the workflow running under its creator's permissions. If the creating user lacks access to a device, ticketing will silently fail for that device. The built‑in monitor has no such dependency and works on all machines. + +## Ticketing Options + +This solution provides **two monitor sets** that perform identical drive‑space detection but handle ticketing differently. **Choose one** and import only that monitor — they are mutually exclusive per device. + +| | Built‑in Ticketing | Workflow Ticketing | +|---|---|---| +| **Monitor** | [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) | [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) | +| **How tickets are created** | CW RMM monitor's built‑in ticketing | Webhook → [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow | +| **Ticket subject** | Set by the monitor; not customizable | Clean and controlled (e.g., `Enhanced Drive Space Monitoring - C - SERVER01 - 10 Percent`) | +| **Ticket body** | Includes monitor‑specific detail | Clean: total / used / free space plus the breached threshold | +| **While the alert persists** | Adds a comment to the open ticket on each detection | One ticket per incident; no comment spam | +| **Resolution** | Auto‑resolves when space recovers | Auto‑closes via a `Close` webhook when space recovers | +| **Device coverage** | Works on all machines | Depends on the workflow + a valid webhook URL + the workflow creator's device permissions | +| **Extra components required** | None | [Trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7), [Workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field, and a webhook instance | +| **Best for** | Partners who want zero extra setup | Partners who want clean, standardized tickets with full lifecycle automation | + +**How to choose:** + +- Pick **Built‑in** if you want the simplest deployment and don't mind monitor‑generated ticket formatting and per‑detection comments. +- Pick **Workflow** if you want clean, consistently formatted tickets that auto‑close on recovery without comment spam — and you can complete the one‑time workflow/webhook setup (and ensure the workflow is created by a user with access to all monitored devices). + +> Whichever you choose, the **group**, the **configuration writer task**, the **threshold / drive / enablement custom fields**, and the **local config file** are shared and identical. Only the monitor — and, for the Workflow option, the trigger / workflow / webhook field — differ. ## Associated Content @@ -42,23 +69,38 @@ The monitoring itself is performed by a lightweight **monitor set** that runs ev | Name | Purpose | |---|---| -| [Enhanced Drive Space Monitoring](/docs/475ce8e8-458e-4901-bdfc-18e79f62c549) | Dynamic group that targets Windows endpoints based on the `EDSM_Enable` custom fields, automatically pulling in devices authorized for monitoring. | +| [Enhanced Drive Space Monitoring](/docs/475ce8e8-458e-4901-bdfc-18e79f62c549) | Dynamic group that targets Windows endpoints based on the `EDSM_Enable` custom fields, automatically pulling in devices authorized for monitoring. Shared by both ticketing options. | ### Task | Name | Purpose | |---|---| -| [Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) | Runs daily to resolve hierarchical thresholds from custom fields and writes the JSON configuration file for the monitor. | +| [Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) | Runs daily to resolve hierarchical thresholds from custom fields and writes the JSON configuration file for the monitor. Also writes the workflow webhook URL (`TicketWebhookUrl`) into the file when the `Ticket_Mgmt_Webhook_Url` field is populated. Shared by both ticketing options. | ### Monitor +> Choose **one** of the following monitors to import. They are mutually exclusive ticketing approaches for the same solution — do not deploy both to the same device. See [Ticketing Options](#ticketing-options) for help choosing. + +| Name | Ticketing | Purpose | +|---|---|---| +| [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) | Built‑in | Runs every hour, reading the local JSON configuration file. Checks drive free space against capacity‑based thresholds and generates / resolves tickets using the monitor's built‑in ticketing. | +| [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) | Workflow | Runs every hour, reading the same JSON configuration file. Performs the same drive checks but creates and closes tickets by firing webhooks to the CWRMM Ticket Management workflow, producing clean tickets with no comment spam. | + +### Trigger (Workflow option only) + | Name | Purpose | |---|---| -| [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) | Executes every hour, reading the local JSON configuration file. Checks drive free space against capacity‑based thresholds and generates or resolves tickets automatically. | +| [CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) | Webhook trigger that receives `Create` / `Close` / `Comment` payloads from the [Workflow] monitor and starts the ticketing workflow. Required only for the Workflow option. | + +### Workflow (Workflow option only) + +| Name | Purpose | +|---|---| +| [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) | Creates, closes, and comments on ConnectWise tickets based on the monitor's webhook payloads. Required only for the Workflow option. | ### Custom Fields: Enablement -These fields act as the “on/off switch” for the solution. They use a Dropdown (`Enable` or `Disable`) to dynamically target devices. +These fields act as the "on/off switch" for the solution. They use a Dropdown (`Enable` or `Disable`) to dynamically target devices. | Name | Level | Type | Purpose | |---|---|---|---| @@ -92,55 +134,63 @@ Use these fields to explicitly ignore specific drive letters, such as a dedicate | [EDSM_DrivesToExclude_Wks](/docs/5bdbc620-2b0f-4217-9009-78ebdfebbda1) | Site | Text Box | Site‑level override for workstation drives to ignore. | | [EDSM_DrivesToExclude](/docs/10713e2f-1457-4e60-8903-232032cc033e) | Endpoint | Text Box | Endpoint‑level override for specific machine exclusions. | -### Custom Fields: 16–300 GB Thresholds +### Custom Fields: 16–300 GB Thresholds Accepts values in percentages (`10%`), Megabytes (`500MB`), or Gigabytes (`15GB`). Set to `0` to disable monitoring for this capacity bucket. | Name | Level | Type | Purpose | |---|---|---|---| -| [EDSM_16To300Threshold_Svr](/docs/b6af6e72-388a-49e8-8cd1-658d240b8813) | Company | Text Box | Company baseline for 16‑300 GB server drives (Default: 10%). | -| [EDSM_16To300Threshold_Wks](/docs/8bc872f6-4810-4414-9532-ddec748df9ea) | Company | Text Box | Company baseline for 16‑300 GB workstation drives (Default: 10%). | -| [EDSM_16To300Threshold_Svr](/docs/84777e7f-983e-4a36-a61b-248f7a83aacf) | Site | Text Box | Site‑level override for 16‑300 GB server drives. | -| [EDSM_16To300Threshold_Wks](/docs/56bf11f7-32ef-477f-872c-1d2751999054) | Site | Text Box | Site‑level override for 16‑300 GB workstation drives. | -| [EDSM_16To300Threshold](/docs/1a511334-ec72-4984-bd5c-fa6dd0e28ce4) | Endpoint | Text Box | Endpoint‑level override for 16‑300 GB drives. | +| [EDSM_16To300Threshold_Svr](/docs/b6af6e72-388a-49e8-8cd1-658d240b8813) | Company | Text Box | Company baseline for 16‑300 GB server drives (Default: 10%). | +| [EDSM_16To300Threshold_Wks](/docs/8bc872f6-4810-4414-9532-ddec748df9ea) | Company | Text Box | Company baseline for 16‑300 GB workstation drives (Default: 10%). | +| [EDSM_16To300Threshold_Svr](/docs/84777e7f-983e-4a36-a61b-248f7a83aacf) | Site | Text Box | Site‑level override for 16‑300 GB server drives. | +| [EDSM_16To300Threshold_Wks](/docs/56bf11f7-32ef-477f-872c-1d2751999054) | Site | Text Box | Site‑level override for 16‑300 GB workstation drives. | +| [EDSM_16To300Threshold](/docs/1a511334-ec72-4984-bd5c-fa6dd0e28ce4) | Endpoint | Text Box | Endpoint‑level override for 16‑300 GB drives. | + +### Custom Fields: 300–1024 GB Thresholds + +| Name | Level | Type | Purpose | +|---|---|---|---| +| [EDSM_300To1024Threshold_Svr](/docs/659635de-dd8e-41dc-8137-cb3a6c691e11) | Company | Text Box | Company baseline for 300‑1024 GB server drives (Default: 30GB). | +| [EDSM_300To1024Threshold_Wks](/docs/ac8bf87a-7ccf-4012-9831-02876c3bda70) | Company | Text Box | Company baseline for 300‑1024 GB workstation drives (Default: 30GB). | +| [EDSM_300To1024Threshold_Svr](/docs/29954063-87cc-4ffe-b3eb-dc07bc07d577) | Site | Text Box | Site‑level override for 300‑1024 GB server drives. | +| [EDSM_300To1024Threshold_Wks](/docs/9a923aa8-2038-49f1-9328-0ef473737799) | Site | Text Box | Site‑level override for 300‑1024 GB workstation drives. | +| [EDSM_300To1024Threshold](/docs/bc76dac2-4367-4eb6-a080-09390667ae57) | Endpoint | Text Box | Endpoint‑level override for 300‑1024 GB drives. | -### Custom Fields: 300–1024 GB Thresholds +### Custom Fields: 1024–4096 GB Thresholds | Name | Level | Type | Purpose | |---|---|---|---| -| [EDSM_300To1024Threshold_Svr](/docs/659635de-dd8e-41dc-8137-cb3a6c691e11) | Company | Text Box | Company baseline for 300‑1024 GB server drives (Default: 30GB). | -| [EDSM_300To1024Threshold_Wks](/docs/ac8bf87a-7ccf-4012-9831-02876c3bda70) | Company | Text Box | Company baseline for 300‑1024 GB workstation drives (Default: 30GB). | -| [EDSM_300To1024Threshold_Svr](/docs/29954063-87cc-4ffe-b3eb-dc07bc07d577) | Site | Text Box | Site‑level override for 300‑1024 GB server drives. | -| [EDSM_300To1024Threshold_Wks](/docs/9a923aa8-2038-49f1-9328-0ef473737799) | Site | Text Box | Site‑level override for 300‑1024 GB workstation drives. | -| [EDSM_300To1024Threshold](/docs/bc76dac2-4367-4eb6-a080-09390667ae57) | Endpoint | Text Box | Endpoint‑level override for 300‑1024 GB drives. | +| [EDSM_1024To4096Threshold_Svr](/docs/2a9bb294-b33e-4cfc-af75-52d5897d3f2d) | Company | Text Box | Company baseline for 1024‑4096 GB server drives (Default: 50GB). | +| [EDSM_1024To4096Threshold_Wks](/docs/6567dd43-e788-40bc-a592-afb94a40fc71) | Company | Text Box | Company baseline for 1024‑4096 GB workstation drives (Default: 50GB). | +| [EDSM_1024To4096Threshold_Svr](/docs/44e80c2c-dfdf-4e60-bbca-8599fad39d4e) | Site | Text Box | Site‑level override for 1024‑4096 GB server drives. | +| [EDSM_1024To4096Threshold_Wks](/docs/25e977e3-ba91-44bc-9f8d-e7054b04e14c) | Site | Text Box | Site‑level override for 1024‑4096 GB workstation drives. | +| [EDSM_1024To4096Threshold](/docs/b0a9f98d-c891-4903-bc9a-c526099a4076) | Endpoint | Text Box | Endpoint‑level override for 1024‑4096 GB drives. | -### Custom Fields: 1024–4096 GB Thresholds +### Custom Fields: 4096+ GB Thresholds | Name | Level | Type | Purpose | |---|---|---|---| -| [EDSM_1024To4096Threshold_Svr](/docs/2a9bb294-b33e-4cfc-af75-52d5897d3f2d) | Company | Text Box | Company baseline for 1024‑4096 GB server drives (Default: 50GB). | -| [EDSM_1024To4096Threshold_Wks](/docs/6567dd43-e788-40bc-a592-afb94a40fc71) | Company | Text Box | Company baseline for 1024‑4096 GB workstation drives (Default: 50GB). | -| [EDSM_1024To4096Threshold_Svr](/docs/44e80c2c-dfdf-4e60-bbca-8599fad39d4e) | Site | Text Box | Site‑level override for 1024‑4096 GB server drives. | -| [EDSM_1024To4096Threshold_Wks](/docs/25e977e3-ba91-44bc-9f8d-e7054b04e14c) | Site | Text Box | Site‑level override for 1024‑4096 GB workstation drives. | -| [EDSM_1024To4096Threshold](/docs/b0a9f98d-c891-4903-bc9a-c526099a4076) | Endpoint | Text Box | Endpoint‑level override for 1024‑4096 GB drives. | +| [EDSM_4096PlusThreshold_Svr](/docs/dcff8864-ef49-45aa-88c8-870911e1d618) | Company | Text Box | Company baseline for 4096+ GB server drives (Default: 100GB). | +| [EDSM_4096PlusThreshold_Wks](/docs/f7570818-8da7-4e29-b52b-ae9ba7085125) | Company | Text Box | Company baseline for 4096+ GB workstation drives (Default: 100GB). | +| [EDSM_4096PlusThreshold_Svr](/docs/c073dd2e-e7da-44b7-b74c-cad86b438946) | Site | Text Box | Site‑level override for 4096+ GB server drives. | +| [EDSM_4096PlusThreshold_Wks](/docs/68c691d5-4c53-4bf0-862f-415f4a1ff73a) | Site | Text Box | Site‑level override for 4096+ GB workstation drives. | +| [EDSM_4096PlusThreshold](/docs/3141a7bf-aea8-4703-92ea-7ad11e65ae6c) | Endpoint | Text Box | Endpoint‑level override for 4096+ GB drives. | + +### Custom Fields: Ticketing (Workflow option only) -### Custom Fields: 4096+ GB Thresholds +Required only when you deploy the [Workflow] monitor. The configuration writer reads this field and writes its value into the config file as `TicketWebhookUrl`, which the [Workflow] monitor uses to reach the ticketing workflow. If you deploy the built‑in monitor only, you may skip this field — the configuration writer tolerates its absence. | Name | Level | Type | Purpose | |---|---|---|---| -| [EDSM_4096PlusThreshold_Svr](/docs/dcff8864-ef49-45aa-88c8-870911e1d618) | Company | Text Box | Company baseline for 4096+ GB server drives (Default: 100GB). | -| [EDSM_4096PlusThreshold_Wks](/docs/f7570818-8da7-4e29-b52b-ae9ba7085125) | Company | Text Box | Company baseline for 4096+ GB workstation drives (Default: 100GB). | -| [EDSM_4096PlusThreshold_Svr](/docs/c073dd2e-e7da-44b7-b74c-cad86b438946) | Site | Text Box | Site‑level override for 4096+ GB server drives. | -| [EDSM_4096PlusThreshold_Wks](/docs/68c691d5-4c53-4bf0-862f-415f4a1ff73a) | Site | Text Box | Site‑level override for 4096+ GB workstation drives. | -| [EDSM_4096PlusThreshold](/docs/3141a7bf-aea8-4703-92ea-7ad11e65ae6c) | Endpoint | Text Box | Endpoint‑level override for 4096+ GB drives. | +| [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) | Company | Text Box | Stores the workflow's webhook URL (copied from the trigger's webhook instance) as its Default Value. | ## Implementation -Follow these steps in order to successfully deploy the Enhanced Drive Space Monitoring solution in your environment. +Follow these steps in order. Steps 1–3 and 6–8 are common to both ticketing options. **Steps 4–5** are where you choose and configure your ticketing approach — import **only** the monitor for the option you choose. -### Step 1: Create the Following Custom Fields +### Step 1: Create the Shared Custom Fields -Create all 35 custom fields listed below in ConnectWise RMM. These are required for the solution to function correctly. +Create all 35 shared custom fields listed below in ConnectWise RMM. These are required regardless of which ticketing option you choose. - [Custom Field: EDSM_16To300Threshold_Svr](/docs/b6af6e72-388a-49e8-8cd1-658d240b8813) - [Custom Field: EDSM_16To300Threshold_Wks](/docs/8bc872f6-4810-4414-9532-ddec748df9ea) @@ -180,33 +230,54 @@ Create all 35 custom fields listed below in ConnectWise RMM. These are required ### Step 2: Create the Group -Once all custom fields exist, create the dynamic group that will automatically target the enabled machines. +Once all shared custom fields exist, create the dynamic group that will automatically target the enabled machines. - [Enhanced Drive Space Monitoring](/docs/475ce8e8-458e-4901-bdfc-18e79f62c549) -### Step 3: Create the Task +### Step 3: Create the Configuration Writer Task Set up the configuration writer task that will write the JSON configuration file on the targeted endpoints. - [Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) -### Step 4: Create the Monitor +> This task also writes the workflow webhook URL into the config file when the `Ticket_Mgmt_Webhook_Url` field is populated. If you choose the built‑in option and skip that field, the task still runs successfully (the relevant row is set to Continue on Failure) and simply writes a placeholder the built‑in monitor ignores. + +### Step 4: Choose Your Ticketing Approach + +Decide whether the partner will use **Built‑in** or **Workflow** ticketing. See [Ticketing Options](#ticketing-options) for the comparison and guidance. Your choice determines which monitor you import in Step 5 and whether you need the workflow components. -Create the monitor that will read the configuration file and perform the actual drive space checks every hour. +### Step 5: Create the Monitor (and Workflow Components, if applicable) + +#### Option A — Built‑in Ticketing + +Import the original monitor. No additional components are required. - [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) -### Step 5: Schedule the Task +#### Option B — Workflow Ticketing + +Import the [Workflow] monitor **and** set up the workflow components. Complete the following in order: + +1. Create the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field (Company, Text Box). +2. Install the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow from the `ProVal - Content` Community. This also installs the [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7). +3. In the workflow's **Trigger** node, create a new webhook instance named `CWRMM Ticket Management for Monitors` and **copy the generated URL**. (See the workflow document's *Create the Webhook Instance* section.) +4. Set that URL as the **Default Value** of the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field and save. Then run the Configuration Writer task once (or wait for the next daily run) so the config file is populated with the real URL before the [Workflow] monitor relies on it. +5. Configure the workflow's **Create Ticket** action (Service Board and assignment) to match your environment. (See the workflow document's *Configure the Create Ticket Action* section.) +6. Import the [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) monitor. + +> **Important — user permissions:** The workflow runs under the context of the user account that creates it. Create the workflow with a user that has access to **all** devices you intend to monitor; otherwise ticket creation/closure will silently fail for any device that user cannot access. See the workflow document for details. + +### Step 6: Schedule the Task Schedule the [Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) task to run once per day against the [Enhanced Drive Space Monitoring](/docs/475ce8e8-458e-4901-bdfc-18e79f62c549) group, as described in the [Schedule Task](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc#schedule-task) section of the task's document. -The monitor is already configured to run every hour against the same group and requires no additional scheduling. +Whichever monitor you imported is already configured to run every hour against the same group and requires no additional scheduling. -### Step 6: Review and Set Threshold Custom Fields +### Step 7: Review and Set Threshold Custom Fields Review the default threshold values and set the threshold custom fields as required for your environments. You can set broad Company‑level baselines, and then configure specific Site‑level or Endpoint‑level overrides where tighter rules or exceptions are needed. Be sure to also configure any specific drive inclusion or exclusion fields if you only want to monitor certain drives (like `CDEF`). -### Step 7: Review and Set Enablement Custom Fields +### Step 8: Review and Set Enablement Custom Fields Set the enablement custom fields to `Enable` for the client, location, or specific endpoint to turn the solution and monitoring on for them. Machines will not be monitored until they are explicitly enabled via these dropdown fields. @@ -214,14 +285,22 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: How does the Enhanced Drive Space Monitoring monitor work? -> A local configuration file (JSON) is written daily by the configuration writer task. The monitor reads this file every hour, evaluates all local fixed drives, filters them according to your include/exclude lists, and compares free space against the appropriate capacity‑bucket threshold. If any drive falls below its limit, an alert is generated; otherwise, a success message is returned and any open ticket is automatically resolved. +> A local configuration file (JSON) is written daily by the configuration writer task. The monitor reads this file every hour, evaluates all local fixed drives, filters them according to your include/exclude lists, and compares free space against the appropriate capacity‑bucket threshold. If any drive falls below its limit, an alert is raised; otherwise, a healthy state is returned and any open ticket is resolved. The two available monitors detect low space identically — they differ only in how they create and close tickets (see [Ticketing Options](#ticketing-options)). + +### Q: What's the difference between the two monitors, and which should I choose? + +> Both monitors read the same config file and detect low drive space the same way. The **built‑in** monitor ([Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371)) uses the monitor's native ticketing — simplest to deploy, but tickets carry monitor‑generated formatting and a comment is added on each detection. The **[Workflow]** monitor ([Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07)) creates clean, consistently formatted tickets via the [CWRMM workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), with no comment spam and automatic closure on recovery — at the cost of a one‑time workflow/webhook setup. See the [Ticketing Options](#ticketing-options) comparison table. + +### Q: Do I need to import both monitors? + +> No. The two monitors are mutually exclusive ticketing approaches for the same solution. Import and deploy **only one** per device. Deploying both would create conflicting or duplicate tickets. -### Q: What are the default thresholds if I don’t set any custom fields? +### Q: What are the default thresholds if I don't set any custom fields? -> - 16–300 GB drives: 10% free space -> - 300–1024 GB drives: 30 GB free -> - 1024–4096 GB drives: 50 GB free -> - 4096+ GB drives: 100 GB free +> - 16–300 GB drives: 10% free space +> - 300–1024 GB drives: 30 GB free +> - 1024–4096 GB drives: 50 GB free +> - 4096+ GB drives: 100 GB free > These defaults are used when no Company, Site, or Endpoint field is populated. ### Q: I changed a threshold custom field. When will the new values take effect? @@ -234,11 +313,34 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: Will the ticket close automatically when free space goes back above the threshold? -> Yes. As soon as all monitored drives are above their limits, the monitor returns a healthy state and the ticket is automatically closed. +> Yes, with either monitor. The built‑in monitor auto‑resolves the ticket when all monitored drives are above their limits. The [Workflow] monitor fires a `Close` webhook on recovery and the workflow closes the ticket. Neither requires manual closure. ### Q: If a drive stays low for several days, will I get a new ticket every day? -> No. After the first alert, if the condition persists, the monitor adds a comment to the existing ticket with the current drive status. A new ticket is only created if the issue was resolved and later re‑occurs. +> No. With the built‑in monitor, the first alert creates a ticket and subsequent detections add a comment to that same ticket. With the [Workflow] monitor, a local state machine ensures exactly one ticket exists per incident and adds no comments. In both cases a new ticket is only created if the issue was resolved and later re‑occurs. + +### Q: Why does the Workflow option need a trigger, a workflow, and a custom field? + +> The [Workflow] monitor doesn't create tickets itself — it sends an HTTP POST (a webhook) to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow, which performs the actual ticket creation/closure in ConnectWise. The [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) provides the webhook endpoint, and the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field stores that endpoint's URL so the configuration writer can pass it to the monitor via the config file. + +### Q: The [Workflow] monitor isn't creating or closing tickets. What should I check? + +> Work through this checklist: +> +> 1. The [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field's Default Value is set to the **real** webhook instance URL (not the placeholder). +> 2. A webhook instance was created in the workflow's trigger and the URL was copied from it. +> 3. The [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) is installed and published, and its Create Ticket action is configured for a valid Service Board. +> 4. The Configuration Writer task was re‑run **after** the URL was set, so the config file contains the real URL. +> 5. The user who created the workflow has access to the affected device (see the next question). +> 6. The monitor's output for an action run — a failed webhook is logged there. + +### Q: Why does the [Workflow] monitor work on some machines but not others? + +> The workflow runs under the context of the user account that created it. If that user lacks permission to a specific device, the workflow cannot create or update tickets for it and ticketing silently fails for that endpoint. Create the workflow with a user that has access to all monitored devices. The built‑in monitor has no such dependency. + +### Q: Can I switch from built‑in to workflow ticketing (or vice versa) later? + +> Yes. Import the monitor for the new approach (and, for workflow, complete the trigger/workflow/webhook setup), then remove or disable the old monitor so both aren't active at once. Note that tickets already opened by the old approach won't be auto‑managed by the new one — close any stragglers manually. ### Q: Why isn't a specific device being monitored? @@ -260,11 +362,11 @@ Set the enablement custom fields to `Enable` for the client, location, or specif > Yes. Threshold fields accept `10%`, `500MB`, or `15GB`. -### Q: How do I disable monitoring for just one capacity bucket, like small 16–300 GB drives? +### Q: How do I disable monitoring for just one capacity bucket, like small 16–300 GB drives? > Set the threshold value for that bucket to `0`. Drives in that range will be ignored. -### Q: The monitor output says “Drive space configuration file not found.” What should I do? +### Q: The monitor output says "Drive space configuration file not found." What should I do? > This means the configuration writer task has not yet run on that endpoint. Run the [Enhanced Drive Space Monitoring Configuration Writer](/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc) task manually or wait for the next daily schedule. @@ -274,11 +376,18 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ## Changelog +### 2026-07-23 + +- **New Optional Workflow Ticketing:** Added a second, optional monitor — [Enhanced Drive Space Monitoring [Workflow]](/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07) — that creates clean tickets and auto‑closes them on recovery by firing webhooks to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow, with no per‑detection comment spam. +- **New Components (Workflow option only):** Added the [CWRMM Ticket Management for Monitors trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) and [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), and the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field that stores the webhook URL. +- **Partner Choice:** The two monitors are mutually exclusive ticketing approaches. Added a [Ticketing Options](#ticketing-options) comparison and reworked Implementation so partners import **only** the monitor they choose; the group, task, and threshold/drive/enablement fields are shared. +- The original built‑in monitor [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371) remains available unchanged. + ### 2026-07-02 -- **Architecture Change:** The original monitoring task that directly generated tickets has been split into a **configuration writer** and a **dedicated monitor set**. The writer runs daily to produce a JSON config file; the new monitor runs every hour, reads that file, and handles all alerting and ticket management. -- **Improved Ticketing:** Duplicate tickets are no longer created for persistent low‑space conditions. The monitor now comments on the existing open ticket. -- **Automatic Ticket Resolution:** Tickets are automatically closed when drive space returns above the threshold. +- **Architecture Change:** The original monitoring task that directly generated tickets has been split into a **configuration writer** and a **dedicated monitor set**. The writer runs daily to produce a JSON config file; the new monitor runs every hour, reads that file, and handles all alerting and ticket management. +- **Improved Ticketing:** Duplicate tickets are no longer created for persistent low‑space conditions. The monitor now comments on the existing open ticket. +- **Automatic Ticket Resolution:** Tickets are automatically closed when drive space returns above the threshold. - Added the new monitor: [Enhanced Drive Space Monitoring](/docs/70d7b9fd-8311-4470-9e7a-674cf577d371). ### 2026-06-24 diff --git a/docs/solutions/memory-threshold-violation-monitoring-cw-rmm.md b/docs/solutions/memory-threshold-violation-monitoring-cw-rmm.md index 1ad033459..66f87509f 100644 --- a/docs/solutions/memory-threshold-violation-monitoring-cw-rmm.md +++ b/docs/solutions/memory-threshold-violation-monitoring-cw-rmm.md @@ -3,13 +3,13 @@ id: 'cda6ee21-e70f-45c3-868c-1800d4aa26d7' slug: /cda6ee21-e70f-45c3-868c-1800d4aa26d7 title: 'Memory Threshold Violation Monitoring' title_meta: 'Memory Threshold Violation Monitoring' -keywords: ['memory', 'monitoring', 'windows', 'alerts', 'thresholds', 'performance'] -description: 'Intelligently monitors sustained high Memory usage using hierarchical custom thresholds with automatic ticket resolution.' +keywords: ['memory', 'monitoring', 'windows', 'alerts', 'thresholds', 'performance', 'workflow', 'trigger'] +description: 'Intelligently monitors sustained high Memory usage using hierarchical custom thresholds. Offers a choice of built‑in or workflow‑based ticketing, both with automatic ticket resolution.' tags: ['performance', 'monitoring', 'windows'] draft: false unlisted: false last_update: - date: 2026-07-15 + date: 2026-07-23 --- ## Purpose @@ -18,25 +18,55 @@ The **Memory Threshold Violation Monitoring** solution continuously tracks Memor Unlike traditional monitoring that looks at a single fixed percentage, this solution uses a **two‑threshold logic**: -1. **High Threshold** – the Memory percentage that starts the timer. -2. **Low Threshold** – the percentage that resets the timer if the Memory falls below it. +1. **High Threshold** – the Memory percentage that starts the timer. +2. **Low Threshold** – the percentage that resets the timer if the Memory falls below it. 3. **Sustained Minutes** – the number of minutes the Memory must remain above the low threshold (after first exceeding the high threshold) before an alert is raised. The actual monitoring is performed by a lightweight monitor that reads a local JSON configuration file every 15 minutes. A separate daily configuration writer task pulls the threshold values from ConnectWise RMM custom fields and writes that file, allowing you to manage policies entirely through custom fields without touching scripts. +The solution ships with **two monitor sets** that detect sustained high Memory identically but handle ticketing differently. You choose the one that fits the partner — see [Ticketing Options](#ticketing-options). + ### Key Capabilities -- **Two‑Threshold Alerting with Automatic Reset:** Alert only on sustained high Memory. Brief spikes that drop back down reset the timer automatically. -- **Automatic Ticket Resolution:** Once Memory falls below the low threshold, the monitor clears and the ticket is automatically closed. -- **Hierarchical Policy Management:** Set Company‑wide defaults, override them at a Site, or set a unique value for a single Endpoint – all through custom fields. -- **OS‑Aware Targeting:** Servers and Workstations automatically use their respective threshold fields (`_Svr` / `_Wks`). -- **Configuration / Monitoring Decoupling:** Threshold changes take effect on the next daily (or manual) run of the configuration writer, without modifying the monitor itself. +- **Two‑Threshold Alerting with Automatic Reset** – Alert only on sustained high Memory. Brief spikes that drop back below the low threshold reset the timer automatically. +- **Choice of Ticketing Approach** – Use the monitor's **built‑in ticketing** for zero extra setup, or the optional **[Workflow] monitor** for clean, standardized tickets driven by the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow. See [Ticketing Options](#ticketing-options). +- **Automatic Ticket Resolution (both options)** – When Memory falls below the low threshold, the open ticket is closed automatically — by the monitor's auto‑resolution rule (built‑in) or by a `Close` webhook (workflow). No manual closure is required. +- **No Duplicate Tickets (both options)** – A persistent high‑Memory condition never spawns repeated tickets. The built‑in monitor comments on the existing ticket; the [Workflow] monitor tracks state locally so exactly one ticket exists per incident (with no comment spam). +- **Informative Tickets** – The alert includes how long ago the spike began, the current Memory usage, the top five Memory‑consuming processes (as a percentage of total physical memory), and the PowerShell command line when PowerShell is among them. The [Workflow] variant presents this with a clean, controlled subject and body. +- **Hierarchical Policy Management** – Set Company‑wide defaults, override them at a Site, or set a unique value for a single Endpoint – all through custom fields. +- **OS‑Aware Targeting** – Servers and Workstations automatically use their respective threshold fields (`_Svr` / `_Wks`). +- **Configuration / Monitoring Decoupling** – Threshold changes take effect on the next daily (or manual) run of the configuration writer, without modifying the monitor itself. ### Important Caveats & Behavior -- **Daily Configuration Update:** The configuration writer task runs once per day. If you change a custom field, the new thresholds will only be picked up after the next scheduled run. You can manually run the task on an endpoint to apply changes immediately. -- **Monitor Interval:** The monitor checks Memory usage every 15 minutes against the stored configuration file. -- **Auto‑Resolution:** Tickets generated by this solution will be closed automatically when the Memory usage drops below the low threshold – no manual intervention required. +- **Daily Configuration Update** – The configuration writer task runs once per day. If you change a custom field, the new thresholds will only be picked up after the next scheduled run. You can manually run the task on an endpoint to apply changes immediately. +- **Monitor Interval** – The monitor checks Memory usage every 15 minutes against the stored configuration file. +- **Ticketing Behavior Depends on the Monitor** – Both monitors auto‑close the ticket on recovery, but the mechanism and the comment handling differ. See [Ticketing Options](#ticketing-options) for the exact behavior of each. +- **One Monitor Per Device** – The two monitors are mutually exclusive ticketing approaches. Import and deploy **only one** of them to a given device; deploying both would create conflicting or duplicate tickets. +- **Workflow Option Coverage** – The [Workflow] monitor depends on a valid webhook URL and on the workflow running under its creator's permissions. If the creating user lacks access to a device, ticketing will silently fail for that device. The built‑in monitor has no such dependency and works on all machines. + +## Ticketing Options + +This solution provides **two monitor sets** that perform identical sustained‑Memory detection but handle ticketing differently. **Choose one** and import only that monitor — they are mutually exclusive per device. + +| | Built‑in Ticketing | Workflow Ticketing | +|---|---|---| +| **Monitor** | [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) | [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) | +| **How tickets are created** | CW RMM monitor's built‑in ticketing | Webhook → [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow | +| **Ticket subject** | Set by the monitor; not customizable | Clean and controlled (e.g., `Memory Threshold Violation - Memory - SERVER01 - 95 Percent`) | +| **Ticket body** | The monitor's alert message in its fixed format (spike time, current usage, top five processes) | A clean, controlled message: how long ago the spike began, the low threshold held above, current usage, and the top five processes (+ PowerShell command line if applicable) | +| **While the alert persists** | Adds a comment to the open ticket on each detection (bulky ticket) | One ticket per incident; no comment spam | +| **Resolution** | Auto‑resolves via the monitor set's automatic resolution rule | Auto‑closes via a `Close` webhook to the workflow | +| **Device coverage** | Works on all machines | Depends on the workflow + a valid webhook URL + the workflow creator's device permissions | +| **Extra components required** | None | [Trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7), [Workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field, and a webhook instance | +| **Best for** | Partners who want zero extra setup | Partners who want clean, standardized tickets with no comment spam | + +**How to choose:** + +- Pick **Built‑in** if you want the simplest deployment and don't mind monitor‑generated ticket formatting and per‑detection comments. +- Pick **Workflow** if you want clean, consistently formatted tickets without comment spam — and you can complete the one‑time workflow/webhook setup (and ensure the workflow is created by a user with access to all monitored devices). Both options auto‑close the ticket on recovery, so the decision comes down to ticket quality and setup versus universal coverage. + +> Whichever you choose, the **group**, the **configuration writer task**, the **threshold / sustained‑minutes / enablement custom fields**, and the **local config file** are shared and identical. Only the monitor — and, for the Workflow option, the trigger / workflow / webhook field — differ. ## Associated Content @@ -44,23 +74,38 @@ The actual monitoring is performed by a lightweight monitor that reads a local J | Name | Purpose | |---|---| -| [Memory Threshold Violation Monitoring](/docs/183946ab-f199-4b68-b92a-6dab5ae19d24) | Dynamic group that targets Windows endpoints based on the `MTVM_Enable` custom fields, automatically pulling in devices authorized for monitoring. | +| [Memory Threshold Violation Monitoring](/docs/183946ab-f199-4b68-b92a-6dab5ae19d24) | Dynamic group that targets Windows endpoints based on the `MTVM_Enable` custom fields, automatically pulling in devices authorized for monitoring. Shared by both ticketing options. | ### Task | Name | Purpose | |---|---| -| [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) | Runs daily to resolve hierarchical thresholds from custom fields and writes the JSON configuration file for the monitor. | +| [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) | Runs daily to resolve hierarchical thresholds from custom fields and writes the JSON configuration file for the monitor. Also writes the workflow webhook URL (`TicketWebhookUrl`) into the file when the `Ticket_Mgmt_Webhook_Url` field is populated. Shared by both ticketing options. | ### Monitor +> Choose **one** of the following monitors to import. They are mutually exclusive ticketing approaches for the same solution — do not deploy both to the same device. See [Ticketing Options](#ticketing-options) for help choosing. + +| Name | Ticketing | Purpose | +|---|---|---| +| [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) | Built‑in | Runs every 15 minutes, reading the local JSON configuration file. Evaluates sustained Memory usage via the two‑threshold logic and generates / resolves tickets using the monitor's built‑in ticketing. | +| [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) | Workflow | Runs every 15 minutes, reading the same JSON configuration file. Performs the same Memory evaluation but creates and closes tickets by firing webhooks to the CWRMM Ticket Management workflow, producing clean tickets with no comment spam. | + +### Trigger (Workflow option only) + | Name | Purpose | |---|---| -| [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) | Executes every 15 minutes, reading the local JSON configuration file. Checks Memory usage and generates or resolves tickets based on the two‑threshold logic. | +| [CWRMM Ticket Management for Monitors](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) | Webhook trigger that receives `Create` / `Close` / `Comment` payloads from the [Workflow] monitor and starts the ticketing workflow. Required only for the Workflow option. | + +### Workflow (Workflow option only) + +| Name | Purpose | +|---|---| +| [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) | Creates, closes, and comments on ConnectWise tickets based on the monitor's webhook payloads. Required only for the Workflow option. | ### Custom Fields: Enablement -These fields act as the “on/off switch” for the solution. They use a Dropdown (`Enable` or `Disable`) to dynamically target devices. +These fields act as the "on/off switch" for the solution. They use a Dropdown (`Enable` or `Disable`) to dynamically target devices. | Name | Level | Type | Purpose | |---|---|---|---| @@ -106,13 +151,21 @@ Enter the number of minutes the Memory must remain above the low threshold (afte | [MTVM_UsageMins_Wks_Site](/docs/bb20ad1a-a6f2-4d6d-9193-ef4051adeba4) | Site | Text Box | Site‑level override for workstations. | | [MTVM_UsageMins](/docs/ac69275d-6200-4bc0-a449-0778149615f0) | Endpoint | Text Box | Endpoint‑level override (applies to both OS types). | +### Custom Fields: Ticketing (Workflow option only) + +Required only when you deploy the [Workflow] monitor. The configuration writer reads this field and writes its value into the config file as `TicketWebhookUrl`, which the [Workflow] monitor uses to reach the ticketing workflow. If you deploy the built‑in monitor only, you may skip this field — the configuration writer tolerates its absence. + +| Name | Level | Type | Purpose | +|---|---|---|---| +| [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) | Company | Text Box | Stores the workflow's webhook URL (copied from the trigger's webhook instance) as its Default Value. | + ## Implementation -Follow these steps in order to successfully deploy the Memory Threshold Violation Monitoring solution in your environment. +Follow these steps in order. Steps 1–3 and 6–8 are common to both ticketing options. **Steps 4–5** are where you choose and configure your ticketing approach — import **only** the monitor for the option you choose. -### Step 1: Create the Following Custom Fields +### Step 1: Create the Shared Custom Fields -Create all 20 custom fields listed below in ConnectWise RMM. These are required for the solution to function correctly. +Create all 20 shared custom fields listed below in ConnectWise RMM. These are required regardless of which ticketing option you choose. - [Custom Field: MTVM_Enable](/docs/cdbbb3d0-31a3-4ac7-816c-f381c8c94c7d) - [Custom Field: MTVM_Enable_Svr](/docs/2fae464f-4fe6-4c42-9957-664365c25fe0) @@ -141,29 +194,50 @@ Create the dynamic group that will automatically target the enabled machines. - [Memory Threshold Violation Monitoring](/docs/183946ab-f199-4b68-b92a-6dab5ae19d24) -### Step 3: Create the Task +### Step 3: Create the Configuration Writer Task Set up the configuration writer script that will run on the targeted endpoints. - [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) -### Step 4: Create the Monitor +> This task also writes the workflow webhook URL into the config file when the `Ticket_Mgmt_Webhook_Url` field is populated. If you choose the built‑in option and skip that field, the task still runs successfully (the relevant row is set to Continue on Failure) and simply writes a placeholder the built‑in monitor ignores. -Set up the monitor that will evaluate Memory usage against the configuration file. +### Step 4: Choose Your Ticketing Approach + +Decide whether the partner will use **Built‑in** or **Workflow** ticketing. See [Ticketing Options](#ticketing-options) for the comparison and guidance. Your choice determines which monitor you import in Step 5 and whether you need the workflow components. + +### Step 5: Create the Monitor (and Workflow Components, if applicable) + +#### Option A — Built‑in Ticketing + +Import the original monitor. No additional components are required. - [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) -### Step 5: Schedule the Task +#### Option B — Workflow Ticketing + +Import the [Workflow] monitor **and** set up the workflow components. Complete the following in order: + +1. Create the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field (Company, Text Box). +2. Install the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow from the `ProVal - Content` Community. This also installs the [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7). +3. In the workflow's **Trigger** node, create a new webhook instance named `CWRMM Ticket Management for Monitors` and **copy the generated URL**. (See the workflow document's *Create the Webhook Instance* section.) +4. Set that URL as the **Default Value** of the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field and save. Then run the Configuration Writer task once (or wait for the next daily run) so the config file is populated with the real URL before the [Workflow] monitor relies on it. +5. Configure the workflow's **Create Ticket** action (Service Board and assignment) to match your environment. (See the workflow document's *Configure the Create Ticket Action* section.) +6. Import the [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) monitor. + +> **Important — user permissions:** The workflow runs under the context of the user account that creates it. Create the workflow with a user that has access to **all** devices you intend to monitor; otherwise ticket creation/closure will silently fail for any device that user cannot access. See the workflow document for details. + +### Step 6: Schedule the Task Schedule the [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) task to run once per day against the [Memory Threshold Violation Monitoring](/docs/183946ab-f199-4b68-b92a-6dab5ae19d24) group, as described in the Schedule Task section of the task's document. -The monitor is already configured to run every 15 minutes against the same group and requires no additional scheduling. +Whichever monitor you imported is already configured to run every 15 minutes against the same group and requires no additional scheduling. -### Step 6: Review and Set Threshold Custom Fields +### Step 7: Review and Set Threshold Custom Fields Review the default threshold values and set the threshold custom fields as required for your environments. You can set broad Company‑level baselines, and then configure specific Site‑level or Endpoint‑level overrides where tighter rules or exceptions are needed. -### Step 7: Review and Set Enablement Custom Fields +### Step 8: Review and Set Enablement Custom Fields Set the enablement custom fields to `Enable` for the client, location, or specific endpoint to turn the solution and monitoring on for them. Machines will not be monitored until they are explicitly enabled via these dropdown fields. @@ -171,17 +245,27 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: How does the Memory Threshold Violation monitor work? -> A local configuration file (JSON) is written daily by the configuration writer task. The monitor reads this file every 15 minutes, samples the Memory usage over 10 seconds, and applies a two‑threshold logic: +> A local configuration file (JSON) is written daily by the configuration writer task. The monitor reads this file every 15 minutes, samples the Memory usage over 10 seconds, and applies a two‑threshold logic: > -> - When Memory first exceeds the **high threshold**, a marker is created and the timer starts. -> - As long as Memory stays above the **low threshold**, the timer continues. -> - If the timer reaches the **sustained minutes**, an alert ticket is created. +> - When Memory first exceeds the **high threshold**, a marker is created and the timer starts. +> - As long as Memory stays above the **low threshold**, the timer continues. +> - If the timer reaches the **sustained minutes**, an alert is raised. > - If at any point Memory drops below the low threshold, the marker is deleted and the timer resets. +> +> The two available monitors detect sustained high Memory identically — they differ only in how they create and close tickets (see [Ticketing Options](#ticketing-options)). + +### Q: What's the difference between the two monitors, and which should I choose? + +> Both monitors read the same config file and detect sustained high Memory the same way, and both close the ticket automatically on recovery. The **built‑in** monitor ([Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435)) uses the monitor's native ticketing — simplest to deploy, but tickets carry monitor‑generated formatting and a comment is added on each detection. The **[Workflow]** monitor ([Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0)) creates clean, consistently formatted tickets via the [CWRMM workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) with no comment spam — at the cost of a one‑time workflow/webhook setup. See the [Ticketing Options](#ticketing-options) comparison table. + +### Q: Do I need to import both monitors? -### Q: What are the default thresholds if I don’t set any custom fields? +> No. The two monitors are mutually exclusive ticketing approaches for the same solution. Import and deploy **only one** per device. Deploying both would create conflicting or duplicate tickets. -> For **servers**: High = 95%, Low = 90%, Sustained Minutes = 30. -> For **workstations**: High = 90%, Low = 85%, Sustained Minutes = 30. +### Q: What are the default thresholds if I don't set any custom fields? + +> For **servers**: High = 95%, Low = 90%, Sustained Minutes = 30. +> For **workstations**: High = 90%, Low = 85%, Sustained Minutes = 30. > These defaults are used when no Company, Site, or Endpoint field is populated. ### Q: I changed a threshold custom field. When will the new values take effect? @@ -194,7 +278,34 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: Will the ticket close automatically when the Memory usage drops? -> Yes. The monitor runs the same script for resolution. When the Memory falls below the low threshold, the marker file is deleted and the script outputs nothing, which signals a healthy state. The monitor’s automatic resolution rule then closes the ticket. +> Yes, with either monitor. The built‑in monitor re‑runs the same script for resolution; when the Memory falls below the low threshold the marker file is deleted and the script outputs nothing, so the monitor's automatic resolution rule closes the ticket. The [Workflow] monitor fires a `Close` webhook on recovery and the workflow closes the ticket. Neither requires manual closure. + +### Q: Will the monitor create a new ticket every time it detects high Memory usage? + +> No. With the built‑in monitor, the first sustained detection creates a ticket and subsequent detections add a comment to that same ticket. With the [Workflow] monitor, a local state machine ensures exactly one ticket exists per incident and adds no comments. In both cases a new ticket is only created if the issue was resolved (Memory dropped below the low threshold) and later re‑occurs. + +### Q: Why does the Workflow option need a trigger, a workflow, and a custom field? + +> The [Workflow] monitor doesn't create tickets itself — it sends an HTTP POST (a webhook) to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow, which performs the actual ticket creation/closure in ConnectWise. The [trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) provides the webhook endpoint, and the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field stores that endpoint's URL so the configuration writer can pass it to the monitor via the config file. + +### Q: The [Workflow] monitor isn't creating or closing tickets. What should I check? + +> Work through this checklist: +> +> 1. The [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field's Default Value is set to the **real** webhook instance URL (not the placeholder). +> 2. A webhook instance was created in the workflow's trigger and the URL was copied from it. +> 3. The [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) is installed and published, and its Create Ticket action is configured for a valid Service Board. +> 4. The Configuration Writer task was re‑run **after** the URL was set, so the config file contains the real URL. +> 5. The user who created the workflow has access to the affected device (see the next question). +> 6. The monitor's output for an action run — a failed webhook is logged there. + +### Q: Why does the [Workflow] monitor work on some machines but not others? + +> The workflow runs under the context of the user account that created it. If that user lacks permission to a specific device, the workflow cannot create or update tickets for it and ticketing silently fails for that endpoint. Create the workflow with a user that has access to all monitored devices. The built‑in monitor has no such dependency. + +### Q: Can I switch from built‑in to workflow ticketing (or vice versa) later? + +> Yes. Import the monitor for the new approach (and, for workflow, complete the trigger/workflow/webhook setup), then remove or disable the old monitor so both aren't active at once. Note that tickets already opened by the old approach won't be auto‑managed by the new one — close any stragglers manually. ### Q: Why isn't a specific device being monitored? @@ -214,7 +325,7 @@ Set the enablement custom fields to `Enable` for the client, location, or specif ### Q: What does the monitor ticket contain? -> The ticket includes the time the Memory spiked above the high threshold, the current Memory usage, a list of the top five Memory‑consuming processes (calculated as a percentage of total physical memory), and if applicable, the command line of any PowerShell process that is consuming Memory. +> The ticket body includes how long ago the Memory spiked above the high threshold, the current Memory usage, a list of the top five Memory‑consuming processes (each process's working set expressed as a percentage of total physical memory), and — if applicable — the command line of any PowerShell process that is consuming Memory. The [Workflow] monitor presents this with a clean, controlled subject (e.g., `Memory Threshold Violation - Memory - SERVER01 - 95 Percent`) and body; the built‑in monitor uses the monitor's default subject and formatting. ### Q: Can I disable monitoring for a single server temporarily without deleting custom fields? @@ -224,24 +335,27 @@ Set the enablement custom fields to `Enable` for the client, location, or specif > Manual changes to the JSON file will be overwritten the next time the configuration writer task runs. Always adjust thresholds through the custom fields to ensure consistency. -### Q: The monitor output says “Memory monitoring configuration file not found.” What should I do? +### Q: The monitor output says "Memory monitoring configuration file not found." What should I do? > This means the configuration writer task has not yet run on that endpoint. Run the [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) task manually or wait for the next daily schedule. -### Q: Will the monitor create a new ticket every time it detects high Memory usage? - -> No. The first time a sustained high‑Memory condition is detected, a ticket is created. On subsequent checks, if the same condition is still active and the original ticket is still open, the monitor will **add a comment** to the existing ticket instead of creating a duplicate. This prevents ticket clutter and keeps all related information in one place. A new ticket is not created until the issue is resolved (Memory drops below low threshold) and then re‑occurs. +### Q: The monitor isn't generating tickets, or the ticket status isn't updating. How can I check if the configuration file is correct? -### Q: The monitor isn’t generating tickets, or the ticket status isn’t updating. How can I check if the configuration file is correct? - -> Verify that the configuration file exists at `C:\ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json`. If the file is missing, run the [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) task manually on the endpoint. If the file exists but the thresholds seem wrong, check the custom fields at the Endpoint, Site, and Company levels; the task will use the first valid value it finds in that order. After updating fields, manually run the task again to refresh the file. +> Verify that the configuration file exists at `C:\ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.json`. If the file is missing, run the [Memory Threshold Violation Monitoring Configuration Writer](/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343) task manually on the endpoint. If the file exists but the thresholds seem wrong, check the custom fields at the Endpoint, Site, and Company levels; the task will use the first valid value it finds in that order. After updating fields, manually run the task again to refresh the file. (For the [Workflow] monitor, also confirm the `TicketWebhookUrl` value in the file is the real webhook URL — see the workflow troubleshooting question above.) ### Q: How can I tell if the monitor is actually running and evaluating Memory usage? -> Check the monitor’s run history in the ConnectWise RMM console. You can also look for the marker file `C:\ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.flag`. If it exists, a high‑Memory event is currently being tracked. If it does not exist, either no spike has occurred or the Memory has dropped below the low threshold and the timer has reset. +> Check the monitor's run history in the ConnectWise RMM console. You can also look for the marker file `C:\ProgramData\_Automation\Script\Test-MemoryUsage\Test-MemoryUsage.flag`. If it exists, a high‑Memory event is currently being tracked. If it does not exist, either no spike has occurred or the Memory has dropped below the low threshold and the timer has reset. ## Changelog +### 2026-07-23 + +- **New Optional Workflow Ticketing:** Added a second, optional monitor — [Memory Threshold Violation Monitoring [Workflow]](/docs/029c88f9-1655-4919-8c25-873ad4b53ca0) — that creates clean tickets and auto‑closes them on recovery by firing webhooks to the [CWRMM Ticket Management for Monitors](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57) workflow, with no per‑detection comment spam. +- **New Components (Workflow option only):** Added the [CWRMM Ticket Management for Monitors trigger](/docs/05c811e6-c6d0-4652-b4b6-2aa83f9605c7) and [workflow](/docs/57daa951-2acc-4be7-a025-0d0ca729ef57), and the [Ticket_Mgmt_Webhook_Url](/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab) custom field that stores the webhook URL. +- **Partner Choice:** The two monitors are mutually exclusive ticketing approaches. Added a [Ticketing Options](#ticketing-options) comparison and reworked Implementation so partners import **only** the monitor they choose; the group, task, and threshold/sustained‑minutes/enablement fields are shared. Both options auto‑close tickets on recovery; the [Workflow] option's advantages are clean tickets and no comment spam. +- The original built‑in monitor [Memory Threshold Violation Monitoring](/docs/919528ea-47be-4700-88e6-55accd98b435) remains available unchanged. + ### 2026-07-15 - Initial version of the document diff --git a/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image1.webp b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image1.webp new file mode 100644 index 000000000..9f5d527f2 Binary files /dev/null and b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image1.webp differ diff --git a/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image2.webp b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image2.webp new file mode 100644 index 000000000..35a10584b Binary files /dev/null and b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image2.webp differ diff --git a/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image3.webp b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image3.webp new file mode 100644 index 000000000..3d96f952a Binary files /dev/null and b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image3.webp differ diff --git a/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image4.webp b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image4.webp new file mode 100644 index 000000000..e437d38ae Binary files /dev/null and b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image4.webp differ diff --git a/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image5.webp b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image5.webp new file mode 100644 index 000000000..5b7a34446 Binary files /dev/null and b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image5.webp differ diff --git a/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image6.webp b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image6.webp new file mode 100644 index 000000000..e03ba08a1 Binary files /dev/null and b/static/img/docs/029c88f9-1655-4919-8c25-873ad4b53ca0/image6.webp differ diff --git a/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image21.webp b/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image21.webp index ae9af5346..a42813260 100644 Binary files a/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image21.webp and b/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image21.webp differ diff --git a/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image26.webp b/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image26.webp new file mode 100644 index 000000000..996b64b65 Binary files /dev/null and b/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image26.webp differ diff --git a/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image3.webp b/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image3.webp index 15ea5a0f1..60bc5bdab 100644 Binary files a/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image3.webp and b/static/img/docs/27c2c3ce-52f1-4deb-9f4f-442f2cd27343/image3.webp differ diff --git a/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image1.webp b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image1.webp new file mode 100644 index 000000000..1c2e43c61 Binary files /dev/null and b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image1.webp differ diff --git a/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image2.webp b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image2.webp new file mode 100644 index 000000000..5bbac4013 Binary files /dev/null and b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image2.webp differ diff --git a/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image3.webp b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image3.webp new file mode 100644 index 000000000..3fa77a756 Binary files /dev/null and b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image3.webp differ diff --git a/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image4.webp b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image4.webp new file mode 100644 index 000000000..1496b1e82 Binary files /dev/null and b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image4.webp differ diff --git a/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image5.webp b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image5.webp new file mode 100644 index 000000000..6309c09a8 Binary files /dev/null and b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image5.webp differ diff --git a/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image6.webp b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image6.webp new file mode 100644 index 000000000..4f9d60b0d Binary files /dev/null and b/static/img/docs/2c9dd7bc-f5aa-48c2-be76-1348e13cda07/image6.webp differ diff --git a/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image1.webp b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image1.webp new file mode 100644 index 000000000..962bb0523 Binary files /dev/null and b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image1.webp differ diff --git a/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image2.webp b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image2.webp new file mode 100644 index 000000000..21d8b3c0d Binary files /dev/null and b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image2.webp differ diff --git a/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image3.webp b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image3.webp new file mode 100644 index 000000000..70633232b Binary files /dev/null and b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image3.webp differ diff --git a/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image4.webp b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image4.webp new file mode 100644 index 000000000..c2dfbf2e8 Binary files /dev/null and b/static/img/docs/57daa951-2acc-4be7-a025-0d0ca729ef57/image4.webp differ diff --git a/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image21.webp b/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image21.webp index 305b5f10e..d47f8df50 100644 Binary files a/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image21.webp and b/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image21.webp differ diff --git a/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image26.webp b/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image26.webp new file mode 100644 index 000000000..20f02c117 Binary files /dev/null and b/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image26.webp differ diff --git a/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image3.webp b/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image3.webp index 1b96f4dc2..1f9a86a73 100644 Binary files a/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image3.webp and b/static/img/docs/5e7c137d-1750-492c-9a66-0359a04c6d3a/image3.webp differ diff --git a/static/img/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab/image1.webp b/static/img/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab/image1.webp new file mode 100644 index 000000000..894f86c70 Binary files /dev/null and b/static/img/docs/8e55deb6-bef8-4501-9e64-7b25e7fcd1ab/image1.webp differ diff --git a/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image1.webp b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image1.webp new file mode 100644 index 000000000..5e9938f1d Binary files /dev/null and b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image1.webp differ diff --git a/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image2.webp b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image2.webp new file mode 100644 index 000000000..7e5bac5b8 Binary files /dev/null and b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image2.webp differ diff --git a/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image3.webp b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image3.webp new file mode 100644 index 000000000..87581c2bd Binary files /dev/null and b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image3.webp differ diff --git a/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image4.webp b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image4.webp new file mode 100644 index 000000000..66dfe3c62 Binary files /dev/null and b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image4.webp differ diff --git a/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image5.webp b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image5.webp new file mode 100644 index 000000000..8dc07f3a8 Binary files /dev/null and b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image5.webp differ diff --git a/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image6.webp b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image6.webp new file mode 100644 index 000000000..0297188fa Binary files /dev/null and b/static/img/docs/92d7aa9c-c75b-4dba-94b4-f1d4f44e9ba9/image6.webp differ diff --git a/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image39.webp b/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image39.webp index 4cd1e21fa..878dba5af 100644 Binary files a/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image39.webp and b/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image39.webp differ diff --git a/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image45.webp b/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image45.webp index 878dba5af..229b8c1ce 100644 Binary files a/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image45.webp and b/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image45.webp differ diff --git a/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image46.webp b/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image46.webp new file mode 100644 index 000000000..a431f3c20 Binary files /dev/null and b/static/img/docs/b2a4b9ec-08bd-4bce-8db7-b155c6bc03bc/image46.webp differ