VB.NETでスキャナーの接続中/未接続/使用中を判定したい。でもWMI(System.Management)は使いたくない…。そんなときはWIA(Windows Image Acquisition)を使うと、デバイス列挙・接続可否・ビジー状態を比較的シンプルに確認できます。実装手順から例外処理、限界と代替案までまとめます。
WMIを使わずに「スキャナーの状態」を判定するときの現実的なゴール
まず前提として、スキャナーの「状態」と一口に言っても、アプリが知りたい情報は複数あります。
- PCから見て接続できるか(USB/ネットワーク含む)
- 他のアプリが使っていてビジーか(取り込み中、予約中など)
- 電源OFF/スタンバイ/カバー開閉/紙詰まりなど、機器固有の詳細状態
WMI(ManagementObject)を避けたい場合、Windows標準の枠組みで狙いやすいのは「接続できるか」「ビジーか」の2点です。細かい状態は、スキャナードライバーがWIAプロパティとして公開していない限り、厳密な判定が難しいケースがあります。
| 知りたいこと | WIAで判定できる可能性 | 補足 |
|---|---|---|
| PCから接続できる/できない | 高い | DeviceManager列挙+Connect()の成否で判断しやすい |
| 使用中(ビジー) | 高い | Connect()でCOM例外→HRESULTがビジー系なら判定可能 |
| 電源OFF/スタンバイ | 中〜低 | 機種・ドライバー次第。Connect Statusが変化しないこともある |
| カバー開閉/紙詰まりなど | 低い | メーカー独自プロパティが公開されている場合のみ取得できる |
解決策:WIA(Windows Image Acquisition)でスキャナーの接続可否・ビジーを確認する
WIAは画像取り込み(スキャナー/カメラ等)向けのWindows標準APIです。VB.NETからはCOM参照を追加することで利用できます。WMIを使わずに、デバイス列挙・接続試行・プロパティ参照を行えるのが利点です。
事前準備:COM参照「Microsoft Windows Image Acquisition Library v2.0」を追加
- Visual Studioで対象プロジェクトを開く
- 参照の追加(Add Reference)→ COMタブ
- Microsoft Windows Image Acquisition Library v2.0 を選択して追加
追加後は、コード側で Imports WIA が使えるようになります。
判定の基本フロー
DeviceManagerでデバイス一覧を列挙するScannerDeviceType(スキャナー)だけに絞るConnect()を試みて接続できるか確認する- 可能ならWIAプロパティ(例:
Name,Connect Status)を読む Connect()で例外が出たら、HRESULTで「ビジー」などを分類する
実装例:VB.NETでスキャナーの状態を取得する(WMIなし)
以下は「接続できるか」「ビジーか」を中心に、スキャナーごとの状態を返すサンプルです。WIAのコレクションは1始まりのことがあるため、For i = 1 To Count形式にしています。
Option Strict On
Imports System.Runtime.InteropServices
Imports WIA
Public Enum ScannerState
Unknown = 0
Connected = 1
Disconnected = 2
Busy = 3
ErrorState = 4
End Enum
Public Class ScannerStatus
Public Property DeviceId As String
Public Property Name As String
Public Property ConnectStatus As Integer?
Public Property State As ScannerState
Public Property HResult As UInteger?
Public Property Message As String
End Class
Public Module WiaScannerStatusChecker
' 代表的に使いやすいWIAデバイスプロパティID
Private Const WIA_DIP_DEV_NAME As Integer = 7
Private Const WIA_DIP_CONNECT_STATUS As Integer = 1025
' 質問で例に挙がりやすい「ビジー」エラー
Private Const WIA_ERROR_BUSY As UInteger = &H80210006UI
Public Function GetAllScannerStatuses() As List(Of ScannerStatus)
Dim results As New List(Of ScannerStatus)()
Dim mgr As DeviceManager = Nothing
Try
mgr = New DeviceManager()
For i As Integer = 1 To mgr.DeviceInfos.Count
Dim info As DeviceInfo = mgr.DeviceInfos(i)
' スキャナーだけを対象にする
If info.Type = WiaDeviceType.ScannerDeviceType Then
results.Add(GetStatusFromDeviceInfo(info))
End If
Next
Finally
ReleaseComObject(mgr)
End Try
Return results
End Function
Private Function GetStatusFromDeviceInfo(info As DeviceInfo) As ScannerStatus
Dim s As New ScannerStatus() With {
.DeviceId = info.DeviceID,
.Name = SafeGetDeviceInfoString(info, WIA_DIP_DEV_NAME, "(Unknown)"),
.State = ScannerState.Unknown
}
Try
' まず接続を試みる(ここが一番の判定ポイント)
Dim dev As Device = Nothing
Try
dev = info.Connect()
' 接続できた場合、Connect Statusが読めれば読む
Dim cs As Integer? = SafeGetDeviceInt(dev, WIA_DIP_CONNECT_STATUS, Nothing)
s.ConnectStatus = cs
If cs.HasValue Then
If cs.Value = 1 Then
s.State = ScannerState.Connected
ElseIf cs.Value = 0 Then
s.State = ScannerState.Disconnected
Else
' ドライバーによっては0/1以外が返る可能性があるため保険
s.State = ScannerState.Connected
s.Message = "Connect Statusの値が想定外のため、接続成功を優先して扱いました。"
End If
Else
' Connect Statusが取れない場合は、Connect成功=接続可能として扱う
s.State = ScannerState.Connected
s.Message = "Connect Statusプロパティが取得できないため、Connect成功を接続可能と判断しました。"
End If
Finally
ReleaseComObject(dev)
End Try
Catch ex As COMException
' COMException.ErrorCode は HRESULT(符号付き)なので UInteger へ
Dim hr As UInteger = CUInt(ex.ErrorCode)
s.HResult = hr
If hr = WIA_ERROR_BUSY Then
s.State = ScannerState.Busy
s.Message = "スキャナーは使用中(ビジー)です。取り込み完了後に再試行してください。"
Else
' ここで「未接続」扱いにするか「エラー」扱いにするかは要件次第
s.State = ScannerState.ErrorState
s.Message = $"接続に失敗しました。HRESULT=0x{hr:X8} / {ex.Message}"
End If
Catch ex As Exception
s.State = ScannerState.ErrorState
s.Message = ex.Message
End Try
Return s
End Function
Private Function SafeGetDeviceInfoString(info As DeviceInfo, propId As Integer, defaultValue As String) As String
Try
Dim p As [Property] = info.Properties(propId)
If p Is Nothing OrElse p.Value Is Nothing Then Return defaultValue
Return Convert.ToString(p.Value)
Catch
Return defaultValue
End Try
End Function
Private Function SafeGetDeviceInt(dev As Device, propId As Integer, defaultValue As Integer?) As Integer?
Try
Dim p As [Property] = dev.Properties(propId)
If p Is Nothing OrElse p.Value Is Nothing Then Return defaultValue
Return Convert.ToInt32(p.Value)
Catch
Return defaultValue
End Try
End Function
Private Sub ReleaseComObject(obj As Object)
If obj Is Nothing Then Return
Try
If Marshal.IsComObject(obj) Then
Marshal.FinalReleaseComObject(obj)
End If
Catch
' 解放に失敗しても致命ではないため握りつぶす
End Try
End Sub
End Module
呼び出し例(一覧表示)
実際にはGUIやログに「状態」を出したいことが多いと思います。例えばコンソールに表示するなら次のように扱えます。
Dim list = WiaScannerStatusChecker.GetAllScannerStatuses()
For Each s In list
Dim csText As String =
If(s.ConnectStatus.HasValue, s.ConnectStatus.Value.ToString(), "(N/A)")
Console.WriteLine($"Name={s.Name} / State={s.State} / ConnectStatus={csText} / Hr={If(s.HResult.HasValue, $"0x{s.HResult.Value:X8}", "")}")
If Not String.IsNullOrEmpty(s.Message) Then
Console.WriteLine($" {s.Message}")
End If
Next
「Connect Status」だけに頼らないほうが良い理由
WIAのConnect Status(例:0=Disconnected / 1=Connected)は便利ですが、現場では次のような理由で過信しないほうが安全です。
- ドライバーによってはプロパティを返さない(取得できない/常に同じ値)
- 電源OFFや省電力状態でも値が変化しないことがある
- ネットワーク機器は「ドライバー的には見えているが、実機は落ちている」状態が起きやすい
そのため、実務では「Connect()が成功するか」+「例外の内容」を軸に、必要に応じてプロパティを補助情報として扱うのが堅実です。
例外処理が実務の肝:COMExceptionをHRESULTで分類する
WIAを使うと、状態の差分は多くの場合「例外」として表面化します。特に重要なのがビジー判定です。例えば、別アプリがスキャナーを掴んでいる、あるいは前回のジョブが残っている、といった状況では Connect() が COMException を投げることがあります。
| 分類 | よくある症状 | 実装上の扱い(例) |
|---|---|---|
| ビジー | 接続や取り込み開始でCOM例外。別アプリが使用中 | ユーザーに「使用中」表示。短いリトライや後回しの設計が有効 |
| 未接続/到達不可 | USB抜け、ネットワーク切断、電源OFFなど | 「未接続」表示。ドライバー/ケーブル/電源/ネットワークを案内 |
| ドライバー起因のエラー | プロパティ取得で失敗、Connectは成功するが以降が不安定 | ログにHRESULTを残し、機種依存として切り分け。SDK/TWAIN検討 |
HRESULTはログに残しておくと、現場での切り分けが格段に楽になります。上のサンプルのように 0x{hr:X8} 形式で出しておくのがおすすめです。
ビジー時のリトライ設計(やりすぎないのがコツ)
ビジーは一時的なことが多い一方、むやみにループするとUIが固まったり、スキャナー側が余計に不安定になったりします。実装としては次のような方針が扱いやすいです。
- リトライ回数は少なめ(例:数回)
- UIスレッドはブロックしない(UIアプリなら非同期/バックグラウンドで)
- 「使用中」の表示を優先し、ユーザー操作(再試行ボタン)で回復させる
判定ロジックを「状態モデル」として整理すると運用が楽になる
実務では「接続できない=未接続」と単純化できない場面が出ます。例えば、権限やサービス停止、ドライバーの不整合などです。そこで、アプリ内部では次のように状態を分けておくと運用が安定します。
| アプリ内の状態 | 意味 | ユーザー向け表示例 | 次にやること |
|---|---|---|---|
| Connected | WIAで接続試行が成功 | 利用可能 | 取り込み処理へ進む |
| Busy | 使用中で接続/開始できない | 使用中 | 完了後に再試行、または待機 |
| Disconnected | Connect Status等で未接続が明確 | 未接続 | 電源/ケーブル/ネットワーク確認 |
| ErrorState | その他のエラー | エラー(詳細ログ参照) | HRESULTで切り分け、環境/ドライバー確認 |
| Unknown | まだ未判定 | 確認中 | 判定処理を実行 |
よくある落とし穴と対策
WIAサービスが停止している
Windowsの「Windows Image Acquisition (WIA)」サービスが停止していると、DeviceManager生成や接続が失敗することがあります。WMIを使わなくても、.NETのServiceControllerで状態確認は可能です(起動/停止の操作は権限が必要なことがあります)。
Imports System.ServiceProcess
Public Function IsWiaServiceRunning() As Boolean
Using sc As New ServiceController("stisvc")
Return sc.Status = ServiceControllerStatus.Running
End Using
End Function
サービスが止まっている環境があり得る運用(サーバーやキオスク端末など)なら、判定前にチェックしてユーザーに案内できるとトラブルが減ります。
コンソールアプリでSTAになっていない
WIAはCOMを扱うため、スレッドのアパートメントモデルが影響することがあります。Windows Forms/WPFのUIアプリは基本的にSTAですが、コンソールアプリの場合は<STAThread>を付けておくと安全です。
<STAThread>
Sub Main()
' WIAを使う処理
End Sub
32bit/64bitの不一致(特に古いスキャナー)
古い機種のWIAドライバーが32bitしか提供されていない場合、アプリをAnyCPUでビルドすると環境によって動かないことがあります。疑わしい場合は、まずはx86ビルドで動作確認し、安定する構成に寄せると切り分けが早いです。
デバイスは列挙できるのにConnectできない
「デバイス一覧には出るのに接続できない」は現場でよくあります。原因候補は多いので、ログとチェック項目を用意しておくのが有効です。
| チェック項目 | 見方 | 対策例 |
|---|---|---|
| USB接続 | 別ポート/別ケーブルで再現するか | ケーブル交換、ハブを避ける |
| ネットワーク | 同一セグメントか、IP到達性 | 有線化、IP固定、FW例外 |
| ドライバー | デバイスマネージャー上の状態 | 再インストール、メーカー最新版へ更新 |
| WIAサービス | サービスがRunningか | 起動、スタートアップ種別の見直し |
より厳密な状態が必要な場合の選択肢(メーカーSDK/TWAIN/SNMP)
WIAで分かりやすいのは「接続できるか」「ビジーか」ですが、業務要件によっては「カバーが開いている」「紙詰まり」「ADFに原稿があるか」など、より詳細な状態が必要になることがあります。その場合は、次の選択肢を検討します。
| 手段 | 得意なこと | 注意点 | 向いているケース |
|---|---|---|---|
| メーカーSDK | 機器固有の詳細状態、ジョブ制御 | 機種依存が強い。配布条件やライセンス確認が必要 | 同一メーカー/同一機種で運用を固定できる |
| TWAIN | スキャン取り込みの互換性が高い | 状態取得はドライバー実装次第。導入/配布がやや重い | 複数メーカー混在のスキャンアプリ |
| SNMP(ネットワーク機) | オンライン/オフライン、紙詰まり等の監視 | MIBや設定が必要。機器/ネットワーク管理側の協力が要る | 複合機をネットワークで集中管理したい |
「状態判定が必須」なのか、「スキャン開始時に失敗したらユーザーへ案内できれば十分」なのかで、実装コストが大きく変わります。まずはWIAで達成できる範囲を押さえ、足りない部分だけを別手段で補うのが現実的です。
運用で効く小技:ログに残すべき情報を最初に決める
スキャナー関連の不具合は、ユーザー環境・ケーブル・ネットワーク・ドライバーの組み合わせで再現条件が揺れやすい領域です。アプリ側で最低限ログに残す情報を決めておくと、原因究明が速くなります。
- デバイス名(WIAのName)
- DeviceID
- 判定した状態(Connected/Busy/Disconnected/Error)
- 例外のHRESULT(16進)とメッセージ
- アプリのビルド種別(x86/AnyCPU)
- OSのバージョン(Windows 10/11等)
特にHRESULTは「後から調べる」ためのキーになります。ユーザーにそのまま表示しなくても、ログには必ず残す設計が安心です。
まとめ:WIAで「WMIなし」のスキャナー状態判定を堅実に実装する
VB.NETでWMI(ManagementObject)を使わずにスキャナーの状態を判定するなら、WIAを使う方法が現実的です。DeviceManagerで列挙し、Connect()の成否とCOMExceptionのHRESULTを中心に扱うことで、接続可否やビジーを実務レベルで判断できます。
- WIAは「接続できるか」「ビジーか」を取りやすい
- 細かい状態はドライバーが公開していないと厳密に取れない
- HRESULTをログに残し、運用で切り分けできるようにする
- より詳細が必要ならメーカーSDK/TWAIN/SNMPも検討する
まずはWIAで達成できる範囲を確実に作り込み、要件に応じて段階的に精度を上げていくのが、スキャナー連携を長く安定して運用する近道です。

コメント