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.
How 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.
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-rmmManages 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.