mcpField
S
scheduled-jobs

scheduled-jobs

github.com/serac-labs開発7771 回インストール9 週間前に更新
リポジトリ

scheduled-jobs について

ServiceNow の管理者や開発者向けに、定期スクリプト、バッチ処理、メンテナンスの設定方法をまとめたスキルです。ES5 の例と、MCP ツールを使ったスケジュール管理や監視の手順を紹介します。

主な機能

  1. 01定期スクリプトジョブの作成
  2. 02一般的な実行頻度の設定
  3. 03バッチ処理と条件付き実行
  4. 04ジョブ履歴とメトリクスの確認
  5. 05ES5 構文によるスクリプト作成

活用シーン

  1. 01古いレコードの定期クローズ
  2. 02レポートの定期生成とメール送信
  3. 03大規模データセットのバッチ処理
  4. 04ジョブ状態やエラーの調査

MCP 対応

ジョブの作成や検索、スクリプトのテスト、実行ログの確認に使う MCP ツールを説明し、ジョブ作成の流れも示します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serac-labs/serac --skill scheduled-jobs

インストール後、Claude Code で/scheduled-jobsを入力して読み込まれたか確認します。

詳細

ServiceNow のスケジュールジョブ

スケジュールジョブは、定期タスク、バッチ処理、メンテナンス操作を自動化します。

ジョブの種類

種類テーブル目的
スケジュールスクリプト実行sysauto_scriptカスタムスクリプトを実行する
レポートスケジューラーsysauto_reportレポートを生成してメールで送信する
テーブルクリーナーsys_auto_flush古いレコードを削除する
LDAP 更新ldap_server_configLDAP データを同期する
ディスカバリーdiscovery_scheduleネットワークを検出する

スケジュールスクリプト実行(ES5)

基本的なスケジュールジョブ
// Table: sysauto_script
// Name: Close Stale Incidents
// Run: Daily at 2:00 AM

// Script (ES5 ONLY!):
;(function executeScheduledJob() {
  var LOG_PREFIX = "[CloseStaleIncidents] "
  var closedCount = 0

  // Find incidents inactive for 30 days
  var staleDate = new GlideDateTime()
  staleDate.addDaysLocalTime(-30)

  var gr = new GlideRecord("incident")
  gr.addQuery("state", "IN", "1,2,3") // New, In Progress, On Hold
  gr.addQuery("sys_updated_on", "<", staleDate)
  gr.addQuery("active", true)
  gr.query()

  gs.info(LOG_PREFIX + "Found " + gr.getRowCount() + " stale incidents")

  while (gr.next()) {
    gr.state = 7 // Closed
    gr.close_code = "Closed/Resolved by Caller"
    gr.close_notes = "Auto-closed due to 30 days of inactivity"
    gr.update()
    closedCount++
  }

  gs.info(LOG_PREFIX + "Closed " + closedCount + " stale incidents")
})()
エラー処理を含むスケジュールジョブ(ES5)
// Name: Sync User Data
// Run: Every 6 hours

;(function executeScheduledJob() {
  var LOG_PREFIX = "[SyncUserData] "
  var stats = {
    processed: 0,
    updated: 0,
    errors: 0,
  }

  try {
    // Get users needing sync
    var gr = new GlideRecord("sys_user")
    gr.addQuery("u_needs_sync", true)
    gr.addQuery("active", true)
    gr.setLimit(1000) // Process in batches
    gr.query()

    while (gr.next()) {
      stats.processed++
      try {
        var updated = syncUserFromSource(gr)
        if (updated) {
          stats.updated++
        }
      } catch (e) {
        stats.errors++
        gs.error(LOG_PREFIX + "Error syncing user " + gr.user_name + ": " + e.message)
      }
    }

    gs.info(LOG_PREFIX + "Sync complete: " + JSON.stringify(stats))

    // Send summary email if errors
    if (stats.errors > 0) {
      sendErrorSummary(stats)
    }
  } catch (e) {
    gs.error(LOG_PREFIX + "Job failed: " + e.message)
    notifyAdmins("User sync job failed: " + e.message)
  }

  function syncUserFromSource(userGr) {
    // Sync logic here
    userGr.u_needs_sync = false
    userGr.u_last_sync = new GlideDateTime()
    return userGr.update()
  }

  function sendErrorSummary(stats) {
    gs.eventQueue("user.sync.errors", null, JSON.stringify(stats), "")
  }

  function notifyAdmins(message) {
    gs.eventQueue("system.job.failure", null, message, "")
  }
})()
バッチ処理ジョブ(ES5)
// Name: Process Large Dataset
// Run: Weekly on Sunday at 1:00 AM

;(function executeScheduledJob() {
  var LOG_PREFIX = "[BatchProcessor] "
  var BATCH_SIZE = 500
  var MAX_RUNTIME = 3600000 // 1 hour in ms
  var startTime = new Date().getTime()

  var processed = 0
  var hasMore = true

  while (hasMore && !isTimeExceeded()) {
    hasMore = processBatch()
  }

  if (hasMore) {
    gs.warn(LOG_PREFIX + "Job stopped due to time limit. Processed: " + processed)
    // Re-queue for next run
    queueContinuation()
  } else {
    gs.info(LOG_PREFIX + "Job complete. Total processed: " + processed)
  }

  function processBatch() {
    var gr = new GlideRecord("u_large_table")
    gr.addQuery("u_processed", false)
    gr.setLimit(BATCH_SIZE)
    gr.query()

    if (!gr.hasNext()) {
      return false
    }

    while (gr.next()) {
      processRecord(gr)
      processed++
    }

    return true
  }

  function processRecord(gr) {
    // Processing logic
    gr.u_processed = true
    gr.u_processed_date = new GlideDateTime()
    gr.update()
  }

  function isTimeExceeded() {
    var elapsed = new Date().getTime() - startTime
    return elapsed > MAX_RUNTIME
  }

  function queueContinuation() {
    // Queue another run
    var job = new GlideRecord("sysauto_script")
    if (job.get("name", "Process Large Dataset - Continuation")) {
      job.next_action = new GlideDateTime()
      job.update()
    }
  }
})()

スケジュール設定

実行頻度
頻度Cron例
5 分ごと0 */5 * * * ?ヘルスチェック
毎時0 0 * * * ?データ同期
毎日午前 0 時0 0 0 * * ?クリーンアップ
毎週日曜日0 0 0 ? * SUNレポート
毎月 1 日0 0 0 1 * ?請求処理
カスタムさまざま個別のニーズ
スケジュールジョブの作成(ES5)
// Create scheduled job programmatically (ES5 ONLY!)
var job = new GlideRecord("sysauto_script")
job.initialize()
job.setValue("name", "Nightly Cleanup")
job.setValue("active", true)

// Schedule: Daily at 2:00 AM
job.setValue("run_type", "daily")
job.setValue("run_time", "02:00:00")
// Or use explicit schedule
job.setValue("run_dayofweek", "daily")

// Script
job.setValue(
  "script",
  "(function executeScheduledJob() {\n" +
    '    var gr = new GlideRecord("sys_audit_delete");\n' +
    '    gr.addQuery("sys_created_on", "<", gs.daysAgo(90));\n' +
    "    gr.deleteMultiple();\n" +
    '    gs.info("Cleanup complete");\n' +
    "})();",
)

// Run as system
job.setValue("run_as", "") // Empty = System

job.insert()
条件付き実行
// Job that checks conditions before running (ES5 ONLY!)
;(function executeScheduledJob() {
  var LOG_PREFIX = "[ConditionalJob] "

  // Check if job should run
  if (!shouldRun()) {
    gs.info(LOG_PREFIX + "Skipping execution - conditions not met")
    return
  }

  // Execute main logic
  executeMainTask()

  function shouldRun() {
    // Check business hours
    var now = new GlideDateTime()
    var hour = parseInt(now.getLocalTime().getByFormat("HH"), 10)

    // Only run outside business hours (before 6am or after 8pm)
    if (hour >= 6 && hour < 20) {
      return false
    }

    // Check for active change freeze
    var freeze = new GlideRecord("change_request")
    freeze.addQuery("type", "freeze")
    freeze.addQuery("state", "implement")
    freeze.query()

    if (freeze.hasNext()) {
      gs.info(LOG_PREFIX + "Change freeze active")
      return false
    }

    return true
  }

  function executeMainTask() {
    // Main job logic here
    gs.info(LOG_PREFIX + "Executing main task")
  }
})()

ジョブの監視

ジョブのステータスを確認(ES5)
// Query scheduled job history (ES5 ONLY!)
var history = new GlideRecord("sys_trigger")
history.addQuery("name", "CONTAINS", "Nightly Cleanup")
history.orderByDesc("sys_created_on")
history.setLimit(10)
history.query()

while (history.next()) {
  gs.info(
    "Job: " +
      history.getValue("name") +
      " | State: " +
      history.getValue("state") +
      " | Next: " +
      history.getValue("next_action"),
  )
}
メトリクス付きジョブ(ES5)
// Job that records performance metrics (ES5 ONLY!)
;(function executeScheduledJob() {
  var LOG_PREFIX = "[MetricsJob] "
  var startTime = new Date().getTime()
  var metrics = {
    startTime: new GlideDateTime().getDisplayValue(),
    recordsProcessed: 0,
    errors: 0,
  }

  try {
    // Main processing
    var gr = new GlideRecord("incident")
    gr.addQuery("active", true)
    gr.query()

    while (gr.next()) {
      processRecord(gr)
      metrics.recordsProcessed++
    }
  } catch (e) {
    metrics.errors++
    gs.error(LOG_PREFIX + "Error: " + e.message)
  } finally {
    // Record metrics
    metrics.endTime = new GlideDateTime().getDisplayValue()
    metrics.duration = (new Date().getTime() - startTime) / 1000
    recordMetrics(metrics)
  }

  function processRecord(gr) {
    // Processing logic
  }

  function recordMetrics(metrics) {
    var metricsRecord = new GlideRecord("u_job_metrics")
    metricsRecord.initialize()
    metricsRecord.setValue("u_job_name", "MetricsJob")
    metricsRecord.setValue("u_start_time", metrics.startTime)
    metricsRecord.setValue("u_end_time", metrics.endTime)
    metricsRecord.setValue("u_duration", metrics.duration)
    metricsRecord.setValue("u_records_processed", metrics.recordsProcessed)
    metricsRecord.setValue("u_errors", metrics.errors)
    metricsRecord.insert()

    gs.info(LOG_PREFIX + "Metrics: " + JSON.stringify(metrics))
  }
})()

MCP ツールの統合

利用可能なツール
ツール目的
snow_schedule_jobスケジュールジョブを作成する
snow_artifact_manage (action=find)既存のジョブを検索する
snow_execute_scriptジョブスクリプトをテストする
snow_get_logsジョブの実行ログを確認する
ワークフローの例
// 1. Create scheduled job
await snow_schedule_job({
  name: "Daily Report Generator",
  run_type: "daily",
  run_time: "06:00:00",
  script: "/* report generation script */",
  active: true,
})

// 2. Test the script
await snow_execute_script({
  script: "/* test job script */",
})

// 3. Check logs
await snow_get_logs({
  filter: 'message CONTAINS "Daily Report"',
  limit: 50,
})

ベストプラクティス

  1. ログ記録 - デバッグ用に詳細なログを記録します
  2. エラー処理 - 通知を含む try-catch を使用します
  3. バッチ処理 - 大規模なデータセットはバッチで処理します
  4. 実行時間の制限 - タイムアウトを防ぐため、実行時間を確認します
  5. オフピーク時間 - 利用率の低い時間帯にスケジュールします
  6. 冪等性 - 複数回実行しても安全にします
  7. 監視 - メトリクスとステータスを記録します
  8. ES5 のみ - 最新の JavaScript 構文は使用しません

コメント