VB.NET(.NET Framework)でWindowsのOS名を取得するとき、My.Computer.Info.OSFullNameは手軽ですが、返ってくる文字列がOSの表示言語に合わせて変わります。ログ出力や機能分岐で「言語に依存しない(できれば英語固定の)OS名」が必要な場合に困らないよう、実務で使える取得方法と設計の考え方を整理します。
My.Computer.Info.OSFullName が「言語依存」になる理由
My.Computer.Info.OSFullName(My.Computer.Info配下のOS情報)は、Windowsが提供する表示用の名称をそのまま返す性質があり、システムの表示言語(UI言語)に合わせてローカライズされた文字列になります。
そのため、たとえばロシア語環境では、OS名が次のようにロシア語として返ることがあります。
Майкрософт Windows 8.1 Профессиональная 6.3.9600.0
この挙動自体は「ユーザーに見せる」用途では正しく便利ですが、次のような用途では不向きになりがちです。
- ログや監査記録に残して、国や言語をまたいで集計したい
- OS名の文字列で分岐(例:Windows 10/11で機能切替)したい
- ライセンス・サポート対象の判定をしたい(ただし「文字列比較」前提は危険)
| 取得したいもの | 例 | 推奨アプローチ |
|---|---|---|
| ユーザー向けの表示名 | Windows 11 Pro(日本語UIなら日本語表記) | OSFullNameなどローカライズ名でOK |
| 言語非依存の識別子(ログ・集計) | 製品名(英語寄り)+ビルド番号 | レジストリやバージョンAPIで「生データ」を取る |
| 機能分岐に必要なOS判定 | 10/11、Server/Client、ビルド閾値 | 数値(メジャー/マイナー/ビルド)で判断する |
「言語に依存しないOS名」を定義し直すと設計が安定する
ここが実務で一番重要です。「OS名」と言っても、要求が混ざりやすいからです。
- 表示名(ブランド名):ユーザーが見て分かりやすいが、言語や表記ゆれがある
- 識別子(判定用の情報):数値やコードで安定するが、人間には分かりにくい
おすすめは、目的別に次のどちらか(または両方)を残すことです。
- 判定用:メジャー/マイナー/ビルド番号、製品種別(Server/Client)、エディションコード
- 表示用:必要なら英語固定の製品名(例:ProductName)、またはローカライズ名(OSFullName)
つまり「英語固定のOS名が欲しい」場合でも、英語の文字列そのものを正として扱うのではなく、数値・コードを正として持ち、英語名は“表示ラベル”として扱うと将来の変更に強くなります。
レジストリ ProductName で英語寄りの製品名を取る
質問で挙がっている次のレジストリ値は、現場でよく使われる「製品名」取得手段です。
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName
多くのWindows環境では、このProductNameが英語表記のまま格納されているケースが多いため、言語非依存(少なくとも「UI言語には引きずられにくい」)な候補になります。実際、ローカライズされた製品名を返す別手段と比べると、ProductNameは英語のまま、というパターンに遭遇しやすいです。
注意点として、これは「必ず英語」と公式に保証されている前提で設計すると危険です。将来のWindowsの仕様変更、特殊なエディション、OEMカスタム、企業イメージ適用などで、想定外の文字列になる可能性はゼロではありません。したがって、文字列一致で厳密判定するのではなく、ログや表示、ゆるい分類に使うのが安全です。
VB.NET(.NET Framework)で ProductName を読むサンプル
64bit OS上で32bitプロセスとして動く場合、レジストリのビュー(32/64)問題が出ます。確実に読むならOpenBaseKeyでビューを指定します。
Imports Microsoft.Win32
Public Module OsNameRegistry
Public Function TryGetProductName(ByRef productName As String) As Boolean
productName = Nothing
Const subKey As String = "SOFTWARE\Microsoft\Windows NT\CurrentVersion"
Const valueName As String = "ProductName"
' まず64bitビューを試す(64bit OSで最も期待通りになりやすい)
If TryReadRegString(RegistryView.Registry64, subKey, valueName, productName) Then
Return True
End If
' 次に32bitビューを試す(32bit OS / 32bit環境など)
If TryReadRegString(RegistryView.Registry32, subKey, valueName, productName) Then
Return True
End If
Return False
End Function
Private Function TryReadRegString(view As RegistryView,
subKey As String,
valueName As String,
ByRef result As String) As Boolean
result = Nothing
Try
Using baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view)
Using key = baseKey.OpenSubKey(subKey, writable:=False)
If key Is Nothing Then Return False
Dim obj = key.GetValue(valueName, Nothing)
If obj Is Nothing Then Return False
Dim s = TryCast(obj, String)
If String.IsNullOrWhiteSpace(s) Then Return False
result = s.Trim()
Return True
End Using
End Using
Catch
' 権限/環境差異などは呼び出し側でフォールバックできるよう握りつぶす
Return False
End Try
End Function
End Module
これで、たとえば次のような文字列が取得できることがあります(環境によって異なります)。
Windows 10 ProWindows 11 ProWindows Server 2022 Standard
ProductName と一緒に取っておくと「実務で強い」関連キー
OS名を「英語固定っぽく」出すだけならProductNameで十分な場合が多いですが、ログや判定の精度を上げるなら、同じ場所にある周辺情報もセットで取るのが強いです。
| 値名(例) | 用途 | よくある例 | 注意点 |
|---|---|---|---|
ProductName | 製品名(表示用ラベル) | Windows 11 Pro | 文字列は将来変更されうる |
EditionID | エディションの分類 | Professional / Enterprise | UI表示名ではない |
InstallationType | Client/Serverの傾向 | Client / Server | 最終判定は別情報と組み合わせ推奨 |
CurrentBuildNumber / CurrentBuild | ビルド番号(判定の核) | 19045 / 22631 など | 値名が複数あるためフォールバックが必要 |
UBR | 更新リビジョン(詳細ログ向け) | Windows 10 19045.XXXX | DWORDなので型変換に注意 |
DisplayVersion | 22H2などの表示 | 22H2 | 古い環境では存在しないことがある |
ポイントは、「OS名(文字列)」だけに頼らず、ビルド番号やエディションIDなど“揺れにくい値”を一緒に取ることです。これだけで、言語差異や表記ゆれへの耐性が大きく上がります。
RtlGetVersion で「言語に依存しないOSバージョン」を確実に取る
OS名そのものよりも「Windowsの系統やバージョン判定」が目的なら、文字列ではなく数値を取るほうが安全です。その代表がntdll.dllのRtlGetVersionです。
なぜわざわざRtlGetVersionなのかというと、.NET Frameworkや一般的なAPIでは、アプリのマニフェスト(対応OS宣言)や互換性設定の影響で、OSバージョンが期待通りに取れないケースがあるからです。RtlGetVersionは比較的「実OSに近い値」が得られやすく、ログ用途・判定用途で重宝します。
VB.NET で RtlGetVersion を呼ぶサンプル
HTML中でVB属性の山括弧が壊れないように、以下のコードは<などにエスケープしています(コピペ後はそのままでも動作します)。
Imports System.Runtime.InteropServices
Public Module OsVersionNative
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Private Structure OSVERSIONINFOEX
Public dwOSVersionInfoSize As Integer
Public dwMajorVersion As Integer
Public dwMinorVersion As Integer
Public dwBuildNumber As Integer
Public dwPlatformId As Integer
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
Public szCSDVersion As String
Public wServicePackMajor As Short
Public wServicePackMinor As Short
Public wSuiteMask As Short
Public wProductType As Byte
Public wReserved As Byte
End Structure
<DllImport("ntdll.dll", CharSet:=CharSet.Unicode)>
Private Function RtlGetVersion(ByRef info As OSVERSIONINFOEX) As Integer
End Function
Public Function TryGetRealWindowsVersion(ByRef major As Integer,
ByRef minor As Integer,
ByRef build As Integer) As Boolean
major = 0 : minor = 0 : build = 0
Try
Dim info As New OSVERSIONINFOEX()
info.dwOSVersionInfoSize = Marshal.SizeOf(GetType(OSVERSIONINFOEX))
Dim status = RtlGetVersion(info)
If status <> 0 Then Return False
major = info.dwMajorVersion
minor = info.dwMinorVersion
build = info.dwBuildNumber
Return True
Catch
Return False
End Try
End Function
End Module
取得したバージョン番号から「英語のOS名」を作る考え方
RtlGetVersionで取れるのは数値です。これをそのままログに残すだけでも言語非依存ですが、運用上「Windows 10/11」くらいは人間が読める形にしたいことも多いです。その場合は、メジャー/マイナー/ビルド番号から自前でラベルを作るのが安全です。
| 判定材料 | 例 | ラベル例(英語) | コメント |
|---|---|---|---|
| Major=10, Minor=0, Build >= 22000 | 10.0.22631 | Windows 11 | Windows 11は「10.0」のままなのでBuildで分ける |
| Major=10, Minor=0, Build < 22000 | 10.0.19045 | Windows 10 | 22H2などの細分は別途DisplayVersion等で補う |
| Major=6, Minor=3 | 6.3.9600 | Windows 8.1 | 旧世代 |
| Major=6, Minor=1 | 6.1.7601 | Windows 7 | サポート切れ環境の検知にも使える |
この方法の良い点は、ローカライズされる余地がないことです。さらに、将来のWindowsでも「未知のビルド番号」が出た場合に、文字列の判定よりもフォールバックが作りやすいです(例:Windows (NT 10.0, Build 26000)のように残せる)。
GetProductInfo でエディションを「言語に依存しないコード」として取得する
「Pro/Enterprise/Education などのエディション」まで言語非依存で判定したいなら、Windows APIのGetProductInfoを使う手があります。これは製品タイプを数値コードとして返すため、文字列よりも安定します(表示名ではありませんが、判定やマッピングに向きます)。
注意点として、GetProductInfoは「Windowsのメジャー/マイナー」などを引数に取るため、取得したバージョン(例:RtlGetVersion)と組み合わせて呼ぶのが実務的です。
Imports System.Runtime.InteropServices
Public Module WindowsEdition
<DllImport("kernel32.dll", SetLastError:=True)>
Private Function GetProductInfo(dwOSMajorVersion As Integer,
dwOSMinorVersion As Integer,
dwSpMajorVersion As Integer,
dwSpMinorVersion As Integer,
ByRef pdwReturnedProductType As Integer) As Boolean
End Function
Public Function TryGetProductType(major As Integer, minor As Integer, ByRef productType As Integer) As Boolean
productType = 0
Try
' Service Packは0でOK(必要ならOSVERSIONINFOEXから渡す)
Return GetProductInfo(major, minor, 0, 0, productType)
Catch
Return False
End Try
End Function
End Module
返ってくるproductTypeは、たとえば「Professional」「Enterprise」などに対応する定数です。全定数をここで網羅するより、実務では次のように扱うと運用が楽です。
- ログには数値コード(例:
productType=48)も一緒に残す - 主要なものだけテーブル化して英語ラベルに変換する
- 未知の値は
UnknownEdition(XXXX)としてフォールバックする
| 方式 | 言語依存 | 判定の安定性 | 人間の読みやすさ |
|---|---|---|---|
| ProductName(レジストリ文字列) | 低い傾向(英語のことが多い) | 中(表記ゆれの可能性) | 高い |
| GetProductInfo(数値コード) | なし | 高い | 低い(変換が必要) |
実務向け:英語固定っぽい「OS名」を組み立てる実装パターン
「言語に依存しない」「なるべく英語固定」「将来のOSでも壊れにくい」を同時に満たすには、次のようなフォールバック付きの合成が強いです。
| 優先 | 取得元 | 得られるもの | 使いどころ |
|---|---|---|---|
| 1 | レジストリ ProductName | 英語寄りの製品名(例:Windows 11 Pro) | 表示・ログ(人間が読む) |
| 2 | RtlGetVersion | Major/Minor/Build | 判定・ログ(言語非依存の核) |
| 3 | レジストリ(Build/UBR/DisplayVersion 等) | 詳細なバージョン文字列 | トラブルシュート・サポート連携 |
| 4 | GetProductInfo | エディションコード | エディション判定が必要な場合 |
合成結果の例
- 人間向けラベル:
Windows 11 Pro - 判定向け識別子:
NT 10.0 (Build 22631, UBR 2861) - 将来の未知環境:
Windows (NT 10.0, Build 26000) / ProductName=Unknown
VB.NET:OS情報をまとめて返すサンプル(フォールバック付き)
以下は「言語非依存の核(数値)」と「英語寄りの製品名(文字列)」を両方返す例です。あくまで雛形なので、ログ形式や例外処理は現場の方針に合わせてください。
Public Class WindowsOsInfo
Public Property ProductName As String
Public Property Major As Integer
Public Property Minor As Integer
Public Property Build As Integer
Public Property DisplayLabelEnglish As String
Public Property VersionLabel As String
End Class
Public Module WindowsOsInfoProvider
Public Function GetWindowsOsInfo() As WindowsOsInfo
Dim info As New WindowsOsInfo()
' 1) ProductName(英語寄りの可能性が高い)
Dim pn As String = Nothing
If OsNameRegistry.TryGetProductName(pn) Then
info.ProductName = pn
Else
info.ProductName = ""
End If
' 2) 実OSに近いバージョン(言語非依存)
Dim major As Integer, minor As Integer, build As Integer
If OsVersionNative.TryGetRealWindowsVersion(major, minor, build) Then
info.Major = major : info.Minor = minor : info.Build = build
info.VersionLabel = $"NT {major}.{minor} (Build {build})"
Else
info.VersionLabel = "NT (Unknown)"
End If
' 3) 表示用ラベル(英語固定っぽく)
' ProductNameが取れればそれを優先、取れなければバージョンから作る
If Not String.IsNullOrWhiteSpace(info.ProductName) Then
info.DisplayLabelEnglish = info.ProductName
Else
info.DisplayLabelEnglish = GuessWindowsFamilyLabel(major, minor, build)
End If
Return info
End Function
Private Function GuessWindowsFamilyLabel(major As Integer, minor As Integer, build As Integer) As String
If major = 10 AndAlso minor = 0 Then
If build >= 22000 Then
Return "Windows 11"
Else
Return "Windows 10"
End If
ElseIf major = 6 AndAlso minor = 3 Then
Return "Windows 8.1"
ElseIf major = 6 AndAlso minor = 2 Then
Return "Windows 8"
ElseIf major = 6 AndAlso minor = 1 Then
Return "Windows 7"
ElseIf major = 6 AndAlso minor = 0 Then
Return "Windows Vista"
ElseIf major = 5 AndAlso minor = 1 Then
Return "Windows XP"
End If
Return $"Windows (NT {major}.{minor}, Build {build})"
End Function
End Module
この形にしておくと、UI表示にはDisplayLabelEnglish、判定やサポート用にはMajor/Minor/BuildやVersionLabelを使えるため、要件が途中で増えても破綻しにくくなります。
「ProductNameは常に英語なのか?」への現実的な答え
結論としては、多くの環境では英語のまま入っているケースが多い一方で、「必ず英語」と断言できる前提にはしないのが安全です。
実務では、次のように扱うのがおすすめです。
- 表示用ラベルとしては
ProductNameはかなり有力(英語で揃えやすい) - 判定の正は、
RtlGetVersionの数値・ビルド番号など(言語に依存しない) ProductNameが想定外でも壊れないよう、フォールバック(Unknown扱い)を入れる- ログには「取得できた生の値」を残しておき、後からマッピングを追加できるようにする
「英語名で揃えたい」という要求はよくありますが、英語文字列そのものをキーにすると、将来の表記ゆれに負けやすいのが落とし穴です。英語名は“見せる”ため、判定は“数値・コード”で、という役割分担が最も事故が少ないです。
よくある落とし穴と対策
| 落とし穴 | 起きる問題 | 対策 |
|---|---|---|
| OSFullNameの文字列で判定してしまう | 言語や表記ゆれで分岐が壊れる | 判定はRtlGetVersionの数値・ビルド番号で行う |
| 32bit/64bitのレジストリビュー | 32bitアプリが別ビューを読んで値が取れない | RegistryView.Registry64/Registry32を順に試す |
| Windows 11はバージョンが10.0のまま | Major/Minorだけでは10/11が区別できない | Build閾値(例:22000以上)を併用する |
| 将来のWindowsで未知の値が来る | マッピングがないと表示が崩れる | 未知値はUnknownとしてログに残し、後から追加 |
| レジストリ値の存在差 | DisplayVersionなどが古いOSで存在しない | 存在チェック+複数キーのフォールバックを用意 |
用途別のおすすめ(結局どれを使うべきか)
最後に「何を使うべきか」を用途別に整理します。ここを押さえると、要件が増えても迷いません。
| 用途 | 必要な情報 | おすすめ手段 | 理由 |
|---|---|---|---|
| ログ出力(運用・監査) | 人が読める名称+詳細 | ProductName+RtlGetVersion(Build) | 英語寄り名称と数値を両方残せる |
| 機能分岐(Windows 10/11など) | 判定に必要な最小情報 | RtlGetVersion(Build閾値) | 文字列比較より壊れにくい |
| エディション判定(Pro/Enterprise等) | エディションの安定判定 | GetProductInfo(コード)+必要なら表示ラベル | 言語に依存しないコードで扱える |
| ユーザー向け画面表示 | 現地語で自然な表示 | My.Computer.Info.OSFullName | ローカライズされているのがむしろ利点 |
まとめ
My.Computer.Info.OSFullNameは「表示用のローカライズ名」を返すため、言語に依存しない識別子が必要な用途(ログの集計、機能分岐、判定)には不向きです。言語非依存でいくなら、レジストリのProductNameで英語寄りの製品名を取りつつ、判定の正はRtlGetVersionの数値(特にBuild)に寄せるのが安定します。さらにエディションが必要ならGetProductInfoのコードを併用し、未知の値に備えたフォールバックを入れておけば、将来のWindowsでも壊れにくい実装になります。

コメント