关于 scheduled-jobs
面向 ServiceNow 开发者和管理员的定时作业开发技能,介绍如何安排后台脚本、处理条件执行并监控运行情况。内容还包括批处理优化、错误处理和故障排查。
主要功能
- 01创建周期性与一次性作业
- 02设置条件执行和运行频率
- 03通过筛选与批次优化处理
- 04管理错误和事件通知
- 05查询日志并监控作业
适用场景
- 01定期清理或处理记录
- 02按计划运行报告或数据同步
- 03排查未执行或卡住的后台作业
MCP 相关
示例使用 MCP 工具创建和查询 ServiceNow 定时作业及日志,并可通过工具运行后台脚本。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ happy-technologies-llc/ happy-platform-skills --skill scheduled-jobs安装后在 Claude Code 中输入/scheduled-jobs确认已加载。
详细介绍
计划任务开发
概述
本技能涵盖用于自动化后台处理的 ServiceNow 计划任务的创建与管理:
- 计划脚本执行(
sysauto_script)——采用类似 cron 的计划周期性运行的任务 - 系统触发器(
sys_trigger)——一次性或有条件运行的任务 - 运行频率模式——每日、每周、每月和 cron 表达式
- 条件执行——仅在满足特定条件时运行
- 性能优化——筛选出需要处理的记录(至关重要!)
- 错误处理——健全的错误管理和通知
- 监控和调试——跟踪执行历史并排查问题
适用场景: 需要自动执行定期任务、批处理、数据清理、通知或任何按计划运行的后台操作时。
适用对象: 需要在无需用户干预的情况下自动执行 ServiceNow 操作的开发人员和管理员。
前提条件
- 角色:
admin(创建和管理计划任务所必需) - 访问权限:
sysauto_script、sys_trigger、syslog表 - 知识要求: GlideRecord API、ServiceNow 服务端 JavaScript
- 相关技能:
admin/script-execution——后台脚本模式admin/batch-operations——批量记录操作
表架构
核心表
| 表 | 用途 | 关键字段 |
|---|---|---|
sysauto_script | 计划脚本执行(周期性) | name, script, run_type, run_time, conditional |
sys_trigger | 系统触发器(一次性/系统事件) | name, script, next_action, trigger_type, state |
syslog | 执行日志 | message, level, source, sys_created_on |
sys_script | 脚本包含项(可复用逻辑) | name, script, active, api_name |
sysauto_script 的运行类型
| 运行类型 | 值 | 说明 |
|---|---|---|
| 运行一次 | on_demand | 仅手动执行 |
| 每日 | daily | 每天在指定时间运行 |
| 每周 | weekly | 在指定的星期几运行 |
| 每月 | monthly | 在每月指定日期运行 |
| 定期运行 | periodically | 按固定间隔运行 |
| 按需运行 | on_demand | 仅手动触发 |
操作步骤
阶段 1:创建计划脚本执行
步骤 1.1:基本每日任务
创建一个每天运行的计划任务。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Daily Incident Cleanup"
script: |
// Daily cleanup of resolved incidents older than 90 days
var cutoffDate = gs.daysAgo(90);
var gr = new GlideRecord('incident');
gr.addQuery('state', 'IN', '6,7,8'); // Resolved, Closed, Cancelled
gr.addQuery('sys_updated_on', '<', cutoffDate);
gr.setLimit(1000); // Process in batches
gr.query();
var archived = 0;
while (gr.next()) {
// Archive logic here
archived++;
}
gs.info('[Daily Incident Cleanup] Processed ' + archived + ' incidents');
active: true
run_type: daily
run_time: "02:00:00"
run_dayofweek: 1
关键字段说明:
run_type: daily——每天运行run_time: "02:00:00"——凌晨 2:00(使用 24 小时制)run_dayofweek: 1——星期一(1=周一,2=周二,……,7=周日)——用于每周任务
步骤 1.2:每周任务
创建一个每周一早上 6 点运行的任务。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Weekly SLA Report"
script: |
// Generate weekly SLA compliance report
gs.info('[Weekly SLA Report] Starting report generation');
var report = {
totalIncidents: 0,
slaBreaches: 0,
slaCompliance: 0
};
var gr = new GlideRecord('task_sla');
gr.addQuery('stage', 'IN', 'completed,cancelled');
gr.addQuery('sys_created_on', '>=', gs.daysAgo(7));
gr.query();
while (gr.next()) {
report.totalIncidents++;
if (gr.has_breached) {
report.slaBreaches++;
}
}
report.slaCompliance = report.totalIncidents > 0
? Math.round((1 - report.slaBreaches / report.totalIncidents) * 100)
: 100;
gs.info('[Weekly SLA Report] Results: ' + JSON.stringify(report));
active: true
run_type: weekly
run_time: "06:00:00"
run_dayofweek: 1
步骤 1.3:每月任务
创建一个每月 1 日运行的任务。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Monthly User Audit"
script: |
// Monthly audit of inactive users
gs.info('[Monthly User Audit] Starting audit');
var cutoffDate = gs.daysAgo(90);
var inactiveUsers = [];
var gr = new GlideRecord('sys_user');
gr.addQuery('active', true);
gr.addQuery('last_login_time', '<', cutoffDate);
gr.addQuery('last_login_time', '!=', '');
gr.query();
while (gr.next()) {
inactiveUsers.push({
user_id: gr.user_name.toString(),
name: gr.name.toString(),
last_login: gr.last_login_time.toString()
});
}
gs.info('[Monthly User Audit] Found ' + inactiveUsers.length + ' inactive users');
// Optional: Send report email
if (inactiveUsers.length > 0) {
var email = new GlideEmailOutbound();
email.setSubject('Monthly Inactive User Report');
email.setFrom('[email protected]');
email.addAddress('[email protected]');
email.setBody('Found ' + inactiveUsers.length + ' users inactive for 90+ days.\n\n' +
JSON.stringify(inactiveUsers, null, 2));
email.send();
}
active: true
run_type: monthly
run_time: "03:00:00"
run_dayofmonth: 1
步骤 1.4:定期任务(每隔 N 分钟/小时)
创建一个每 15 分钟运行的任务。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Integration Queue Processor"
script: |
// Process integration queue every 15 minutes
gs.info('[Queue Processor] Starting processing');
var gr = new GlideRecord('x_custom_integration_queue');
gr.addQuery('state', 'pending');
gr.addQuery('retry_count', '<', 3);
gr.orderBy('priority');
gr.orderBy('sys_created_on');
gr.setLimit(100); // Batch size
gr.query();
var processed = 0;
var failed = 0;
while (gr.next()) {
try {
// Process queue item
processQueueItem(gr);
gr.state = 'completed';
gr.update();
processed++;
} catch (e) {
gr.retry_count = gr.retry_count + 1;
gr.error_message = e.message;
gr.state = gr.retry_count >= 3 ? 'failed' : 'pending';
gr.update();
failed++;
gs.error('[Queue Processor] Error: ' + e.message);
}
}
gs.info('[Queue Processor] Completed. Processed=' + processed + ', Failed=' + failed);
function processQueueItem(record) {
// Your processing logic here
}
active: true
run_type: periodically
run_period: 900000
注意: run_period 的单位是毫秒(900000 = 15 分钟)
| 间隔 | 毫秒数 |
|---|---|
| 1 分钟 | 60000 |
| 5 分钟 | 300000 |
| 15 分钟 | 900000 |
| 30 分钟 | 1800000 |
| 1 小时 | 3600000 |
| 4 小时 | 14400000 |
| 12 小时 | 43200000 |
阶段 2:条件执行
步骤 2.1:条件任务(仅在满足条件时运行)
创建一个仅在有记录需要处理时运行的任务。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Process Pending Approvals"
script: |
// Process stale approval requests
gs.info('[Approval Processor] Starting');
var staleThreshold = gs.hoursAgo(24);
var gr = new GlideRecord('sysapproval_approver');
gr.addQuery('state', 'requested');
gr.addQuery('sys_created_on', '<', staleThreshold);
gr.query();
var escalated = 0;
while (gr.next()) {
// Escalate stale approvals
escalateApproval(gr);
escalated++;
}
gs.info('[Approval Processor] Escalated ' + escalated + ' approvals');
function escalateApproval(approval) {
// Send reminder notification
var email = new GlideEmailOutbound();
email.setSubject('Reminder: Approval Pending - ' + approval.document_id.getDisplayValue());
email.addAddress(approval.approver.email);
email.setBody('You have a pending approval request that requires your attention.\n\n' +
'Document: ' + approval.document_id.getDisplayValue() + '\n' +
'Requested: ' + approval.sys_created_on.getDisplayValue());
email.send();
}
active: true
run_type: daily
run_time: "09:00:00"
conditional: true
condition: |
// Only run if there are stale approvals
var staleThreshold = gs.hoursAgo(24);
var gr = new GlideRecord('sysapproval_approver');
gr.addQuery('state', 'requested');
gr.addQuery('sys_created_on', '<', staleThreshold);
gr.setLimit(1);
gr.query();
answer = gr.hasNext();
关键字段:
conditional: true——启用条件检查condition——用于设置answer = true/false的脚本
步骤 2.2:仅在营业时间运行
创建一个仅在营业时间运行的任务。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Business Hours Queue Monitor"
script: |
// Monitor queue during business hours
gs.info('[Queue Monitor] Checking queue status');
var gr = new GlideRecord('x_custom_queue');
gr.addQuery('state', 'pending');
gr.addQuery('priority', 1);
gr.query();
if (gr.getRowCount() > 10) {
// Alert on high queue depth
gs.eventQueue('queue.high_depth', gr, gr.getRowCount());
}
active: true
run_type: periodically
run_period: 600000
conditional: true
condition: |
// Only run between 8 AM and 6 PM, Monday-Friday
var now = new GlideDateTime();
var hour = parseInt(now.getLocalTime().getHourOfDayLocalTime());
var dayOfWeek = now.getDayOfWeekLocalTime();
// Monday=2, Friday=6 in GlideDateTime
var isWeekday = dayOfWeek >= 2 && dayOfWeek <= 6;
var isBusinessHours = hour >= 8 && hour < 18;
answer = isWeekday && isBusinessHours;
阶段 3:性能优化
至关重要: 必须优化计划任务,以避免性能问题。最常见的错误是处理所有记录,而不是仅筛选出需要处理的记录。
步骤 3.1:高效查询模式
不佳模式(全表扫描):
// DON'T DO THIS - Scans entire table every time
var gr = new GlideRecord('incident');
gr.query();
while (gr.next()) {
if (gr.state == 1 && gr.priority == 1) {
// Process
}
}
良好模式(筛选查询):
// DO THIS - Only retrieves records needing processing
var gr = new GlideRecord('incident');
gr.addQuery('state', 1); // New
gr.addQuery('priority', 1); // P1
gr.addQuery('processed', false); // Not yet processed
gr.setLimit(100); // Batch limit
gr.query();
while (gr.next()) {
// Process
}
第 3.2 步:批处理模式
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Batch Data Processor"
script: |
// Efficient batch processing pattern
var BATCH_SIZE = 100;
var MAX_BATCHES = 10;
var TABLE = 'incident';
var QUERY = 'state=1^priority=1^u_needs_processing=true';
gs.info('[Batch Processor] Starting');
var totalProcessed = 0;
var batchNum = 0;
while (batchNum < MAX_BATCHES) {
var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.setLimit(BATCH_SIZE);
gr.query();
if (!gr.hasNext()) {
gs.info('[Batch Processor] No more records to process');
break;
}
var batchProcessed = 0;
while (gr.next()) {
try {
processRecord(gr);
gr.u_needs_processing = false;
gr.update();
batchProcessed++;
totalProcessed++;
} catch (e) {
gs.error('[Batch Processor] Error processing ' + gr.number + ': ' + e.message);
}
}
gs.info('[Batch Processor] Batch ' + (batchNum + 1) + ' complete: ' + batchProcessed + ' records');
batchNum++;
}
gs.info('[Batch Processor] Total processed: ' + totalProcessed);
function processRecord(record) {
// Your processing logic
}
active: true
run_type: periodically
run_period: 300000
第 3.3 步:使用聚合进行计数
不佳模式(加载所有记录):
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();
var count = gr.getRowCount(); // Loads all records first!
推荐模式(使用聚合):
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
var count = 0;
if (ga.next()) {
count = parseInt(ga.getAggregate('COUNT'));
}
第 3.4 步:索引感知查询
确保查询使用已建立索引的字段。常见的索引字段:
| 表 | 索引字段 |
|---|---|
incident | number, sys_id, active, state, priority, assigned_to, assignment_group |
task | number, sys_id, active, state, assigned_to, assignment_group |
sys_user | sys_id, user_name, email, active |
sys_user_group | sys_id, name, active |
验证索引:
Tool: SN-Query-Table
Parameters:
table_name: sys_index
query: table=incident
fields: name,columns,active
第 4 阶段:错误处理和通知
第 4.1 步:全面的错误处理
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Robust Data Sync"
script: |
// Robust scheduled job with comprehensive error handling
var JOB_NAME = 'Data Sync';
var startTime = new GlideDateTime();
var stats = {
processed: 0,
errors: 0,
skipped: 0,
errorDetails: []
};
gs.info('[' + JOB_NAME + '] Starting execution');
try {
// Main processing logic
var gr = new GlideRecord('x_custom_sync_queue');
gr.addQuery('state', 'pending');
gr.setLimit(100);
gr.query();
while (gr.next()) {
try {
if (!validateRecord(gr)) {
stats.skipped++;
continue;
}
syncRecord(gr);
gr.state = 'completed';
gr.update();
stats.processed++;
} catch (recordError) {
stats.errors++;
stats.errorDetails.push({
sys_id: gr.sys_id.toString(),
error: recordError.message
});
gr.state = 'error';
gr.error_message = recordError.message;
gr.update();
gs.error('[' + JOB_NAME + '] Record error: ' + gr.sys_id + ' - ' + recordError.message);
}
}
// Success summary
gs.info('[' + JOB_NAME + '] Summary: Processed=' + stats.processed +
', Errors=' + stats.errors + ', Skipped=' + stats.skipped);
} catch (fatalError) {
// Fatal error - entire job failed
gs.error('[' + JOB_NAME + '] FATAL ERROR: ' + fatalError.message);
gs.error('[' + JOB_NAME + '] Stack: ' + fatalError.stack);
// Send alert notification
sendAlertEmail(JOB_NAME, fatalError, stats);
} finally {
// Always log duration
var endTime = new GlideDateTime();
var duration = GlideDateTime.subtract(startTime, endTime).getNumericValue() / 1000;
gs.info('[' + JOB_NAME + '] Duration: ' + duration + ' seconds');
// Send error report if there were errors
if (stats.errors > 0) {
sendErrorReport(JOB_NAME, stats);
}
}
function validateRecord(record) {
return record.getValue('source_id') && record.getValue('target_table');
}
function syncRecord(record) {
// Your sync logic here
}
function sendAlertEmail(jobName, error, stats) {
var email = new GlideEmailOutbound();
email.setSubject('[ALERT] Scheduled Job Failed: ' + jobName);
email.setFrom('[email protected]');
email.addAddress('[email protected]');
email.setBody('The scheduled job "' + jobName + '" has failed.\n\n' +
'Error: ' + error.message + '\n\n' +
'Stats at failure:\n' + JSON.stringify(stats, null, 2));
email.send();
}
function sendErrorReport(jobName, stats) {
var email = new GlideEmailOutbound();
email.setSubject('[WARN] Scheduled Job Errors: ' + jobName);
email.setFrom('[email protected]');
email.addAddress('[email protected]');
email.setBody('The scheduled job "' + jobName + '" completed with errors.\n\n' +
'Summary:\n' +
'- Processed: ' + stats.processed + '\n' +
'- Errors: ' + stats.errors + '\n' +
'- Skipped: ' + stats.skipped + '\n\n' +
'Error Details:\n' + JSON.stringify(stats.errorDetails, null, 2));
email.send();
}
active: true
run_type: periodically
run_period: 900000
第 4.2 步:基于事件的通知
使用 ServiceNow 事件发送通知,而不是直接发送电子邮件。
创建事件注册:
Tool: SN-Create-Record
Parameters:
table_name: sysevent_register
data:
event_name: "scheduled.job.failed"
suffix: "alert"
script_action: true
description: "Scheduled job failure alert"
在脚本中使用:
// In your scheduled job script
try {
// Processing logic
} catch (e) {
gs.eventQueue('scheduled.job.failed', null, JOB_NAME, e.message);
}
第 5 阶段:监控和调试
第 5.1 步:查询作业执行历史记录
查找最近的执行记录:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: source=Scheduled Job^sys_created_on>javascript:gs.hoursAgo(24)
fields: message,level,sys_created_on
limit: 100
orderByDesc: sys_created_on
查找特定作业日志:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: messageLIKE[Daily Incident Cleanup]^sys_created_on>javascript:gs.hoursAgo(24)
fields: message,level,sys_created_on
limit: 50
查找错误日志:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: level=2^source=Scheduled Job^sys_created_on>javascript:gs.hoursAgo(24)
fields: message,sys_created_on,source
limit: 50
第 5.2 步:检查作业状态
列出所有活动的计划作业:
Tool: SN-Query-Table
Parameters:
table_name: sysauto_script
query: active=true
fields: name,run_type,run_time,run_period,next_action,sys_updated_on
limit: 100
检查特定作业:
Tool: SN-Query-Table
Parameters:
table_name: sysauto_script
query: name=Daily Incident Cleanup
fields: name,active,run_type,run_time,next_action,script
第 5.3 步:调试正在运行的作业
检查系统触发器:
Tool: SN-Query-Table
Parameters:
table_name: sys_trigger
query: next_action>javascript:gs.nowDateTime()^state=0
fields: name,next_action,trigger_type,state
limit: 50
检查卡住的作业:
Tool: SN-Query-Table
Parameters:
table_name: sys_trigger
query: state=1^sys_updated_on<javascript:gs.hoursAgo(1)
fields: name,state,started_at,sys_updated_on
第 6 阶段:按需执行
第 6.1 步:立即执行作业
使用 SN-Execute-Background-Script:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Execute scheduled job on demand
var job = new GlideRecord('sysauto_script');
job.addQuery('name', 'Daily Incident Cleanup');
job.query();
if (job.next()) {
var sched = new ScheduleOnce();
sched.script = job.script;
sched.setName('Manual Run: ' + job.name);
sched.schedule();
gs.info('Job scheduled for immediate execution: ' + job.name);
}
description: Trigger scheduled job manually
第 6.2 步:创建一次性触发器
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sys_trigger
data:
name: "One-Time Data Migration"
script: |
gs.info('[Migration] Starting one-time migration');
// Your migration logic here
gs.info('[Migration] Complete');
next_action: 2026-02-06 14:00:00
trigger_type: 0
state: 0
触发器类型值:
| 值 | 描述 |
|---|---|
| 0 | 运行一次 |
| 1 | 定期运行 |
| 2 | 由事件触发 |
第 7 阶段:高级模式
第 7.1 步:自调度模式
对于运行时间很长的操作,可将其拆分为多次执行并串联起来。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Large Data Migration"
script: |
// Self-scheduling pattern for large operations
var RECORDS_PER_RUN = 1000;
var PROPERTY_NAME = 'x_custom.migration.last_id';
var lastId = gs.getProperty(PROPERTY_NAME, '');
gs.info('[Migration] Starting from: ' + (lastId || 'beginning'));
var gr = new GlideRecord('x_custom_source');
if (lastId) {
gr.addQuery('sys_id', '>', lastId);
}
gr.orderBy('sys_id');
gr.setLimit(RECORDS_PER_RUN);
gr.query();
var processed = 0;
var newLastId = '';
while (gr.next()) {
migrateRecord(gr);
processed++;
newLastId = gr.sys_id.toString();
}
if (processed > 0) {
gs.setProperty(PROPERTY_NAME, newLastId);
gs.info('[Migration] Processed ' + processed + ' records. Last ID: ' + newLastId);
// Check if more records exist
var remaining = new GlideAggregate('x_custom_source');
remaining.addQuery('sys_id', '>', newLastId);
remaining.addAggregate('COUNT');
remaining.query();
if (remaining.next()) {
var count = parseInt(remaining.getAggregate('COUNT'));
gs.info('[Migration] ' + count + ' records remaining');
if (count > 0) {
// Schedule next run immediately
scheduleNextRun();
}
}
} else {
gs.info('[Migration] Complete! No more records to process.');
gs.setProperty(PROPERTY_NAME, ''); // Reset for future runs
}
function migrateRecord(record) {
// Migration logic
}
function scheduleNextRun() {
var trigger = new GlideRecord('sys_trigger');
trigger.initialize();
trigger.name = 'Migration Continuation - ' + gs.nowDateTime();
trigger.script = 'gs.include("MigrationScheduler");';
trigger.trigger_type = 0;
trigger.state = 0;
var nextAction = new GlideDateTime();
nextAction.addSeconds(10);
trigger.next_action = nextAction;
trigger.insert();
gs.info('[Migration] Scheduled next run in 10 seconds');
}
active: false
run_type: on_demand
第 7.2 步:依赖链模式
创建依赖其他作业完成后才运行的作业。
使用 MCP:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Job Chain - Step 1: Extract"
script: |
// Step 1: Extract data
gs.info('[Extract] Starting data extraction');
// Your extraction logic
var extractedData = [];
// ... populate extractedData
// Store in temp table for next step
var temp = new GlideRecord('x_custom_job_data');
temp.initialize();
temp.job_name = 'DataSync';
temp.step = 'extract';
temp.data = JSON.stringify(extractedData);
temp.status = 'completed';
temp.insert();
// Trigger next step
var trigger = new GlideRecord('sys_trigger');
trigger.initialize();
trigger.name = 'Job Chain - Step 2: Transform';
trigger.script = 'new x_custom.JobChain().runStep2();';
trigger.trigger_type = 0;
trigger.state = 0;
var nextAction = new GlideDateTime();
nextAction.addSeconds(5);
trigger.next_action = nextAction;
trigger.insert();
gs.info('[Extract] Complete. Triggered Step 2.');
active: true
run_type: daily
run_time: "01:00:00"
第 7.3 步:分布式处理模式
将大型工作负载拆分到多个计划触发器中。
// Main job that distributes work
var WORKERS = 5;
var TABLE = 'incident';
var QUERY = 'active=true^needs_processing=true';
// Count total records
var ga = new GlideAggregate(TABLE);
ga.addEncodedQuery(QUERY);
ga.addAggregate('COUNT');
ga.query();
var totalRecords = 0;
if (ga.next()) {
totalRecords = parseInt(ga.getAggregate('COUNT'));
}
if (totalRecords == 0) {
gs.info('[Distributor] No records to process');
return;
}
var batchSize = Math.ceil(totalRecords / WORKERS);
gs.info('[Distributor] Distributing ' + totalRecords + ' records across ' + WORKERS + ' workers');
// Create worker triggers
for (var i = 0; i < WORKERS; i++) {
var trigger = new GlideRecord('sys_trigger');
trigger.initialize();
trigger.name = 'Worker ' + (i + 1) + ' of ' + WORKERS;
trigger.script = 'processWorkerBatch(' + i + ', ' + batchSize + ');';
trigger.trigger_type = 0;
trigger.state = 0;
trigger.next_action = gs.nowDateTime();
trigger.insert();
}
脚本模板
模板 1:标准清理作业
// Standard cleanup job template
var JOB_NAME = 'Data Cleanup';
var TABLE = 'your_table';
var QUERY = 'active=false^sys_updated_on<javascript:gs.daysAgo(90)';
var BATCH_SIZE = 500;
var DRY_RUN = true; // Set to false to actually delete
gs.info('[' + JOB_NAME + '] Starting');
var deleted = 0;
var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.setLimit(BATCH_SIZE);
gr.query();
while (gr.next()) {
if (DRY_RUN) {
gs.info('[' + JOB_NAME + '] [DRY RUN] Would delete: ' + gr.sys_id);
} else {
gr.deleteRecord();
}
deleted++;
}
gs.info('[' + JOB_NAME + '] ' + (DRY_RUN ? '[DRY RUN] ' : '') + 'Deleted ' + deleted + ' records');
模板 2:通知作业
// Notification job template
var JOB_NAME = 'Overdue Task Reminder';
var TABLE = 'task';
var QUERY = 'active=true^due_date<javascript:gs.nowDateTime()^state!=3';
gs.info('[' + JOB_NAME + '] Starting');
var notified = 0;
var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.query();
while (gr.next()) {
if (gr.assigned_to.email) {
var email = new GlideEmailOutbound();
email.setSubject('Overdue Task: ' + gr.number);
email.setFrom('[email protected]');
email.addAddress(gr.assigned_to.email);
email.setBody('Your task ' + gr.number + ' is overdue.\n\n' +
'Description: ' + gr.short_description + '\n' +
'Due Date: ' + gr.due_date.getDisplayValue());
email.send();
notified++;
}
}
gs.info('[' + JOB_NAME + '] Sent ' + notified + ' notifications');
模板 3:数据同步作业
// Data sync job template
var JOB_NAME = 'External System Sync';
var SOURCE_TABLE = 'x_custom_staging';
var TARGET_TABLE = 'cmdb_ci_server';
var BATCH_SIZE = 100;
var stats = { created: 0, updated: 0, errors: 0 };
gs.info('[' + JOB_NAME + '] Starting sync');
var gr = new GlideRecord(SOURCE_TABLE);
gr.addQuery('sync_status', 'pending');
gr.setLimit(BATCH_SIZE);
gr.query();
while (gr.next()) {
try {
var target = new GlideRecord(TARGET_TABLE);
target.addQuery('serial_number', gr.serial_number);
target.query();
if (target.next()) {
// Update existing
updateRecord(target, gr);
target.update();
stats.updated++;
} else {
// Create new
target.initialize();
updateRecord(target, gr);
target.insert();
stats.created++;
}
gr.sync_status = 'completed';
gr.sync_date = gs.nowDateTime();
gr.update();
} catch (e) {
gr.sync_status = 'error';
gr.sync_error = e.message;
gr.update();
stats.errors++;
gs.error('[' + JOB_NAME + '] Error: ' + e.message);
}
}
gs.info('[' + JOB_NAME + '] Summary: Created=' + stats.created +
', Updated=' + stats.updated + ', Errors=' + stats.errors);
function updateRecord(target, source) {
target.name = source.hostname;
target.serial_number = source.serial_number;
target.ip_address = source.ip_address;
// Add more field mappings
}
模板 4:审计作业
// Audit job template
var JOB_NAME = 'Security Audit';
var AUDIT_RESULTS = [];
gs.info('[' + JOB_NAME + '] Starting audit');
// Audit 1: Users with admin role but no MFA
var gr = new GlideRecord('sys_user_has_role');
gr.addQuery('role.name', 'admin');
gr.addQuery('user.active', true);
gr.addQuery('user.two_factor_auth', false);
gr.query();
while (gr.next()) {
AUDIT_RESULTS.push({
type: 'admin_no_mfa',
user: gr.user.user_name.toString(),
severity: 'high'
});
}
// Audit 2: Service accounts with interactive login
var serviceAccounts = new GlideRecord('sys_user');
serviceAccounts.addQuery('user_name', 'CONTAINS', 'svc_');
serviceAccounts.addQuery('locked_out', false);
serviceAccounts.addQuery('web_service_access_only', false);
serviceAccounts.query();
while (serviceAccounts.next()) {
AUDIT_RESULTS.push({
type: 'svc_interactive_login',
user: serviceAccounts.user_name.toString(),
severity: 'medium'
});
}
// Log results
gs.info('[' + JOB_NAME + '] Found ' + AUDIT_RESULTS.length + ' findings');
// Store audit results
if (AUDIT_RESULTS.length > 0) {
var auditRecord = new GlideRecord('x_custom_audit_log');
auditRecord.initialize();
auditRecord.audit_type = JOB_NAME;
auditRecord.findings = JSON.stringify(AUDIT_RESULTS);
auditRecord.finding_count = AUDIT_RESULTS.length;
auditRecord.insert();
}
工具用法摘要
| 操作 | MCP 工具 | 用途 |
|---|---|---|
| 创建作业 | SN-Create-Record | 创建 sysauto_script 记录 |
| 更新作业 | SN-Update-Record | 修改作业设置 |
| 查询作业 | SN-Query-Table | 列出作业并检查其状态 |
| 查看日志 | SN-Query-Table | 查询 syslog 以查看执行历史记录 |
| 立即执行 | SN-Execute-Background-Script | 立即运行作业逻辑 |
| 获取架构 | SN-Get-Table-Schema | 了解表结构 |
最佳实践
- 先筛选: 始终筛选出需要处理的记录,切勿扫描整张表
- 设置限制: 使用 setLimit() 分批处理,防止超时
- 注意索引: 查询已建立索引的字段,以提升性能
- 错误处理: 使用 try-catch 包裹所有逻辑,并进行适当的日志记录
- 试运行模式: 在生产环境执行前,先使用 DRY_RUN = true 进行测试
- 充分记录日志: 使用带有作业名称前缀的结构化日志
- 监控耗时: 记录执行时间,以识别运行缓慢的作业
- 非高峰时段调度: 在非高峰时段(凌晨 2-5 点)运行高负载作业
- 条件执行: 使用条件跳过不必要的运行
- 失败时通知: 作业失败时务必发送警报
故障排除
作业未运行
症状: 计划作业未在预期时间执行 原因:
- 作业的
active: false - run_time 格式无效
- 条件脚本返回 false
- ServiceNow 调度器服务已停止
解决方案:
Tool: SN-Query-Table
Parameters:
table_name: sysauto_script
query: name=Your Job Name
fields: active,run_type,run_time,next_action,conditional,condition
作业超时
症状: 作业因超时错误而失败
原因:
- 处理的记录过多
- 查询效率低下
- 未限制批次大小
解决方案:
- 在查询中添加 setLimit()
- 使用批处理模式
- 将查询筛选到已建立索引的字段
- 将作业拆分为多个较小的作业
未显示日志
症状: 作业运行,但看不到 gs.info() 输出
原因:
- 日志级别筛选
- 查看了错误的时间范围
- 日志记录前发生脚本错误
解决方案:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: sys_created_on>javascript:gs.minutesAgo(30)^sourceLIKEScheduled
fields: message,level,source,sys_created_on
limit: 100
作业运行频率过高
症状: 定期作业的运行频率高于预期
原因:
- run_period 使用了错误的单位(毫秒,而非秒!)
- 同一作业存在多个启用的版本
解决方案:
Tool: SN-Query-Table
Parameters:
table_name: sysauto_script
query: nameLIKEYour Job
fields: name,active,run_period
条件作业从未运行
症状: 条件作业每次都被跳过
原因:
- 条件脚本出错
- 条件始终计算为 false
- 未设置
answer变量
解决方案: 直接测试条件脚本:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Test your condition
var gr = new GlideRecord('your_table');
gr.addQuery('your_query');
gr.setLimit(1);
gr.query();
var answer = gr.hasNext();
gs.info('Condition result: ' + answer);
示例
示例 1:完整的清理作业
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Cleanup Old Attachments"
script: |
var JOB_NAME = 'Attachment Cleanup';
var DAYS_OLD = 365;
var BATCH_SIZE = 100;
gs.info('[' + JOB_NAME + '] Starting');
var cutoff = gs.daysAgo(DAYS_OLD);
var deleted = 0;
var gr = new GlideRecord('sys_attachment');
gr.addQuery('sys_created_on', '<', cutoff);
gr.addQuery('table_name', 'NOT IN', 'kb_knowledge,sys_user');
gr.setLimit(BATCH_SIZE);
gr.query();
while (gr.next()) {
gr.deleteRecord();
deleted++;
}
gs.info('[' + JOB_NAME + '] Deleted ' + deleted + ' attachments');
active: true
run_type: weekly
run_time: "03:00:00"
run_dayofweek: 7
示例 2:带重试功能的集成作业
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "External API Sync"
script: |
var JOB_NAME = 'API Sync';
var MAX_RETRIES = 3;
var BATCH_SIZE = 50;
gs.info('[' + JOB_NAME + '] Starting');
var gr = new GlideRecord('x_custom_api_queue');
gr.addQuery('status', 'pending');
gr.addQuery('retry_count', '<', MAX_RETRIES);
gr.orderBy('priority');
gr.setLimit(BATCH_SIZE);
gr.query();
var stats = { success: 0, retry: 0, failed: 0 };
while (gr.next()) {
try {
var result = callExternalAPI(gr);
gr.status = 'completed';
gr.response_data = result;
stats.success++;
} catch (e) {
gr.retry_count++;
gr.last_error = e.message;
if (gr.retry_count >= MAX_RETRIES) {
gr.status = 'failed';
stats.failed++;
} else {
gr.status = 'pending';
stats.retry++;
}
}
gr.update();
}
gs.info('[' + JOB_NAME + '] Results: ' + JSON.stringify(stats));
function callExternalAPI(record) {
var rest = new sn_ws.RESTMessageV2();
rest.setEndpoint('https://api.example.com/sync');
rest.setHttpMethod('POST');
rest.setRequestBody(JSON.stringify({
id: record.external_id.toString(),
data: record.payload.toString()
}));
var response = rest.execute();
if (response.getStatusCode() != 200) {
throw new Error('API error: ' + response.getStatusCode());
}
return response.getBody();
}
active: true
run_type: periodically
run_period: 300000
相关技能
admin/script-execution- 后台脚本模式admin/batch-operations- 批量记录操作admin/update-set-management- 在更新集中捕获作业itsm/incident-lifecycle- 事件自动化