VB.NETでスキャナーの状態を判定する方法|WMIなしでWIA(Windows Image Acquisition)を使う

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 が使えるようになります。

判定の基本フロー

  1. DeviceManagerでデバイス一覧を列挙する
  2. ScannerDeviceType(スキャナー)だけに絞る
  3. Connect()を試みて接続できるか確認する
  4. 可能ならWIAプロパティ(例:Name, Connect Status)を読む
  5. 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アプリなら非同期/バックグラウンドで)
  • 「使用中」の表示を優先し、ユーザー操作(再試行ボタン)で回復させる

判定ロジックを「状態モデル」として整理すると運用が楽になる

実務では「接続できない=未接続」と単純化できない場面が出ます。例えば、権限やサービス停止、ドライバーの不整合などです。そこで、アプリ内部では次のように状態を分けておくと運用が安定します。

アプリ内の状態意味ユーザー向け表示例次にやること
ConnectedWIAで接続試行が成功利用可能取り込み処理へ進む
Busy使用中で接続/開始できない使用中完了後に再試行、または待機
DisconnectedConnect 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>を付けておくと安全です。

&lt;STAThread&gt;
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で達成できる範囲を確実に作り込み、要件に応じて段階的に精度を上げていくのが、スキャナー連携を長く安定して運用する近道です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次