【実務・中級編】【印刷ジョブ監視と自動復旧】WMI (Win32_PrintJob) を用いたプリンタ詰まり・エラーの検知とジョブ自動再送信 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptの極意:WMIでプリンタの悲鳴を聞き漏らさない!印刷ジョブ監視と自動復旧への道

「またプリンタが止まった…」
「あのジョブ、まだ終わらないんだけど?」

オフィスで働く皆さん、印刷に関するトラブルは日常茶飯事ではありませんか? 詰まった用紙を取り除き、エラーランプを消し、そしてあの保留された印刷ジョブをどうにかしなければならない。そんな煩雑な作業に、あなたの貴重な時間は浪費されていませんか?

本稿では、VBScriptとWMI (Windows Management Instrumentation) を駆使し、ネットワークプリンタの印刷ジョブ状態をリアルタイムで監視、そして「用紙切れ」や「エラー」といったトラブル発生時に自動でジョブを再送信・削除通知を行う、そんな夢のような自動化スクリプトの構築方法を伝授します。

これは単なるコピペで動くコード例ではありません。オブジェクトのライフサイクル、WMIクエリの最適化、そしてエラーハンドリングの深淵まで、私が長年培ってきたVBScript開発の「極意」を惜しみなく開示します。この知識があれば、あなたの書くスクリプトは「動く」だけでなく、「堅牢で」「保守性の高い」プロダクションコードへと昇華するでしょう。

なぜ、VBScriptでWMIなのか? – 効率化の隠された真実

まず、なぜこの課題に対してVBScriptとWMIの組み合わせが最適なのか、その理由を明確にしておきましょう。

  • VBScriptの普遍性: Windows環境において、VBScriptは特別なインストールなしに実行できる標準的なスクリプト言語です。WSH (Windows Script Host) 環境が整っていれば、すぐに利用できます。VB.NETやPowerShellも強力ですが、手軽さという点ではVBScriptに一日の長があります。
  • WMIの力: WMIは、Windowsシステム管理のための強力なインターフェースです。OS、ハードウェア、ソフトウェアに関する膨大な情報を取得・操作できます。`Win32_PrintJob`クラスを使えば、印刷ジョブの状態を詳細に把握できるのです。

「でも、WMIって重いんじゃないの?」
「VBScriptで複雑な処理は無理でしょ?」

そう思われた方もいるかもしれません。しかし、それはWMIオブジェクトのライフサイクルと、VBScriptのオブジェクト操作に対する理解が浅いからです。

WMIオブジェクトの「魂」を理解する:ライフサイクルの最適化

WMIオブジェクト、特にCOMオブジェクトは、その生成と破棄にコストがかかります。これを理解せずに、無闇にオブジェクトを生成・破棄していると、スクリプトは驚くほど遅くなり、リソースを圧迫します。

「なぜ、このコードはこんなに遅いのか?」
それは、ループの中で毎回`GetObject`や`ConnectServer`を呼び出しているからかもしれません。

「どう設計すべきか?」
WMIオブジェクトは、可能な限り生成回数を最小限に抑え、共通のオブジェクトは使い回す。これが鉄則です。今回の印刷ジョブ監視でも、WMIサービスへの接続は一度きりにし、その接続オブジェクトを保持しながら必要な情報を取得します。

パフォーマンスの「重み」を知る:WMIクエリの「賢い」使い方

WMIクエリ(`ExecQuery`)は強力ですが、無造作に実行するとシステムに大きな負荷をかけます。特に、大量の情報を取得しようとすると、その「重み」を肌で感じることになります。

「なぜ、このクエリはこんなに遅いのか?」
それは、`SELECT `で全てのプロパティを取得しようとしているからかもしれません。あるいは、`WHERE`句が適切でなく、意図しない広範囲のデータをスキャンしている可能性もあります。

「どう設計すべきか?」
必要な情報だけをピンポイントで取得する。これがパフォーマンス向上の鍵です。`SELECT`句で必要なプロパティ名を指定し、`WHERE`句で対象を絞り込む。この「選択と集中」が、WMIクエリを高速化し、スクリプト全体の応答性を向上させます。

印刷ジョブ監視スクリプトの設計思想:堅牢性と保守性を両立する

さて、いよいよ具体的なスクリプトの設計に入りましょう。目指すのは、単に動くだけでなく、バグが起きにくく、後から修正・拡張しやすい、いわゆる「プロダクションレベル」のコードです。

1. WMI接続の集中管理

前述の通り、WMI接続はリソースを消費します。スクリプトの冒頭でWMIサービスへの接続を確立し、そのオブジェクトをスクリプト全体で共有します。

2. `Win32_PrintJob`オブジェクトの活用

`Win32_PrintJob`オブジェクトは、印刷ジョブに関する詳細な情報を持っています。特に注目すべきプロパティは以下の通りです。

  • `JobStatus`: ジョブの状態(例: “Printing”, “Spooling”, “Paused”, “Error”)
  • `Status`: より詳細な状態コード(例: “Printing”, “Error printing”, “Paper jam”)
  • `Document`: ドキュメント名
  • `Owner`: ジョブの所有者
  • `Submitted`: ジョブが送信された日時
  • `Size`: ジョブのサイズ (バイト単位)

3. エラーハンドリングの徹底

印刷ジョブの監視や操作は、ネットワークの状況、プリンタの応答、権限の問題など、様々な要因で失敗する可能性があります。これを考慮し、`On Error Resume Next`と`Err.Number`による詳細なエラーチェックを各処理ブロックに組み込みます。

4. 設定値の外部化

プリンタ名、監視間隔、エラー時のアクションなどは、スクリプト内に直接記述するのではなく、定数や外部ファイル(INIファイルなど)で管理することで、保守性を高めます。今回は、スクリプト冒頭の定数宣言で対応します。

5. ログ出力による「見える化」

スクリプトの実行状況や発生したエラーをログファイルに出力することで、問題発生時の原因特定や、スクリプトの動作確認が格段に容易になります。

実践! WMIを活用した印刷ジョブ監視・自動復旧スクリプト

それでは、これらの設計思想に基づいたVBScriptコード例をご紹介しましょう。このスクリプトは、指定したプリンタの印刷ジョブを定期的に監視し、エラー状態のジョブを検知して、再送信または削除通知を行います。

‘=============================================================================
‘ スクリプト名: MonitorPrintJob.vbs
‘ 説明: WMI (Win32_PrintJob) を用いてプリンタの印刷ジョブ状態を監視し、
‘ エラー状態のジョブを自動的に再送信または通知する。
‘ 作者: [あなたの名前/チーム名]
‘ 日付: 2023/10/27
‘ 警告: このスクリプトは、実行環境の権限、ネットワーク設定、
‘ プリンタの機種によって動作が異なる場合があります。
‘ 本番環境での利用前に、十分なテストを行ってください。
‘=============================================================================

Option Explicit

‘ — 設定値 —
Const PRINTER_NAME = “【ここに監視したいプリンタ名を指定してください】” ‘ 例: “MyNetworkPrinter”
Const MONITOR_INTERVAL_SECONDS = 60 ‘ 監視間隔 (秒)
Const LOG_FILE = “C:\Logs\PrintJobMonitor.log” ‘ ログファイルパス
Const ADMIN_EMAIL = “admin@example.com” ‘ エラー通知用メールアドレス (※メール送信機能は別途実装が必要です)
Const JOB_ACTION_ON_ERROR = “DELETE” ‘ エラー時のジョブアクション: “DELETE” or “RESEND” (※”RESEND”は高度な実装が必要です)

‘ — グローバル変数 —
Dim objWMI, objLogFile, objFSO

‘ — メイン処理 —
Sub Main()
‘ FSO (FileSystemObject) の初期化
Set objFSO = CreateObject(“Scripting.FileSystemObject”)

‘ ログファイルの初期化 (日付スタンプを付与)
If Not InitLogFile() Then
WScript.Echo “エラー: ログファイルの初期化に失敗しました。スクリプトを終了します。”
Exit Sub
End If

‘ WMI サービスへの接続 (スクリプト全体で共有)
On Error Resume Next
Set objWMI = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\” & “.” & “\root\cimv2”)
If Err.Number <> 0 Then
LogMessage “WMI接続エラー: ” & Err.Description & ” (コード: ” & Err.Number & “)”
WScript.Echo “WMI接続エラーが発生しました。ログファイルを確認してください。”
Set objFSO = Nothing
Exit Sub
End If
On Error GoTo 0 ‘ エラーハンドリングを通常に戻す

LogMessage “印刷ジョブ監視スクリプトを開始しました。対象プリンタ: ” & PRINTER_NAME
WScript.Echo “印刷ジョブ監視を開始しました。ログファイル: ” & LOG_FILE

‘ 無限ループで監視を続ける
Do
CheckPrintJobs
WScript.Sleep MONITOR_INTERVAL_SECONDS 1000 ‘ 指定された間隔だけ待機
Loop

‘ 通常はこのループから抜けることはありませんが、念のためリソース解放
Set objWMI = Nothing
Set objFSO = Nothing
End Sub

‘ — 印刷ジョブのチェック処理 —
Sub CheckPrintJobs()
Dim colPrintJobs, objJob, strQuery

‘ WMIクエリ: 指定プリンタの印刷ジョブを取得
‘ Win32_PrintJob クラスの Status プロパティでエラー状態をフィルタリング
‘ (参考: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-printjob)
‘ 一般的なエラー状態: “Error printing”, “Paper jam”, “Low ink” など
‘ より詳細な状態は Printer Properties -> Advanced -> Status を確認
strQuery = “SELECT FROM Win32_PrintJob WHERE Name LIKE ‘%” & PRINTER_NAME & “%’ AND (Status = ‘Error printing’ OR Status = ‘Paper jam’ OR Status = ‘Low ink’)”

On Error Resume Next
Set colPrintJobs = objWMI.ExecQuery(strQuery)
If Err.Number <> 0 Then
LogMessage “WMIクエリ実行エラー: ” & Err.Description & ” (コード: ” & Err.Number & “)”
‘ エラーが発生してもループは継続する
Exit Sub
End If
On Error GoTo 0

If colPrintJobs.Count > 0 Then
LogMessage colPrintJobs.Count & “件のエラー状態の印刷ジョブを検出しました。”

For Each objJob In colPrintJobs
Dim strJobName, strStatus, strOwner, strDocument
strJobName = objJob.Name
strStatus = objJob.Status
strOwner = objJob.Owner
strDocument = objJob.Document

LogMessage ” – ジョブ: ” & strJobName & “, ステータス: ” & strStatus & “, 所有者: ” & strOwner & “, ドキュメント: ” & strDocument

‘ エラー発生時のアクションを実行
ProcessErrorJob objJob
Next
Else
‘ LogMessage “エラー状態の印刷ジョブはありません。” ‘ 頻繁なログ出力を避けるためコメントアウト
End If

Set colPrintJobs = Nothing
End Sub

‘ — エラー発生ジョブの処理 —
Sub ProcessErrorJob(ByRef objJob)
Dim strJobAction

strJobAction = UCase(JOB_ACTION_ON_ERROR)

Select Case strJobAction
Case “DELETE”
LogMessage ” -> ジョブを削除します: ” & objJob.Name
On Error Resume Next
objJob.Delete_
If Err.Number <> 0 Then
LogMessage ” -> ジョブ削除エラー: ” & Err.Description & ” (コード: ” & Err.Number & “)”
Else
LogMessage ” -> ジョブを正常に削除しました。”
‘ 必要であれば、管理者へ削除通知メールを送信 (実装は省略)
‘ SendNotificationEmail objJob, “Deleted due to error”
End If
On Error GoTo 0

Case “RESEND”
‘ 注意: ジョブの再送信は、一般的にWMIからは直接サポートされていません。
‘ これは、ジョブを一度削除し、再度送信する、あるいは
‘ プリンタドライバやスプーラサービスとの連携が複雑になるため、
‘ 高度な実装が必要になります。
‘ ここでは、削除と再送信の概念を示すためのコメントとして残します。
LogMessage ” -> ジョブの再送信を試みます (※高度な実装が必要です)。”
‘ 1. ジョブを削除
‘ 2. 元のドキュメントを特定し、再度印刷コマンドを実行
‘ (元のドキュメントへのアクセス権や、印刷方法の特定が困難な場合が多い)
‘ 3. 通知メール送信
‘ SendNotificationEmail objJob, “Attempted to resend, manual intervention may be required”
LogMessage ” -> ジョブ再送信機能は現在実装されていません。”

Case Else
LogMessage ” -> 未定義のジョブアクション ‘” & JOB_ACTION_ON_ERROR & “‘ です。ジョブはそのままです。”
End Select
End Sub

‘ — ログファイル初期化処理 —
Function InitLogFile() As Boolean
Dim strLogDir

strLogDir = objFSO.GetParentFolderName(LOG_FILE)

‘ ログディレクトリが存在しない場合は作成
If Not objFSO.FolderExists(strLogDir) Then
On Error Resume Next
objFSO.CreateFolder strLogDir
If Err.Number <> 0 Then
WScript.Echo “エラー: ログディレクトリ ‘” & strLogDir & “‘ の作成に失敗しました。”
InitLogFile = False
Exit Function
End If
On Error GoTo 0
End If

‘ ログファイルに書き込み権限があるか確認
On Error Resume Next
Dim objLog
Set objLog = objFSO.OpenTextFile(LOG_FILE, 8, True) ‘ 8: Append, True: Create if not exist
If Err.Number <> 0 Then
WScript.Echo “エラー: ログファイル ‘” & LOG_FILE & “‘ への書き込み権限がありません。”
InitLogFile = False
Exit Function
End If
objLog.Close
Set objLog = Nothing
On Error GoTo 0

LogMessage “— ログファイル初期化 —”
InitLogFile = True
End Function

‘ — ログメッセージ出力処理 —
Sub LogMessage(ByRef strMessage)
Dim objLogFile
Dim strTimestamp

‘ タイムスタンプ生成 (YYYY/MM/DD HH:MM:SS 形式)
strTimestamp = FormatDateTime(Now, 2) & ” ” & FormatDateTime(Now, 4)

On Error Resume Next
‘ ログファイルを開き、追記モードで書き込む
Set objLogFile = objFSO.OpenTextFile(LOG_FILE, 8, True) ‘ 8: Append, True: Create if not exist
If Err.Number = 0 Then
objLogFile.WriteLine “[” & strTimestamp & “] ” & strMessage
objLogFile.Close
Else
WScript.Echo “【ログ出力エラー】: ” & Err.Description & ” – メッセージ: ” & strMessage
End If
On Error GoTo 0
Set objLogFile = Nothing
End Sub

‘ — メイン処理の実行 —
Main

‘ — 補足: メール通知機能の実装例 (必要に応じて追加) —
‘ Sub SendNotificationEmail(ByRef objJob, ByRef strReason)
‘ Dim objOutlook, objMail
‘ Dim strSubject, strBody

‘ strSubject = “プリンタジョブエラー通知: ” & objJob.Name
‘ strBody = “以下の印刷ジョブでエラーが検出され、処理されました。” & vbCrLf & vbCrLf
‘ strBody = strBody & “ジョブ名: ” & objJob.Name & vbCrLf
‘ strBody = strBody & “ドキュメント: ” & objJob.Document & vbCrLf
‘ strBody = strBody & “所有者: ” & objJob.Owner & vbCrLf
‘ strBody = strBody & “ステータス: ” & objJob.Status & vbCrLf
‘ strBody = strJobAction & vbCrLf & vbCrLf
‘ strBody = strBody & “処理内容: ” & strReason & vbCrLf

‘ On Error Resume Next
‘ Set objOutlook = CreateObject(“Outlook.Application”)
‘ If Err.Number = 0 Then
‘ Set objMail = objOutlook.CreateItem(0) ‘ olMailItem
‘ With objMail
‘ .To = ADMIN_EMAIL
‘ .Subject = strSubject
‘ .Body = strBody
‘ .Send ‘ または .Display で確認
‘ End With
‘ Set objMail = Nothing
‘ Set objOutlook = Nothing
‘ Else
‘ LogMessage “Outlookアプリケーションの起動に失敗しました。メール通知はスキップされます。エラー: ” & Err.Description
‘ End If
‘ On Error GoTo 0
‘ End Sub

コード解説:プロダクションコードへのこだわり

  • `Option Explicit`: VBScriptの基本ですが、変数の宣言漏れを防ぎ、バグの温床となるミスを未然に防ぎます。これは「なぜ宣言が必要なのか」を理解している開発者だけが徹底できることです。
  • 定数による設定値管理: `PRINTER_NAME`, `MONITOR_INTERVAL_SECONDS` などの設定値は、スクリプト冒頭の定数で一元管理します。これにより、設定変更時にスクリプト全体を読み解く必要がなくなり、保守性が飛躍的に向上します。
  • WMI接続の`GetObject`: `GetObject(“winmgmts:{impersonationLevel=impersonate}!\\” & “.” & “\root\cimv2”)` は、WMIサービスへの接続を確立する標準的な方法です。`impersonate`レベルを指定することで、スクリプト実行ユーザーの権限で操作できます。この接続オブジェクトはループの外で一度だけ生成し、再利用します。
  • `Win32_PrintJob`クエリの最適化: `SELECT ` ではなく、必要最低限のプロパティを取得するようにクエリを記述することを推奨します。ただし、今回の例では`Status`プロパティでフィルタリングするために、“ を使用せざるを得ない状況もあります。しかし、`WHERE`句で対象プリンタとエラー状態を絞り込んでいるため、パフォーマンスへの影響は最小限に抑えられています。
  • エラーハンドリング (`On Error Resume Next`と`Err.Number`): 各WMI操作やファイル操作の直後に`Err.Number`をチェックし、エラーが発生した場合はログに記録して続行します。これにより、一部のジョブで問題が発生しても、スクリプト全体が停止することを防ぎます。
  • ログ出力 (`LogMessage` Sub): タイムスタンプ付きでログファイルに詳細な情報を記録します。これにより、問題発生時の原因究明が容易になり、「何が起こったのか」を正確に把握できます。ログファイルパスも設定値として外部化しています。
  • `JOB_ACTION_ON_ERROR`定数: エラー発生時のアクション(ジョブ削除または再送信)を切り替えられるようにしています。「再送信」は高度な実装が必要なため、現時点では「削除」のみを推奨し、再送信はコメントアウトしています。 ここで「なぜ再送信が難しいのか」を理解することが、開発者としての成長を促します。
  • FSO (FileSystemObject): ログファイルの作成やディレクトリの存在確認に使用します。COMオブジェクトなので、こちらも必要に応じて解放します。

ファイル・データベース連携の注意点:落とし穴を避ける

このスクリプトをさらに発展させ、例えばデータベースにジョブ履歴を記録したり、設定ファイルを外部から読み込んだりする場合、いくつか注意すべき点があります。

  • ファイルアクセス権: ログファイルや設定ファイルへの書き込み権限が、スクリプト実行ユーザーに与えられているかを確認してください。権限不足は、スクリプトが正常に動作しない最も一般的な原因の一つです。
  • ファイルロック: 複数のスクリプトインスタンスが同時に同じファイルにアクセスしようとすると、ファイルロックが発生し、エラーの原因となります。排他制御(ロック機構)を実装するか、ファイルアクセスをシリアル化(一つずつ処理する)するなどの対策が必要です。
  • データベース接続: データベースに接続する場合、接続文字列の管理、接続プーリング(可能な場合)、トランザクション処理、そして何よりもエラーハンドリングが重要になります。データベース操作はネットワーク遅延やサーバーダウンの影響を受けやすいため、リトライ処理なども検討すべきです。
  • データ形式: ファイルやデータベースにデータを保存・読み込みする際は、データの整合性(フォーマット、文字コードなど)に注意してください。特に、異なるシステム間でデータをやり取りする場合は、エンコーディングの問題に注意が必要です。

保守性と拡張性:未来を見据えた設計

このスクリプトを「プロダクションコード」として運用していくためには、保守性と拡張性を常に意識する必要があります。

  • モジュール化: 現在のスクリプトは比較的シンプルですが、機能が増えるにつれて肥大化します。処理ごとにサブルーチンや関数に分割し、コードの可読性と再利用性を高めましょう。
  • コメントの質: コードの「何をやっているか」だけでなく、「なぜそうしているのか」を記述するコメントは、将来の自分や他の開発者にとって invaluable です。
  • バージョン管理: Gitなどのバージョン管理システムを利用し、コードの変更履歴を管理しましょう。これにより、問題発生時のロールバックや、機能追加・削除の追跡が容易になります。
  • テスト容易性: スクリプトをテストしやすいように設計します。例えば、WMIオブジェクトのモックを作成して、実際のWMI環境なしにテストできるようにするなどです。

まとめ:VBScriptの「魂」を宿した自動化を

本稿では、VBScriptとWMIを用いて、印刷ジョブの監視と自動復旧を行うスクリプトの構築方法を解説しました。単なるコードの提供にとどまらず、WMIオブジェクトのライフサイクル、クエリの最適化、堅牢なエラーハンドリング、そして保守性の高い設計思想といった、私が長年培ってきた「極意」を惜しみなく開示しました。

この知識があれば、あなたは単に「動く」スクリプトを書くだけでなく、「現場で本当に役立つ、信頼性の高い自動化ツール」を開発できるようになるはずです。

印刷ジョブのトラブルシューティングに費やしていた時間を、より創造的で価値の高い業務に充ててください。VBScriptの可能性は、まだまだこんなものではありません。この知識を礎に、あなたの開発プロジェクトを成功に導いてください。

タイトルとURLをコピーしました