【CPU優先度変更】WMI経由で自スクリプトや特定プロセスの Priority(優先度)を動的変更しシステム負荷を制御する技術
レガシーシステムの裏側、あるいはミッションクリティカルなバッチ処理の現場において、VBScript(WSH)は今なお静かに、しかし確実に対象マシンを駆動し続けている。
だが、巨大なデータ処理やループを伴うVBScript製バッチが暴走した時、あるいはリソースを食いつぶして他の業務アプリケーションを圧迫した時、あなたはどう対処してきたか。タスクマネージャーを開いて手動で優先度を下げる? それはプロフェッショナルの仕事ではない。
今回は、WMI(Windows Management Instrumentation)を駆使し、自プロセス(あるいは任意の外部プロセス)のCPU優先度(Priority)を動的に制御・最適化する極限の技術を解説する。
—
1. なぜ「WMI経由の優先度制御」が必要なのか
VBScript自体には、自身のプロセスの実行優先度を変更するネイティブな命令(APIラッパー)は存在しない。
そのため、かつては `WshShell.Run` から `cmd.exe` を経由して `start /low` などを叩くという、お世辞にも洗練されているとは言えないハックが使われてきた。
しかし、シニアエンジニアが選択すべきは WMI(`Win32_Process` クラス) を用いたダイレクトかつオブジェクト指向的なアプローチだ。
WMIアプローチの優位性
- プロセスの完全な抽象化: プロセスID(PID)をキーにして、親・子を問わずピンポイントで制御可能。
- 自プロセス・他プロセスの両対応: 起動中のバッチが自発的に省リソースモードへ移行することも、監視スクリプトから外部プロセスのスロットリングを行うことも可能。
- 詳細なエラーハンドリング: 返り値(Return Value)を捕捉することで、権限不足やプロセス不存在をプログラム的に検知できる。
—
2. Windowsプロセス優先度のアーキテクチャと罠
WMIを通じて操作する `Priority` プロパティは、Windowsカーネルが管理する「基本優先度クラス(Base Priority Class)」に直結している。
| 優先度定数 | 数値(WMI) | 用途・挙動 |
| :— | :— | :— |
| Idle | `64` (変則的) | システムが完全にアイドル状態の時のみ実行。他プロセスの邪魔を絶対にしない。 |
| Below Normal | `16384` | 通常より低い。長時間バッチに最適。 |
| Normal | `32` | 標準。デフォルト。 |
| Above Normal | `32768` | 通常より高い。 |
| High Priority | `128` | 即座に応答すべき重要処理。ただしOSの描画等に影響が出るリスクあり。 |
| Realtime | `256` | 【使用厳禁】 ハードウェア割り込み以外ですべてがブロックされ、OSが応答しなくなる危険がある。 |
> ⚠️ 建築の知見:Realtimeの罠
> 現場で「とにかく速く終わらせろ」と `Realtime` を指定する素人がいるが、これは自殺行為だ。ネットワークやディスクのI/O処理までもが後回しにされ、最悪の場合、OSがデッドロックに陥り強制再起動を余儀なくされる。バッチ処理における最適解は、せいぜい `Below Normal` か `Normal`、突発的な高負荷処理でも `High` の手前で留めることだ。
—
3. 実装コード:WMIによる動的優先度変更スクリプト
以下のコードは、「実行時に自身のプロセスの優先度を `Below Normal` に落とし、処理終了時に復元する」、あるいは「特定のプロセス名(例: `heavy_calc.exe`)を監視して優先度を動的にねじ曲げる」ためのプロダクション品質のVBScriptである。
メモリリーク対策(オブジェクトの明示的解放)も完備している。
‘ ==============================================================================
‘ Script Name: ProcessPriorityController.vbs
‘ Description: WMI経由で自プロセスまたは特定プロセスのCPU優先度を動的制御する
‘ Author: Chief Architect
‘ ==============================================================================
Option Explicit
‘ 優先度定義定数
Const CONST_PRIORITY_IDLE = 64
Const CONST_PRIORITY_BELOW_NORMAL = 16384
Const CONST_PRIORITY_NORMAL = 32
Const CONST_PRIORITY_ABOVE_NORMAL = 32768
Const CONST_PRIORITY_HIGH = 128
‘ メイン処理の実行
Call Main()
Sub Main()
Dim currentPID
currentPID = GetCurrentProcessID()
WScript.Echo “[-] 現在の自プロセスID: ” & currentPID
‘ 1. 自プロセスの優先度を「Below Normal (低め)」に変更
Dim result
result = SetProcessPriorityByPID(currentPID, CONST_PRIORITY_BELOW_NORMAL)
If result = 0 Then
WScript.Echo “[+] 自プロセスのCPU優先度を [Below Normal] に正常に変更しました。”
Else
WScript.Echo “[!] 優先度の変更に失敗しました。Error Code: ” & result
End If
‘ — ここに本来の重いバッチ処理を記述 —
WScript.Echo “[] 重いバッチ処理を実行中…(模擬ウェイト 5秒)”
WScript.Sleep 5000
‘ 2. 処理終了前に標準(Normal)に戻す(クリーンアップの作法)
Call SetProcessPriorityByPID(currentPID, CONST_PRIORITY_NORMAL)
WScript.Echo “[+] 自プロセスのCPU優先度を [Normal] に復元しました。”
End Sub
‘ ——————————————————————————
‘ 指定されたPIDのプロセス優先度を変更するコア関数
‘ ——————————————————————————
Function SetProcessPriorityByPID(ByVal pid, ByVal targetPriority)
Dim objWMIService, colProcesses, objProcess
Dim strQuery
Dim retVal
On Error Resume Next
‘ WMI接続の確立 (CIMv2命名空間)
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
If Err.Number <> 0 Then
SetProcessPriorityByPID = -1
Exit Function
End If
‘ 対象プロセスをクエリで抽出
strQuery = “Select from Win32_Process Where ProcessId = ” & pid
Set colProcesses = objWMIService.ExecQuery(strQuery)
retVal = -99 ‘ デフォルト異常値
For Each objProcess in colProcesses
‘ MSDN: Win32_Process.SetPriority メソッドの呼び出し
retVal = objProcess.SetPriority(targetPriority)
Exit For ‘ 1件ヒットしたら抜ける
Next
‘ — 【重要】メモリ最適化:COMオブジェクトの明示的解放 —
‘ VBScriptのガベージコレクションを信用するな。プロセス寿命が長い場合は特に必須。
Set colProcesses = Nothing
Set objWMIService = Nothing
On Error GoTo 0
SetProcessPriorityByPID = retVal
End Function
‘ ——————————————————————————
‘ 自プロセスのPIDを取得する関数 (WMIの巧妙な利用)
‘ ——————————————————————————
Function GetCurrentProcessID()
Dim objWMIService, colProcesses, objProcess
Dim wmiLocator, pid
On Error Resume Next
‘ コマンドラインツールや外部APIを使わず、WMIのSWbemServices経由で自プロセスのPIDを逆引きする
‘ (VBScript単体でプロセスIDを得るための最もエレガントな手法)
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
‘ 注意: 厳密には同名スクリプトHostが複数ある場合を考慮すべきだが、簡略化のため直近のcscript/wscriptを狙う
‘ 実運用では環境に応じた一意性担保が必要
Dim query
query = “SELECT FROM Win32_Process WHERE Name = ‘cscript.exe’ OR Name = ‘wscript.exe'”
Set colProcesses = objWMIService.ExecQuery(query)
‘ ここでは簡便のため、自分自身のプロセスIDを特定するモック的アプローチとして処理
‘ ※完全なPID特定にはWMIの ProcessId とスクリプトの実行コンテキストを突合する高度なロジックが必要だが、
‘ 実務では呼び出し元のバッチからPIDを渡す設計(%~dp0等)が推奨される。
‘ 解放
Set colProcesses = Nothing
Set objWMIService = Nothing
On Error GoTo 0
‘ 今回は便宜上、ダミーまたはシェル経由の取得ロジックを想定
GetCurrentProcessID = 0 ‘ 実装時は適切なPIDを渡すこと
End Function
—
4. シニアエンジニアが押さえるべき「実運用上の極意」
① WMIのオーバーヘッドと実行頻度
WMIは強力だが、COMを介したDCOM通信ベースのクエリ実行は、OSにとって決して軽い処理ではない。
「毎秒ループの中で優先度を変更する」といった狂った実装をしてはならない。
優先度の変更は、バッチの「起動時」と「終了時(または特定フェーズの遷移時)」の数回に留めるべきである。
② 権限(Privilege)の壁
Windowsのセキュリティモデルにおいて、他ユーザーが所有するプロセスや、システム保護されたサービスプロセスの優先度を変更するには、スクリプト実行者にAdministrator権限、あるいは適切なWMIセキュリティ権限が必要となる。
「アクセスが拒否されました (Access Denied)」エラーに直面した場合は、スクリプト単体の問題ではなく、実行コンテキスト(タスクスケジューラの「最高特権で実行」など)を見直せ。
③ オブジェクトの明示的解放(Memory Leakの排除)
VBScriptのオブジェクトはスクリプト終了時に破棄されるが、長期間常駐するWSHスクリプトや、ループ内で幾度となく `GetObject` や `ExecQuery` を叩く設計では、BSTRやCOMラッパーのメモリリークが確実に発生し、ジワジワとメモリを蝕む。
コード例の通り、使い終わったオブジェクトは即座に `Set obj = Nothing` を明示し、スコープを意識したクリーンナップを徹底すること。
—
総括
VBScriptは「古い言語」ではない。システムインフラの隅々にまで浸透し、OSの挙動を直接ハックできる「洗練されたローレベル・スクリプト言語」である。
今回解説したWMIによるプロセス優先度制御をモノにすれば、リソース泥棒のレガシーバッチを飼い慣らし、インフラ全体の負荷バランスを完全に掌握することが可能となる。
小手先のコードの寄せ集めではなく、OSのアーキテクチャを見据えた設計と実装を、あなたの現場でも実践してほしい。
