Geoff Chappell, Software Analyst
If only for now, this article is specific to 32-bit Windows (i386 or x86).
Windows comes in an ever-increasing variety of editions. A long-standing identifier is the product suite, which is most familiar to Windows programmers as the wSuiteMask member of the OSVERSIONINFOEX structure that is filled by the user-mode function GetVersionEx. Kernel-mode programmers have easy access to the product suite through the very similar function RtlGetVersion. Both functions pick their 16-bit product suite from the 32-bit SuiteMask member at offset 0x02D0 of the KUSER_SHARED_DATA structure that the kernel makes addressable at 0xFFDF0000 in kernel mode and 0x7FFE0000 in user mode. The kernel’s own record of the product suite can be read more obscurely through ExVerifySuite.
The product suite is a combination of bit flags. It is first determined from the following registry value:
Key: | HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ProductOptions |
Value: | ProductSuite |
Type: | REG_MULTI_SZ |
From as far back as Windows 2000, the kernel allows 256 bytes for the data and parses it only if it has the expected type. The builds of Windows NT 4.0 that query this value get however much memory is needed but do not fuss over the data type. Each recognised string in the data corresponds to one bit in the suite mask:
String From Registry Data | Suite Mask | Applicable Versions |
---|---|---|
Small Business | 0x0001 (VER_SUITE_SMALLBUSINESS) | 4.0 from Windows NT 4.0 SP3, and higher |
Enterprise | 0x0002 (VER_SUITE_ENTERPRISE) | 4.0 from Windows NT 4.0 SP3, and higher |
BackOffice | 0x0004 (VER_SUITE_BACKOFFICE) | 4.0 from Windows NT 4.0 SP3, and higher |
CommunicationServer | 0x0008 (VER_SUITE_COMMUNICATIONS) | 4.0 from Windows NT 4.0 SP3, and higher |
Terminal Server | 0x0010 (VER_SUITE_TERMINAL) | 5.0 and higher |
Small Business(Restricted) | 0x0020 (VER_SUITE_SMALLBUSINESS_RESTRICTED) | 4.0 from Windows NT 4.0 SP4, and higher |
EmbeddedNT | 0x0040 (VER_SUITE_EMBEDDEDNT) | 4.0 from Windows NT 4.0 SP4, and higher |
DataCenter | 0x0080 (VER_SUITE_DATACENTER) | 5.0 and higher |
Personal | 0x0200 (VER_SUITE_PERSONAL) | 5.0 from Windows 2000 SP1, and higher |
Blade | 0x0400 (VER_SUITE_BLADE) | 5.0 from Windows 2000 SP1, and higher |
Embedded(Restricted) | 0x0800 (VER_SUITE_EMBEDDED_RESTRICTED) | 5.2 and higher |
Security Appliance | 0x1000 (VER_SUITE_SECURITY_APPLIANCE) | 5.2 and higher |
Storage Server | 0x2000 (VER_SUITE_STORAGE_SERVER) | 5.2 from Windows Server 2003 SP1, and higher |
Compute Server | 0x4000 (VER_SUITE_COMPUTE_SERVER) | 5.2 from Windows Server 2003 SP1, and higher |
WH Server | 0x8000 (VER_SUITE_WH_SERVER) | 5.2 from Windows Server 2003 SP2, 6.0 from Windows Vista SP1, and higher |
PhoneNT | 0x00010000 | 6.2 and higher |
Strings that are not in the list are ignored. Note that VER_SUITE_SINGLEUSERTS (0x0100) is not learnt from the ProductSuite value.
Although Windows NT 4.0 SP3 has the first kernel that is known to read the ProductSuite value, its KERNEL32 function GetVersionEx supports only the OSVERSIONINFO structure not the OSVERSIONINFOEX that is needed for learning the suite mask. Support for learning the suite mask through GetVersionEx begins with Windows NT 4.0 SP4, as does the storage in the KUSER_SHARED_DATA.
Unsurprisingly, the possible product suites did eventually outgrow the 16-bit allowance in the OSVERSIONINFOEX structure. Less unsurprising is that the continuation to 32 bits seems to be undocumented. The only known way to retrieve it in user mode, short of looking directly in the KUSER_SHARED_DATA, is through the NTDLL function RtlGetVersion, and only then by supplying a further-extended OSVERSIONINFOEX to fill in. Give the size as 0x0124 (for the Unicode form), and the 32-bit suite mask is filled in at offset 0x011C.
The kernel interprets the ProductSuite value during phase 0 of initialisation. Starting with Windows Vista, however, the suite mask that is obtained this way is not entirely believed. If the license value Kernel-ProductInfo is present and has 4 bytes of REG_DWORD data, then the suite mask is reappraised (still in phase 0). The effective outcome is that the following are all cleared from the ProductSuite value:
and are instead selected from the Kernel-ProductInfo:
Kernel-ProductInfo | Suite | Applicable Versions |
---|---|---|
0x01 (PRODUCT_ULTIMATE) |
none | 6.0 and higher |
0x02 (PRODUCT_HOME_BASIC) | VER_SUITE_PERSONAL | 6.0 and higher |
0x03 (PRODUCT_HOME_PREMIUM) | VER_SUITE_PERSONAL | 6.0 and higher |
0x04 (PRODUCT_ENTERPRISE) | none | 6.0 and higher |
0x05 (PRODUCT_HOME_BASIC_N) | VER_SUITE_PERSONAL | 6.0 and higher |
0x06 (PRODUCT_BUSINESS) | none | 6.0 and higher |
0x07 (PRODUCT_STANDARD_SERVER) | none | 6.0 and higher |
0x08 (PRODUCT_DATACENTER_SERVER) | VER_SUITE_DATACENTER | 6.0 and higher |
0x09 (PRODUCT_SMALLBUSINESS_SERVER) | VER_SUITE_SMALLBUSINESS | 6.0 before Windows Vista SP1 |
VER_SUITE_SMALLBUSINESS VER_SUITE_SMALLBUSINESS_RESTRICTED |
6.0 from Windows Vista SP1, and higher | |
0x0A (PRODUCT_ENTERPRISE_SERVER) | VER_SUITE_ENTERPRISE | 6.0 and higher |
0x0B (PRODUCT_STARTER) | VER_SUITE_PERSONAL | 6.0 and higher |
0x0C (PRODUCT_DATACENTER_SERVER_CORE) | VER_SUITE_DATACENTER | 6.0 and higher |
0x0D (PRODUCT_STANDARD_SERVER_CORE) | none | 6.0 and higher |
0x0E (PRODUCT_ENTERPRISE_SERVER_CORE) | VER_SUITE_ENTERPRISE | 6.0 and higher |
0x0F (PRODUCT_ENTERPRISE_SERVER_IA64) | VER_SUITE_ENTERPRISE | 6.0 and higher |
0x10 (PRODUCT_BUSINESS_N) | none | 6.0 and higher |
0x11 (PRODUCT_WEB_SERVER) | VER_SUITE_BLADE | 6.0 and higher |
0x12 (PRODUCT_CLUSTER_SERVER) | none | 6.0 and higher |
0x13 (PRODUCT_HOME_SERVER) | none | 6.0 before Windows Vista SP1 |
VER_SUITE_WH_SERVER | 6.0 from Windows Vista SP1, and higher | |
0x14 (PRODUCT_STORAGE_EXPRESS_SERVER) | VER_SUITE_STORAGE_SERVER | 6.0 and higher |
0x15 (PRODUCT_STORAGE_STANDARD_SERVER) | VER_SUITE_STORAGE_SERVER | 6.0 and higher |
0x16 (PRODUCT_STORAGE_WORKGROUP_SERVER) | VER_SUITE_STORAGE_SERVER | 6.0 and higher |
0x17 (PRODUCT_STORAGE_ENTERPRISE_SERVER) | VER_SUITE_STORAGE_SERVER | 6.0 and higher |
0x18 (PRODUCT_SERVER_FOR_SMALLBUSINESS) | none | 6.0 before Windows Vista SP1 |
VER_SUITE_SMALLBUSINESS VER_SUITE_SMALLBUSINESS_RESTRICTED |
6.0 from Windows Vista SP1, and higher | |
0x19 (PRODUCT_SMALLBUSINESS_SERVER_PREMIUM) | VER_SUITE_SMALLBUSINESS | 6.0 before Windows Vista SP1 |
VER_SUITE_SMALLBUSINESS VER_SUITE_SMALLBUSINESS_RESTRICTED |
6.0 from Windows Vista SP1, and higher | |
0x1A (PRODUCT_HOME_PREMIUM_N) | VER_SUITE_PERSONAL | 6.0 from Windows Vista SP1, and higher |
0x1B (PRODUCT_ENTERPRISE_N) | none | 6.0 from Windows Vista SP1, and higher |
0x1C (PRODUCT_ULTIMATE_N) | none | 6.0 from Windows Vista SP1, and higher |
0x1D (PRODUCT_WEB_SERVER_CORE) | VER_SUITE_BLADE | 6.0 from Windows Vista SP1, and higher |
0x1E (PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT) | none | 6.0 from Windows Vista SP1, and higher |
0x1F (PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY) | none | 6.0 from Windows Vista SP1, and higher |
0x20 (PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING) | none | 6.0 from Windows Vista SP1, and higher |
0x21 (PRODUCT_SERVER_FOUNDATION) | VER_SUITE_SMALLBUSINESS VER_SUITE_SMALLBUSINESS_RESTRICTED |
6.0 from Windows Vista SP1 |
none | 6.0 from Windows Vista SP2, and higher | |
0x22 (PRODUCT_HOME_PREMIUM_SERVER) | VER_SUITE_WH_SERVER | 6.0 from Windows Vista SP1, and higher |
0x23 (PRODUCT_SERVER_FOR_SMALLBUSINESS_V) | VER_SUITE_SMALLBUSINESS VER_SUITE_SMALLBUSINESS_RESTRICTED |
6.0 from Windows Vista SP1, and higher |
0x24 (PRODUCT_STANDARD_SERVER_V) | none | 6.0 from Windows Vista SP1, and higher |
0x25 (PRODUCT_DATACENTER_SERVER_V) | VER_SUITE_DATACENTER | 6.0 from Windows Vista SP1, and higher |
0x26 (PRODUCT_ENTERPRISE_SERVER_V) | VER_SUITE_ENTERPRISE | 6.0 from Windows Vista SP1, and higher |
0x27 (PRODUCT_DATACENTER_SERVER_CORE_V) | VER_SUITE_DATACENTER | 6.0 from Windows Vista SP1, and higher |
0x28 (PRODUCT_STANDARD_SERVER_CORE_V) | none | 6.0 from Windows Vista SP1, and higher |
0x29 (PRODUCT_ENTERPRISE_SERVER_CORE_V) | VER_SUITE_ENTERPRISE | 6.0 from Windows Vista SP1, and higher |
0x2A (PRODUCT_HYPERV) | none | 6.0 from Windows Vista SP1, and higher |
0x2B (PRODUCT_STORAGE_EXPRESS_SERVER_CORE) | VER_SUITE_STORAGE_SERVER | 6.1 and higher |
0x2C (PRODUCT_STORAGE_STANDARD_SERVER_CORE) | VER_SUITE_STORAGE_SERVER | 6.1 and higher |
0x2D (PRODUCT_STORAGE_WORKGROUP_SERVER_CORE) | VER_SUITE_STORAGE_SERVER | 6.1 and higher |
0x2E (PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE) | VER_SUITE_STORAGE_SERVER | 6.1 and higher |
0x3B (PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT) | none | 6.1 and higher |
0x3C (PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL) | none | 6.1 and higher |
0x3D (PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC) | none | 6.1 and higher |
0x3E (PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC) | none | 6.1 and higher |
0x41 (PRODUCT_EMBEDDED) | none | 6.1 before Windows 7 SP1 |
VER_SUITE_EMBEDDEDNT | 6.1 from Windows 7 SP1, and higher |
Whether there have been practical implications from the cases where different builds of ostensibly the same product are assessed differently as suites is not known. The possibility must be at least suspected as having caused trouble, however. Some of the suite masks act as indicators of a lesser capability: it could be distressing to pick this up from a Service Pack upgrade.
Recognition of Windows as a Terminal Server becomes an issue in phase 2 of the kernel’s initialisation. Though the defined bits for the product suite allow for VER_SUITE_TERMINAL (0x0010) among the ones that are recognised by the applicable service packs of Windows NT 4.0, the first support in a Windows version examined for this study is from Windows 2000. Broadly speaking, the kernel may in phase 2 interpret more registry values to override VER_SUITE_TERMINAL from the ProductSuite or to set VER_SUITE_SINGLEUSERTS (0x0100). The general trend, as Terminal Server has become an everyday Windows feature, has favoured setting both.
In version 5.0, if VER_SUITE_TERMINAL is set from the earlier registry evaluation, the kernel clears it in phase 2 unless the following registry value has non-zero data:
Key: | HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Terminal Server |
Value: | TSEnabled |
Default: | 0 |
In effect, to have the suite mask show a Terminal Server on Windows 2000 you must have both Terminal Server in the ProductSuite value and a non-zero TSEnabled. In versions 5.1 and 5.2, if TSEnabled has non-zero data, then the kernel forces VER_SUITE_TERMINAL to be set: you thus get a Terminal Server from either registry value. In version 6.0 and higher, the kernel simply sets VER_SUITE_TERMINAL regardless of registry values.
If the kernel thinks to set VER_SUITE_SINGLEUSERTS, the registry value that decides the matter is:
Key: | HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Terminal Server |
Value: | TSAppCompat |
Default: | 1 in 5.0 to 5.2; 0 in 6.0 and higher |
The value is surely meant to have REG_DWORD data, and is documented as such (in the Microsoft Knowledge Base article Examining the Terminal Server Key in HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control), with 0 to specify a Remote Administration Server and 1 an Application Server. Zero, including if defaulted, means that only one interactive session is permitted at a time. Indicating this as a restriction of VER_SUITE_TERMINAL seems to be the point to VER_SUITE_SINGLEUSERTS. The algorithm varies with the versions, however, such that it looks to have been possible in versions 5.1 and 5.2 for VER_SUITE_TERMINAL to be set while VER_SUITE_SINGLEUSERTS remains clear only because the kernel did not evaluate it. Whether this has ever mattered in practice is not known.
In version 5.0, if VER_SUITE_TERMINAL is known from both ProductSuite and TSEnabled, then VER_SUITE_SINGLEUSERTS gets set too if TSAppCompat is zero. In versions 5.1 and 5.2, if VER_SUITE_TERMINAL is known from TSEnabled, then VER_SUITE_SINGLEUSERTS gets set too if TSAppCompat is zero. In version 6.0 and higher, the kernel sets VER_SUITE_TERMINAL unless TSAppCompat is non-zero.
This completes the identification of the product suite. The kernel saves it as the SuiteMask in the KUSER_SHARED_DATA and also writes it as multi-string data for the ProductSuite value. Of course, this will ordinarily mean writing the same data back to the registry as was read earlier. The REG_MULTI_SZ strings are always written in increasing order of the bit flags. Two of the defined flags are skipped: VER_SUITE_SINGLEUSERTS which is not learnt from this registry value; and VER_SUITE_WH_SERVER for reasons unknown. Failure to write the ProductSuite value is fatal to Windows, causing the bug check SYSTEM_LICENSE_VIOLATION (0x9A).