Win32_BIOSでBIOSバージョン取得:SMBIOSPresent/SMBIOSBIOSVersionの安全なチェック方法(VB.NET・WMI)

VB.NETからWMIのWin32_BIOSを使ってBIOSバージョンを取得する際、SMBIOSPresentがFalseのPCや仮想環境に遭遇すると、SMBIOSBIOSVersionが空になり取得処理が落ちることがあります。本記事では例外回避のチェック順序と、BIOSVersionを使った堅牢なフォールバック実装を整理します。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

結論:SMBIOSPresent を見てから SMBIOSBIOSVersion を読む

Win32_BIOS クラスで「SMBIOS 由来の情報(SMBIOSBIOSVersion など)」を使いたい場合は、SMBIOSPresent が True であることを先に確認する運用が最も安全です。SMBIOSPresent が False(または取得できない)環境では、SMBIOSBIOSVersion が空(null)になったり、機種固有のWMI実装の癖で想定外の値が返ったりすることがあります。

  • SMBIOSPresent = True:SMBIOSBIOSVersion を優先して取得する
  • SMBIOSPresent = False:SMBIOSBIOSVersion には依存せず、BIOSVersion / Version などでフォールバックする
  • SMBIOSPresent が Nothing/未取得:安全側に倒してフォールバック(または例外を握りつぶさずログに残す)

一方、BIOSVersion プロパティは SMBIOS に依存しないケースが多いため、まずは BIOSVersion を使う実装に寄せておくと、現場ではトラブルが減ります(ただし機種や仮想環境によっては BIOSVersion も空になることがあるため、複数候補で組み立てるのが現実的です)。

Win32_BIOS でよく使うプロパティの違い

「どのプロパティを見れば“BIOSバージョン”として扱えるか」は環境差が出やすいポイントです。Win32_BIOS の代表的なプロパティを、依存関係と戻り値のクセに着目して整理します。

プロパティ主な型SMBIOS依存特徴実運用の注意
SMBIOSPresentBooleanSMBIOS情報が提供されているかを示すNothing の場合もあるため、取得可否を加味して扱う
SMBIOSBIOSVersionString高いSMBIOSが提供する「BIOSバージョン文字列」SMBIOSPresent=False では null/空文字になりやすい。まずSMBIOSPresentを確認
BIOSVersionString または String[]低い(環境差あり)ベンダー実装次第で配列で返ることがある配列→結合が必要。改行や余計な空白が混ざることがある
VersionString低い「バージョン」らしい文字列が入ることがあるSMBIOSBIOSVersion/BIOSVersion が取れない場合の保険として扱う
NameString低い製品名に近い文字列(バージョンが混ざる場合あり)「BIOSバージョン」としては過剰情報になりやすい
ManufacturerString低いBIOSベンダー名表示用途には有用。バージョン識別には単独では不足
ReleaseDateString(WMI日時形式)低いリリース日表示するならWMI日時→DateTime変換を行う

SMBIOSPresent が False になる典型パターン

「SMBIOSが無いなんてあるの?」と思いがちですが、現場では意外と遭遇します。原因は大きく分けて次のようなものです。

パターン起こりやすい症状実務での対処
古い/特殊なファームウェア古いPC、産業機器、組み込み機器SMBIOSPresent=False、SMBIOSBIOSVersion が nullBIOSVersion / Version を優先し、それも無い場合は別経路(レジストリ等)を検討
仮想環境の実装差一部のハイパーバイザー、特殊なクラウド環境取得値が固定文字列、または空複数プロパティで候補を作り、空なら「取得不可」として扱う
WMI提供側の問題WMIリポジトリ破損、権限/サービス不調ManagementException、Get() 自体が失敗例外を捕捉し、診断用ログ(例外メッセージ)を残す
セキュリティ/制限最小構成OS、制限された環境一部プロパティだけ取得できない「取得できない前提」でフォールバック設計

SMBIOSBIOSVersion を参照すると例外が起きるのか

結論から言うと、「SMBIOSPresent=False だから即WMI例外が起きる」とは限りません。多くの環境では、SMBIOSBIOSVersion の値が Nothing(null)や空文字になり、.ToString() を呼んだタイミングで NullReferenceException(VBなら「Null 参照」系の例外)が発生します。

ただし、以下のようなケースでは WMI 側の例外(ManagementException)が起きる可能性もあります。

  • WMIサービスやプロバイダーが不調で、プロパティ参照時に内部エラーになる
  • クエリの書き方や管理スコープが特殊で、取得できるプロパティが制限されている
  • 取得した ManagementObject が破棄されている、または接続が切れている(稀)

そのため実装としては、SMBIOSPresent のチェック値が Nothing/空文字でないことのチェック必要に応じて例外捕捉の3点セットで考えると、予期せぬ停止を防ぎやすくなります。

おすすめの判定フロー(チェック順序)

「SMBIOSBIOSVersion が取れるならそれを使いたい」「無理なら別の方法でBIOSバージョンを返したい」という要求を満たす、現実的なフローを表にまとめます。

優先度条件取得対象採用理由
SMBIOSPresent=True かつ SMBIOSBIOSVersion が空でないSMBIOSBIOSVersionSMBIOS由来の「それっぽい」バージョンが得られやすい
BIOSVersion が空でないBIOSVersion(配列なら結合)SMBIOSが無い/壊れている環境でも取れることがある
Version が空でないVersion最後の砦として使えることがある
上記が全て空取得不可として継続無理に推測して誤判定するより、明示的に「不明」を返す方が安全

VB.NET 実装例:最小の安全対策

まずは質問に近い形で、「SMBIOSPresent を見てから SMBIOSBIOSVersion を読む」最小実装です。Nullチェックを入れても、ToString() 前提のままだと抜け漏れが出るため、空文字の可能性も合わせて処理します。

Imports System.Management

Dim search As New ManagementObjectSearcher(
    New SelectQuery("SELECT SMBIOSPresent, SMBIOSBIOSVersion, BIOSVersion, Version FROM Win32_BIOS")
)

For Each info As ManagementObject In search.Get()

    Dim smbiosPresent As Boolean = False
    If info("SMBIOSPresent") IsNot Nothing Then
        ' WMI は Boolean が Object で返るため、文字列化して TryParse するのが安全
        Boolean.TryParse(info("SMBIOSPresent").ToString(), smbiosPresent)
    End If

    If smbiosPresent Then
        Dim s As String = ""
        If info("SMBIOSBIOSVersion") IsNot Nothing Then
            s = info("SMBIOSBIOSVersion").ToString()
        End If

        If Not String.IsNullOrWhiteSpace(s) Then
            MsgBox(s.Trim())
            Continue For
        End If
    End If

    ' フォールバック:BIOSVersion(配列になることがある)
    If info("BIOSVersion") IsNot Nothing Then
        Dim vObj = info("BIOSVersion")
        Dim v As String = ""

        If TypeOf vObj Is String() Then
            Dim arr = DirectCast(vObj, String())
            v = String.Join(" ", arr)
        Else
            v = vObj.ToString()
        End If

        If Not String.IsNullOrWhiteSpace(v) Then
            MsgBox(v.Trim())
            Continue For
        End If
    End If

    ' さらにフォールバック:Version
    If info("Version") IsNot Nothing Then
        Dim v2 = info("Version").ToString()
        If Not String.IsNullOrWhiteSpace(v2) Then
            MsgBox(v2.Trim())
            Continue For
        End If
    End If

    MsgBox("BIOS バージョン情報を取得できませんでした。")

Next

このレベルでも「SMBIOSが無い環境での停止」はかなり減ります。ただし、配列結合の結果に空要素が混ざる、例外が飛ぶ、複数インスタンスが返るなど、現場の“汚れたデータ”に耐えたいなら次の実装が有効です。

VB.NET 実装例:実運用向けの堅牢版(関数化+例外処理)

業務ツールや資産管理のように「絶対に落としたくない」場合は、取得処理を関数化して、取得できた最初の妥当値を返す方式にすると扱いやすくなります。例外は握りつぶすのではなく、ログに残せる設計にしておくと調査が楽です。

Imports System.Management
Imports System.Text

Public Module BiosReader

    Public Function GetBiosVersionString() As String
        Dim query As String =
            "SELECT SMBIOSPresent, SMBIOSBIOSVersion, BIOSVersion, Version, Manufacturer, ReleaseDate " &
            "FROM Win32_BIOS"

        Try
            Using searcher As New ManagementObjectSearcher(query)
                For Each mo As ManagementObject In searcher.Get()

                    ' 1) SMBIOSPresent を安全に読む
                    Dim smbiosPresent As Boolean = TryGetBoolean(mo, "SMBIOSPresent")

                    ' 2) SMBIOS があるなら SMBIOSBIOSVersion を最優先
                    If smbiosPresent Then
                        Dim s As String = TryGetString(mo, "SMBIOSBIOSVersion")
                        If Not String.IsNullOrWhiteSpace(s) Then
                            Return NormalizeVersionString(s)
                        End If
                    End If

                    ' 3) BIOSVersion(配列対策)
                    Dim biosV As String = TryGetBiosVersion(mo)
                    If Not String.IsNullOrWhiteSpace(biosV) Then
                        Return NormalizeVersionString(biosV)
                    End If

                    ' 4) 最後の砦:Version
                    Dim v As String = TryGetString(mo, "Version")
                    If Not String.IsNullOrWhiteSpace(v) Then
                        Return NormalizeVersionString(v)
                    End If

                    ' ここまで来たら、そのインスタンスでは取得できない
                Next
            End Using

        Catch ex As ManagementException
            ' TODO: ログに ex.Message を出すなど(例:イベントログ、ファイル、ETW)
            Return ""
        Catch ex As UnauthorizedAccessException
            Return ""
        Catch
            Return ""
        End Try

        Return ""
    End Function

    Private Function TryGetString(mo As ManagementBaseObject, propName As String) As String
        Try
            Dim v = mo(propName)
            If v Is Nothing Then Return ""
            Return v.ToString()
        Catch
            Return ""
        End Try
    End Function

    Private Function TryGetBoolean(mo As ManagementBaseObject, propName As String) As Boolean
        Dim s As String = TryGetString(mo, propName)
        Dim b As Boolean = False
        If String.IsNullOrWhiteSpace(s) Then Return False
        Boolean.TryParse(s, b)
        Return b
    End Function

    Private Function TryGetBiosVersion(mo As ManagementBaseObject) As String
        Try
            Dim vObj = mo("BIOSVersion")
            If vObj Is Nothing Then Return ""

            If TypeOf vObj Is String() Then
                Dim arr = DirectCast(vObj, String())
                Dim sb As New StringBuilder()
                For Each item In arr
                    If Not String.IsNullOrWhiteSpace(item) Then
                        If sb.Length > 0 Then sb.Append(" ")
                        sb.Append(item.Trim())
                    End If
                Next
                Return sb.ToString()
            End If

            Return vObj.ToString()

        Catch
            Return ""
        End Try
    End Function

    Private Function NormalizeVersionString(s As String) As String
        ' 余計な改行・タブをスペースに寄せ、前後の空白を落とす
        Dim t = s.Replace(vbCr, " ").Replace(vbLf, " ").Replace(vbTab, " ")
        Do While t.Contains("  ")
            t = t.Replace("  ", " ")
        Loop
        Return t.Trim()
    End Function

End Module

ポイントは次の通りです。

  • クエリで必要なプロパティだけを SELECT する(取得負荷と意図が明確になる)
  • SMBIOSPresent の値は Boolean として返ることもあれば、文字列変換が必要なこともあるため TryParse で吸収する
  • BIOSVersion は配列で返ることがあるので、空要素を除外して結合する
  • 返却前に正規化(改行や余計なスペースを潰す)して、比較・保存に耐える形にする

さらに実務で効く改善:複数候補を「一つの表示文字列」にまとめる

資産管理やサポート窓口では、単一の「バージョン」だけでなく、メーカーやリリース日も合わせて表示できると問い合わせ対応が速くなります。Win32_BIOS の情報をまとめて1行にする例を紹介します。

Imports System.Management
Imports System.Globalization
Imports System.Text

Public Function GetBiosSummary() As String
    Dim query As String =
        "SELECT SMBIOSPresent, SMBIOSBIOSVersion, BIOSVersion, Version, Manufacturer, ReleaseDate " &
        "FROM Win32_BIOS"

    Using searcher As New ManagementObjectSearcher(query)
        For Each mo As ManagementObject In searcher.Get()

            ' バージョン候補を同じ ManagementObject から組み立てる
            Dim version As String = ""
            Dim smbiosPresent As Boolean = False
            If mo("SMBIOSPresent") IsNot Nothing Then
                Boolean.TryParse(mo("SMBIOSPresent").ToString(), smbiosPresent)
            End If

            If smbiosPresent AndAlso mo("SMBIOSBIOSVersion") IsNot Nothing Then
                version = mo("SMBIOSBIOSVersion").ToString()
            End If

            If String.IsNullOrWhiteSpace(version) AndAlso mo("BIOSVersion") IsNot Nothing Then
                Dim vObj = mo("BIOSVersion")
                If TypeOf vObj Is String() Then
                    version = String.Join(" ", DirectCast(vObj, String()))
                Else
                    version = vObj.ToString()
                End If
            End If

            If String.IsNullOrWhiteSpace(version) AndAlso mo("Version") IsNot Nothing Then
                version = mo("Version").ToString()
            End If

            If String.IsNullOrWhiteSpace(version) Then version = "(unknown)"
            version = version.Replace(vbCr, " ").Replace(vbLf, " ").Replace(vbTab, " ").Trim()

            Dim maker As String = ""
            If mo("Manufacturer") IsNot Nothing Then maker = mo("Manufacturer").ToString().Trim()

            Dim release As String = ""
            If mo("ReleaseDate") IsNot Nothing Then
                Try
                    Dim dt = ManagementDateTimeConverter.ToDateTime(mo("ReleaseDate").ToString())
                    release = dt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
                Catch
                    release = ""
                End Try
            End If

            Dim sb As New StringBuilder()
            If Not String.IsNullOrWhiteSpace(maker) Then
                sb.Append(maker).Append(" / ")
            End If
            sb.Append(version)
            If Not String.IsNullOrWhiteSpace(release) Then
                sb.Append(" / ").Append(release)
            End If

            Return sb.ToString()
        Next
    End Using

    Return ""
End Function

このように「メーカー / バージョン / 日付」を併記しておくと、同じバージョン文字列でもベンダー違いの取り違えを防げます。ログに残す場合も読みやすくなります。

PowerShell での事前確認(現場での切り分けが速くなる)

コードを直す前に、対象端末がどのプロパティを返しているかを素早く確認すると、原因切り分けが楽になります。管理者権限が不要なことも多いので、まずは次のコマンドで出力を見てください。

Get-CimInstance -ClassName Win32_BIOS |
  Select-Object SMBIOSPresent, SMBIOSBIOSVersion, BIOSVersion, Version, Manufacturer, ReleaseDate

もし SMBIOSPresent が False で SMBIOSBIOSVersion が空なら、VB.NET 側で SMBIOSBIOSVersion を前提にしない設計が必要です。逆に SMBIOSPresent が True なのに SMBIOSBIOSVersion が空なら、ファームウェア実装やWMIの癖を疑い、BIOSVersion/Version も合わせて採用する方針が妥当です。

トラブルシューティング:よくある症状と対策

Win32_BIOS は便利ですが、環境差の影響を強く受けます。ありがちなハマりどころを、症状ベースでまとめます。

症状よくある原因対策
SMBIOSBIOSVersion が Nothing で ToString() で落ちるSMBIOSPresent=False、または値が未設定SMBIOSPresent を先に確認+IsNot Nothing/空文字チェックを入れる
BIOSVersion が String() で、表示すると System.String[] になる配列を ToString() しているTypeOf で配列判定し、String.Join で結合する
同じ端末でも取得文字列に改行や余計な空白が混ざるベンダーの実装差正規化(改行→スペース、連続スペース削除)して保存・比較する
ManagementException が発生し、Get() 自体が失敗するWMIサービス不調、WMIリポジトリ破損、アクセス制限例外を捕捉して処理継続。端末側はWMIの健全性確認を実施(運用手順に従う)
取得値が固定文字列(例:To Be Filled By O.E.M.)になる仮想環境やテンプレート、未設定のSMBIOS情報固定値は「不明」とみなし、別情報(メーカー/モデル/シリアル等)と合わせて扱う

設計の考え方:BIOSバージョン取得は「正解が1つ」とは限らない

BIOSバージョンは本来ファームウェアが持つ情報ですが、アプリが参照する経路は複数あります。Win32_BIOS はその一つに過ぎず、環境によっては値が欠落します。そのため実装では、次のような“現場前提”を置くと失敗しにくくなります。

  • 最優先は「落ちないこと」:取得に失敗してもアプリが停止しない設計(例外捕捉+空文字返却)
  • 値が取れたら勝ち、ではなく妥当性を確認:空文字、固定文言、異常に長い文字列は弾く
  • 比較用途なら正規化が必須:改行・連続スペース・前後空白を統一し、ログやDBで比較しやすくする
  • 運用で必要な粒度を決める:画面表示なら多少の揺れは許容、資産管理なら厳密に揺れを抑える

よくある質問

SMBIOSPresent を見ずに SMBIOSBIOSVersion の Nothing チェックだけではだめ?

最小限の安全策としては動くことが多いですが、SMBIOSPresent=False の環境では「そもそも SMBIOSBIOSVersion を信頼できない」ため、設計としては先に SMBIOSPresent を見て分岐させた方が、意図が明確で保守しやすいです。特に将来、取得結果をログ集計したり、端末の世代判定に使ったりする場合に差が出ます。

SMBIOSPresent が Nothing のときは True とみなしていい?

おすすめしません。Nothing は「取得できない」可能性を含むため、安全側(False扱い)に倒してフォールバックした方が、例外や誤判定を減らせます。必要ならログに「SMBIOSPresent を取得できなかった」という情報を残すと、後で端末差を追いやすくなります。

BIOSVersion と SMBIOSBIOSVersion のどちらが“正しい”の?

どちらが正しいかは環境次第です。SMBIOSBIOSVersion は SMBIOS 由来の文字列で、ベンダーが想定する「表示向け」になっていることが多い一方、BIOSVersion は配列で返ったり、表記が揺れたりします。実務では「SMBIOSがあるなら SMBIOSBIOSVersion を優先し、無ければ BIOSVersion/Version で補う」というハイブリッドが安定します。

最後に:チェック順序を固定し、フォールバックで吸収する

Win32_BIOS で BIOS バージョンを取得するなら、SMBIOSPresent → SMBIOSBIOSVersion → BIOSVersion → Versionの順に候補を評価し、空や例外を前提にフォールバックするのが実運用で強いアプローチです。端末差が避けられない領域だからこそ、チェック順序と正規化をテンプレ化しておくと、後々の保守コストが下がります。

この記事を書いた人

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

コメント

コメントする

目次