Get windows version api

Статья показывает способы работы с API операционных систем на примере определения версии системы.


Содержание

Каждая операционная система имеет свой способ версионирования. Очевидно, что способ определения версии системы завязан на особенности конкретной ОС. Мы рассмотрим способы определения версии на Windows, Linux и Mac OS X.

Попутно мы освоим более важную вещь: работу с API операционных систем. API — это сокращение от Application Programming Interface. Каждая ОС предоставляет программисту низкоуровневые библиотеки, формирующие цельный интерфейс, позволяющий приложению реагировать на события ОС, либо делегировать ей свои задачи, а также общаться с другими процессами. Этот интерфейс и называется API операционной системы.

Структура API ОС

По историческим причинам большинство API ОС предоставляет средства в процедурном стиле и на языке C89. Такие API состоят из функций, макросов-констант и структур, представляющих объекты системы.

API ОС принято оставлять неизменными на протяжении десятилетий, чтобы даже скомпилированные программы продолжали работать, и не нужно было править исходники или перекомпилировать их заново. Расплата за это — неудобный, построенный по старым стандартам интерфейс. Но эта проблема легко решается с помощью написания фасадов, предоставляющих более удобный и высокоуровневый интерфейс. Готовые фасады есть во многих программных библиотеках.

Обычно функции в составе API ОС реализованы в динамических библиотеках, то есть их адреса и реализации становятся известны только после запуска программы, что и позволяет использовать новые версии ОС без перекомпиляции предназначенных для неё программ.

Кроме функций API есть также вызовы ядра ОС, которые работают с помощью системных прерываний (т.е. требуют помощи процессора), а не на основе стека и прыжка по адресу функции. Вызов ядра — дорогая операция, потому что в процессе вызова процессор переходит из защищённого режима (userspace mode) в режим прямой работы с памятью и устройствами (kernel mode), а затем возвращается обратно в защищённый режим.

Вызовы ядра — это единственный способ попросить систему об операции, выходящей за полномочия процесса программы, например, о создании нового процесса. Есть разница в отношении к системным вызовам в мире UNIX и в мире Windows

  • В UNIX-системах вызовы ядра доступны программисту напрямую, и считается нормальным их использовать
  • В Windows вызовы ядра обычно начинаются с префикса “Rtl”, и не рекомендуются для использования программистом. Для любой функции с префиксом “Rtl” есть замена в составе WinAPI (обычно это функция с тем же именем за вычетом префикса “Rtl”).

Функции API ОС и вызовы ядра вместе называют вызовами API ОС. Эти вызовы лежат в основе множества операций по работе с файлами, устройствами и сетью, но стоят дороже обычного вызова функции в собственном коде программы.

API для получения версии в Windows

Версия Windows отображается в модуле настройки, доступном из панели управления:

Скриншот

Узнать версию ОС программно Windows можно с помощью WinAPI. WinAPI расшифровывается как Windows API. Он детально задокументирован на сайте msdn.microsoft.com — портале от компании Microsoft, предназначенном для сторонних разработчиков. Найти документацию по функциям WinAPI можно, передав в поисковик запрос “msdn FunctionName” или “FunctionName site:msdn.microsoft.com”. Также помогают запросы из разряда “winapi how to …”.

Большая часть WinAPI доступна при подключении единственного заголовочного файла Windows.h. А это — небольшой пример:

// Программа выводит строку
//   Running under Windows Major.Minor
// где Minor - старший кодовый номер версии,
//   а Major - младший кодовый номер версии

// В современном WinAPI функция GetVersion считается устаревшей,
//   поэтому пример годится лишь для демонстрации WinAPI.
int main()
{
    // Конструкция `::GetVersion()` указывает на вызов фукнции GetVersion
    //   из глобального пространства имён.
    const DWORD encodedVersion = ::GetVersion();
    const unsigned majorVersion = unsigned(LOBYTE(LOWORD(encodedVersion)));
    const unsigned minorVersion = unsigned(HIBYTE(LOWORD(encodedVersion)));

    std::printf("Running under Windows %u.%u\n", majorVersion, minorVersion);
}

Имейте ввиду, что функция GetVersion возвращает кодовый номер версии, который нужно сопоставить с таблицей версий и определить человекопонятное имя.

В WinAPI действует ряд соглашений в именовании:

  • функции именуются в UpperCamelCase, например, GetVersion, GetVersionEx. Сокращения используются в редких случаях, например, суффиксом “Ex” помечают расширенную версию более старой фукнции.
  • имена типов данных именуются в верхнем регистре, часто с сокращениями: RECT, POINT, HMENU (handle to menu), OSVERSIONINFO (OS version information) и так далее
  • для создания и удаления системных объектов, например, типа XYZ, предоставляются функции с именами вида “CreateXYZ” и “DestroyXYZ”
  • многие привычные типы данных получили свои синонимы (в целях максимального контроля над стабильностью)
    • WORD — машинное слово, 16-битное целое без знака, аналог uint16_t
    • DWORD — удвоенное машинное слово, 32-битное целое без знака, аналог uint32_t
    • BOOL — синоним типа int, введённый по той причине, что в языке C89 нет типа bool. Тип bool появился в C++, а тип _Bool — в C99
    • LPCSTR — синоним const char *, расшифровывается как Left-Pointer to Const String, т.е. изменяемый (способный стоять в левой части присваивания) указатель на неизменяемую строку в стиле C, завершённую нулевым символом.
    • LPSTR — синоним char *, расшифровывается как Left-Pointer to String, т.е. изменяемый (способный стоять в левой части присваивания) указатель на изменяемую строку в стиле C, завершённую нулевым символом.
    • LPCWSTR — синоним const wchar_t *, расшифровывается как Left-Pointer Wide Const String, т.е. вместо типа char использует тип wide char, способный представлять символы одной из кодировок Unicode — UTF16
// В современном WinAPI функция GetVersionEx считается устаревшей,
//   поэтому пример годится лишь для демонстрации WinAPI.
int main()
{
    // Заполняем структуру нулевыми байтами (специальный синтаксис C89).
    OSVERSIONINFO osVersion = { 0 };
    // WinAPI требует указывать размер структуры, чтобы иметь возможность
    //  расширять структуру в новых версиях ОС.
    osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    const BOOL succeed = ::GetVersionEx(&osVersion);
    // Функция может вернуть FALSE только если параметр равен nullptr,
    //  либо в структуре неверное поле dwOSVersionInfoSize.
    assert(succeed);

    const unsigned majorVersion = osVersion.dwMajorVersion;
    const unsigned minorVersion = osVersion.dwMinorVersion;

    std::printf("Running under Windows %d.%d\n", majorVersion, minorVersion);
}

Вы могли заметить, что в предыдущих двух примерах используются устаревшие функции. Начиная с Windows 8, компания Microsoft решила объявить устаревшими GetVersion и GetVersionEx, и они теперь могут возвращать неправильное значение согласно спецификации.

Вместо них предлагается использовать функции из заголовка VersionHelpers.h. Эти функции не реализованы в библиотеках WinAPI и всего лишь дают фасад для вызова VerifyVersionInfo. Функции из состава VersionHelpers.h имеют более привычное именование версий ОС в сравнении с GetVersion и GetVersionEx.

const char *GetOsVersionName()
{
    if (IsWindows8Point1OrGreater())
    {
        return "8.1";
    }
    if (IsWindows8OrGreater())
    {
        return "8";
    }
    if (IsWindows7OrGreater())
    {
        return "7";
    }
    if (IsWindowsVistaOrGreater())
    {
        return "Vista";
    }
    if (IsWindowsXPOrGreater())
    {
        return "XP";
    }
    return "Unknown";
}

void PrintVersion3rdWay()
{
    const char *osVersionName = GetOsVersionName();

    std::printf("Running under Windows %s\n", osVersionName);
}

API для получения версии в Linux

Мы будем рассматривать получение версии на примере Ubuntu, т.к. этот дистрибутив использует подавляющая часть пользователей GNU/Linux. Через графический интерфейс информацию о системе можно узнать в панели настроек:

Скриншот

Для API в GNU/Linux характерна разобщённость: разные разработчики создают различные библиотеки, подходы к организации API этих библиотек и уровень квалификации их авторов может сильно варьироваться. Есть некоторые островки стабильности среди этого хаоса:

  • стандарт POSIX, который расширяет стандартную библиотеку C89 новыми функциями, и переносим на любую UNIX-подобную систему, в том числе на MacOSX и даже частично на Windows; в Linux большая часть POSIX доступна из заголовка unistd.h
  • системные вызовы ядра Linux, которые остаются стабильными на протяжении десятилетий, и доступны из заголовков в каталоге sys/
  • особенно популярные, распространнённые в разных дистрибутивах и проверенные библиотеки, интерфейс которых тщательно спроектирован и не меняется на протяжении многих лет
    • примеры таких библиотек: Qt, GTK, SDL2

Для систематизации документации в Linux придумано несколько разных решений. В первую очередь рассмотрим команду man (от слова “manual”). Команда man — это команда UNIX-систем, отображающая форматированную документацию в собственном формате man, набор которой зависит от установленных в системе пакетов. Вот такой вывод даёт команда “man printf”:

Скриншот

Нетрудно понять, что это документация не о функции “printf” стандартной библиотеки C89, а об утилите командной строки с тем же названием. Дело в том, что справка man распределена по разделам:

  • раздел 1 содержит документацию по утилитам командной строки, и к нему можно обраться командой “man 1 printf”
  • раздел 2 содержит документацию по интерфейсам и системным вызовам ядра Linux, и команда “man 2 printf” не даст результата:
$ man 2 printf
Нет справочной страницы для printf в разделе 2
Смотрите 'man 7 undocumented' в справке, если недоступны справочные страницы.
  • раздел 3 содержит документацию по функциям языка C, входящим в стандартную библиотеку, в стандарт POSIX и в прикладные библиотеки. Обраться к этой документации можно командой “man 3 printf”
  • есть и другие разделы, но они не представляют интереса для наших задач

Документация из разделов man есть не только на Linux-машинах, но и в интернете, например на сайте opennet.ru. Получить эту документацию можно, используя запрос вида “man printf” в поисковике.

Для получения версии ядра Linux существует системный вызов uname, информация о котором есть в разделе 2 справки man.

NAME
       uname - get name and information about current kernel

SYNOPSIS
       #include <sys/utsname.h>

       int uname(struct utsname *buf);

DESCRIPTION
       uname() returns system information in the structure pointed to by buf.  The utsname struct is defined in <sys/utsname.h>:

           struct utsname {
               char sysname[];    /* Operating system name (e.g., "Linux") */
               char nodename[];   /* Name within "some implementation-defined
                                     network" */
               char release[];    /* Operating system release (e.g., "2.6.28") */
               char version[];    /* Operating system version */
               char machine[];    /* Hardware identifier */
           #ifdef _GNU_SOURCE
               char domainname[]; /* NIS or YP domain name */
           #endif
           };

       The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are terminated by a null byte ('\0').

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

Из документации понятно поведение функции, и указано, что функция находится в заголовке “sys/utsname.h”. Можно написать функцию, которая будет получать и печатать информацию о версии ядра:

#include <cstdio>
#include <unistd.h>
#include <sys/utsname.h>
#include <cassert>

void PrintKernelInfo()
{
    utsname kernelInfo;
    const int code = uname(&kernelInfo);
    assert(code == 0); (void)code;

    std::printf("-- Kernel info --\n"
                "  domainname=%s\n"
                "  machine=%s\n"
                "  nodename=%s\n"
                "  release=%s\n"
                "  sysname=%s\n"
                "  version=%s\n",
                kernelInfo.domainname,
                kernelInfo.machine,
                kernelInfo.nodename,
                kernelInfo.release,
                kernelInfo.sysname,
                kernelInfo.version);
}

Результат вызова функции приведён ниже. Из него понятно, что поле nodename содержит имя компьютера в сети, а поля sysname и release — версию ядра операционной системы. Поля domainname, machine и version не несут особенно важной информации, и появления имени дистрибутива Ubuntu в поле version является особенностью сборки, а не общей закономерностью.

-- Kernel info --
  domainname=(none)
  machine=x86_64
  nodename=sergey-A17
  release=4.4.0-47-generic
  sysname=Linux
  version=#68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016

Для определения имени и номера версии дистрибутивы могут быть доступны разные приёмы:

  • современные дистрибутивы предоставляют утилиту командной строки “lsb_release”, выводящую версию дистрибутива, и эта утилита может быть вызвана программно с помощью “popen”; полный вывод доступен при запуске с параметром “lsb_release -a”, а справочную информацию можно узнать при запуске с параметром “lsb_release -h”
  • та же информация обычно доступна в текстовом файле “/etc/lsb-release”, и вы можете вывести его в консоль командой “cat /etc/lsb-release”
  • файл “/etc/os-release” может отсутствовать или иметь разный формат в некоторых дистрибутивах, в Ubuntu 16.04 он выглядит так:
NAME="Ubuntu"
VERSION="16.04.1 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.1 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

Реализация чтения файла или запуска lsb_release командой popen останется за пределами статьи.

API для получения версии в MacOSX

В MacOSX доступен тот же самый API POSIX, включающий в себя и вызов uname. Кроме POSIX также доступны

  • системные вызовы ядра Darwin
  • библиотека Carbon языка C89, которая в последних версиях OSX считается устаревшей
  • библиотеки (фреймворки) языка Objective-C, которые могут быть использованы совместно с кодом на языке C89
    • для смешивания Objective-C и C в одном файле используются файлы с расширением “.m”, к которым можно подключать заголовки “.h”
    • для смешивания Objective-C и C++ в одном файле используются файлы с расширением “.mm”, к которым также можно подключать заголовки “.h”

Ранний способ определения версии OSX — функция “Gestalt” из файла “CarbonCore/OSUtils.h”. К сожалению, эта функция объявлена устаревшей в MacOSX 10.8.

// Для успешной компоновки проекта нужно подключить фреймворк CoreServices
#include <cstdio>
#include <CoreServices/CoreServices.h>

int PrintMacOsXVersion()
{
    SInt32 majorVersion = 0;
    SInt32 minorVersion = 0;
    SInt32 bugFixVersion = 0;

    Gestalt(gestaltSystemVersionMajor, &majorVersion);
    Gestalt(gestaltSystemVersionMinor, &minorVersion);
    Gestalt(gestaltSystemVersionBugFix, &bugFixVersion);

    std::printf("Running on Mac OS X %d.%d.%d\n", majorVersion, minorVersion, bugFixVersion);

    return 0;
}

Один из эвристических способов определения версии OSX связан с определением версии ядра Darwin, и последующей выборке по таблице:

16.x.x  macOS 10.12.x Sierra
15.x.x  OS X  10.11.x El Capitan
14.x.x  OS X  10.10.x Yosemite
13.x.x  OS X  10.9.x  Mavericks
12.x.x  OS X  10.8.x  Mountain Lion
11.x.x  OS X  10.7.x  Lion
10.x.x  OS X  10.6.x  Snow Leopard
 9.x.x  OS X  10.5.x  Leopard
 8.x.x  OS X  10.4.x  Tiger
 7.x.x  OS X  10.3.x  Panther
 6.x.x  OS X  10.2.x  Jaguar
 5.x    OS X  10.1.x  Puma

Для определения версии ядра можно использовать многофункциональный системный вызов sysctl:

#include <errno.h>
#include <sys/sysctl.h>

void PrintMacOsXVersion()
{
    char osKernelRelease[256];
    size_t strSize = sizeof(osKernelRelease);
    int ret = sysctlbyname("kern.osrelease", osKernelRelease, &strSize, NULL, 0);
    assert(ret == 0);
    // osKernelRelease теперь хранит строку вида 12.0.0
}

Кодировка строк в API ОС

В современных UNIX-подобных системах для работы со строками используется кодировка UTF-8, которая является мультибайтовой. Это означет, что в UTF8 базовой единицей является тип “char” размером в 1 байт, но один символ Unicode представляется либо одним, либо несколькими байтами. Для правильного разделения строки UTF8 на символы следует сканировать строку последовательно из начала в конец:

  • все ASCII-символы с кодами от 0 до 127 представляются одним байтом так же, как в ASCII, поэтому любой текст в кодировке ASCII является и текстом в кодировке UTF-8, например, большинство английских текстов соответствуют требованиям UTF-8
  • для остальных символов значение первого байта находится вне диапазона ASCII

В Windows в процессе перехода на кодировку Unicode было принято иное решение: используется базовый тип “wchar_t” (размер которого на Windows равен 2 байтам, а на UNIX-платформах — 4-м), и символы кодируются в UTF-16. Вопреки частому заблуждению, кодировка UTF-16 точно так же является мультибайтовой: базовой единицей является тип “wchar_t” с размером 2 байта на Windows, но один символ Unicode представляется либо одним, либо несколькими “wchar_t”.

На UNIX-подобных ОС тип “wchar_t” и соответствующие ему типы “std::wstring”, “std::wostream”, функции “std::to_wstring”, глобальные переменные “std::wcout” работают в кодировке UCS32, в которой символы представляются четырьмя байтами.

Из-за очевидного перерасхода памяти wide-строки, основанные на wchar_t, непопулярны в системном программировании для UNIX, но крайне популярны на Windows, где wide-строки более компактны. Кроме того, именно с UTF-16 работает ядро Windows и современные вызовы WinAPI.

Современный C++ имеет средства для конвертации между “std::string” в UTF8 и “std::wstring” прямо в стандартной библиотеке:

std::string WideStringToUtf8(const std::wstring &wideString)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
    return convert.to_bytes(wideString);
}

std::wstring Utf8ToWideString(const std::string &utf8String)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
    return convert.from_bytes(utf8String);
}

Ещё несколько нюансов Wide Strings, которые следует знать:

  • литералы для Wide String записываются так: L"Wide String Value"
  • в Windows для для каждой функции и структуры WinAPI, работающей со строками, есть две версии: одна с суффиксом “A”, другая с суффисом “W”
    • версия с суффиксом A работает в текущей кодировке ANSI-кодировке, которая обынчо является однобайтовой и не может представить символы алфавита, отличного от языка системы, например, Windows с английской локалью сломает кодировку кириллических строк при использовании ANSI-версий функций
    • версия с суффиксом W работает в кодировке UTF16 и принимает указатели на “wchar_t” строки вместо указателей на “char”-строки, только такие функции обеспечивают простую и корректную работу в мультиязычной системе
    • версия функции WinAPI без суффикса обычно прсто является макросом, указывающим либо на ANSI-версию, либо на UTF16-версию в зависимости от настроек компиляции. В Visual Studio управление данным поведением возможно через свойство “Unicode” в основных настройках проекта.

Время на прочтение5 мин

Количество просмотров27K

Как же определить версию Windows, работающую в корпоративном окружении?

Вопрос кажется простым, правда?

Microsoft позволяет определить номер версии Windows различными способами:

  • Существуют значения в реестре, которым отчаянно не хватает документации.
  • Есть множество командлетов PowerShell, вызовов Windows API и т. п.
  • Также есть варианты для конечного пользователя, например, команда winver, которая вызывает всплывающее окно с версией Windows.
  • И много других способов…

Разобраться во всём этом вам поможет наш пост.

Существует множество инструментов, позволяющих определить, какая версия Windows запущена у ваших клиентов, например, SCCM и PDQ. В этом посте мы рассмотрим встроенные способы определения версии Windows.

▍ Реестр

Для определения запущенной в системе версии Windows можно использовать следующие значения реестра:

Примечание: перечисленные в таблице значения официально не задокументированы Microsoft (см. ниже).

Предупреждение

Microsoft не сообщала об изменениях в этих значениях реестра, не документировала их официальную поддержку и не гарантировала, что в будущем не появится критических изменений. Из-за этого описанными выше ключами реестра пользоваться бывает иногда неудобно, учитывая непостоянство изменений этих ключей, вносимых Microsoft в прошлом. Примеры:

  • ReleaseID не рекомендуется к использованию, начиная с версии 21H1. ReleaseID для 21H1 остаётся равным 2009.
  • Server 2012R2 не имеет ReleaseID и DisplayVersion (они пока не были добавлены в Windows)
  • Server 2016 имеет ReleaseID (1607), но не имеет DisplayVersion
  • Server 2019 имеет ReleaseID (1809), но не имеет DisplayVersion

▍ PowerShell

Ниже приведено несколько примеров того, как можно использовать PowerShell, чтобы определить версию Windows, которая работает в системе:

# При помощи класса System.Environment
[System.Environment]::OSVersion

# При помощи класса CIM Win32_OperatingSystem
Get-CimInstance Win32_OperatingSystem

# При помощи исполняемого файла systeminfo
systeminfo.exe /fo csv | ConvertFrom-Csv

# При помощи командлета Get-ComputerInfo
# ПРИМЕЧАНИЕ: начиная с 21H1 OsHardwareAbstractionLayer не рекомендуется к использованию
Get-ComputerInfo | Select WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer

▍ Windows API Call

Единственный поддерживаемый (задокументированный) систематический способ определения версии Windows — при помощи вызова Windows API класса AnalyticsInfo. Это можно сделать через PowerShell:

<#
    Класс AnalyticsInfo - задокументированный способ отслеживания версии ОС. Он возвращает
    строковое значение. Формат этой строки не задокументирован, и нельзя полагаться
    на определённое значение. Эти значения можно использовать только чтобы отличать
    одну версию ОС от другой.
    https://docs.microsoft.com/uwp/api
        /windows.system.profile.analyticsversioninfo.devicefamilyversion
    Этот API недоступен на Server Core
#>

$AnalyticsInfo = [Windows.System.Profile.AnalyticsInfo,Windows.System.Profile,ContentType=WindowsRuntime]
$VersionInfo = $AnalyticsInfo.GetMember( 'get_VersionInfo' )
$AnalyticsVersionInfo = $VersionInfo.Invoke( $Null, $Null )

# На моей тестовой машине этот код возвращает `2814751015109593`
$AnalyticsVersionInfo.DeviceFamilyVersion

<#
    Строго говоря, строку *можно* парсить, если вам любопытно, что в ней,
    хотя этого делать *нельзя*
    https://stackoverflow.com/questions/31783604/windows-10-get-devicefamilyversion
#>

$v  = [System.Int64]::Parse( $AnalyticsVersionInfo.DeviceFamilyVersion )
$v1 = ( $v -band 0xFFFF000000000000l ) -shr 48
$v2 = ( $v -band 0x0000FFFF00000000l ) -shr 32
$v3 = ( $v -band 0x00000000FFFF0000l ) -shr 16
$v4 =   $v -band 0x000000000000FFFFl

# На моей тестовой машине этот код возвращает `10.0.19043.985`
[System.Version]::Parse( "$v1.$v2.$v3.$v4" )

<#
    Не опубликовано *никакого* способа декодирования, позволяющего преобразовать
    какое-то из приведённых выше значений в удобную для отображения версию,
    например `21H1`
    Показанная ниже альтернатива доступна только в последних версиях ОС, 
    начиная с Azure Stack HCI, версии 20H2
#>

Get-ComputerInfo -Property 'osDisplayVersion'

▍ Варианты для конечного пользователя

В документации Microsoft перечислено несколько команд, которые конечные пользователи могут применять для определения запущенной версии Windows. Например, чтобы выяснить версию Windows, можно использовать команду winver или меню Параметров Windows. Эти способы предназначаются больше для конечных пользователей, чем для масштабного определения версии системы. Ниже показаны примеры:

▍ Почему это важно

После определения запущенной в системе версии Windows можно использовать эту информацию выполнения детерминированных действий: обновлений Windows, установки патчей и т. п. Например:

Можно запросить значение реестра DisplayVersion (см. раздел «Реестр» выше), чтобы определить запущенную версию Windows. Затем можно задать перечисленные ниже значения реестра, чтобы сообщить Windows, какая версия должна быть запущена в системе. При помощи трёх ключей реестра вы полностью контролируете то, до какой версии Windows ваши системы будут пытаться обновиться!

Эти значения реестра можно задавать или напрямую, или через групповую политику.

▍ Куда двигаться дальше

Хотя вам необязательно управлять версиями Windows, запущенными на компьютерах компании, ей было бы ценно знать, какие это версии Windows. По крайней мере, потому, что Microsoft регулярно прекращает поддержку разных версий Windows.

Стоит также заметить, что показанные выше примеры — это неполный список способов определения версии Windows, однако он полезен для людей, управляющих окружением Windows. Эти способы оказались полезными для меня при устранении проблем, задании политик и т. п. Надеюсь, вам они тоже пригодятся.

Кроме того, ситуация с управлением версиями Windows постоянно меняется, поэтому я напишу ещё один пост, когда Microsoft перестанет рекомендовать перечисленные здесь способы.

Дополнительные ссылки

  • Microsoft nixes update deferral settings, but gives us a TargetReleaseVersionInfo
  • Windows 10 Version History
  • Windows Server Versions.

Read version record from Windows

Demonstrate the different ways of reading the version information from Windows.

Getting the Windows version sound in first glance easier as it is. It depends on many configuration setting and also on how the application was build
which version exact number you will get.

Version read from the operating system

Read Windows Version

Table of contents

  • Version read from the operating system
  • Characteristic of the APIs
  • API descriptions
    • API GetVersion
    • API GetVersionEx
    • API Kernel32Library
    • API RegistryCurrentVersion
    • API RegistryCurrentVersionNumbers
    • API RtlGetNtVersionNumbers
    • API RtlGetVersion
    • API VersionHelper
  • Windows Compatibility Mode
  • Compatibility Manifest
  • License

Characteristic of the APIs

API Accuracy Deprecated Documented Compatibility Mode Compatibility Manifest
GetVersion yes yes User Mode dependent yes
GetVersionEx yes yes User Mode dependent yes
Kernel32Library no no no independent no
RegistryCurrentVersion no no no independent no
RegistryCurrentVersionNumbers unknown no no independent no
RtlGetNtVersionNumbers unknown no no independent no
RtlGetVersion yes no Kernel Mode dependent no
VersionHelper yes no User Mode independent yes

API descriptions

API GetVersion

Microsoft documentation: GetVersion on MSDN

#include <Windows.h>
const auto version = GetVersion();

The «GetVersion» WinAPI function returns the verion in one DWORD. The parts of the version can be extracted using the «LOWORD» and «LOWORD» macros.
The function is deprecated since Windows 8.1 and requires an Compatibility Manifest added to the application (See below). Furthermore it depends
on the Windows Compatibility Mode settings for the application which Windows Version will get returned (See section below).

API GetVersionEx

Microsoft documentation: GetVersionEx on MSDN

#include <Windows.h>
OSVERSIONINFOEX versionInformation{};
SecureZeroMemory(&versionInformation, sizeof(OSVERSIONINFOEX));
versionInformation.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&versionInformation));

The «GetVersionEx» WinAPI function returns the Windows version using an struct as out parameter. The parts of the version are stored as member variables in the struct.
The function is deprecated since Windows 8.1 and requires an Compatibility Manifest added to the application (See below). Furthermore it depends
on the Windows Compatibility Mode settings for the application which Windows Version will get returned (See section below).

API Kernel32Library

#include <Windows.h>
#pragma comment(lib, "version")
const auto bufferSize = GetFileVersionInfoSize(L"kernel32.dll", nullptr);

const auto versionInformationBuffer = std::vector<byte>(bufferSize, 0);
const auto bufferFilledSize = GetFileVersionInfo(L"kernel32.dll", 0, bufferSize, versionInformationBuffer.data());

UINT versionLength = 0;
const auto queryValueResult = VerQueryValue(versionInformationBuffer.data(), 
    L"\\", 
    reinterpret_cast<LPVOID*>(&m_Version), 
    &versionLength);

Here the Windows version is determined by getting the file version of the «kernel32.dll».This by it natural independent of any compatibility manifest or compatibility mode.
Mostly that works well for the major and minor version of Windows, but for the build version part it delivers often not an accurate results.

API RegistryCurrentVersion

#include <Windows.h>
const auto result = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
    LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", 0, KEY_READ, &currentVersionKey);

uint32_t dataLength = 256;
std::vector<byte> buffer(dataLength, L'\0');
const auto queryResult = RegQueryValueEx(currentVersionKey,
    L"CurrentVersion", nullptr, nullptr, buffer.data(), reinterpret_cast<LPDWORD>(&dataLength));

This solution is getting the Windows version directly from the Registry. There is an «CurrentVersion» value in the registry path

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

Unfortunately it seems to doesn’t get updated anymore since the Windows version 6.3.
This solution is independend of any compatibility manifest or compatibility mode.

API RegistryCurrentVersionNumbers

#include <Windows.h>
const auto result = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
    LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", 0, KEY_READ, &currentVersionKey);

uint32_t dataLength = 256;
std::vector<byte> buffer(dataLength, L'\0');
auto queryResult = RegQueryValueEx(currentVersionKey,
    L"CurrentMajorVersionNumber", nullptr, nullptr, buffer.data(), reinterpret_cast<LPDWORD>(&dataLength));
queryResult = RegQueryValueEx(currentVersionKey,
    L"CurrentMinorVersionNumber", nullptr, nullptr, buffer.data(), reinterpret_cast<LPDWORD>(&dataLength));
queryResult = RegQueryValueEx(currentVersionKey,
    L"CurrentBuildNumber", nullptr, nullptr, buffer.data(), reinterpret_cast<LPDWORD>(&dataLength));

This solution is getting the Windows version directly from the Registry as well. There is the Windows version defined in these values:

  • CurrentMajorVersionNumber
  • CurrentMinorVersionNumber
  • CurrentBuildNumber

The the following registry path:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

The version number seems to be always accurat but as these registry values are not defined in any documentation this cannot be guatenteed.
This solution is independend of any compatibility manifest or compatibility mode.

API RtlGetNtVersionNumbers

#include <Windows.h>
typedef VOID(NTAPI* RtlGetNtVersionNumbersFunc)(LPDWORD pdwMajorVersion, LPDWORD pdwMinorVersion, LPDWORD pdwBuildNumber);
const auto ntDll = GetModuleHandle(L"ntdll.dll");
const auto rtlGetNtVersionNumbers = (RtlGetNtVersionNumbersFunc)GetProcAddress(m_NtDll, "RtlGetNtVersionNumbers");

rtlGetNtVersionNumbers(reinterpret_cast<LPDWORD>(&mmajorVersion), 
    reinterpret_cast<LPDWORD>(&minorVersion), 
    reinterpret_cast<LPDWORD>(&buildNumber));

This API also seems to give an accurate version number which is independend of compatibility manifest and compatibility mode.
Unfortunately it is completely undocumented for use in kernel and user mode.

API RtlGetVersion

Microsoft documentation: RtlGetVersion on MSDN

#include <Windows.h>
const auto ntDll = GetModuleHandle(L"ntdll.dll");
const auto rtlVersion = (RtlGetVersionFunc)GetProcAddress(ntDll, "RtlGetVersion");
const auto result = m_RtlVersion(&versionInformation);

The «RtlGetVersion» is documented for kernel mode but not for user mode code. It is able to provide an accurate version number but it is dependend on
the compatibility mode (See section below) in case it is used in an user mode application.

API VersionHelper

Microsoft documentation: VersionHelper on MSDN

#include <VersionHelpers.h>
const auto isWindows10 = ::IsWindows10OrGreater();

This is an API which can help to determine the versions of Windows like Windows 10. It isn’t able to provide the exact number of the version.
In case the goal is only to differntiate Windows system that API works very accurate and is also the currently from Microsoft recommended API.
The version helper API is independend of the compatibility manifest and compatibility mode.

Windows Compatibility Mode

Microsoft documentation: Make older apps or programs compatible with Windows

Windows has the option to run applications in an lower Windows version. For example it can be defined that an application running on Windows 10 should run in a Windows 7 compatibility environment.
This causes that some of the WinAPI will return the Windows 7 version number even if the application runs on Windows 10.

CompatibilityMode.mp4

Compatibility Manifest

Microsoft documentation: Comaptibility manifest on MSDN

The compatibility manifest defines with which Windows version the application is compatible with. The manifest will let Windows provide a different set of options to the running application.
The Windows version provided by some WinAPI are also different dependent on the compatibility manifest. For example in case the compatibility manifest doesn’t
contain the Windows 10, the manifest dependend APIs will provide the Windows 8.1 version number even if the application will run on Windows 10.

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
    <assemblyIdentity version="1.0.0.0" name="VersionOfWindows.app"/>
    <!-- 
        This manifest entries are important for getting the Windows version correctly
        while using the Version Helper header
    -->
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- A list of the Windows versions that this application has been tested on
           and is designed to work with. Uncomment the appropriate elements
           and Windows will automatically select the most compatible environment. -->

            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />

            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />

            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />

            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />

            <!-- Windows 10 -->
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
        </application>
    </compatibility>
</assembly>

License

MIT License

Contents

Introduction

There are times when you need to know information about the version of Windows your program is running on. For example:

  • when reporting bugs;
  • when wishing to take different actions on different operating systems.

The Windows API provides some version information that is common to all 32 bit Windows operating systems. Delphi’s run time library (RTL) makes access to this information very straightforward. We will begin our exploration by seeing how to get the information from the Windows API. Next we will look at Delphi’s RTL support and begin the development of a static class that uses this information to provide easy access to OS information.

Having reviewed the basic information we will then examine additional information provided by the operating system on later Windows NT OSs. We will use this information to enhance Delphi’s RTL support for NT and to extend our static class.

Since the additional information we get from Windows is only available on later NT OSs, we will see how to get similar information for early NT OSs by accessing the registry. We will enhance our static class to be able to utilise the information from the registry.

The last enhancement of our static class will be made when we look at how to detect further information about Windows XP. In particular we will find out how to detect Windows Media Center and Tablet editions and how to learn if a program is running on 64 bit Windows.

Our exploration begins by discussing the basic information about the OS that is available on all Windows systems.

Common Windows API OS Information

Windows provides the GetVersionEx API function that fills a structure with information describing the version of the underlying operating system. This function works for all 32 bit versions of Windows. In Delphi the structure is defined in the Windows unit as TOSVersionInfo – see Listing 1 below.

  1type
  2  
  3  _OSVERSIONINFOA = record
  4    dwOSVersionInfoSize: DWORD;
  5    dwMajorVersion: DWORD;
  6    dwMinorVersion: DWORD;
  7    dwBuildNumber: DWORD;
  8    dwPlatformId: DWORD;
  9    szCSDVersion: array[0..127] of AnsiChar;
 10  end;
 11  
 12  _OSVERSIONINFO = _OSVERSIONINFOA;
 13  TOSVersionInfoA = _OSVERSIONINFOA;
 14  
 15  TOSVersionInfo = TOSVersionInfoA;

Listing 1

The structure’s fields are explained in Table 1:

Fields of TOSVersionInfo

Field Description
dwOSVersionInfoSize Size of the structure in bytes.
dwMajorVersion

Major version of the operating system. Possible values are:

4
Windows NT 4.0, Windows Me, Windows 98 or Windows 95.
5
Windows Server 2003, Windows XP or Windows 2000.
dwMinorVersion

Minor version of the operating system. Possible values are:

0
Windows 2000, Windows NT 4.0 or Windows 95.
1
Windows XP.
2
Windows Server 2003.
10
Windows 98.
90
Windows Me.
dwBuildNumber Build number of the operating system. On Windows 9x, the low order word contains the build number and the high order word contains the major and minor version numbers.
dwPlatformId

Operating system platform. This member can take one of the following values:

VER_PLATFORM_WIN32_NT
Windows NT OSs, i.e. Windows Server 2003, Windows XP, Windows 2000 or Windows NT.
VER_PLATFORM_WIN32_WINDOWS
Windows 9x OSs, i.e. Windows Me, Windows 98 or Windows 95.
VER_PLATFORM_WIN32s
Win32s on Windows 3.1. (This will not occur on Delphi since Delphi applications will not run on Win32s).
szCSDVersion

Buffer containing a null terminated string identifying any installed service pack. On Windows NT OSs this string is the actual service pack name while on Windows 9x it is a single letter code that is interpreted as follows:

B or C
Represents Windows 95 OSR2.
A
Represents Windows 98 SE (Second Edition).

Table 1

Listing 2 shows how to use GetVersionEx to fill a TOSVersionInfo structure with operating system information:

  1var
  2  OSVI: TOSVersionInfo;
  3begin
  4  
  5  FillChar(OSVI, SizeOf(OSVI), 0);
  6  OSVI.dwOSVersionInfoSize := SizeOf(OSVI);
  7  if not GetVersionEx(OSVI) then
  8    raise Exception.Create('Error calling GetVersionEx');
  9  
 10end;

Listing 2

Here we zero the TOSVersionInfo structure then set the dwOSVersionInfoSize field to the size of the structure – a common Windows API idiom. We then call GetVersionEx, raising an exception if the function returns false. If the function returns true then the information stored in the structure is valid.

We would normally go on to use the fields of TOSVersionInfo in our program. However, Delphi steps in to make this process easier, as can be seen below.

Delphi RTL Support

Delphi’s SysUtils unit provides some global variables that make accessing basic operating system version information very straightforward, saving the need to call GetVersionEx ourselves. The variables provide the same information as the TOSVersionInfo structure, as can be seen in Table 2.

Delphi’s Win32XXX Global Variables

Variable Field Notes
Win32Platform dwPlatformId Operating system platform. Can take one of the VER_PLATFORM_* values noted in Table 1.
Win32MajorVersion dwMajorVersion Major version of the operating system.
Win32MinorVersion dwMinorVersion Minor version of the operating system.
Win32BuildNumber dwBuildNumber Build number of the operating system. On Windows 9x systems the high order word is zeroed.
Win32CSDVersion szCSDVersion Buffer containing a null terminated string identifying any installed service pack. For more information see Table 1.

Table 2

Delphi initializes the global variables in the SysUtils unit’s initialization section. It does this by calling the InitPlatformId procedure which is shown in Listing 3 below:

  1procedure InitPlatformId;
  2var
  3  OSVersionInfo: TOSVersionInfo;
  4begin
  5  OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  6  if GetVersionEx(OSVersionInfo) then
  7    with OSVersionInfo do
  8    begin
  9      Win32Platform := dwPlatformId;
 10      Win32MajorVersion := dwMajorVersion;
 11      Win32MinorVersion := dwMinorVersion;
 12      if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
 13        Win32BuildNumber := dwBuildNumber and $FFFF
 14      else
 15        Win32BuildNumber := dwBuildNumber;
 16      Win32CSDVersion := szCSDVersion;
 17    end;
 18end;

Listing 3

Setting up TOSVersionInfo and calling GetVersionEx should be familiar from our previous discussion. Once Delphi has the version information, the global variables are simply assigned the value of the associated TOSVersionInfo field. The only exception to this is with Win32BuildNumber which, as noted in Table 2, has its high word zeroed in Windows 9x systems.

Platform

Listing 3 detects the platform by testing Win32Platform. We will learn more about detecting the platform below.

OS Version Information Class: Version 1

Having learned about the basic Windows and Delphi support for getting OS version information we are now ready to create a new static class, TOSInfo, that exposes OS version information in a user friendly way. We will use only the information provided by Delphi’s global variables in this class’s methods.

We begin by starting a new unit, UOSInfo.pas, and declaring a new, empty, class in the unit’s interface section:

  1type
  2  TOSInfo = class(TObject)
  3  public
  4  end;

Listing 4

In the following subsections we will add methods to the class.

Finding the OS Platform

The most basic distinction between Windows operating systems is that between platforms. We can find the platform by examining Delphi’s Win32Platform global variable. The value of the variable will be one of the integer constants described in the dwPlatformId entry in Table 1. These constants are defined in Delphi’s Windows unit.

Our class will have two methods to test the platform – one to detect Windows 95 (IsWin9x) and one to test for the NT platform (IsWinNT). Since Delphi programs will not run on Win32s we will not provide a method to detect that platform.

Testing platforms

Users should not assume that when IsWin9x returns false we must have an NT platform – to be sure the IsWinNT method should be tested instead and vice versa.

We can add the appropriate declarations to TOSInfo‘s interface section and implement the methods as in Listing 5:

  1class function TOSInfo.IsWin9x: Boolean;
  2begin
  3  Result := (Win32Platform = VER_PLATFORM_WIN32_WINDOWS);
  4end;
  5
  6class function TOSInfo.IsWinNT: Boolean;
  7begin
  8  Result := (Win32Platform = VER_PLATFORM_WIN32_NT);
  9end;

Listing 5

OS Product

Now we have code that can check the platform we can move on to detecting which operating system product our applications are running on. There are various combinations of major and minor version numbers that, along with the platform, determine the product. These are summarised in Table 3.

Mapping of OS version numbers to Windows products

Major
Version
Minor
Version
Windows 9x
Platform
Windows NT
Platform
4 0 Windows 95 Windows NT 4
4 10 Windows 98
4 90 Windows Me
5 0 Windows 2000
5 1 Windows XP
5 2 Windows Server 2003

Table 3

We will design our new method, Product, to return an enumerated value that indicates the underlying operating system. We define this enumeration in the interface section of UOSInfo.pas as follows:

  1type
  2  TOSProduct = (
  3    osUnknown,          
  4    osWin95,            
  5    osWin98,            
  6    osWinMe,            
  7    osWinNT4,           
  8    osWin2000,          
  9    osWinXP,            
 10    osWinServer2003     
 11  );

Listing 6

Now we can add our Product method to TOSInfo. The method’s implementation is shown in Listing 7:

  1class function TOSInfo.Product: TOSProduct;
  2begin
  3  Result := osUnknown;
  4  if IsWin9x then
  5  begin
  6    case Win32MajorVersion of
  7      4:
  8      begin
  9        case Win32MinorVersion of
 10          0: Result := osWin95;
 11          10: Result := osWin98;
 12          90: Result := osWinMe;
 13        end;
 14      end;
 15    end;
 16  end
 17  else if IsWinNT then
 18  begin
 19    case Win32MajorVersion of
 20      4:
 21      begin
 22        case Win32MinorVersion of
 23          0: Result := osWinNT4;
 24        end;
 25      end;
 26      5:
 27      begin
 28        case Win32MinorVersion of
 29          0: Result := osWin2000;
 30          1: Result := osWinXP;
 31          2: Result := osWinServer2003;
 32        end;
 33      end;
 34    end;
 35  end;
 36end;

Listing 7

This method is quite simple. It first sets an «unknown» return value in case the method fails to find the OS. We then take different branches according to the platform. Within each branch we test for valid combinations of major and minor versions, per Table 3, and return the value representing the OS.

Build Number

Once we know which product we’re dealing with we may also want to know its build number. We will therefore add a BuildNumber method to our class. Its implementation is provided in Listing 8. It simply returns the value of Delphi’s Win32BuildNumber variable. (Remember, Delphi took care of masking out the most significant word when on the Windows 9x platform, so we don’t need to do that here).

  1class function TOSInfo.BuildNumber: Integer;
  2begin
  3  Result := Win32BuildNumber;
  4end;

Listing 8

Service Pack

The final method we will implement here is ServicePack. Earlier, in Table 1, it was noted that Windows provides the full name of NT platform service packs but only supplies a code letter on the Windows 9x platform. Listing 9 shows how we use Delphi’s Win32CSDVersion variable to help us implement the method.

  1class function TOSInfo.ServicePack: string;
  2begin
  3  Result := '';
  4  if IsWin9x then
  5  begin
  6    if Win32CSDVersion <> '' then
  7    begin
  8      case Product of
  9        osWin95:
 10          if UpCase(Win32CSDVersion[1]) in ['B', 'C'] then
 11            Result := 'OSR2';
 12        osWin98:
 13          if UpCase(Win32CSDVersion[1]) = 'A' then
 14            Result := 'SE';
 15      end;
 16    end;
 17  end
 18  else if IsWinNT then
 19    Result := Win32CSDVersion;
 20end;

Listing 9

This method begins by assuming there is no service pack installed, so it sets the default result to the empty string. We then detect the platform. For Windows 9x we look for the characters ‘A’, ‘B’ or ‘C’ in Win32CSDVersion and return the required service pack name if they are found. For the NT platform we simply return the value of Win32CSDVersion since this will either be the empty string or will contain the service pack name in full.

NT 4 Complications

This is not quite the whole story – this method won’t detect Windows NT 4 Service Pack 6a. We’ll address this problem later in the article.

We have now got all the information we can by using the Windows API that is common to all versions of Windows. It’s time to move on to investigate further information that is specific to certain Windows platforms and OS versions.

Extended Windows API OS Information

So far we can detect the name of an OS product, but for the NT platform that’s not enough. Think for a moment about Windows XP – there are both the Home and Professional editions. For Windows 2000 it’s similarly complicated – we have both server (e.g. Advanced Server) and workstation (Professional) editions.

TOSVersionInfoEx

So, how do we get to this information? We make a start by examining the information provided by an extension of TOSVersionInfo named, unsurprisingly, TOSVersionInfoEx. Listing 10 shows a Pascal declaration of the structure. Note that Delphi does not declare it, so we need to include the definition in the interface section of UOSInfo.pas.

  1type
  2  TOSVersionInfoEx = packed record
  3    dwOSVersionInfoSize: DWORD;
  4    dwMajorVersion: DWORD;
  5    dwMinorVersion: DWORD;
  6    dwBuildNumber: DWORD;
  7    dwPlatformId: DWORD;
  8    szCSDVersion: array[0..127] of AnsiChar;
  9    wServicePackMajor: WORD;
 10    wServicePackMinor: WORD;
 11    wSuiteMask: WORD;
 12    wProductType: Byte;
 13    wReserved: Byte;
 14  end;

Listing 10

We can see that the first six fields of TOSVersionInfoEx are the same as TOSVersionInfo (explained in Table 1). The new structure simply adds fields to the end of the old structure. Table 4 explains the purpose of the new fields:

Additional fields of TOSVersionInfoEx

Field Description
wServicePackMajor Major version number of the latest Service Pack installed on the system. If no Service Pack has been installed, the value is zero.
wServicePackMinor Minor version number of the latest Service Pack installed on the system.
wSuiteMask

Bit mask that identifies the product suites available on the system. This member can be a combination of the numerous VER_SUITE_* values defined by Microsoft. The values we will use here are:

VER_SUITE_ENTERPRISE
Windows Server 2003 Enterprise Edition, Windows 2000 Advanced Server or Windows NT 4.0 Enterprise Edition.
VER_SUITE_DATACENTER
Windows Server 2003 Datacenter Edition or Windows 2000 Datacenter Server.
VER_SUITE_PERSONAL
Windows XP Home Edition.
VER_SUITE_BLADE
Windows Server 2003 Web Edition.
wProductType

Additional information about the operating system product. This member can take one of the following values:

VER_NT_WORKSTATION
The system is a workstation edition. It is running Windows XP Professional, Windows XP Home, Windows 2000 Professional or Windows NT 4.0 Workstation.
VER_NT_DOMAIN_CONTROLLER
The system is a domain controller.
VER_NT_SERVER
The system is a server.
wReserved Reserved for future use.

Table 4

Delphi defines neither the VER_NT_* nor VER_SUITE_* constants discussed in Table 4, so we must add the definitions to our UOSInfo unit’s interface section. Listing 11 shows the required definitions. Note that the listing declares all documented VER_SUITE_* flags, not just those described in Table 4.

  1const
  2  
  3  VER_NT_WORKSTATION = $0000001;
  4  VER_NT_DOMAIN_CONTROLLER = $0000002;
  5  VER_NT_SERVER = $0000003;
  6
  7  
  8  VER_SUITE_SMALLBUSINESS = $00000001;
  9  VER_SUITE_ENTERPRISE = $00000002;
 10  VER_SUITE_BACKOFFICE = $00000004;
 11  VER_SUITE_COMMUNICATIONS = $00000008;
 12  VER_SUITE_TERMINAL = $00000010;
 13  VER_SUITE_SMALLBUSINESS_RESTRICTED = $00000020;
 14  VER_SUITE_EMBEDDEDNT = $00000040;
 15  VER_SUITE_DATACENTER = $00000080;
 16  VER_SUITE_SINGLEUSERTS = $00000100;
 17  VER_SUITE_PERSONAL = $00000200;
 18  VER_SUITE_SERVERAPPLIANCE = $00000400;
 19  VER_SUITE_BLADE = VER_SUITE_SERVERAPPLIANCE;

Listing 11

So, we’ve got a new structure that gives us extra information to play with, but how to we get the information from Windows?

Populating TOSVersionInfoEx

TOSVersionInfoEx is only supported on some NT platforms (those after NT 4 Service Pack 5) and not at all on the Windows 9x platform. As a result we have to check that we can use it. Here’s an apparent paradox: how do we check what OS we are using before calling the code that checks the OS?

The answer is quite simple, if a little convoluted. We call GetVersionEx() passing it a TOSVersionInfoEx instead of a TOSVersionInfo structure. If that call fails we have to pass GetVersionEx() a TOSVersionInfo structure instead. In actual fact we pass TOSVersionInfoEx again with the dwOSVersionInfoSize field set to the size of a TOSVersionInfo structure. This works because the first fields of TOSVersionInfoEx are the same as TOSVersionInfo.

Listing 12 shows how this is done. On OSs that do not support TOSVersionInfoEx, only the first six fields will be completed. The IsExtended variable is true if the whole of the structure is populated and false if not.

  1var
  2  POSV: POSVersionInfo; 
  3  IsExtended: Boolean;  
  4begin
  5  
  6  
  7  FillChar(OSV, SizeOf(OSV), 0);
  8  
  9  
 10  
 11  {$TYPEDADDRESS OFF}
 12  POSVI := @OSV;
 13  {$TYPEDADDRESS ON}
 14  
 15  OSV.dwOSVersionInfoSize := SizeOf(TOSVersionInfoEx);
 16  IsExtended := GetVersionEx(POSV^);
 17  if not IsExtended then
 18  begin
 19    
 20    OSV.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
 21    if not GetVersionEx(POSV^) then
 22      
 23      raise Exception.Create('Can''t get OS info');
 24  end;
 25  
 26end;

Listing 12

Let us examine the code in detail. We first zero the TOSVersionInfoEx record then set its dwOSVersionInfoSize field to the size of the extended structure. Then we call the GetVersionEx() API function. If this function fails we reset the dwOSVersionInfoSize field to the size of a TOSVersionInfo structure and try again. This call should succeed. If it fails we raise an exception.

Now the Windows unit defines GetVersionEx() to accept a TOSVersionInfo parameter rather than one of type TOSVersionInfoEx. Because of Delphi’s strong typing we have to be a little underhand to get GetVersionEx() to accept our TOSVersionInfoEx parameter. This is accomplished by taking the address of the TOSVersionInfoEx record and casting it to a pointer to a TOSVersionInfo structure. Then we de-reference the pointer when passing to GetVersionEx(). Dirty but it works!

In the above code the IsExtended variable enables us to remember whether the structure contains extended information or not. Another method of checking is to read the structure size stored in the dwOSVersionInfoSize field – it will be SizeOf(TOSVersionInfoEx) if we have extended information and SizeOf(TOSVersionInfo) if not.

We now have some additional information at our disposal with which to describe later NT platform operating systems. Rather than using this directly, we will follow Delphi’s example and provide some global variables to store the extended OS version information. Later we will adapt our TOSInfo class to use these new global variables.

Extending Delphi’s RTL Support

Recall that Delphi’s Win32XXX global variables are set to the values of the fields of TOSVersionInfo when the program starts up. So it makes sense to provide more global variables that get their values from the additional fields of TOSVersionInfoEx. We will also provide a Boolean variable that records whether the extended information is available. The new variables are described in Table 5.

Additional Win32XXX global variables

Variable Field Notes
Win32ServicePackMajor wServicePackMajor Major version of any installed service pack or 0 if no such pack. Default value 0.
Win32ServicePackMinor wServicePackMinor Minor version of any installed service pack. Default value 0.
Win32SuiteMask wSuiteMask Bit flags that identify the product suites available on the system. Valid bit flags are defined by the VER_SUITE_XXX constants declared in Listing 11. Default value 0.
Win32ProductType wProductType Additional information about the operating system. Possible values are given by the VER_NT_XXX constants declared in Listing 11. Default value 0.
Win32HaveExInfo – N/a – Flag true if we have extended operation system version information and false if not. When this flag is false the variables above have no meaning and are zeroed.

Table 5

We will declare the new global variables in the interface section of UOSInfo as shown in Listing 13.

  1var
  2  Win32HaveExInfo: Boolean = False;
  3  Win32ServicePackMajor: Integer = 0;
  4  Win32ServicePackMinor: Integer = 0;
  5  Win32SuiteMask: Integer = 0;
  6  Win32ProductType: Integer = 0;

Listing 13

All that remains to do is to try to get the extended information at start up and to store the required values in the global variables. Using the SysUtils InitPlatformId procedure as an example we will add a routine named InitPlatfornIdEx to UOSInfo‘s implementation section and call it in the initialization section. Listing 14 gives the required code.

  1procedure InitPlatformIdEx;
  2var
  3  OSVI: TOSVersionInfoEx;
  4  POSVI: POSVersionInfo;
  5begin
  6  FillChar(OSVI, SizeOf(OSVI), 0);
  7  {$TYPEDADDRESS OFF}
  8  POSVI := @OSVI;
  9  {$TYPEDADDRESS ON}
 10  OSVI.dwOSVersionInfoSize := SizeOf(TOSVersionInfoEx);
 11  Win32HaveExInfo := GetVersionEx(POSVI^);
 12  if Win32HaveExInfo then
 13  begin
 14    Win32ServicePackMajor := OSVI.wServicePackMajor;
 15    Win32ServicePackMinor := OSVI.wServicePackMinor;
 16    Win32SuiteMask := OSVI.wSuiteMask;
 17    Win32ProductType := OSVI.wProductType;
 18  end;
 19end;
 20
 21
 22
 23initialization
 24
 25InitPlatformIdEx;
 26
 27end.

Listing 14

This routine works in a similar same way to that presented in Listing 12. The main difference is that we don’t try to call GetVersionEx with TOSVersionInfo if the call with TOSVersionInfoEx fails. This is not necessary since Delphi has already recorded the information that TOSVersionInfo provides. We simply use Win32HaveExInfo to record whether GetVersionEx succeeds and set the remaining global variables if so.

OS Version Information Class: Version 2

Now that we’ve explored how to get the additional OS information from the Windows API we are ready to extend our OS version information class accordingly.

Service Pack Versions

We will begin by adding two new parameterless class methods – Win32ServicePackMajor and Win32ServicePackMinor – and implementing them as shown in Listing 15. The methods return the major and minor service pack versions for supported Windows NT operating systems or 0 if the OS is not supported or has no service pack applied. As can be seen in the listing, the methods simply return the value of the corresponding global variable that we defined in the previous section.

  1class function TOSInfo.ServicePackMajor: Integer;
  2begin
  3  Result := Win32ServicePackMajor;
  4end;
  5
  6class function TOSInfo.ServicePackMinor: Integer;
  7begin
  8  Result := Win32ServicePackMinor;
  9end;

Listing 15

These methods do not work for NT4 service pack 5 and earlier – the ServicePack method should be checked in these cases.

Product Type

Now we turn our attention to the information stored in the Win32ProductType variable. We have the following possibilities:

  • The variable is not valid, either because we have an NT platform OS that doesn’t support the extended OS info, or because we have a non-NT platform.
  • We have a supported NT platform in which case the variable informs us whether we have a workstation, server or domain controller system.

We will define a class method to distinguish these possibilities. The method will return an enumerated value of type TOSProductType that we will define in our unit’s interface section as:

  1type
  2  TOSProductType = (
  3    ptNA,                 
  4    ptUnknown,            
  5    ptNTWorkstation,      
  6    ptNTServer,           
  7    ptNTDomainController  
  8  );

Listing 16

Listing 17 shows the implementation of the new method, which should have its prototype added to the class declaration.

  1class function TOSInfo.ProductType: TOSProductType;
  2begin
  3  if IsWinNT then
  4  begin
  5    case Win32ProductType of
  6      VER_NT_WORKSTATION: Result := ptNTWorkstation;
  7      VER_NT_SERVER: Result := ptNTServer;
  8      VER_NT_DOMAIN_CONTROLLER: Result := ptNTDomainController;
  9      else Result := ptUnknown;
 10    end;
 11  end
 12  else
 13    Result := ptNA;
 14end;

Listing 17

We first check to see if we have an NT platform OS and return ptNA if not. If we have an NT platform OS then we check the value of the Win32ProductType global against its possible values and return the associated value from TOSProductType if a match is found. Where the value is unrecognised we return ptUnknown. You may have noticed that we don’t use Win32HaveExInfo to check whether extended OS information is supported. This is because Win32ProductType is set to zero in this event, causing all the case clauses to fail and ptUnknown to be returned.

Checking for Server OSs

We may also find it useful to be able to quickly check whether the operating system is a server system. To do this we add a new class method named IsServer to TOSInfo. Listing 18 defines the method.

  1class function TOSInfo.IsServer: Boolean;
  2begin
  3  Result := ProductType in [ptNTServer, ptNTDomainController];
  4end;

Listing 18

The method is defined in terms of the ProductType method – it simply checks if ProductType returns a value that represents an NT server OS.

Warning

IsServer will always return false if we have an NT4 server that has service pack 5 or lower. The solution of this problem will be discussed later in the article.

Finding the OS Edition

As we have already noted, each NT operating system version is sub-divided into various «editions». We will now extend TOSInfo to be able to detect and describe these editions. We do this by defining a new class method – Edition – that returns a string that describes the edition. The method will return the empty string if the edition is not known or if we are not using the NT platform. Listing 19 shows the method’s implementation.

  1class function TOSInfo.Edition: string;
  2begin
  3  Result := '';
  4  if IsWinNT then
  5  begin
  6    if Win32HaveExInfo then
  7    begin
  8      
  9      if IsServer then
 10      begin
 11        
 12        case Product of
 13          osWinNT4:
 14          begin
 15            if CheckSuite(VER_SUITE_ENTERPRISE) then
 16              Result := 'Server 4.0, Enterprise Edition'
 17            else
 18              Result := 'Server 4.0';
 19          end;
 20          osWin2000:
 21          begin
 22            if CheckSuite(VER_SUITE_DATACENTER) then
 23              Result := 'Datacenter Server'
 24            else if CheckSuite(VER_SUITE_ENTERPRISE) then
 25              Result := 'Advanced Server'
 26            else
 27              Result := 'Standard Edition';
 28          end;
 29          osWinServer2003:
 30          begin
 31            if CheckSuite(VER_SUITE_DATACENTER) then
 32              Result := 'DataCenter Edition'
 33            else if CheckSuite(VER_SUITE_ENTERPRISE) then
 34              Result := 'Enterprise Edition'
 35            else if CheckSuite(VER_SUITE_BLADE) then
 36              Result := 'Web Edition'
 37            else
 38              Result := 'Standard Edition';
 39          end;
 40        end;
 41      end
 42      else
 43      begin
 44        
 45        case Product of
 46          osWinNT4:
 47            Result := 'Workstation 4.0';
 48          osWin2000:
 49            Result := 'Professional';
 50          osWinXP:
 51          begin
 52            if CheckSuite(VER_SUITE_PERSONAL) then
 53              Result := 'Home Edition'
 54            else
 55              Result := 'Professional';
 56          end;
 57        end;
 58      end;
 59    end;
 60  end;
 61end;

Listing 19

We first set the result to the empty string in case we fail to find any information about the edition. We then check that we have an NT operating system – we are done if not. Next we check that we have extended OS information available by examining Win32HaveExInfo. Again we have nothing more to do if this variable is false.

The rest of the method depends on whether we have a server OS or a workstation version. We proceed similarly in either case. First we determine the OS product. For some workstations this is all the information we need to determine the edition (for example the only Windows 2000 workstation is the Professional Edition). In other cases we then need to check the operating system suite bitmask (stored in Win32SuiteMask) to see which VER_SUITE_* constants it contains. This bitmask gives us the appropriate edition.

Once again, this method will not work for NT4 service pack 5 and earlier. An empty string will be returned by Edition in this case. We will fix this problem later in the article where we will modify the method to work when Win32HaveExInfo is false.

To help keep Edition‘s code tidy we define a private helper method – CheckSuite – to examine the Win32SuiteMask bitmask. We pass CheckSuite the required bit flag and it returns true if Win32SuiteMask contains the flag. CheckSuite is defined in Listing 20.

  1class function TOSInfo.CheckSuite(const Suite: Integer): Boolean;
  2begin
  3  Result := Win32SuiteMask and Suite <> 0;
  4end;

Listing 20

This completes our discussion of extended OS information available from Windows. In the next section we will look at getting more information about early NT 4 operating systems from the registry.

Getting Information About NT 4 from the Registry

It has been noted above that the extended operation system information provided by the Windows API is not available in versions of NT 4 prior to Service Pack 6. In this section we will look at how to overcome this problem by reading the required information from the registry.

There are two pieces of relevant information we can get from the registry:

  • The NT product type.
  • Whether NT 4 Service Pack 6 or 6a is installed.

Product Type

Let us first look at how to get the product type. Windows stores information about the edition of the NT4 OS in the registry under this key:

HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions

The key’s ProductName value stores a code that indicates the product type. Values and their interpretation are shown in Table 6.

NT4 Registry Entries

Code Edition
WINNT Workstation
LANMANNT Server
SERVERNT Advanced Server

Table 6

Service Pack 6a

We detect whether NT Service Pack 6a is installed by checking that Win32CSDVersion contains the string ‘Service Pack 6’ and then checking for the existence of the registry key:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Hotfix\Q246009

If the key exists we have Service Pack 6a. Arcane, isn’t it?

OS Version Information Class: Version 3

Having seen what information we can find from the registry, and how to get it, we can update our operating system information class to work correctly with earlier versions of Windows NT 4.

First we will define a couple of new private static methods to get the required information from the registry and then we will update various public methods.

Registry Access Methods

We will create two new private methods that access the registry: GetNT4ProductType to read the product type and IsNT4SP6a to check for NT 4 Service Pack 6a.

GetNT4ProductType will read the product type from the registry and return the equivalent TOSProductType code. Before doing this we will need to define a new TOSProductType value – ptNTAdvancedServer to represent NT 4 Advanced Server. Listing 21 shows the revised declaration of TOSProductType.

  1type
  2  TOSProductType = (
  3    ptNA,                 
  4    ptUnknown,            
  5    ptNTWorkstation,      
  6    ptNTServer,           
  7    ptNTDomainController, 
  8    ptNTAdvancedServer    
  9  );

Listing 21

Having extended TOSProductType we can now implement GetNT4ProductType as per Listing 22.

  1class function TOSInfo.GetNT4ProductType: TOSProductType;
  2var
  3  Reg: TRegistry;
  4  ProductType: string;
  5begin
  6  Result := ptUnknown;
  7  Reg := TRegistry.Create;
  8  try
  9    Reg.RootKey := HKEY_LOCAL_MACHINE;
 10    if Reg.OpenKeyReadOnly(
 11      'SYSTEM\CurrentControlSet\Control\ProductOptions'
 12    ) then
 13    begin
 14      ProductType := Reg.ReadString('ProductType');
 15      if SameText(ProductType, 'WINNT') then
 16        Result := ptNTWorkstation
 17      else if SameText(ProductType, 'LANMANNT') then
 18        Result := ptNTServer
 19      else if SameText(ProductType, 'SERVERNT') then
 20        Result := ptNTAdvancedServer;
 21      Reg.CloseKey;
 22    end;
 23  finally
 24    Reg.Free;
 25  end;
 26end;

Listing 22

We first set a default result of ptUnknown in case we can’t read an expected value from the registry. Then we create a TRegistry object via which we access the registry. Next we open the key described in the previous section and read the ProductType value. Finally the product type code is mapped onto an equivalent TOSProductType value. Remember that Table 6 lists the possible values of the ProductType registry entry.

Now we can look at the definition of IsNT4SP6a. See Listing 23.

  1class function TOSInfo.IsNT4SP6a: Boolean;
  2var
  3  Reg: TRegistry;
  4begin
  5  if (Product = osWinNT4)
  6    and SameText(Win32CSDVersion, 'Service Pack 6') then
  7  begin
  8    
  9    
 10    Reg := TRegistry.Create;
 11    try
 12      Reg.RootKey := HKEY_LOCAL_MACHINE;
 13      Result := Reg.KeyExists(
 14        'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Hotfix\Q246009'
 15      );
 16    finally
 17      Reg.Free;
 18    end;
 19  end
 20  else
 21    
 22    Result := False;
 23end;

Listing 23

First we check whether we have Windows NT 4 and that Service Pack 6 is being reported by Delphi’s Win32CSDVersion global variable. Then we create a TRegistry object and use it to check for the presence of the registry key that indicates Service Pack 6a is present. We return true if the key is present and false otherwise.

Now we have the registry access methods we need it’s time to move on to modify some of TOSInfo’s public methods to make use of the registry information.

Updated Public Methods

We need to update four public methods to take account of the new information we have about NT 4. They are:

  • ProductType – to get the NT 4 product types from the registry.
  • IsServer – to take account of the new ptNTAdvancedServer product type.
  • Edition – to describe the NT 4 editions that we learn about from the registry.
  • ServicePack – to include detection of NT 4 service pack 6a.

We begin with ProductType. Listing 24 shows the revised method in full.

  1class function TOSInfo.ProductType: TOSProductType;
  2begin
  3  if IsWinNT then
  4  begin
  5    if Win32HaveExInfo then
  6    begin
  7      case Win32ProductType of
  8        VER_NT_WORKSTATION: Result := ptNTWorkstation;
  9        VER_NT_SERVER: Result := ptNTServer;
 10        VER_NT_DOMAIN_CONTROLLER: Result := ptNTDomainController;
 11        else Result := ptUnknown;
 12      end;
 13    end
 14    else
 15      Result := GetNT4ProductType;
 16  end
 17  else
 18    Result := ptNA;
 19end;

Listing 24

We now check if Win32HaveExInfo is true and proceed as before if so. When extended version information is not present we know we must look in the registry for the product type, which we do by calling the new GetNT4ProductType private method.

Next up is IsServer. As Listing 25 shows, all we need to do is add ptNTAdvancedServer to the list of server product types that are tested.

  1class function TOSInfo.IsServer: Boolean;
  2begin
  3  Result := ProductType in
  4    [ptNTServer, ptNTDomainController, ptNTAdvancedServer];
  5end;

Listing 25

The changes we must make to the Edition method are show in Listing 26. For the sake of brevity the listing shows only the required changes and the surrounding context.

  1class function TOSInfo.Edition: string;
  2begin
  3  Result := '';
  4  if IsWinNT then
  5  begin
  6    if Win32HaveExInfo then
  7    begin
  8      
  9    end
 10    else
 11    begin
 12      
 13      case GetNT4ProductType of
 14        ptNTWorkstation: Result := 'WorkStation';
 15        ptNTServer: Result := 'Server';
 16        ptNTAdvancedServer: Result := 'Advanced Server'
 17      end;
 18      if Result <> '' then
 19        Result := Result + Format(
 20          ' %d.%d', [Win32MajorVersion, Win32MinorVersion]
 21        );
 22    end;
 23  end;
 24end;

Listing 26

Taking our lead from Listing 24, we add an else clause that executes when Win32HaveExInfo returns false on an NT platform OS. The code in the else clause gets the product type from the registry by calling GetNT4ProductType and then returns a description of the returned product type. We then append the major and minor versions of the OS to the description.

The last method to be modified is ServicePack. Listing 27 shows the changes. Only the code relating to the NT platform is changed, so the existing Windows 9x code is omitted from the listing.

  1class function TOSInfo.ServicePack: string;
  2begin
  3  Result := '';
  4  if IsWin9x then
  5  begin
  6    
  7  end
  8  else if IsWinNT then
  9  begin
 10    if IsNT4SP6a then
 11      Result := 'Service Pack 6a'
 12    else
 13      Result := Win32CSDVersion;
 14  end;
 15end;

Listing 27

All that needs to be done after testing for the Windows NT platform is to call IsNT4SP6a to check for NT 4’s service pack 6a. If SP6a is found, we return ‘Service Pack 6a’, otherwise the value of the Win32CSDVersion variable is returned as before.

We have now completed our review of NT 4 version information that can be read from the registry. In the next section we will find out more about different versions of Windows XP.

More XP Related OS Information

We now turn our attention to providing a little more information about Windows XP editions. XP comes in several flavours, some of which we have already dealt with and others that we haven’t examined yet. In particular we will see how to detect:

  • XP Media Centre Edition
  • XP Tablet Edition
  • 64 bit versions of XP

XP Media Center and Tablet Editions

Surprisingly, we find out about these systems using the GetSystemMetrics() API call by passing the SM_MEDIACENTER or SM_TABLETPC flags to the function. A non-zero return from GetSystemMetrics() indicates the specified edition is present.

64 Bit Versions of XP

With the advent of 64 bit versions of Windows we may need to detect if our application is running on such an operating system.

Any application compiled with current versions of Delphi (up to Delphi 2006 at the time of writing) is 32 bit. 64 bit Windows runs 32 bit applications in the WOW64 subsystem. So, we need a way to find out if our application is running in this subsystem. Windows XP provides a function to 32 bit applications named IsWow64Process that provides this information.

IsWow64Process is not available to OSs earlier than Windows XP. Consequently we must link to this function dynamically. Static linking would cause our application to fail on all OSs but XP.

OS Version Information Class: Version 4

Having explored how we can detect XP Media Center and Tablet editions and find if we are running on 64 bit Windows, we can now update our TOSInfo static class to take account of these XP specific issues.

Supporting XP Media Center and Tablet Editions

We will add two new public static methods to TOSInfo that detect XP Media Center and Tablet editions – IsMediaCenter and IsTablet. We will also modify the Edition method to detect these editions.

Listing 28 shows the implementation of the new IsMediaCenter and IsTablet methods. They simply call GetSystemMetrics with the appropriate flag.

  1class function TOSInfo.IsMediaCenter: Boolean;
  2begin
  3  Result := GetSystemMetrics(SM_MEDIACENTER) <> 0;
  4end;
  5
  6class function TOSInfo.IsTablet: Boolean;
  7begin
  8  Result := GetSystemMetrics(SM_TABLETPC) <> 0;
  9end;

Listing 28

Unfortunately Delphi does not define SM_MEDIACENTER and SM_TABLETPC, so we will need to define these in our code. We may as well make these publicly available, so add the constants to the interface section of UOSInfo.pas as shown in Listing 29.

  1const
  2  SM_MEDIACENTER = 87;  
  3  SM_TABLETPC = 86;     

Listing 29

Armed with the two new IsMediaCenter and IsTablet methods we can now modify TOSInfo.Edition to detect XP Media Center and Tablet editions. Listing 30 shows the changes that need to be made to the method.

  1class function TOSInfo.Edition: string;
  2begin
  3  Result := '';
  4  if IsWinNT then
  5  begin
  6    if Win32HaveExInfo then
  7    begin
  8      
  9      if IsServer then
 10      begin
 11        
 12      end
 13      else
 14      begin
 15        
 16        case Product of
 17          
 18          osWinXP:
 19          begin
 20            if IsMediaCenter then
 21              Result := 'Media Center Edition'
 22            else if IsTablet then
 23              Result := 'Tablet PC Edition'
 24            else if CheckSuite(VER_SUITE_PERSONAL) then
 25              Result := 'Home Edition'
 26            else
 27              Result := 'Professional';
 28          end;
 29        end;
 30      end;
 31    end
 32    else
 33    begin
 34      
 35    end;
 36  end;
 37end;

Listing 30

Here we simply update the Windows XP workstation section of the code to test for Media Center and Tablet editions. If we find neither we check for XP Home and Professional as before.

Detecting 64 Bit Windows

Our approach will be to create a new method of our TOSInfo static class – IsWOW64 – that is a wrapper round the IsWow64Process API function we discussed above. The new method, shown in Listing 31, returns true when running on 64 bit windows and false otherwise.

  1class function TOSInfo.IsWOW64: Boolean;
  2type
  3  TIsWow64Process = function( 
  4    Handle: THandle;
  5    var Res: BOOL
  6  ): BOOL; stdcall;
  7var
  8  IsWow64Result: BOOL;              
  9  IsWow64Process: TIsWow64Process;  
 10begin
 11  
 12  IsWow64Process := GetProcAddress(
 13    GetModuleHandle('kernel32'), 'IsWow64Process'
 14  );
 15  if Assigned(IsWow64Process) then
 16  begin
 17    
 18    if not IsWow64Process(GetCurrentProcess, IsWow64Result) then
 19      raise Exception.Create('Bad process handle');
 20    
 21    Result := IsWow64Result;
 22  end
 23  else
 24    
 25    Result := False;
 26end;

Listing 31

TIsWow64Process prototypes the IsWow64Process API function. The return value of this function indicates whether the it succeeded or failed – it does not indicate whether the WOW64 subsystem is present. In fact the function’s Res parameter is used for this purpose – it is set true if running on WOW64 and false otherwise.

Our method tries to load IsWow64Process from kernel32.dll and stores a pointer to the function in the IsWow64Process local variable. If we succeed in loading the function we call it, passing the current process handle and storing the function’s output value in IsWow64Result. In the unlikely event that the function fails (i.e. returns false) we raise an exception. Otherwise the value of IsWow64Result is returned.

Should we fail to load IsWow64Process we know that we are running on an OS prior to Windows XP that does not have a 64 bit support. Therefore we return false.

Our examination of how to get operating system version information is now complete.

Demo code

A demo program to accompany this article can be found in the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-23 sub-directory. Alternatively download a zip file containing all the demos by going to the repository’s landing page and clicking the Clone or download button and selecting Download ZIP.

See the demo’s README.md file for details.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an «AS IS» basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo’s LICENSE.md file for licensing details.

System Information Unit

You may also wish to look at my System Information Unit that contains similar code to that presented in this article.

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website’s Issues page on GitHub. Make sure you mention that the issue relates to «article #23».
  2. For bugs in the demo code see the article-demo project’s README.md file for details of how to report them.

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

This article explains how to determine Windows version and edition. The method is very simple: The Windows API provides two functions, GetVersion/GetVersionEx, that fill the structure OSVERSIONINFO/OSVERSIONINFOEX with information such as major and minor version, product type, suite mask, platform, and so forth. This article, however, and the code provided here, does not list all the operating systems ever released by Microsoft, only the most usual ones.

The GetVersionEx() function from Platform SDK obtains extended information about the version of the operating system that is currently running. It uses an OSVERSIONINFO or OSVERSIONINFOEX (for Windows NT 4.0 SP6 and later) structure to retrieve the operating system version information.

The information in OSVERSIONINFO includes major and minor version numbers, a build number, a platform identifier, and descriptive text about the operating system.

The information in OSVERSIONINFOEX includes major and minor version numbers, a build number, a platform identifier, and information about product suites and the latest Service Pack installed on the system.

Note: GetVersionEx only returns correct information if the process does not run under a compatibility layer.

OS Versions

Before trying to retrieve the system version, I will explain how one can differentiate among different versions and editions.

The Win32 platform groups the Win95, Win98, and WinMe operating systems.

All of them have the Major Version equal to 4, and the difference is made by the Minor Version: 0 for Win95, 10 for Win98, and 90 for WinMe. Win95 also comes in an OSR2 release, and you must use the CSD Version to determine the actual edition. If it is ‘C’ or ‘B’, it is a OSR2 edition. For Win98, if the CSD Version is ‘A’, Second Edition is present.

The Windows network platform gathers WinNT, Win2000, WinXP, Win Server 2003, Win Vista, and Win Server 2008.

The first difference is made by the major version:

  • 3 is for Windows NT 3.5
  • 4 is for the Windows NT 4
  • 5 is for Windows 2000, Windows XP, Windows Server 2003 and Windows Home Server
  • 6 is for Windows Vista, Windows Server 2008 and Windows 7

The minor version is used, for instance, to differentiate among Windows 2000 (0), Windows XP(1), Windows Home Server (2) and Windows Server 2003 (2). Both Windows Vista and Windows Server 2008 have the same minor version, 0. Windows 7 and Windows Server 2008 R2 have the minor version 1.

The product type groups the operating systems in servers (3), workstations (2), and domain controllers (1).

Another flag that differentiate between editions is the suite mask. It can indicate an enterprise edition (0x0002), data center edition (0x0080), a personal/home edition (0x0200), or a web edition (0x0400).

A better, visual, grouping of the operating systems is shown in the following images.

For Windows NT:

For Windows 2000, XP, Server 2003 and Home Server:

For Windows Vista, Server 2008 and Windows 7:

In addition to that, on Windows Vista and Windows Server 2008 a new function is available in kernel32.dll, called GetProductInfo(). It retrieves the product type for the operating system on the local computer, and maps the type to the product types supported by the specified operating system. You have to supply the major and minor version of the OS and the major and minor version of the service pack; then it returns a value that can indicate an unknown product, and unlicensed product or a specific edition such as business, ultimate, enterprise, enterprise server, and so on. For more about the function and possible value, see MSDN.

Determining Windows Vista, Windows Server 2008 and Windows 7 edition based on the product type returned by GetProductInfo():

.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Просмотр файлов через командную строку windows
  • Как выключить брандмауэр защитник windows
  • Microsoft windows customer experience improvement program uploader
  • Spider solitaire windows 7 download
  • Windows loader by daz status notification