Help us improve
Share bugs, ideas, or general feedback.
From kaseya-datto-rmm
Manages Datto RMM jobs: run quick and scheduled jobs on devices, monitor status and lifecycle, view stdout/stderr results, handle component scripts, variables, and execution.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin datto-rmmHow this skill is triggered — by the user, by Claude, or both
Slash command
/kaseya-datto-rmm:jobsThe 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.
Manages ConnectWise Automate scripts: lists by folder, executes on computers with parameters (PowerShell, batch, VBScript, shell), retrieves history and results.
Manages Datto RMM devices: lists, searches, monitors endpoints (workstations, servers, ESXi, networks). Covers UIDs, hostnames, MACs, statuses, UDFs, warranty info, operations.
Lists, executes, monitors, and manages SuperOps.ai runbooks/scripts on assets. Covers PowerShell, Batch, Bash, Python types, parameters, scheduling, results for MSP automation.
Share bugs, ideas, or general feedback.
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) |