From datto-rmm
Manages Datto RMM jobs: running quick jobs, scheduling, monitoring status, viewing results. Covers component scripts, job variables, stdout/stderr, and job lifecycle.
How this skill is triggered — by the user, by Claude, or both
Slash command
/datto-rmm:jobsWhen to use
When running quick jobs, scheduling jobs, monitoring job status, and viewing results. Use when: datto job, rmm job, quick job, run script, component job, job status, job results, scheduled job, or remote execution.
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Jobs in Datto RMM execute component scripts on devices. Quick jobs run immediately; scheduled jobs run at specified times. Each job can accept variables, produces output (stdout/stderr), and has a status lifecycle. This skill covers job execution, monitoring, and results retrieval.
Jobs in Datto RMM execute component scripts on devices. Quick jobs run immediately; scheduled jobs run at specified times. Each job can accept variables, produces output (stdout/stderr), and has a status lifecycle. This skill covers job execution, monitoring, and results retrieval.
| Type | Description | Use Case |
|---|---|---|
| Quick Job | Runs immediately | Ad-hoc tasks, troubleshooting |
| Scheduled Job | Runs at specified time | Maintenance, recurring tasks |
| Policy Job | Runs based on policy | Automated responses |
Created → Queued → Running → Completed/Failed
│
└─→ Timeout
Components are the scripts/programs that jobs execute:
interface Job {
// Identifiers
jobUid: string; // Unique job ID
jobId: number; // Legacy numeric ID
// Target
deviceUid: string; // Target device
hostname: string; // Device hostname
siteUid: string; // Device's site
// Component
componentUid: string; // Component being run
componentName: string; // Component display name
// Status
status: JobStatus; // Current status
startedAt?: number; // Execution start (Unix ms)
completedAt?: number; // Completion time (Unix ms)
// Results
exitCode?: number; // Process exit code
stdout?: string; // Standard output
stderr?: string; // Standard error
// Variables
variables?: Record<string, string>; // Input variables
// Timestamps
createdAt: number; // Job creation time
queuedAt: number; // When queued
}
type JobStatus = 'created' | 'queued' | 'running' | 'completed' | 'failed' | 'timeout';
interface Component {
uid: string; // Component UID
name: string; // Display name
description: string; // What the component does
category: string; // Component category
osType: string; // "Windows", "macOS", "Linux"
variables: ComponentVariable[];
}
interface ComponentVariable {
name: string; // Variable name
type: string; // "string", "number", "boolean"
required: boolean; // Is required
defaultValue?: string; // Default if not provided
description: string; // Variable purpose
}
GET /api/v2/components?max=250
Authorization: Bearer {token}
Response:
{
"components": [
{
"uid": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"name": "Clear Temp Files",
"description": "Clears Windows temp directories",
"category": "Maintenance",
"osType": "Windows",
"variables": [
{
"name": "days",
"type": "number",
"required": false,
"defaultValue": "7",
"description": "Delete files older than X days"
}
]
}
]
}
POST /api/v2/device/{deviceUid}/quickjob
Authorization: Bearer {token}
Content-Type: application/json
{
"componentUid": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"variables": {
"days": "30"
}
}
Response:
{
"jobUid": "j1k2l3m4-n5o6-7890-pqrs-123456789def",
"status": "queued",
"deviceUid": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"componentUid": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"createdAt": 1707991200000
}
GET /api/v2/job/{jobUid}
Authorization: Bearer {token}
Response (Running):
{
"jobUid": "j1k2l3m4-n5o6-7890-pqrs-123456789def",
"status": "running",
"startedAt": 1707991260000,
"deviceUid": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"hostname": "ACME-DC01",
"componentName": "Clear Temp Files"
}
Response (Completed):
{
"jobUid": "j1k2l3m4-n5o6-7890-pqrs-123456789def",
"status": "completed",
"startedAt": 1707991260000,
"completedAt": 1707991320000,
"exitCode": 0,
"stdout": "Deleted 156 files totaling 2.3 GB",
"stderr": "",
"deviceUid": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"hostname": "ACME-DC01",
"componentName": "Clear Temp Files"
}
GET /api/v2/device/{deviceUid}/jobs?max=50
Authorization: Bearer {token}
GET /api/v2/site/{siteUid}/jobs?max=50
Authorization: Bearer {token}
async function runJobAndWait(client, deviceUid, componentUid, variables = {}, options = {}) {
const { timeoutMs = 300000, pollIntervalMs = 5000 } = options;
// Create the job
const createResponse = await client.request(
`/api/v2/device/${deviceUid}/quickjob`,
{
method: 'POST',
body: JSON.stringify({ componentUid, variables })
}
);
const jobUid = createResponse.jobUid;
const startTime = Date.now();
// Poll for completion
while (true) {
const job = await client.request(`/api/v2/job/${jobUid}`);
if (job.status === 'completed' || job.status === 'failed' || job.status === 'timeout') {
return {
success: job.status === 'completed' && job.exitCode === 0,
job
};
}
// Check timeout
if (Date.now() - startTime > timeoutMs) {
return {
success: false,
job,
error: 'Job polling timeout exceeded'
};
}
await sleep(pollIntervalMs);
}
}
async function findComponentByName(client, name) {
const response = await client.request('/api/v2/components?max=250');
const components = response.components || [];
// Exact match
const exact = components.find(c =>
c.name.toLowerCase() === name.toLowerCase()
);
if (exact) return { found: true, component: exact };
// Partial match
const matches = components.filter(c =>
c.name.toLowerCase().includes(name.toLowerCase())
);
if (matches.length === 0) {
return { found: false, suggestions: [] };
}
if (matches.length === 1) {
return { found: true, component: matches[0] };
}
return {
found: false,
ambiguous: true,
suggestions: matches.map(c => ({
name: c.name,
uid: c.uid,
category: c.category
}))
};
}
async function runJobOnMultipleDevices(client, deviceUids, componentUid, variables = {}) {
const jobs = [];
for (const deviceUid of deviceUids) {
try {
const response = await client.request(
`/api/v2/device/${deviceUid}/quickjob`,
{
method: 'POST',
body: JSON.stringify({ componentUid, variables })
}
);
jobs.push({
deviceUid,
jobUid: response.jobUid,
status: 'queued'
});
} catch (error) {
jobs.push({
deviceUid,
error: error.message,
status: 'failed'
});
}
// Respect rate limits
await sleep(100);
}
return jobs;
}
async function monitorJobs(client, jobUids, options = {}) {
const { onUpdate, timeoutMs = 600000, pollIntervalMs = 10000 } = options;
const startTime = Date.now();
const results = new Map();
// Initialize tracking
jobUids.forEach(uid => results.set(uid, { status: 'unknown' }));
while (true) {
let allComplete = true;
for (const jobUid of jobUids) {
const current = results.get(jobUid);
if (['completed', 'failed', 'timeout'].includes(current.status)) {
continue;
}
try {
const job = await client.request(`/api/v2/job/${jobUid}`);
results.set(jobUid, job);
if (!['completed', 'failed', 'timeout'].includes(job.status)) {
allComplete = false;
}
if (onUpdate) {
onUpdate(jobUid, job);
}
} catch (error) {
results.set(jobUid, { status: 'error', error: error.message });
}
}
if (allComplete) break;
if (Date.now() - startTime > timeoutMs) {
break;
}
await sleep(pollIntervalMs);
}
return Array.from(results.entries()).map(([jobUid, data]) => ({
jobUid,
...data
}));
}
function summarizeJobResult(job) {
const duration = job.completedAt && job.startedAt
? Math.round((job.completedAt - job.startedAt) / 1000)
: null;
return {
jobUid: job.jobUid,
device: job.hostname,
component: job.componentName,
status: job.status,
exitCode: job.exitCode,
duration: duration ? `${duration}s` : 'N/A',
success: job.status === 'completed' && job.exitCode === 0,
output: job.stdout?.substring(0, 500) || '',
errors: job.stderr?.substring(0, 500) || ''
};
}
| Error | Status | Cause | Resolution |
|---|---|---|---|
| Device offline | 400 | Device not online | Wait for device or use scheduled job |
| Component not found | 404 | Invalid componentUid | Verify component exists |
| Missing variable | 400 | Required variable not provided | Include all required variables |
| Job not found | 404 | Invalid jobUid | Verify job was created |
| Permission denied | 403 | API restrictions | Check component permissions |
{
"errorCode": "DEVICE_OFFLINE",
"message": "Cannot run quick job on offline device",
"details": {
"deviceUid": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"lastSeen": 1707900000000
}
}
async function safeRunJob(client, deviceUid, componentUid, variables = {}) {
// Verify device is online
const device = await client.request(`/api/v2/device/${deviceUid}`);
if (device.status !== 'online') {
return {
success: false,
error: `Device is ${device.status}`,
lastSeen: new Date(device.lastSeen).toISOString()
};
}
// Verify component exists and get required variables
const component = await client.request(`/api/v2/component/${componentUid}`);
// Check required variables
const missingVars = component.variables
.filter(v => v.required && !variables[v.name])
.map(v => v.name);
if (missingVars.length > 0) {
return {
success: false,
error: `Missing required variables: ${missingVars.join(', ')}`
};
}
// Run the job
try {
const response = await client.request(
`/api/v2/device/${deviceUid}/quickjob`,
{
method: 'POST',
body: JSON.stringify({ componentUid, variables })
}
);
return { success: true, jobUid: response.jobUid };
} catch (error) {
return { success: false, error: error.message };
}
}
| Exit Code | Typical Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Misuse of command |
| 126 | Permission denied |
| 127 | Command not found |
| 130 | Script terminated (Ctrl+C) |
| 137 | Killed (SIGKILL) |
| 143 | Terminated (SIGTERM) |
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin datto-rmmLists, searches, manages, and monitors Datto RMM endpoints. Covers device identifiers, types, statuses, UDFs, and warranty information.
Lists, executes, monitors, and manages automated scripts on assets via SuperOps.ai RMM. Covers script types, execution parameters, scheduling, and result handling for MSP automation.
Lists, executes, and retrieves results from ConnectWise Automate scripts (PowerShell, batch, VBScript, shell). Handles parameters, execution history, and script folders.