AgentSkillsCN

Jobs

当您需要管理Datto RMM作业时,可使用此技能:快速执行作业、安排作业计划、监控作业状态并查看执行结果。本技能涵盖组件脚本、作业变量、执行状态、stdout/stderr输出,以及作业管理流程。

SKILL.md
--- frontmatter
description: >
  Use this skill when working with Datto RMM jobs - running quick jobs,
  scheduling jobs, monitoring job status, and viewing results. Covers
  component scripts, job variables, execution status, stdout/stderr output,
  and job management workflows.
triggers:
  - datto job
  - rmm job
  - quick job
  - run script
  - component job
  - job status
  - job results
  - scheduled job
  - remote execution

Datto RMM Job Management

Overview

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.

Key Concepts

Job Types

TypeDescriptionUse Case
Quick JobRuns immediatelyAd-hoc tasks, troubleshooting
Scheduled JobRuns at specified timeMaintenance, recurring tasks
Policy JobRuns based on policyAutomated responses

Job Lifecycle

code
Created → Queued → Running → Completed/Failed
                      │
                      └─→ Timeout

Component Scripts

Components are the scripts/programs that jobs execute:

  • Built-in Datto components
  • Custom PowerShell/Bash scripts
  • Third-party integrations

Field Reference

Job Object

typescript
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';

Component Object

typescript
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
}

API Patterns

List Components

http
GET /api/v2/components?max=250
Authorization: Bearer {token}

Response:

json
{
  "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"
        }
      ]
    }
  ]
}

Create Quick Job

http
POST /api/v2/device/{deviceUid}/quickjob
Authorization: Bearer {token}
Content-Type: application/json

{
  "componentUid": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
  "variables": {
    "days": "30"
  }
}

Response:

json
{
  "jobUid": "j1k2l3m4-n5o6-7890-pqrs-123456789def",
  "status": "queued",
  "deviceUid": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
  "componentUid": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
  "createdAt": 1707991200000
}

Get Job Status

http
GET /api/v2/job/{jobUid}
Authorization: Bearer {token}

Response (Running):

json
{
  "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):

json
{
  "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 Jobs for Device

http
GET /api/v2/device/{deviceUid}/jobs?max=50
Authorization: Bearer {token}

Get Jobs for Site

http
GET /api/v2/site/{siteUid}/jobs?max=50
Authorization: Bearer {token}

Workflows

Run Job and Wait for Completion

javascript
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);
  }
}

Find Component by Name

javascript
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
    }))
  };
}

Batch Job Execution

javascript
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;
}

Monitor Running Jobs

javascript
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
  }));
}

Job Result Summary

javascript
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 Handling

Common Job API Errors

ErrorStatusCauseResolution
Device offline400Device not onlineWait for device or use scheduled job
Component not found404Invalid componentUidVerify component exists
Missing variable400Required variable not providedInclude all required variables
Job not found404Invalid jobUidVerify job was created
Permission denied403API restrictionsCheck component permissions

Error Response Example

json
{
  "errorCode": "DEVICE_OFFLINE",
  "message": "Cannot run quick job on offline device",
  "details": {
    "deviceUid": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
    "lastSeen": 1707900000000
  }
}

Safe Job Execution

javascript
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 };
  }
}

Best Practices

  1. Verify device online - Check status before quick jobs
  2. Provide all variables - Include required and optional variables
  3. Poll with backoff - Don't poll too frequently
  4. Handle long-running jobs - Set appropriate timeouts
  5. Log job results - Store stdout/stderr for troubleshooting
  6. Use meaningful variable values - Document what each variable does
  7. Test components first - Validate on test devices
  8. Monitor exit codes - 0 typically means success
  9. Handle stderr - Warnings may appear in stderr even on success
  10. Batch carefully - Respect rate limits when running on multiple devices

Job Status Interpretation

Exit CodeTypical Meaning
0Success
1General error
2Misuse of command
126Permission denied
127Command not found
130Script terminated (Ctrl+C)
137Killed (SIGKILL)
143Terminated (SIGTERM)

Related Skills