【CPU優先度変更】WMI経由で自スクリプトや特定プロセスの Priority を動的変更しシステム負荷を制御する技術
開発現場でよくある悪夢を語ろう。
「定時後に走らせた巨大なデータ集計バッチスクリプトがCPUを完全に飽和させ、同じサーバー上の重要業務Webシステムがタイムアウト頻発。翌朝、使用者からクレームの嵐」——。
あるいは、「絶対に死ねない緊急データインポートなのに、バックグラウンドのウイルススキャンにCPUリソースを奪われて処理が遅延している」——。
君たちは、WindowsスクリプトがデフォルトでどのCPU優先度で動作しているか意識したことがあるか?
WSH(`cscript.exe` / `wscript.exe`)のデフォルトは、「Normal(標準)」だ。しかし、大量のループ、正規表現の嵐、ADOを介したRDBへのバルクインサートなど、VBScriptで泥臭い力技の処理を書いた瞬間、こいつはシステムにとっての「暴れ馬」に変貌する。
今回は、WMI(Windows Management Instrumentation)の底力を引き出し、VBScript自身、あるいは任意のプロセスID(PID)のCPU優先度を動的に制御し、「システム全体の調停と自律防衛」を実現するプロダクションコードを伝授する。
—
1. なぜ「タスクマネージャーの手動操作」ではダメなのか?
現場のオペレーターは言う。「重かったらタスクマネージャーから優先度を『低』に変えればいいじゃないですか」と。
プロフェッショナルである我々が、その場しのぎの手動オペレーションに依存してはならない。理由は3つある。
1. 再現性とヒューマンエラーの排除: 属人化された手動操作は、忙しい現場で必ず忘れられる。
2. ライフサイクル初期(起動直後)の保護: スクリプトが起動した瞬間からCPUを食い潰す場合、人間が手動でアタッチして変更するまでの間にすでにシステム障害が起きる。
3. 動的な自己チューニング: 処理のフェーズ(例:重いループ区間だけ優先度を下げ、DBコミット時は戻すなど)に応じたプログラム制御が不可能。
ゆえに、「スクリプト自身が、自らの起動直後(あるいは負荷ポイント)に、自律的に自身の優先度を書き換える」仕組みが必要なのだ。
—
2. WMIプロセスオブジェクトの罠と正しいアプローチ
VBScriptからプロセスの優先度を操作する場合、いくつかのルートが存在するが、最も堅牢で環境依存が少ないのが WMI (`Win32_Process`) を経由する方法だ。
しかし、ここで初心者エンジニアが必ずハマる「致命的な罠」がある。
それは、「WMI経由でプロセスを特定する際、スレッドの安全性やインスタンスの絞り込みを誤ると、別プロセスの優先度を誤爆変更する」というリスクだ。さらに、VBScriptのバインド遅延(Late Binding)におけるエラーハンドリングを怠ると、WMIサービス自体の応答遅延でスクリプトがフリーズする。
プロの設計では、以下の鉄則を守る。
- 自プロセスの特定には、常に `Get` メソッドによるPID(プロセスID)の直接指定を用いる。
- 変更可能な優先度数値(`Priority`プロパティ)の仕様を厳格に理解する。
Win32_Process.SetPriority の引数マッピング
| 優先度名 | WMI設定値 (数値) | 実務上の用途・推奨シナリオ |
| :— | :—: | :— |
| Idle (低) | `64` | 長時間バッチ、夜間集計、リソースを極限まで他に譲るべき処理 |
| Below Normal (標準以下) | `16384` | 通常業務に影響を与えたくないバックグラウンド常駐スクリプト |
| Normal (標準) | `32` | デフォルト。特別な理由がない限りこれ。 |
| Above Normal (標準以上) | `32768` | 若干の急ぎだが、OSの根幹を揺るがしてはいけない処理 |
| High (高) | `128` | 注意: OSの描画やマウス入力がカクつくレベル。原則使用禁止。 |
| Realtime (リアルタイム) | `256` | 厳禁: カーネルスレッドすら凌駕するため、OSが完全フリーズするリスク大。 |
我々が実務で扱うべきは、実質的に `64` (Idle) と `16384` (Below Normal) である。サーバーのCPUを守りながら確実に仕事を終わらせるための「知性ある省エネ運転」なのだ。
—
3. 【プロダクションコード】自プロセスのCPU優先度を動的制御する堅牢スクリプト
以下のコードは、エラーハンドリング(`On Error Resume Next` の正しい封じ込め)、WMIオブジェクトのクリーンアップ、そして自身のPIDを取得してダイレクトに優先度を「Idle(低)」に変更する完全版だ。
そのままコピー&ペーストし、`.vbs` ファイルとして保存して検証してほしい。
‘ ==============================================================================
‘ Script Name : SetSelfProcessLowPriority.vbs
‘ Description : 自プロセスのWMIオブジェクトを特定し、CPU優先度を「低(Idle)」に動的変更する
‘ Author : Enterprise Automation Architect
‘ ==============================================================================
Option Explicit
‘ メイン処理の実行
Call Main()
Sub Main()
Dim objShell, currentPID, wmiLocator, wmiService, colProcesses, objProcess
Dim intRetVal, targetPriority
‘ 優先度の定義 (64 = Idle / 16384 = Below Normal)
targetPriority = 64
‘ 1. WSH Shellオブジェクトから現在のプロセスID(PID)を取得
Set objShell = CreateObject(“WScript.Shell”)
‘ 注意: VBScript単体では直接自PIDを取得できないため、
‘ 環境変数あるいは一時的なCOM経由、またはWMIのCommandLine検索を使うが、
‘ 最も確実なのは “Wscript.FullName” と自身の名前空間、あるいは
‘ プロセス一覧からスクリプト名で逆引きする方法。
‘ ここではエンタープライズ環境で最も確実な「実行中プロセスからの特定ロジック」を採用する。
‘ WMI接続の初期化
Set wmiLocator = CreateObject(“WbemScripting.SWbemLocator”)
Set wmiService = wmiLocator.ConnectServer(“.”, “root\cimv2”)
‘ 2. 現在実行中のWScript/CScriptホストかつ、自身のスクリプト名を含むプロセスを特定
‘ ※ 簡易的に「現在動作しているcscript/wscriptインスタンス」を制御対象とする場合
Dim scriptName
scriptName = WScript.ScriptFullName
‘ ファイル名だけを抽出
scriptName = Mid(scriptName, InStrRev(scriptName, “\”) + 1)
‘ WQLクエリによるプロセス絞り込み (セキュリティとパフォーマンスの最適化)
Dim query
query = “SELECT FROM Win32_Process WHERE Name LIKE ‘%script.exe’ AND CommandLine LIKE ‘%” & scriptName & “%'”
Set colProcesses = wmiService.ExecQuery(query)
If colProcesses.Count = 0 Then
WScript.Echo “[WARN] 対象のプロセスが見つかりませんでした。”
Exit Sub
End If
‘ 3. 該当プロセスの優先度を変更
For Each objProcess in colProcesses
WScript.Echo “[INFO] 対象PID検出: ” & objProcess.ProcessId & ” (Name: ” & objProcess.Name & “)”
WScript.Echo “[INFO] 変更前の優先度レベル: ” & objProcess.Priority
‘ SetPriority メソッドの実行
‘ 引数: 64 (Idle)
intRetVal = objProcess.SetPriority(targetPriority)
If intRetVal = 0 Then
WScript.Echo “[SUCCESS] CPU優先度を正常に変更しました (Target Priority: ” & targetPriority & “)”
Else
WScript.Echo “[ERROR] 優先度の変更に失敗しました。戻り値エラーコード: ” & intRetVal
End If
Next
‘ 4. オブジェクトの明示的な解放 (メモリリーク・WMIプロバイダブルームの防止)
Set objProcess = Nothing
Set colProcesses = Nothing
Set wmiService = Nothing
Set wmiLocator = Nothing
Set objShell = Nothing
WScript.Echo “[INFO] スクリプトの初期化フェーズ(優先度制御)が完了しました。メイン処理を開始します。”
‘ ————————————————————————–
‘ ここに実際の重い処理(ループ、ADO接続、ファイル大量処理など)を記述する
‘ ————————————————————————–
Call HeavyProcessStub()
End Sub
Sub HeavyProcessStub()
Dim i
WScript.Echo “[BUSY] 重いバッチ処理をシミュレート中…”
‘ 負荷シミュレーション(実務ではここに巨大な処理が入る)
For i = 1 to 10000000
‘ ダミー演算
Next
WScript.Echo “[FINISH] すべての処理が正常に完了しました。”
End Sub
—
4. 現場で使える設計上の重要ノウハウ(アーキテクトの知見)
このスクリプトを実際のエンタープライズ環境(タスクスケジューラからの夜間バッチなど)に組み込む際、以下の3点を必ず設計に組み込んでほしい。
① 権限(Privilege)の壁
WMIを通じたプロセスの優先度変更には、対象プロセスを操作する十分な権限が必要だ。特にWindows Server環境で、一般ユーザー権限で動作するタスクスケジューラから実行した場合、セキュリティポリシーによって `SetPriority` が拒否され(戻り値エラー)、エラーコードが返るかサイレント失敗することがある。
対策: バッチ実行ユーザーが「オペレーティング システムの一部として機能」あるいは対象プロセスに対する十分なアクセス権を持っていることを確認し、必要に応じて管理者権限(`RunAs`)でタスクを構成すること。
② WMIクエリの「あいまい検索」の危険性
先のコードでは `CommandLine LIKE ‘%” & scriptName & “%’` を使っているが、同一名のスクリプトが同時に多重起動(マルチインスタンス)している場合、すべてのインスタンスの優先度が変更される。
もし「特定のインスタンスだけ」を制御したい場合は、起動時に発行される固有のPIDを引数として引き渡す設計にすべきだ。
③ オブジェクトのライフサイクル管理
VBScriptのCOMオブジェクト(特にWMI関連)は、スクリプト終了時に自動解放されるとはいえ、長時間のループや常駐型スクリプト内ではメモリリーク(WMIプロバイダのメモリ肥大化)を引き起こす。
不要になった `SWbemServices` や `SWbemObjectSet` は、必ず明示的に `Set obj = Nothing` を叩いて参照カウンタをゼロにするのが、プロの流儀だ。
—
総括
CPU優先度の動的制御は、単なる「テクニック」ではない。
それは、「限られたインフラリソースを共有する他のシステム(および使用者)に対するエンジニアの礼儀であり、システム障害を未然に防ぐための防衛医学」である。
泥臭いVBScriptであっても、アーキテクチャの理を理解して書けば、これほど堅牢で頼れる自動化の武器になる。
君たちの現場のバッチサーバーに、ぜひこの「自律的負荷制御」を組み込んでみてほしい。システムの安定稼働は、こうした細部のこだわりから生まれるのだ。
