伝説のチーフアーキテクトが語る、VBScriptとWMIで拓く印刷ジョブ自動復旧の極意
長年、レガシーシステムと格闘し、自動化の道を切り拓いてきた者として、VBScript、そしてWindows Management Instrumentation (WMI) の深淵に触れる機会は数え切れない。特に、オフィスにおける印刷トラブルは、日々の業務を停滞させる癌であり、その自動復旧は長年の課題であった。本稿では、WMIの`Win32_PrintJob`クラスを駆使し、印刷ジョブの状態を監視、エラー発生時には自動的にジョブを再送信または削除通知するという、実用的な運用自動化スクリプトの構築について、その真髄を語り尽くす。
1. VBScriptとWMI: レガシーシステムにおける「生き血」
VBScriptは、そのシンプルさゆえに、多くのレガシーシステムで今なお現役で稼働している。VB.NETやC#といったモダンな言語が登場する以前、VBScriptはWindows環境における「デファクトスタンダード」とも言えるスクリプト言語だった。しかし、その「シンプルさ」は、時として「機能不足」や「パフォーマンスの限界」と紙一重である。
WMIは、Windowsシステム管理における強力な武器だ。COM (Component Object Model) インターフェイスを通じて、OS、ハードウェア、ソフトウェアの設定や状態をプログラムから操作できる。VBScriptからWMIを利用することは、まるでレガシーシステムに「生き血」を注ぎ込むようなもの。眠っていた機能を呼び覚まし、新たな価値を創造する。
1.1. WMIオブジェクトのライフサイクル管理: パフォーマンスの癌を取り除く
VBScriptからWMIを利用する際に、多くの初学者が陥る罠がある。それは、WMIオブジェクトのライフサイクル管理の軽視だ。COMオブジェクトは、生成されるとメモリを消費し、リソースを占有する。これを適切に解放しないと、スクリプトの実行が遅延し、最悪の場合はシステム全体の不安定化を招く。
1.2. VBScriptにおけるオブジェクト解放の極意
VBScriptにおけるCOMオブジェクトの解放は、`Set obj = Nothing` が基本となる。しかし、これが本当に「解放」されているかの確認は容易ではない。特に、ループ処理内でWMIオブジェクトを繰り返し生成・利用する場合、その影響は無視できない。
‘ 悪い例: オブジェクトが解放されないままループを続ける
Dim objWMIService, colItems, objItem
Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)
Set colItems = objWMIService.ExecQuery(“SELECT FROM Win32_Process”)
For Each objItem In colItems
‘ objItem を利用する処理
‘ …
‘ Set objItem = Nothing ‘ ここで解放されていない
Next
Set colItems = Nothing ‘ これだけでは不十分
Set objWMIService = Nothing
‘ 良い例: 各ループの終わりに明示的に解放
Dim objWMIService, colItems, objItem
Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)
Set colItems = objWMIService.ExecQuery(“SELECT FROM Win32_Process”)
For Each objItem In colItems
‘ objItem を利用する処理
‘ …
Set objItem = Nothing ‘ 各ループの終わりに解放
Next
Set colItems = Nothing ‘ 外側のコレクションも解放
Set objWMIService = Nothing
この「`.Nothing`」代入は、COMオブジェクトへの参照を解除し、ガベージコレクションの対象とするための指示だ。しかし、VBScriptのCOMオブジェクト管理は、C++のような厳密なリソース管理とは異なる。それでも、この明示的な解放を習慣づけることが、パフォーマンス劣化を防ぐための第一歩となる。
1.3. レガシー環境への配慮: 互換性と安定性の両立
本テーマである印刷ジョブ監視は、古くから存在するシステム環境においても依然として重要な課題である。Windows Server 2003やXPといった、もはやサポートが終了したOSが稼働する環境での保守を強いられることもあるだろう。
WMIは、これらのレガシーOSにおいても比較的高い互換性を持つ。しかし、バージョンによっては利用できるクラスやプロパティに差異がある場合もあるため、対象OSのドキュメントを事前に確認しておくことが肝要だ。
2. 印刷ジョブ監視スクリプトの構築: WMI (Win32_PrintJob) の活用
いよいよ本題である、印刷ジョブ監視スクリプトの構築に入る。WMIの `Win32_PrintJob` クラスは、印刷ジョブに関する詳細な情報を提供してくれる。
2.1. `Win32_PrintJob` クラスの主要プロパティ
- `Name`: ジョブの名前
- `JobID`: ジョブのID
- `Status`: ジョブの状態 (例: “Printing”, “Paused”, “Error”)
- `JobStatus`: より詳細なジョブ状態を示す数値コード
- `Owner`: ジョブの所有者 (ユーザー名)
- `DataType`: ジョブのデータ形式 (例: “RAW”)
- `Size`: ジョブのサイズ (バイト単位)
- `Document`: ドキュメント名
- `PrinterName`: ジョブが送信されたプリンタ名
2.2. ジョブ状態の検知とコード化
`Win32_PrintJob` の `JobStatus` プロパティは、印刷ジョブの状態を数値コードで表す。これらのコードは、Windows APIの `EnumJobs` 関数で返される `JOB_STATUS` フラグに対応している。VBScriptから直接APIを呼び出すのは難易度が高いが、WMIはこれらの情報を抽象化して提供してくれる。
主要な状態コードと、それに対応する意味合いは以下の通り。
| JobStatus コード | 意味 | VBScriptでの判定例 |
| :————— | :———————————- | :—————————————————- |
| 0 (0x0000) | `JOB_STATUS_PAUSED` | `(job.JobStatus AND 1) = 1` |
| 2 (0x0002) | `JOB_STATUS_ERROR` | `(job.JobStatus AND 2) = 2` |
| 4 (0x0004) | `JOB_STATUS_DELETING` | `(job.JobStatus AND 4) = 4` |
| 8 (0x0008) | `JOB_STATUS_PRINTING` | `(job.JobStatus AND 8) = 8` |
| 16 (0x0010) | `JOB_STATUS_OFFLINE` | `(job.JobStatus AND 16) = 16` |
| 32 (0x0020) | `JOB_STATUS_KNOWNLENGTH` | `(job.JobStatus AND 32) = 32` |
| 64 (0x0040) | `JOB_STATUS_RESTART` | `(job.JobStatus And 64) = 64` |
| 128 (0x0080) | `JOB_STATUS_REMOTE` | `(job.JobStatus And 128) = 128` |
| 256 (0x0100) | `JOB_STATUS_DELIVERED` | `(job.JobStatus And 256) = 256` |
| 512 (0x0200) | `JOB_STATUS_SENT_TO_PRINTER` | `(job.JobStatus And 512) = 512` |
| 1024 (0x0400) | `JOB_STATUS_WAITING_FOR_PORT` | `(job.JobStatus And 1024) = 1024` |
| 2048 (0x0800) | `JOB_STATUS_VERIFYING` | `(job.JobStatus And 2048) = 2048` |
| 4096 (0x1000) | `JOB_STATUS_PENDING_DELETION` | `(job.JobStatus And 4096) = 4096` |
| 8192 (0x2000) | `JOB_STATUS_PAPER_JAM` | `(job.JobStatus And 8192) = 8192` |
| 16384 (0x4000) | `JOB_STATUS_OUT_OF_PAPER` | `(job.JobStatus And 16384) = 16384` |
| 32768 (0x8000) | `JOB_STATUS_COMMUNICATIONS_FAILURE` | `(job.JobStatus And 32768) = 32768` |
| 65536 (0x10000) | `JOB_STATUS_COVER_OPEN` | `(job.JobStatus And 65536) = 65536` |
| 131072 (0x20000) | `JOB_STATUS_DRIVER_ERROR` | `(job.JobStatus And 131072) = 131072` |
| 262144 (0x40000) | `JOB_STATUS_PENS_DOWN` | `(job.JobStatus And 262144) = 262144` |
| 524288 (0x80000) | `JOB_STATUS_NOT_CALIBRATED` | `(job.JobStatus And 524288) = 524288` |
| 1048576 (0x100000)|`JOB_STATUS_TOWER_FAILURE` | `(job.JobStatus And 1048576) = 1048576` |
| 2097152 (0x200000)|`JOB_STATUS_HARDWARE_FAILURE` | `(job.JobStatus And 2097152) = 2097152` |
| 4194304 (0x400000)|`JOB_STATUS_USER_INTERVENTION` | `(job.JobStatus And 4194304) = 4194304` |
| 8388608 (0x800000)|`JOB_STATUS_OUTPUT_TRAY_MISSING` | `(job.JobStatus And 8388608) = 8388608` |
注意: 上記は一部であり、完全なリストではありません。正確な情報はMicrosoftの公式ドキュメントを参照してください。
2.3. 実践的なスクリプト例: エラー検知と自動再送信
以下に、定期的に実行され、エラー状態の印刷ジョブを検知し、自動的に再送信(または削除)を試みるVBScriptの例を示す。
‘===============================================================================
‘ PrintJobMonitor.vbs
‘ 印刷ジョブの状態を監視し、エラー発生時に自動復旧を試みるスクリプト
‘
‘ 対象OS: Windows XP SP2 以降、Windows Server 2003 以降
‘ 実行方法: タスクスケジューラで定期実行
‘===============================================================================
Option Explicit
Dim strComputer : strComputer = “.” ‘ ローカルマシンを対象
Dim objWMIService : Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)
Dim colPrintJobs : Set colPrintJobs = objWMIService.ExecQuery(“SELECT FROM Win32_PrintJob”)
Dim objPrintJob
Dim lngJobStatus
‘——————————————————————————-
‘ エラー状態のジョブを処理するメインループ
‘——————————————————————————-
WScript.Echo “— 印刷ジョブ監視開始 —”
If colPrintJobs.Count = 0 Then
WScript.Echo “現在、印刷キューにジョブはありません。”
Else
For Each objPrintJob In colPrintJobs
lngJobStatus = objPrintJob.JobStatus
‘ エラー状態、用紙切れ、一時停止などを検知
If IsJobInErrorState(lngJobStatus) Then
HandleErrorJob objPrintJob
ElseIf (lngJobStatus And 1) = 1 Then ‘ JOB_STATUS_PAUSED (一時停止)
WScript.Echo “ジョブID: ” & objPrintJob.JobID & ” (” & objPrintJob.Name & “) は一時停止しています。手動での確認が必要です。”
‘ 必要に応じて、一時停止解除の試みなどを実装
End If
‘ オブジェクトを明示的に解放
Set objPrintJob = Nothing
Next
End If
WScript.Echo “— 印刷ジョブ監視終了 —”
‘——————————————————————————-
‘ 印刷ジョブがエラー状態か判定する関数
‘——————————————————————————-
Function IsJobInErrorState(ByVal status)
‘ JOB_STATUS_ERROR (2), JOB_STATUS_PAPER_JAM (8192), JOB_STATUS_OUT_OF_PAPER (16384)
‘ JOB_STATUS_COMMUNICATIONS_FAILURE (32768), JOB_STATUS_COVER_OPEN (65536)
‘ JOB_STATUS_DRIVER_ERROR (131072), JOB_STATUS_PENS_DOWN (262144)
‘ JOB_STATUS_NOT_CALIBRATED (524288), JOB_STATUS_TOWER_FAILURE (1048576)
‘ JOB_STATUS_HARDWARE_FAILURE (2097152), JOB_STATUS_USER_INTERVENTION (4194304)
‘ JOB_STATUS_OUTPUT_TRAY_MISSING (8388608)
‘ これらのフラグが立っている場合にエラーとみなす
If (status And 2) = 2 Or _
(status And 8192) = 8192 Or _
(status And 16384) = 16384 Or _
(status And 32768) = 32768 Or _
(status And 65536) = 65536 Or _
(status And 131072) = 131072 Or _
(status And 262144) = 262144 Or _
(status And 524288) = 524288 Or _
(status And 1048576) = 1048576 Or _
(status And 2097152) = 2097152 Or _
(status And 4194304) = 4194304 Or _
(status And 8388608) = 8388608 Then
IsJobInErrorState = True
Else
IsJobInErrorState = False
End If
End Function
‘——————————————————————————-
‘ エラー状態のジョブを処理するサブルーチン
‘——————————————————————————-
Sub HandleErrorJob(ByRef job)
Dim strPrinterName : strPrinterName = job.PrinterName
Dim strJobID : strJobID = job.JobID
Dim strJobName : strJobName = job.Name
Dim strOwner : strOwner = job.Owner
Dim strDocument : strDocument = job.Document
WScript.Echo “————————————————–”
WScript.Echo “エラー検知: ジョブID: ” & strJobID & ” (” & strJobName & “)”
WScript.Echo ” プリンタ: ” & strPrinterName
WScript.Echo ” ドキュメント: ” & strDocument
WScript.Echo ” 所有者: ” & strOwner
WScript.Echo ” Raw JobStatus: ” & job.JobStatus ‘ デバッグ用
‘=======================================================================
‘ ここで、エラー復旧のロジックを実装する
‘=======================================================================
‘ 例1: エラーをログに記録し、ジョブを削除する
LogEvent “ERROR”, “印刷ジョブ ‘” & strDocument & “‘ (ID: ” & strJobID & “) on printer ‘” & strPrinterName & “‘ is in error state. Job will be deleted.”
DeletePrintJob strComputer, strPrinterName, strJobID
‘ 例2: 特定のエラーコードの場合に、ジョブを再送信する (より高度な実装が必要)
‘ if (job.JobStatus And 16384) = 16384 Then ‘ JOB_STATUS_OUT_OF_PAPER
‘ WScript.Echo “用紙切れのため、ジョブを削除し、再送信を試みます。”
‘ ‘ ここで、元の印刷ジョブを再生成するロジックを実装する
‘ ‘ (例: 元のファイルを再度印刷キューにコピーするなど、アプリケーション依存)
‘ DeletePrintJob strComputer, strPrinterName, strJobID
‘ ‘ ReSendPrintJob … (別途実装)
‘ End If
‘ 例3: 管理者に通知する
‘ SendEmailNotification “Printing Error Detected”, “Job ID: ” & strJobID & ” on Printer: ” & strPrinterName & ” is in error state.”
WScript.Echo “エラー対応完了: ジョブID: ” & strJobID & ” の処理を行いました。”
WScript.Echo “————————————————–”
End Sub
‘——————————————————————————-
‘ 印刷ジョブを削除するサブルーチン
‘——————————————————————————-
Sub DeletePrintJob(ByRef computer, ByRef printerName, ByRef jobID)
Dim objPrintJob
Dim objWMIServices
Dim strQuery
On Error Resume Next ‘ エラー発生時も続行
Set objWMIServices = GetObject(“winmgmts:\\” & computer & “\root\cimv2”)
strQuery = “SELECT FROM Win32_PrintJob WHERE JobID = ” & jobID & ” AND PrinterName = ‘” & printerName & “‘”
Set objPrintJob = objWMIServices.ExecQuery(strQuery)
If objPrintJob.Count > 0 Then
Dim jobInstance
For Each jobInstance In objPrintJob
‘ Delete メソッドを呼び出す
‘ Delete メソッドは WMI オブジェクトに直接紐づいているため、
‘ WMI クエリ結果から取得したインスタンスに対して実行する
Dim returnValue
returnValue = jobInstance.Delete()
If returnValue = 0 Then
WScript.Echo “ジョブID: ” & jobID & ” の削除に成功しました。”
Else
WScript.Echo “ジョブID: ” & jobID & ” の削除に失敗しました。返り値: ” & returnValue
LogEvent “WARNING”, “Failed to delete print job ID: ” & jobID & ” on printer: ” & printerName & “. Return value: ” & returnValue
End If
Exit For ‘ 該当ジョブは一つのはずなのでループを抜ける
Next
Else
WScript.Echo “ジョブID: ” & jobID & ” が見つかりませんでした。既に削除されているか、別のキューにある可能性があります。”
End If
Set objPrintJob = Nothing
Set objWMIServices = Nothing
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
End Sub
‘——————————————————————————-
‘ ログ出力関数 (簡易版)
‘——————————————————————————-
Sub LogEvent(level, message)
Dim logFile : logFile = “C:\Logs\PrintJobMonitor.log” ‘ ログファイルのパスを指定
Dim fso : Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim ts
Dim logEntry
If Not fso.FolderExists(fso.GetParentFolderName(logFile)) Then
fso.CreateFolder(fso.GetParentFolderName(logFile))
End If
Set ts = fso.OpenTextFile(logFile, 8, True) ‘ 8: Append, True: Create if not exists
logEntry = Now() & ” [” & level & “] ” & message
ts.WriteLine logEntry
ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub
‘===============================================================================
‘ スクリプト終了時のオブジェクト解放
‘===============================================================================
Set colPrintJobs = Nothing
Set objWMIService = Nothing
2.4. コード解説と考慮事項
- `GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)`: WMIサービスへの接続。`root\cimv2` は標準の名前空間。
- `ExecQuery(“SELECT FROM Win32_PrintJob”)`: `Win32_PrintJob` クラスから全ての印刷ジョブ情報を取得。
- `IsJobInErrorState(lngJobStatus)`: ビット演算 (`And`) を用いて、複数のエラーフラグを効率的に判定。
- `HandleErrorJob` サブルーチン: エラー検知時の処理をカプセル化。
- `DeletePrintJob`: 該当ジョブをWMIの`Delete`メソッドで削除。
- 再送信ロジック: コメントアウトされている部分が、より高度な再送信処理の実装箇所。これは、元の印刷データをどのように取得し、再度印刷キューに投入するか、というアプリケーションレベルのロジックに依存するため、汎用的なコード化は難しい。例えば、元となるファイルパスをジョブ情報から辿る、あるいは印刷ログから再構築するなど、環境に応じた設計が必要となる。
- 通知: 管理者へのメール送信などの通知機能を実装することも可能(別途SMTPライブラリなどが必要)。
- `LogEvent` 関数: 簡易的なログ出力機能。本番運用では、イベントログへの書き込みや、より詳細なログフォーマットを検討すべき。
- オブジェクト解放: 各ループの終わりに `Set objPrintJob = Nothing` を実行。スクリプト終了前にも、`objWMIService` や `colPrintJobs` を解放。
3. システム間連携とWindows API
VBScriptから直接Windows APIを呼び出すのは、API Text File (ATF) や `CreateObject(“WScript.Shell”).Run` を介した外部コマンド実行など、いくつかの方法があるが、COMオブジェクトを介したWMIの利用に比べると、一般的に複雑で、エラーハンドリングも煩雑になる。
`Win32_PrintJob` クラスは、内部的には `EnumJobs` といったWin32 APIをラップしていると考えることができる。そのため、WMIを利用することは、APIを直接叩くよりも、VBScriptのような高レベル言語からは扱いやすい。
しかし、WMIでは実現できない、あるいはパフォーマンス的に不利な処理に関しては、APIの利用も視野に入れる必要がある。例えば、特定のプリンタドライバの低レベルな状態を取得したり、より詳細なエラーコードを解析したりする場合だ。
3.1. VB.NETやC#との連携
もし、より複雑な処理や、パフォーマンスが要求される場合、あるいは現代的な開発手法を導入したい場合は、VB.NETやC#でWMIやAPIを直接操作するアプリケーションを開発し、VBScriptからそれを呼び出す、というアーキテクチャも考えられる。
例えば、VB.NETでWMIのイベント監視を利用して、ジョブの状態変化をリアルタイムに検知し、エラー発生時に外部のログファイルやデータベースに記録する。VBScriptは、そのログファイルを定期的にポーリングし、特定のパターンがあれば自動復旧処理を実行する、といった分業も可能だ。
‘ VBScriptからVB.NETアプリケーションを呼び出す例
Dim objApp As Object
Set objApp = CreateObject(“YourNamespace.PrintMonitorApp”) ‘ 登録したCOMコンポーネント
objApp.RunMonitor
Set objApp = Nothing
4. まとめ: 運用自動化の「深淵」へ
VBScriptとWMIを組み合わせた印刷ジョブ監視スクリプトは、オフィスにおける印刷トラブルという、地味ながらも根深い問題を解決するための一歩となる。本稿で解説したオブジェクトのライフサイクル管理、WMIクラスの的確な利用、そしてレガシー環境への配慮は、単なるスクリプト作成に留まらず、システム運用の「質」を高めるための基礎となる。
印刷ジョブの自動復旧は、その一例に過ぎない。WMIは、ネットワーク、ディスク、サービス、イベントログなど、Windowsシステムのあらゆる側面を管理するための強力なインターフェイスを提供する。この深淵を覗き込むことで、君たちの手で、さらに多くの運用タスクを自動化し、より生産性の高いIT環境を築き上げることができるはずだ。
レガシーシステムは、時に厄介な存在だが、その上に築かれた知見は、決して色褪せることはない。VBScriptの魂は、今もなお、我々エンジニアの心に生き続けているのだ。
