C#: Определение версии, названия, сервис-пака и архитектуры Windows.

Преамбула

Вот уж не думал, что данная задача будет достойна заметки, но оказалось, что нормально определить версию (а главное, удобоваримое название и архитектуру) просто так не получится.


Стандартный способ

В .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

В общем, без бубна и отдельной таблицы, сопоставляющей номера версий с конкретными названиями, нормально выводится только сервис-пак.

Через WMI

Интересующая информация хранится в классе 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

Как уже упоминалось выше, данные об архитектуре ОС можно получить через 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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *