mcpField
S
script-execution

script-execution

github.com/happy-technologies-llc開発4142 回インストール2 日前に更新
リポジトリ

script-execution について

ServiceNow の管理者や開発者向けのスクリプト実行スキルです。MCP ツールで対応できない操作や複雑なデータ処理に使うサーバーサイド JavaScript の実行方法を、ログやデバッグ、修正スクリプトとともに説明します。

主な機能

  1. 01sys_trigger によるスクリプト自動実行
  2. 02読み取り・更新・挿入・削除のパターン
  3. 03ログ記録と実行結果の確認
  4. 04バッチ処理と非同期実行
  5. 05修正スクリプト作成とトラブルシューティング

活用シーン

  1. 01MCP ツールで未対応の ServiceNow 操作
  2. 02ServiceNow レコードの一括処理や更新
  3. 03ログを使ったスクリプトのテストとエラー調査
  4. 04監査可能な修正スクリプトの作成

MCP 対応

SN-Execute-Background-Script などの MCP ツールでサーバーサイド JavaScript を実行し、ログの照会や修正スクリプトの作成も行います。

インストール

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

$npx skills add https://github.com/happy-technologies-llc/happy-platform-skills --skill script-execution

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

詳細

スクリプトの実行

概要

このスキルでは、ServiceNow でバックグラウンドスクリプトを安全かつ効果的に実行する方法を説明します。

  • sys_trigger を使用したスクリプトの自動実行(画期的な方法)
  • 安全なスクリプトのパターンとテンプレート
  • 手動実行用の修正スクリプトの生成
  • エラー処理とログ記録のベストプラクティス
  • デバッグとトラブルシューティングの手法

使用する場面: MCP ツールで必要な操作に対応できない場合、または複雑なデータ操作が必要な場合です。

対象ユーザー: ServiceNow でサーバーサイド JavaScript を実行する必要がある管理者と開発者です。

前提条件

  • ロール: admin(バックグラウンドスクリプトの実行には完全な管理者権限が必要です)
  • アクセス: sys_trigger、syslog、sys_script_fix テーブル
  • 知識: GlideRecord API、ServiceNow のサーバーサイド JavaScript
  • 注意: バックグラウンドスクリプトは昇格された権限で実行されるため、慎重にテストしてください

実行方法

方法の比較
方法自動化速度使用例
sys_trigger完全約1~2秒すべての自動タスクに推奨
UI endpoint完全約1~2秒trigger が失敗した場合の代替手段
Fix Script手動可変監査証跡、スケジュール実行
Scripts - Background手動即時1回限りの対話型テスト

手順

フェーズ 1:自動実行(推奨)
ステップ 1.1:基本的なスクリプト実行

sys_trigger を使用する方法では、すぐに実行され、実行後に自動削除されるスケジュール済みジョブを作成します。

MCP を使用する場合:

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    gs.info('Hello from automated script execution!');

    // Your logic here
    var count = 0;
    var gr = new GlideRecord('incident');
    gr.addQuery('active', true);
    gr.addQuery('priority', 1);
    gr.setLimit(10);
    gr.query();

    while (gr.next()) {
      count++;
      gs.info('Found P1 incident: ' + gr.number);
    }

    gs.info('Total P1 incidents found: ' + count);
  description: Count active P1 incidents
  execution_method: trigger

仕組み:

  1. sys_trigger テーブルにスケジュール済みジョブを作成します
  2. next_action を即時実行に設定します
  3. trigger がスクリプトを実行します
  4. スクリプトはシステムログに出力を記録します
  5. 実行後、trigger は自動的に削除されます
ステップ 1.2:実行を確認する

システムログを確認する:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: message=*automated script*^sys_created_on>javascript:gs.minutesAgo(5)
  fields: message,level,sys_created_on,source
  limit: 20

ログレベル:

レベルメソッド目的
0gs.info()情報メッセージ
1gs.warn()警告メッセージ
2gs.error()エラーメッセージ
3gs.debug()デバッグメッセージ(有効な場合)
フェーズ 2:安全なスクリプトのパターン
ステップ 2.1:クエリのパターン(読み取り専用)

データを読み取るための安全なパターンです。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Safe read-only pattern
    var results = [];

    var gr = new GlideRecord('incident');
    gr.addQuery('active', true);
    gr.addQuery('priority', 1);
    gr.setLimit(100);  // Always set a limit
    gr.query();

    while (gr.next()) {
      results.push({
        number: gr.number.toString(),
        short_description: gr.short_description.toString(),
        assigned_to: gr.assigned_to.getDisplayValue(),
        created: gr.sys_created_on.toString()
      });
    }

    gs.info('Query Results: ' + JSON.stringify(results, null, 2));
  description: Query active P1 incidents (read-only)
ステップ 2.2:更新のパターン(安全確認付き)

安全対策を講じてレコードを更新するためのパターンです。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Safe update pattern with validation
    var TABLE = 'incident';
    var QUERY = 'active=true^priority=1^assigned_toISEMPTY';
    var MAX_UPDATES = 10;  // Limit updates per execution

    // DRY RUN flag - set to false to actually update
    var DRY_RUN = true;

    var updated = 0;
    var skipped = 0;

    var gr = new GlideRecord(TABLE);
    gr.addEncodedQuery(QUERY);
    gr.setLimit(MAX_UPDATES);
    gr.query();

    gs.info('Found ' + gr.getRowCount() + ' records to process');

    while (gr.next()) {
      // Validate before update
      if (!gr.canWrite()) {
        gs.warn('Cannot write to: ' + gr.number);
        skipped++;
        continue;
      }

      if (DRY_RUN) {
        gs.info('[DRY RUN] Would update: ' + gr.number);
      } else {
        gr.work_notes = 'Automated: Escalating unassigned P1';
        gr.assignment_group = 'Critical Incidents Team';
        gr.update();
        gs.info('Updated: ' + gr.number);
        updated++;
      }
    }

    gs.info('Summary: Updated=' + updated + ', Skipped=' + skipped + ', DryRun=' + DRY_RUN);
  description: Escalate unassigned P1 incidents (with dry run)
ステップ 2.3:挿入のパターン(重複チェック付き)

レコードを安全に作成するためのパターンです。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Safe insert pattern with duplicate check
    var TABLE = 'sys_properties';
    var PROPERTY_NAME = 'custom.automation.enabled';
    var PROPERTY_VALUE = 'true';

    // Check for existing
    var existing = new GlideRecord(TABLE);
    existing.addQuery('name', PROPERTY_NAME);
    existing.query();

    if (existing.next()) {
      gs.warn('Property already exists: ' + PROPERTY_NAME + ' = ' + existing.value);

      // Update if different
      if (existing.value != PROPERTY_VALUE) {
        existing.value = PROPERTY_VALUE;
        existing.update();
        gs.info('Updated property value to: ' + PROPERTY_VALUE);
      }
    } else {
      // Create new
      var gr = new GlideRecord(TABLE);
      gr.initialize();
      gr.name = PROPERTY_NAME;
      gr.value = PROPERTY_VALUE;
      gr.description = 'Created by automated script';
      var sysId = gr.insert();
      gs.info('Created property: ' + PROPERTY_NAME + ' (sys_id: ' + sysId + ')');
    }
  description: Create or update system property
ステップ 2.4:削除のパターン(確認付き)

最大限の注意を払ってレコードを削除するためのパターンです。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // DANGEROUS: Delete pattern - use with extreme caution
    var TABLE = 'sys_user_preference';
    var QUERY = 'user.active=false^sys_created_on<javascript:gs.daysAgo(365)';
    var MAX_DELETES = 10;

    // SAFETY: Always start with dry run
    var DRY_RUN = true;
    var CONFIRMATION_CODE = 'DELETE_CONFIRMED_2026';
    var PROVIDED_CODE = 'INTENTIONALLY_WRONG';  // Must match to delete

    if (!DRY_RUN && PROVIDED_CODE !== CONFIRMATION_CODE) {
      gs.error('SAFETY: Confirmation code mismatch. Aborting delete.');
      return;
    }

    var gr = new GlideRecord(TABLE);
    gr.addEncodedQuery(QUERY);
    gr.setLimit(MAX_DELETES);
    gr.query();

    var count = 0;
    var deleted = [];

    while (gr.next()) {
      count++;
      if (DRY_RUN) {
        gs.info('[DRY RUN] Would delete: ' + gr.sys_id + ' - ' + gr.name);
      } else {
        deleted.push(gr.sys_id.toString());
        gr.deleteRecord();
      }
    }

    if (DRY_RUN) {
      gs.info('[DRY RUN] Would delete ' + count + ' records');
    } else {
      gs.info('Deleted ' + deleted.length + ' records: ' + JSON.stringify(deleted));
    }
  description: Delete old user preferences (dry run mode)
フェーズ 3:修正スクリプトの生成
ステップ 3.1:監査証跡用の修正スクリプトを作成する

監査証跡やスケジュール実行が必要な場合に使用します。

MCP を使用する場合:

Tool: SN-Create-Fix-Script
Parameters:
  name: FIX_IncidentDataCleanup_20260206
  script: |
    // Fix Script: Clean up old resolved incidents
    // Author: Admin
    // Date: 2026-02-06
    // JIRA: PROJ-123

    var cutoffDays = 90;
    var cutoffDate = gs.daysAgo(cutoffDays);

    gs.info('=== Starting Incident Cleanup ===');
    gs.info('Cutoff date: ' + cutoffDate);

    var gr = new GlideRecord('incident');
    gr.addQuery('state', 'IN', '6,7,8');  // Resolved, Closed, Cancelled
    gr.addQuery('sys_updated_on', '<', cutoffDate);
    gr.query();

    gs.info('Found ' + gr.getRowCount() + ' incidents to archive');

    // Archive logic here

    gs.info('=== Cleanup Complete ===');
  description: Clean up old resolved incidents for data hygiene

修正スクリプトを手動で作成する:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script_fix
  data:
    name: FIX_IncidentDataCleanup_20260206
    script: |
      // Script content here
    description: Clean up old resolved incidents
    active: true
ステップ 3.2:修正スクリプトをクエリする

既存の修正スクリプトを検索する:

Tool: SN-Query-Table
Parameters:
  table_name: sys_script_fix
  query: active=true
  fields: name,description,sys_created_on,run_count
  limit: 50
フェーズ 4:エラー処理
ステップ 4.1:try-catch パターン

エラーを処理するため、スクリプトは必ず try-catch で囲みます。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Comprehensive error handling pattern
    var scriptName = 'UpdateIncidentPriorities';
    var startTime = new GlideDateTime();

    try {
      gs.info('[' + scriptName + '] Starting execution');

      // Main logic
      var gr = new GlideRecord('incident');
      gr.addQuery('active', true);
      gr.setLimit(100);
      gr.query();

      var processed = 0;
      var errors = 0;

      while (gr.next()) {
        try {
          // Process each record
          processed++;
          // ... logic here
        } catch (recordError) {
          errors++;
          gs.error('[' + scriptName + '] Error processing ' + gr.number + ': ' + recordError.message);
        }
      }

      gs.info('[' + scriptName + '] Completed. Processed=' + processed + ', Errors=' + errors);

    } catch (e) {
      gs.error('[' + scriptName + '] Fatal error: ' + e.message);
      gs.error('[' + scriptName + '] Stack: ' + e.stack);
    } finally {
      var endTime = new GlideDateTime();
      var duration = GlideDateTime.subtract(startTime, endTime).getNumericValue() / 1000;
      gs.info('[' + scriptName + '] Duration: ' + duration + ' seconds');
    }
  description: Update incident priorities with full error handling
ステップ 4.2:トランザクションのロールバックパターン

複雑な更新をまとめて成功または失敗させる場合に使用します。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Transaction-like pattern with rollback capability
    var changes = [];
    var success = true;

    try {
      // Step 1: Update incident
      var incident = new GlideRecord('incident');
      incident.get('[incident_sys_id]');
      var oldState = incident.state.toString();
      incident.state = 6;  // Resolved
      incident.update();
      changes.push({ table: 'incident', sys_id: incident.sys_id.toString(), field: 'state', old: oldState, new: '6' });
      gs.info('Step 1 complete: Incident updated');

      // Step 2: Update related task
      var task = new GlideRecord('sc_task');
      task.get('[task_sys_id]');
      var oldTaskState = task.state.toString();
      task.state = 3;  // Closed Complete
      task.update();
      changes.push({ table: 'sc_task', sys_id: task.sys_id.toString(), field: 'state', old: oldTaskState, new: '3' });
      gs.info('Step 2 complete: Task updated');

      // Step 3: Something that might fail
      // Simulating failure for demo
      // throw new Error('Simulated failure');

      gs.info('All steps completed successfully');

    } catch (e) {
      gs.error('Error occurred: ' + e.message);
      gs.info('Rolling back ' + changes.length + ' changes...');

      // Rollback in reverse order
      for (var i = changes.length - 1; i >= 0; i--) {
        var change = changes[i];
        var rollback = new GlideRecord(change.table);
        rollback.get(change.sys_id);
        rollback[change.field] = change.old;
        rollback.update();
        gs.info('Rolled back: ' + change.table + '.' + change.sys_id);
      }

      gs.info('Rollback complete');
    }
  description: Multi-step update with rollback capability
フェーズ 5:ログ記録のベストプラクティス
ステップ 5.1:構造化ログ

一貫したログ形式を使用します。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Structured logging pattern
    var Logger = {
      scriptName: 'DataMigration',

      info: function(message, data) {
        var logMessage = '[' + this.scriptName + '] [INFO] ' + message;
        if (data) logMessage += ' | Data: ' + JSON.stringify(data);
        gs.info(logMessage);
      },

      warn: function(message, data) {
        var logMessage = '[' + this.scriptName + '] [WARN] ' + message;
        if (data) logMessage += ' | Data: ' + JSON.stringify(data);
        gs.warn(logMessage);
      },

      error: function(message, error) {
        var logMessage = '[' + this.scriptName + '] [ERROR] ' + message;
        if (error) logMessage += ' | Error: ' + error.message;
        gs.error(logMessage);
      },

      metric: function(name, value) {
        gs.info('[' + this.scriptName + '] [METRIC] ' + name + '=' + value);
      }
    };

    // Usage
    Logger.info('Starting migration', { source: 'incident', target: 'x_custom_incident' });
    Logger.metric('records_processed', 150);
    Logger.warn('Skipped record due to missing field', { number: 'INC0012345' });
    Logger.error('Failed to process record', new Error('Invalid reference'));
  description: Demonstrate structured logging pattern
ステップ 5.2:スクリプトログをクエリする

スクリプト実行ログを検索する:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: source=Script execution^sys_created_on>javascript:gs.hoursAgo(1)
  fields: message,level,sys_created_on
  limit: 100

スクリプト名で絞り込む:

Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: messageLIKE[DataMigration]^sys_created_on>javascript:gs.hoursAgo(1)
  fields: message,level,sys_created_on
  limit: 100
フェーズ 6:高度なパターン
ステップ 6.1:進捗状況を記録するバッチ処理

長時間実行される操作に使用します。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Batch processing with progress tracking
    var BATCH_SIZE = 100;
    var MAX_BATCHES = 10;
    var TABLE = 'incident';
    var QUERY = 'active=true';

    var totalProcessed = 0;
    var totalRecords = 0;
    var batchNum = 0;

    // Get total count
    var countGR = new GlideAggregate(TABLE);
    countGR.addEncodedQuery(QUERY);
    countGR.addAggregate('COUNT');
    countGR.query();
    if (countGR.next()) {
      totalRecords = parseInt(countGR.getAggregate('COUNT'));
    }
    gs.info('Total records to process: ' + totalRecords);

    // Process in batches
    while (batchNum < MAX_BATCHES) {
      var gr = new GlideRecord(TABLE);
      gr.addEncodedQuery(QUERY);
      gr.orderBy('sys_created_on');
      gr.chooseWindow(batchNum * BATCH_SIZE, (batchNum + 1) * BATCH_SIZE);
      gr.query();

      if (!gr.hasNext()) {
        gs.info('No more records to process');
        break;
      }

      var batchProcessed = 0;
      while (gr.next()) {
        // Process record
        batchProcessed++;
        totalProcessed++;
      }

      var progress = Math.round((totalProcessed / totalRecords) * 100);
      gs.info('Batch ' + (batchNum + 1) + ' complete: ' + batchProcessed + ' records | Progress: ' + progress + '%');

      batchNum++;
    }

    gs.info('Processing complete. Total processed: ' + totalProcessed + '/' + totalRecords);
  description: Batch processing with progress tracking
ステップ 6.2:非同期実行

非常に長時間実行されるスクリプトでは、複数の実行を連鎖させます。

Tool: SN-Execute-Background-Script
Parameters:
  script: |
    // Self-scheduling pattern for long operations
    var RECORDS_PER_EXECUTION = 1000;
    var DELAY_SECONDS = 10;

    // Get or create tracking property
    var tracker = gs.getProperty('x_custom.migration.last_processed', '');

    var gr = new GlideRecord('incident');
    if (tracker) {
      gr.addQuery('sys_created_on', '>', tracker);
    }
    gr.orderBy('sys_created_on');
    gr.setLimit(RECORDS_PER_EXECUTION);
    gr.query();

    var lastProcessed = '';
    var count = 0;

    while (gr.next()) {
      // Process record
      count++;
      lastProcessed = gr.sys_created_on.toString();
    }

    if (count > 0) {
      // Update tracker
      gs.setProperty('x_custom.migration.last_processed', lastProcessed);
      gs.info('Processed ' + count + ' records. Last: ' + lastProcessed);

      // Schedule next execution
      if (count == RECORDS_PER_EXECUTION) {
        var trigger = new GlideRecord('sys_trigger');
        trigger.initialize();
        trigger.name = 'Continue Migration ' + new GlideDateTime().getDisplayValue();
        trigger.next_action = new GlideDateTime();
        trigger.next_action.addSeconds(DELAY_SECONDS);
        trigger.script = 'gs.include("MigrationScript");';  // Call script include
        trigger.trigger_type = 0;  // Run once
        trigger.insert();
        gs.info('Scheduled next execution in ' + DELAY_SECONDS + ' seconds');
      } else {
        gs.info('Migration complete!');
        gs.setProperty('x_custom.migration.last_processed', '');  // Reset
      }
    } else {
      gs.info('No more records to process');
    }
  description: Self-scheduling migration script

スクリプトテンプレート

テンプレート 1:データ監査
// Data Audit Template
var TABLE = 'incident';
var AUDIT_FIELDS = ['state', 'priority', 'assigned_to'];

var audit = {};
AUDIT_FIELDS.forEach(function(field) {
  audit[field] = {};
});

var gr = new GlideRecord(TABLE);
gr.addQuery('active', true);
gr.query();

while (gr.next()) {
  AUDIT_FIELDS.forEach(function(field) {
    var value = gr.getDisplayValue(field) || '(empty)';
    audit[field][value] = (audit[field][value] || 0) + 1;
  });
}

gs.info('Audit Results:\n' + JSON.stringify(audit, null, 2));
テンプレート 2:参照データの検証
// Reference Validation Template
var TABLE = 'incident';
var REF_FIELD = 'assigned_to';
var REF_TABLE = 'sys_user';

var invalid = [];
var gr = new GlideRecord(TABLE);
gr.addQuery('active', true);
gr.addQuery(REF_FIELD + '.active', false);  // Reference to inactive record
gr.query();

while (gr.next()) {
  invalid.push({
    number: gr.number.toString(),
    invalid_ref: gr.getDisplayValue(REF_FIELD)
  });
}

gs.info('Found ' + invalid.length + ' records with invalid references');
gs.info(JSON.stringify(invalid, null, 2));
テンプレート 3:フィールドの一括更新
// Bulk Field Update Template
var TABLE = 'incident';
var QUERY = 'active=true^category=inquiry';
var UPDATES = {
  subcategory: 'general',
  contact_type: 'email'
};
var DRY_RUN = true;

var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.query();

var updated = 0;
while (gr.next()) {
  if (DRY_RUN) {
    gs.info('[DRY RUN] Would update ' + gr.number);
  } else {
    for (var field in UPDATES) {
      gr[field] = UPDATES[field];
    }
    gr.update();
    updated++;
  }
}

gs.info((DRY_RUN ? '[DRY RUN] ' : '') + 'Updated ' + updated + ' records');

ツールの使用方法の概要

操作MCP ツール目的
実行SN-Execute-Background-Scriptサーバーサイド JavaScript を実行します
修正スクリプトSN-Create-Fix-Script監査可能なスクリプトを作成します
ログのクエリSN-Query-Table実行結果を確認します
レコード作成SN-Create-Record修正スクリプトを手動で作成します

ベストプラクティス

  • まずは必ずドライランを実行する: 実際に実行する前に、DRY_RUN = true でテストします
  • 上限を設定する: スクリプトの暴走を防ぐため、必ず setLimit() を使用します
  • 詳細にログを記録する: デバッグには構造化ログを使用します
  • エラーを処理する: すべてのスクリプトを try-catch で囲みます
  • スクリプトを文書化する: コメントに作成者、日付、チケットの参照情報を記載します
  • ハードコーディングを避ける: sys_ids とクエリには変数を使用します
  • 本番環境以外でテストする: テストしていないスクリプトを本番環境で実行しないでください
  • トランザクションを使用する: 複数ステップの操作ではロールバックパターンを検討します

トラブルシューティング

スクリプトが実行されない

症状: システムログに出力がない 原因:

  • sys_trigger の権限
  • ログが記録される前にスクリプトの構文エラーが発生している 解決策:
Tool: SN-Query-Table
Parameters:
  table_name: sys_trigger
  query: nameLIKEMCP^sys_created_on>javascript:gs.minutesAgo(5)
  fields: name,state,next_action,script
スクリプトがタイムアウトする

症状: 実行が途中で止まり、タイムアウトエラーが発生する 原因:

  • 処理するレコードが多すぎる
  • クエリの効率が悪い 解決策:
  • setLimit() を追加します
  • バッチ処理パターンを使用します
  • 適切なインデックスを使ってクエリを最適化します
ログが表示されない

症状: スクリプトは実行されるが、gs.info() の出力が表示されない 原因:

  • ログレベルによるフィルタリング
  • 誤った時間範囲を確認している 解決策:
Tool: SN-Query-Table
Parameters:
  table_name: syslog
  query: sys_created_on>javascript:gs.minutesAgo(10)
  fields: message,level,source,sys_created_on
  limit: 50

関連スキル

  • admin/batch-operations - レコードの一括操作
  • admin/update-set-management - スクリプトの変更を追跡
  • admin/deployment-workflow - インスタンス間でスクリプトをデプロイ

参考資料

コメント