Вот уж не думал, что данная задача будет достойна заметки, но оказалось, что нормально определить версию (а главное, удобоваримое название и архитектуру) просто так не получится.
В .NET Framework в классе Environment
есть свойство OSVersion
, но анализировать информацию, полученную от него, довольно неудобно. Например:
— конструкция Console.WriteLine("Version: " + Environment.OSVersion.VersionString);
выведет следующую строку:
Microsoft Windows NT 6.1.7601 Service Pack 1
— можно получить нормально, пожалуй, только сервис-пак:
Console.WriteLine("ServicePack: " + Environment.OSVersion.ServicePack);
ServicePack: Service Pack 1
— и номер версии как в команде ver
, отдельно от остального, и то почти, остается паразитное 65536
из поля Reversion
:
Console.WriteLine("Version: " + Environment.OSVersion.Version.ToString());
Version: 6.1.7601.65536
В общем, без бубна и отдельной таблицы, сопоставляющей номера версий с конкретными названиями, нормально выводится только сервис-пак.
Интересующая информация хранится в классе Win32_OperatingSystem
(CIM_OperatingSystem
из пространства имен root\\CIMV2
), сначала сделаем общую функцию для обращения к полям класса:
private static string CallWMI(string WMIProperty) { string ret = null; ManagementObjectSearcher mos = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM CIM_OperatingSystem"); try { foreach (ManagementObject MObj in mos.Get()) { ret = MObj.GetPropertyValue(WMIProperty).ToString(); } } catch (Exception ex) { ErrorMessage = ex.Message; } return ret; }
Алгоритм довольно простой:
1. Создаем ManagementObjectSearcher
с запросом к нужному классу.
2. Выполняем запрос (mos.Get()
) и вытаскиваем ManagementObject
‘ы, на самом деле, в данном случае, объект будет один.
3. Получаем значение поля, название которого передается функции в переменной WMIProperty
(ret = MObj.GetPropertyValue(WMIProperty).ToString();
)
Внимание! Все операции по выполнению WMI-запроса и получению данных из полей, необходимо выполнять в конструкции try/catch
, потому что, если поля вдруг не будет, то произойдет exception, как например, произошло при обращении к полю, содержащему информацию о сервис-паке на системе Windows 8.1 без сервис-пака:
Да, WMI штука местами непредсказуемая и падучая в самых неожиданных местах, так что не забываем отлавливать эксепшны.
Наименования полей с нужной информацией:
— Version
: содержит номер версии (как в консольной команде ver
), например 6.1.7601
— Caption
: человеко-читаемое название операционной системы (Microsoft Windows 7 Максимальная
)
— CSDVersion
: версия сервис-пака (Service Pack 1
)
— OSArchitecture
: архитектура операционной системы (32-bit
или 64-bit
). К этому полю вернемся позже.
Пример получения наименования ОС:
public static string GetOSVersionWMI() { ErrorMessage = string.Empty; return CallWMI("Version"); }
Интересующие данные хранятся в HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\
Нужные значения ключа:
— CurrentVersion
: первая часть номера версии ОС (6.1
)
— CurrentBuildNumber
: билд, вторая часть номера версии (7601
)
— ProductName
: наименование операционной системы (Windows 7 Ultimate
)
— CSDVersion
: сервис-пак (Service Pack 1
)
Минус способа очевиден. Фактически, все эти поля в Реестре нужны исключительно для информации, и какая-нибудь «колхозная» сборка, шаловливый пользователь или дурная программа вполне могут эти значения переписать.
Можно также создать общую функцию для доступа к данному ключу Реестра:
private static string CallRegistry(string RegValueName) { string ret = null; try { RegistryKey key = Registry.LocalMachine. OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion\"); ret = key.GetValue(RegValueName).ToString(); key.Close(); } catch (Exception ex) { ErrorMessage = ex.Message; ret = null; } return ret; }
Алгоритм стандартный:
1. Открываем ключ Реестра
2. Получаем нужное значение по его имени
3. Закрываем ключ
И опять же, не забывайте про отлов exceptions с помощью try/catch
! Ровно по той же причине, что и при использовании WMI. И, в данном случае, даже в том же самом месте. Убедиться можно на скриншотах в конце заметки.
Примеры использования.
Получение названия ОС:
public static string GetOSNameReg() { ErrorMessage = string.Empty; return CallRegistry("ProductName"); }
Получение номера версии:
public static string GetOSVersionReg() { ErrorMessage = string.Empty; string Version = CallRegistry("CurrentVersion"); string Build = CallRegistry("CurrentBuildNumber"); if ((Version == null) || (Build == null)) { return null; } return Version + "." + Build; }
Следующая задача — определить архитектуру системы (32- или 64-битная).
Как уже упоминалось выше, данные об архитектуре ОС можно получить через WMI. Эти сведения хранятся в поле OSArchitecture
класса Win32_OperatingSystem
, однако, этот способ не сработает для Windows XP, а возможно и Vista. В XP поля OSArchitecture
нет, и обращение к нему вызовет exception:
Честно подсмотрен на stackoverflow.
Алгоритм таков:
1. Проверяем размер IntPtr
. IntPtr
— это специальный платформо-зависимый тип, который используется для сохранения указателя, если нет желания использовать «небезопасный» код.
По замыслу автора оригинального алгоритма, если IntPtr.Size == 8
, значит, сам выполняемый процесс 64-разрядный, а значит, и система, в которой он выполняется — 64-разрядная. В оригинальном модуле Wow.cs даже было заведено отдельное свойство Is64BitProcess
, которое устанавливалось по результату этой проверки.
Но, насколько я понимаю, в том и фишка среды выполнения .NET Framework, что она сама подстраивается под ОС, и на 32-разрядной ОС .NET’овские процессы 32-разрядные, а на 64-разрядных, соответственно, 64-разрядные. Выполняется же в итоге код MSIL, и как его выполнять, решает именно что Framework. Поправьте меня, если я ошибаюсь.
Во всяком случае, при использовании оригинального класса, у меня и свойство Is64BitProcess
(для процесса), и свойство Is64BitOperatingSystem
(для ОС) принимали одно и то же значение в зависимости от разрядности ОС, но вне зависимости от версии (XP, 7, 8.1, 10).
2. Если IntPtr.Size
не равен 8
, значит, с помощью WinAPI функций проверяется:
2.1 Наличие в kernel32.dll
функции IsWow64Process
2.2 Проверяется, успешно ли завершилась функция IsWow64Process
, примененная к текущему процессу.
3.3. Проверяется ее результат.
3.4. Если все условия — истина, значит это 32-разрядный процесс, выполняющийся на 64-разрядной системе.
Функция, проверяющая, присутствует ли в DLL указанная функция (естественно, в секции export):
static bool ModuleContainsFunction(string moduleName, string methodName) { IntPtr hModule = GetModuleHandle(moduleName); if (hModule != IntPtr.Zero) return GetProcAddress(hModule, methodName) != IntPtr.Zero; return false; }
Свойство класса, ответственное за определение архитектуры:
public static bool Is64BitOperatingSystem { get { // Clearly if this is a 64-bit process we must be on a 64-bit OS. if (IntPtr.Size == 8) return true; // Ok, so we are a 32-bit process, but is the OS 64-bit? // If we are running under Wow64 than the OS is 64-bit. bool isWow64; return ModuleContainsFunction("kernel32.dll", "IsWow64Process") && IsWow64Process(GetCurrentProcess(), out isWow64) && isWow64; } }
Класс VersionDetect.cs
Весь пример целиком
Win32_OperatingSystem class
Тема на Киберфоруме
Вопрос на stackowerflow