scheduled-jobs について
ServiceNow の管理者や開発者向けに、定期スクリプト、バッチ処理、メンテナンスの設定方法をまとめたスキルです。ES5 の例と、MCP ツールを使ったスケジュール管理や監視の手順を紹介します。
主な機能
- 01定期スクリプトジョブの作成
- 02一般的な実行頻度の設定
- 03バッチ処理と条件付き実行
- 04ジョブ履歴とメトリクスの確認
- 05ES5 構文によるスクリプト作成
活用シーン
- 01古いレコードの定期クローズ
- 02レポートの定期生成とメール送信
- 03大規模データセットのバッチ処理
- 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_config | LDAP データを同期する |
| ディスカバリー | 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,
})
ベストプラクティス
- ログ記録 - デバッグ用に詳細なログを記録します
- エラー処理 - 通知を含む try-catch を使用します
- バッチ処理 - 大規模なデータセットはバッチで処理します
- 実行時間の制限 - タイムアウトを防ぐため、実行時間を確認します
- オフピーク時間 - 利用率の低い時間帯にスケジュールします
- 冪等性 - 複数回実行しても安全にします
- 監視 - メトリクスとステータスを記録します
- ES5 のみ - 最新の JavaScript 構文は使用しません
コメント
Markdown 対応