batch-operations について
大量のレコード作成、更新、インポートを扱う ServiceNow 管理者、開発者、データ移行担当者向けのスキルです。ServiceNow のツールやスクリプトを用いたバッチ処理、性能調整、エラー追跡、再試行、ロールバックを説明します。
主な機能
- 01レコードの一括作成と更新
- 02関連レコード作成のパターン
- 03バッチ処理とクエリの最適化
- 04レコード単位のエラー追跡と再試行
- 05ロールバックとデータ移行のパターン
活用シーン
- 01ServiceNow レコードの一括作成・更新
- 02テーブル間のデータ移行
- 03外部 JSON データのインポート
- 04エラーを追跡しながらレコードを一括修正
MCP 対応
SN-Batch-Create、SN-Batch-Update、SN-Create-Record、SN-Execute-Background-Script などの MCP ツールによる一括・並列操作を説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ happy-technologies-llc/ happy-platform-skills --skill batch-operationsインストール後、Claude Code で/batch-operationsを入力して読み込まれたか確認します。
詳細
一括操作
概要
このスキルでは、ServiceNow で一括操作を効率的に行う手法について説明します。
- 適切なリレーションを設定した大量のレコード作成
- 検証とエラー処理を伴う一括更新
- 大規模なデータセットを扱う際のパフォーマンス上の考慮事項
- トランザクション管理とロールバック戦略
- 並列処理のパターン
使用する場面: 多数のレコードを一度に作成または更新するとき、データ移行、一括インポート、大量のデータ修正を行うとき。
対象ユーザー: 管理者、開発者、データ移行のスペシャリスト。
前提条件
- ロール:
adminまたはテーブル固有の書き込み権限 - アクセス: 対象テーブルと関連する参照テーブル
- 知識: GlideRecord API、テーブルのリレーション、ServiceNow のデータモデル
- 環境: 大規模な操作は、まず本番環境以外でテストしてください
パフォーマンスのガイドライン
レコード数の推奨事項
| 件数 | 方法 | 推定所要時間 |
|---|---|---|
| 1~10 | MCP の並列呼び出し | < 5 秒 |
| 10~100 | SN-Batch-Create/Update | 5~30 秒 |
| 100~1,000 | バックグラウンドスクリプトのバッチ | 1~5 分 |
| 1,000~10,000 | バッチ処理を行うスケジュール済みジョブ | 10~60 分 |
| 10,000 以上 | インポートセットまたはデータポンプ | 数時間 |
MCP の並列処理
テスト済み・検証済み: 1 つのメッセージで 43 件以上の MCP 並列呼び出しに成功しています。
Parallel MCP calls are preferred for:
- Independent record operations
- Different tables
- No interdependencies
- Maximum throughput
手順
フェーズ 1:大量のレコード作成
ステップ 1.1:シンプルな一括作成
1 回の操作で複数のレコードを作成します。
MCP を使用:
Tool: SN-Batch-Create
Parameters:
records:
- table_name: incident
data:
short_description: "Server down - Web01"
caller_id: [user_sys_id]
category: hardware
priority: 1
- table_name: incident
data:
short_description: "Server down - Web02"
caller_id: [user_sys_id]
category: hardware
priority: 1
- table_name: incident
data:
short_description: "Server down - Web03"
caller_id: [user_sys_id]
category: hardware
priority: 1
応答:
{
"success": true,
"created": [
{ "table": "incident", "sys_id": "abc123...", "number": "INC0010001" },
{ "table": "incident", "sys_id": "def456...", "number": "INC0010002" },
{ "table": "incident", "sys_id": "ghi789...", "number": "INC0010003" }
],
"errors": []
}
ステップ 1.2:MCP の並列呼び出し
最大のスループットを得るには、1 つのメッセージで MCP を並列に呼び出します。
インシデントの並列作成(10 件):
# Call 1:
Tool: SN-Create-Record
Parameters:
table_name: incident
data:
short_description: "Batch test 1"
category: inquiry
# Call 2:
Tool: SN-Create-Record
Parameters:
table_name: incident
data:
short_description: "Batch test 2"
category: inquiry
# Call 3-10: Continue pattern...
すべての呼び出しが同時に実行されるため、操作間の待機はありません。
ステップ 1.3:リレーションを設定した作成
親レコードと子レコードを、リレーションを維持したまま作成します。
MCP を使用(依存関係があるため逐次実行):
# Step 1: Create parent record
Tool: SN-Create-Record
Parameters:
table_name: change_request
data:
short_description: "Server Infrastructure Upgrade"
category: hardware
type: standard
# Step 2: Create child tasks (use parent sys_id from step 1)
Tool: SN-Batch-Create
Parameters:
records:
- table_name: change_task
data:
parent: [change_request_sys_id]
short_description: "Backup existing configuration"
change_task_type: planning
order: 100
- table_name: change_task
data:
parent: [change_request_sys_id]
short_description: "Install new hardware"
change_task_type: implementation
order: 200
- table_name: change_task
data:
parent: [change_request_sys_id]
short_description: "Verify functionality"
change_task_type: testing
order: 300
ステップ 1.4:複雑な作成処理のバックグラウンドスクリプト
複雑なロジックや非常に大規模なバッチには、次を使用します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Batch creation with relationships
var projectData = [
{
name: 'Project Alpha',
tasks: [
{ name: 'Task A1', order: 1 },
{ name: 'Task A2', order: 2 },
{ name: 'Task A3', order: 3 }
]
},
{
name: 'Project Beta',
tasks: [
{ name: 'Task B1', order: 1 },
{ name: 'Task B2', order: 2 }
]
}
];
var created = { projects: 0, tasks: 0 };
projectData.forEach(function(project) {
// Create project
var prj = new GlideRecord('pm_project');
prj.initialize();
prj.short_description = project.name;
var prjId = prj.insert();
created.projects++;
// Create tasks
project.tasks.forEach(function(task) {
var tsk = new GlideRecord('pm_project_task');
tsk.initialize();
tsk.parent = prjId;
tsk.short_description = task.name;
tsk.order = task.order;
tsk.insert();
created.tasks++;
});
gs.info('Created project: ' + project.name + ' with ' + project.tasks.length + ' tasks');
});
gs.info('Summary: ' + JSON.stringify(created));
description: Batch create projects with tasks
フェーズ 2:一括更新
ステップ 2.1:シンプルな一括更新
sys_id で複数のレコードを更新します。
MCP を使用:
Tool: SN-Batch-Update
Parameters:
updates:
- table_name: incident
sys_id: [incident1_sys_id]
data:
priority: 2
state: 2
work_notes: "Escalated per management request"
- table_name: incident
sys_id: [incident2_sys_id]
data:
priority: 2
state: 2
work_notes: "Escalated per management request"
- table_name: incident
sys_id: [incident3_sys_id]
data:
priority: 2
state: 2
work_notes: "Escalated per management request"
ステップ 2.2:クエリベースの一括更新
クエリに一致するすべてのレコードを更新します。
バックグラウンドスクリプトを使用:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Query-based bulk update
var TABLE = 'incident';
var QUERY = 'active=true^category=inquiry^priority=5';
var UPDATES = {
priority: 4,
work_notes: 'Priority adjusted per SLA review'
};
var MAX_UPDATES = 1000;
var DRY_RUN = true;
var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.setLimit(MAX_UPDATES);
gr.query();
gs.info('Found ' + gr.getRowCount() + ' records matching query');
var updated = 0;
var errors = 0;
while (gr.next()) {
try {
if (DRY_RUN) {
gs.info('[DRY RUN] Would update: ' + gr.number);
} else {
for (var field in UPDATES) {
gr[field] = UPDATES[field];
}
gr.update();
updated++;
}
} catch (e) {
errors++;
gs.error('Error updating ' + gr.number + ': ' + e.message);
}
}
gs.info('Complete: Updated=' + updated + ', Errors=' + errors + ', DryRun=' + DRY_RUN);
description: Bulk update incidents by query
ステップ 2.3:条件付き更新
レコードの条件に応じて、異なる更新を適用します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Conditional bulk updates
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('priority', 'IN', '1,2');
gr.setLimit(500);
gr.query();
var stats = { p1_updates: 0, p2_updates: 0 };
while (gr.next()) {
if (gr.priority == 1) {
// P1 gets immediate attention
if (gr.assigned_to.nil()) {
gr.assignment_group = 'Critical Response Team';
gr.work_notes = 'Auto-assigned to Critical Response Team';
gr.update();
stats.p1_updates++;
}
} else if (gr.priority == 2) {
// P2 gets SLA check
var age = GlideDateTime.subtract(new GlideDateTime(gr.sys_created_on), new GlideDateTime()).getDayPart();
if (age > 2 && gr.state == 1) {
gr.escalation = 1;
gr.work_notes = 'Auto-escalated: P2 older than 2 days without progress';
gr.update();
stats.p2_updates++;
}
}
}
gs.info('Conditional updates complete: ' + JSON.stringify(stats));
description: Apply conditional updates based on priority
フェーズ 3:パフォーマンスの最適化
ステップ 3.1:バッチ処理パターン
大規模なデータセットを、管理しやすいバッチに分けて処理します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Optimized batch processing
var CONFIG = {
table: 'incident',
query: 'active=true',
batchSize: 200,
maxBatches: 50,
pauseBetweenBatches: false // true adds 100ms delay
};
var totalProcessed = 0;
var batchNum = 0;
var startTime = new GlideDateTime();
while (batchNum < CONFIG.maxBatches) {
var gr = new GlideRecord(CONFIG.table);
gr.addEncodedQuery(CONFIG.query);
gr.orderBy('sys_created_on');
gr.chooseWindow(batchNum * CONFIG.batchSize, (batchNum + 1) * CONFIG.batchSize);
gr.query();
if (!gr.hasNext()) {
break;
}
var batchProcessed = 0;
while (gr.next()) {
// Process record (minimal operations for speed)
batchProcessed++;
}
totalProcessed += batchProcessed;
batchNum++;
// Progress logging every 10 batches
if (batchNum % 10 === 0) {
gs.info('Progress: Batch ' + batchNum + ', Total processed: ' + totalProcessed);
}
// Optional pause to reduce system load
if (CONFIG.pauseBetweenBatches) {
gs.sleep(100);
}
}
var endTime = new GlideDateTime();
var duration = GlideDateTime.subtract(startTime, endTime).getNumericValue() / 1000;
var rate = totalProcessed / duration;
gs.info('Complete: ' + totalProcessed + ' records in ' + duration.toFixed(2) + ' seconds (' + rate.toFixed(0) + ' records/sec)');
description: Optimized batch processing with metrics
ステップ 3.2:クエリの最適化
一括操作には、効率的なクエリパターンを使用します。
件数の集計には GlideAggregate を使用:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Efficient counting with GlideAggregate
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.groupBy('priority');
ga.query();
var results = [];
while (ga.next()) {
results.push({
priority: ga.priority.getDisplayValue(),
count: parseInt(ga.getAggregate('COUNT'))
});
}
gs.info('Priority Distribution:\n' + JSON.stringify(results, null, 2));
description: Count incidents by priority efficiently
一括更新には setWorkflow(false) を使用:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Disable business rules for bulk operations
var gr = new GlideRecord('incident');
gr.addQuery('state', 7); // Closed
gr.addQuery('sys_updated_on', '<', gs.daysAgo(365));
gr.setLimit(1000);
gr.query();
// Disable workflows/business rules
gr.setWorkflow(false);
var count = 0;
while (gr.next()) {
gr.archived = true;
gr.update();
count++;
}
gs.info('Archived ' + count + ' old closed incidents (business rules disabled)');
description: Bulk archive with business rules disabled
警告: ワークフローを無効にするのは、その影響を十分に理解している場合に限ってください。
ステップ 3.3:メモリの最適化
大規模なデータセットによるメモリの問題を防ぎます。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Memory-efficient processing
var BATCH_SIZE = 100;
var offset = 0;
var totalProcessed = 0;
var hasMore = true;
while (hasMore) {
// Use GlideRecord with window to avoid loading all records
var gr = new GlideRecord('cmdb_ci');
gr.addQuery('operational_status', 1);
gr.orderBy('sys_id'); // Consistent ordering
gr.chooseWindow(offset, offset + BATCH_SIZE);
gr.query();
if (gr.getRowCount() === 0) {
hasMore = false;
break;
}
while (gr.next()) {
// Process record - avoid storing in arrays
// Direct operations only
totalProcessed++;
}
offset += BATCH_SIZE;
// Check if we got fewer than batch size (last batch)
if (gr.getRowCount() < BATCH_SIZE) {
hasMore = false;
}
}
gs.info('Processed ' + totalProcessed + ' CI records');
description: Memory-efficient CI processing
フェーズ 4:エラー処理
ステップ 4.1:レコードごとのエラー処理
成功と失敗を個別に記録します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Comprehensive error tracking
var results = {
success: [],
errors: [],
skipped: []
};
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.setLimit(100);
gr.query();
while (gr.next()) {
try {
// Validation
if (gr.short_description.nil()) {
results.skipped.push({
number: gr.number.toString(),
reason: 'Missing short description'
});
continue;
}
// Attempt update
gr.work_notes = 'Batch processed on ' + new GlideDateTime().getDisplayValue();
gr.update();
results.success.push({
number: gr.number.toString(),
sys_id: gr.sys_id.toString()
});
} catch (e) {
results.errors.push({
number: gr.number.toString(),
error: e.message
});
}
}
gs.info('Results Summary:');
gs.info(' Success: ' + results.success.length);
gs.info(' Errors: ' + results.errors.length);
gs.info(' Skipped: ' + results.skipped.length);
if (results.errors.length > 0) {
gs.error('Errors:\n' + JSON.stringify(results.errors, null, 2));
}
if (results.skipped.length > 0) {
gs.warn('Skipped:\n' + JSON.stringify(results.skipped, null, 2));
}
description: Batch update with comprehensive error tracking
ステップ 4.2:再試行パターン
一時的な障害に備えて、再試行ロジックを実装します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Retry pattern for batch operations
var MAX_RETRIES = 3;
var RETRY_DELAY = 1000; // milliseconds
function updateWithRetry(tableName, sysId, data) {
var attempts = 0;
var lastError = null;
while (attempts < MAX_RETRIES) {
attempts++;
try {
var gr = new GlideRecord(tableName);
if (gr.get(sysId)) {
for (var field in data) {
gr[field] = data[field];
}
gr.update();
return { success: true, attempts: attempts };
} else {
return { success: false, error: 'Record not found', attempts: attempts };
}
} catch (e) {
lastError = e.message;
gs.warn('Attempt ' + attempts + ' failed: ' + e.message);
if (attempts < MAX_RETRIES) {
gs.sleep(RETRY_DELAY * attempts); // Exponential backoff
}
}
}
return { success: false, error: lastError, attempts: attempts };
}
// Example usage
var recordsToUpdate = [
{ sys_id: '[sys_id_1]', data: { state: 2 } },
{ sys_id: '[sys_id_2]', data: { state: 2 } }
];
var results = [];
recordsToUpdate.forEach(function(record) {
var result = updateWithRetry('incident', record.sys_id, record.data);
result.sys_id = record.sys_id;
results.push(result);
});
gs.info('Update Results:\n' + JSON.stringify(results, null, 2));
description: Batch update with retry logic
ステップ 4.3:トランザクションのロールバック
バッチ操作が失敗した場合のロールバックを実装します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Batch operation with rollback capability
var operations = []; // Track for potential rollback
var allSuccess = true;
try {
// Operation 1
var inc1 = new GlideRecord('incident');
inc1.get('[incident_1_sys_id]');
var oldState1 = inc1.state.toString();
inc1.state = 6;
inc1.update();
operations.push({ table: 'incident', sys_id: inc1.sys_id.toString(), field: 'state', oldValue: oldState1 });
// Operation 2
var inc2 = new GlideRecord('incident');
inc2.get('[incident_2_sys_id]');
var oldState2 = inc2.state.toString();
inc2.state = 6;
inc2.update();
operations.push({ table: 'incident', sys_id: inc2.sys_id.toString(), field: 'state', oldValue: oldState2 });
// Operation 3 (simulated failure)
// throw new Error('Simulated failure for testing rollback');
gs.info('All ' + operations.length + ' operations completed successfully');
} catch (e) {
allSuccess = false;
gs.error('Error occurred: ' + e.message);
gs.info('Rolling back ' + operations.length + ' operations...');
// Rollback in reverse order
for (var i = operations.length - 1; i >= 0; i--) {
var op = operations[i];
try {
var rollback = new GlideRecord(op.table);
if (rollback.get(op.sys_id)) {
rollback[op.field] = op.oldValue;
rollback.setWorkflow(false); // Avoid triggering additional logic
rollback.update();
gs.info('Rolled back: ' + op.table + ' ' + op.sys_id);
}
} catch (rollbackError) {
gs.error('Rollback failed for ' + op.sys_id + ': ' + rollbackError.message);
}
}
gs.info('Rollback complete');
}
description: Batch operation with transaction-like rollback
フェーズ 5:データ移行パターン
ステップ 5.1:テーブル間の移行
変換を行いながら、テーブル間でデータを移行します。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Table-to-table migration
var SOURCE_TABLE = 'x_old_app_requests';
var TARGET_TABLE = 'sc_request';
var BATCH_SIZE = 100;
var fieldMapping = {
'old_number': 'number',
'old_description': 'description',
'requestor': 'requested_for',
'submit_date': 'opened_at'
};
var migrated = 0;
var errors = 0;
var source = new GlideRecord(SOURCE_TABLE);
source.addQuery('migrated', false);
source.setLimit(BATCH_SIZE);
source.query();
while (source.next()) {
try {
var target = new GlideRecord(TARGET_TABLE);
target.initialize();
// Apply field mapping
for (var oldField in fieldMapping) {
var newField = fieldMapping[oldField];
target[newField] = source[oldField];
}
// Transform values
target.state = mapState(source.old_state);
target.priority = mapPriority(source.old_priority);
var newSysId = target.insert();
// Mark source as migrated
source.migrated = true;
source.migrated_to = newSysId;
source.update();
migrated++;
} catch (e) {
errors++;
gs.error('Error migrating ' + source.old_number + ': ' + e.message);
}
}
gs.info('Migration complete: ' + migrated + ' migrated, ' + errors + ' errors');
function mapState(oldState) {
var stateMap = { 'open': 1, 'in_progress': 2, 'closed': 3 };
return stateMap[oldState] || 1;
}
function mapPriority(oldPriority) {
var priorityMap = { 'critical': 1, 'high': 2, 'medium': 3, 'low': 4 };
return priorityMap[oldPriority] || 4;
}
description: Table-to-table data migration with transformation
ステップ 5.2:外部データのインポート
外部ソース(JSON 配列)からデータをインポートします。
Tool: SN-Execute-Background-Script
Parameters:
script: |
// External data import pattern
var importData = [
{ email: '[email protected]', first_name: 'John', last_name: 'Doe', department: 'IT' },
{ email: '[email protected]', first_name: 'Jane', last_name: 'Smith', department: 'HR' },
{ email: '[email protected]', first_name: 'Bob', last_name: 'Wilson', department: 'Finance' }
];
var results = { created: 0, updated: 0, errors: 0 };
importData.forEach(function(data) {
try {
// Check for existing record
var existing = new GlideRecord('sys_user');
existing.addQuery('email', data.email);
existing.query();
if (existing.next()) {
// Update existing
existing.first_name = data.first_name;
existing.last_name = data.last_name;
existing.department = lookupDepartment(data.department);
existing.update();
results.updated++;
gs.info('Updated user: ' + data.email);
} else {
// Create new
var newUser = new GlideRecord('sys_user');
newUser.initialize();
newUser.email = data.email;
newUser.user_name = data.email.split('@')[0];
newUser.first_name = data.first_name;
newUser.last_name = data.last_name;
newUser.department = lookupDepartment(data.department);
newUser.active = true;
newUser.insert();
results.created++;
gs.info('Created user: ' + data.email);
}
} catch (e) {
results.errors++;
gs.error('Error importing ' + data.email + ': ' + e.message);
}
});
gs.info('Import complete: ' + JSON.stringify(results));
function lookupDepartment(name) {
var dept = new GlideRecord('cmn_department');
dept.addQuery('name', name);
dept.query();
if (dept.next()) {
return dept.sys_id.toString();
}
return '';
}
description: Import external user data with upsert logic
ツールの使用概要
| 操作 | MCP ツール | 目的 |
|---|---|---|
| 一括作成 | SN-Batch-Create | 複数のレコードを作成 |
| 一括更新 | SN-Batch-Update | sys_id で複数のレコードを更新 |
| 並列作成 | SN-Create-Record(複数) | 最大のスループット |
| 並列更新 | SN-Update-Record(複数) | 最大のスループット |
| 複雑なバッチ処理 | SN-Execute-Background-Script | 高度なロジック |
| クエリ | SN-Query-Table | 一括操作の対象レコードを検索 |
パフォーマンス比較
| 方法 | 100 件 | 1,000 件 | 10,000 件 |
|---|---|---|---|
| MCP の逐次呼び出し | 約 50 秒 | 約 500 秒 | 実用的ではありません |
| MCP の並列呼び出し(10) | 約 5 秒 | 約 50 秒 | 約 500 秒 |
| SN-Batch-Create | 約 3 秒 | 約 30 秒 | 約 300 秒 |
| バックグラウンドスクリプト | 約 2 秒 | 約 20 秒 | 約 200 秒 |
| setWorkflow(false) | 約 1 秒 | 約 10 秒 | 約 100 秒 |
ベストプラクティス
- まずテストする: DRY_RUN=true を指定するか、小規模なサブセットで実行します
- 上限を設定する: 無制限のレコードを処理しないでください
- 適切にバッチ処理する: パフォーマンスを最適化するには、1 バッチあたり 100~500 件にします
- 進捗を記録する: 10~100 件の処理ごとに進捗を報告します
- エラーに対処する: レコードごとに try-catch を実装します
- 変更を追跡する: ロールバックに備えて古い値を保存します
- ビジネスルールを考慮する: 適切な場合にのみ setWorkflow(false) を使用します
- パフォーマンスを監視する: 処理速度(レコード数/秒)を追跡します
- トランザクションを使用する: 関連する変更をまとめ、アトミックな操作にします
- 操作を記録する: 監査のため、変更内容を記録します
トラブルシューティング
操作のタイムアウト
症状: スクリプトの実行が処理の途中で停止します
原因:
- 1 回の実行で処理するレコード数が多すぎる
- 複雑なビジネスルールが実行された 解決策:
- バッチサイズを小さくします
- 自己スケジューリングのパターンを使用します
- 適切な場合はワークフローを無効にします
メモリエラー
症状: 「メモリ不足」またはパフォーマンスの低下
原因:
- 一度に読み込むレコード数が多すぎる
- 結果を配列に格納している 解決策:
- ページネーションには chooseWindow() を使用します
- 処理したデータはすぐに破棄します
- 結果をメモリに蓄積しないでください
重複レコードが作成される
症状: 同じレコードが複数作成されます
原因:
- 一意制約のチェックがない
- スクリプトが複数回実行された 解決策:
- 挿入前に必ず既存のレコードを確認します
- 重複排除には一意キーを使用します
- 冪等性チェックを追加します
ビジネスルールが実行されない
症状: 期待される副作用が発生しません
原因:
- setWorkflow(false) が使用されている
- レコードが正しく更新されていない 解決策:
- ルールの実行が必要な場合は setWorkflow(false) を削除します
- フィールドの更新が有効であることを確認します
- ビジネスルールの条件を確認します
関連スキル
admin/script-execution- バックグラウンドスクリプトの実行admin/update-set-management- バッチ操作の変更を追跡admin/user-provisioning- ユーザーの一括操作admin/deployment-workflow- インスタンス間でバッチスクリプトをデプロイ