Such a seemingly simple task as determining the version of the operating
system or the operating system on which an application is running has been
surprisingly difficult for many programmers. In the past, the enormous
prevalence of errors in version checking in both DOS and Windows led
Microsoft to various forms of DOS and Windows version "spoofing" simply to
compensate for the fact that developers didn't quite get it
right.* The introduction of Microsoft's 32-bit platforms in many ways has
made a confusing situation even more confusing. In this article, we'll
review how you determine the platform on which your application is running.
Why do you care about this? Why do you need to know about platform and version checking? For the most part, if you confine yourself to programming with VBA and VB (or MSForm) controls, or if your code simply uses VBA to access some of the many COM objects available, operating system and version typically don't concern you. They are very significant, however, in three cases in particular:
Your program relies heavily on direct calls to the Win32 API. Although there is supposed to be (and, for many practical programming problems actually is) a common application programming interface that spans Microsoft's 32-bit platforms, platform differences nevertheless abound. Some API functions are present on one platform and absent in the other, or are present in one version but absent in others. Some API functions behave differently on one platform than on another, or on one version than on another.
Your program relies on direct registry access to retrieve information.
Although the basic structure of the registries in the 32-bit Windows
platforms are largely identical, they aren't quite identical. One of the
differences, of course, is the presence of some top-level keys
(HKEY_PERFORMANCE_DATA in Windows NT and HKEY_DYN_DATA in
Windows 9x) that are present in only one operating system platform. A second
is the fact that information may be stored in one key on Windows NT, for
instance, and in another key on Windows 9x.
Your program looks for files in particular directories, and those directories are different in Windows 9x and Windows NT. A good example is the prevalence of system files in \Windows\System in Windows 9x and in Windows\System32 in Windows NT.
32-bit applications, of course, can only run on 32-bit operating systems. This means, as you're no doubt aware, that your application may be running under Windows 9x or Windows NT. It also means, as you may not be aware, that your application may be running on Windows 3.1 under Win32s (even though that's becoming more and more unlikely as the base of Windows 3.x machines decline).
Traditionally, the GetVersion function is used to retrieve version information. The original implementation of the function in the 16-bit Windows API returns a long (four-byte) integer. Its lower-order word contains the version of Windows: the low-order byte contains the major version number, while the high-order byte contains the minor version number. The high-order word reports the MS-DOS version: its major version is stored in the high-order byte, and its minor version is in the low-order byte. This seems clear enough, but for some reason, many developers had difficulty with it. Two problems were particularly common:
Confusing the major and minor version numbers.
Probably because the bytes used to store the major and minor version
numbers are the reverse of one another for DOS and Windows, it was very
common to retrieve Windows' minor version number and treat it as the major
version, and to retrieve the major version and interpret it as the minor
version. When this happened, 3.10 -- the official version number reported by
Win32 -- became transmogrified into Windows version 10.3.
Testing for a particular version, rather than a baseline version.
Almost all applications are written to take advantage of a particular
API or some feature that is new to the underlying operating system. In
these cases, the application has to require that it run on the new version of
that operating system, and not on earlier versions. One would think that
developers would remember, though, that particularly in the software
industry, nothing is immutable, and new versions inevitably give rise to
even newer versions. In numerous instances, however, someone forgot this, and
checked the version in a way similar to the following code fragment:
If Not (nMajorVersion = 3 And nMinorVersion = 10)
MsgBox "You'd better think about upgrading!", vbOKOnly,
_
"Wrong O/S"
IsVersionCorrect = False
End If
Except in the rare case that the programmer's intention was really to run only under Windows 3.1, the program should check for version 3.1 or greater; instead, it checks for version 3.1 and refuses to run if it finds anything else.
GetVersion is still present under Win32. However, in large measure to prevent silly mistakes like this, the Win32 API includes a new function, GetVersionEx, that also happens to provide the best way to determine the platform and version of the entire family of Microsoft's 32-bit systems software. Its syntax is:
BOOL GetVersionEx(LPOSVERSIONINFO _
lpVersionInformation) ;
For Visual Basic programmers, this translates into the following Declare statement that should be stored in a code module:
Public Declare Function GetVersionEx Lib "kernel32" _
Alias "GetVersionExA" _
(lpVersionInformation As OSVERSIONINFO) As Boolean
The function returns TRUE if it succeeds and FALSE if it fails. Its single
parameter is an OSVERSIONINFO data structure
that is defined as shown in Table 1. The only
requirement in calling the function is that the OSVERSIONINFO's dwOSVersionInfoSize
member be assigned an accurate value beforehand.
Table 1. The OSVERSIONINFO structure
| Member | Type | Description |
dwOSVersionInfoSize |
DWORD | The size of the OSVERSIONINFO structure. It should be set to len(OSVERSIONINFO) before the GetVersionEx function is called. |
dwMajorVersion |
DWORD | The major version of the operating system |
dwMinorVersion |
DWORD | The minor version of the operating system |
dwBuildNumber |
DWORD | The low-order word contains the operating system's build number; the high-order word identifies the operating system's major and minor versions. |
dwPlatformId |
DWORD | The platform supported by the
operating system. It can have any of the following constants: |
szCSDVersion[128] |
TCHAR | A zero-terminated string containing additional information about the operating system. |
For Visual Basic programmers, this data structure can be defined by the
following Type statement, which is best included in the same code module as the GetVersionEx declaration:
Type OSVERSIONINFO
dwOSVersionInfoSize As Long
dwMajorVersion As Long
dwMinorVersion As Long
dwBuildNumber As Long
dwPlatformId As Long
szCSDVersion As String * 128
End Type
Note that, along with the user-defined type, you also have to include a definition of the platform constants, as follows:
Public Const VER_PLATFORM_WIN32s = 0
Public Const VER_PLATFORM_WIN32_WINDOWS = 1
Public Const VER_PLATFORM_WIN32_NT = 2
This makes it very easy to use Visual Basic to determine the platform on which
your application is running. Note from the Declare statement that the lpVersionInformation data structure is passed by reference to the function, and
that its szCSDVersion member is defined as a fixed-length string. The GetVersionEx function can then be used to determine the version, as in the GetPlatform function shown in Listing 1. You can call the function by using a code
fragment like that shown in Listing 2.
Listing 1. The VB GetPlatform function: determining the OS
Public Function GetPlatform() As Long
Dim udtVer As OSVERSIONINFO
udtVer.dwOSVersionInfoSize = Len(udtVer)
If (GetVersionEx(udtVer)) Then
GetPlatform = udtVer.dwPlatformId
Else
GetPlatform = 0' returns Win32 as lowest common denominator
End If
End Function
Listing 2. Calling the GetPlatform function
Dim strPlatform As String
Select Case GetPlatform
Case VER_PLATFORM_WIN32_WINDOWS
strPlatform = "Win9x"
Case VER_PLATFORM_WIN32_NT
strPlatform = "WinNT"
Case VER_PLATFORM_WIN32s
strPlatform = "Probably unable to run..."
' Possibly raise an error here
End Select
GetVersionEx does have one relatively small limitation as far as
determining operating system platform is concerned: it doesn't allow you to
distinguish between Windows NT Workstation and Windows NT Server, the two
varieties of Windows NT. If you want to do that, you'll have to check the
Windows NT registry; one of the string values shown in Table 2
that indicates the type of Windows NT is stored as the default value of
HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions
Table 2. Possible values of the ProductOptions subkey
| REG_SZ String | Description | Windows NT Versions |
| WINNT | Windows NT Workstation | all |
| SERVERNT | Windows NT Server | 3.5+ |
| LANMANNT | Windows NT Advanced Server | 3.1 |
So if you're certain that your application is running under Windows NT, you can determine whether the operating system is NT Server or NT Workstation by calling the IsWorkStation function, which is shown in Listing 4. (The Win32 API declarations needed for registry access are shown in Listing 3; these all can be stored in a code module.)
Listing 3. Win32 API declarations for registry access
Declare Function RegOpenKey Lib "advapi32.dll" _
Alias "RegOpenKeyA" _
(ByVal hKey As Long, ByVal lpctstr As String, _
phkey As Long) As Long
Declare Function RegQueryValue Lib "advapi32.dll" _
Alias "RegQueryValueA" _
(ByVal hKey As Long, ByVal lpSubkey As String, _
ByVal lpValue As String, pcbData As Long) As Long
Declare Function RegCloseKey Lib "advapi32.dll" _
(ByVal hKey As Long) As Long
Public Const HKEY_LOCAL_MACHINE = &H80000002
Public Const ERROR_SUCCESS = 0&
Listing 4. The IsNTWorkstation function
Public Function IsNTWorkstation()
Dim hKey As Long, lenNTType As Long
Dim lngDataType As Long
Dim strNTType As String
IsNTWorkstation = False
If Not (GetPlatform = VER_PLATFORM_WIN32_NT) Then
' Probably raise an error and then
' Exit Function
End If
If RegOpenKey(HKEY_LOCAL_MACHINE, _
"System\CurrentControlSet\Control\ProductOptions", _
hKey) = ERROR_SUCCESS Then
strNTType = String(32, 0)
lenNTType = Len(strNTType)
If RegQueryValue(hKey, "", strNTType, lenNTType) _
= ERROR_SUCCESS Then
If UCase(Left(strNTType, InStr(1, strNTType, Chr(0)) - 1)) = _
"WINNT" Then
IsNTWorkstation = True
End If
End If
RegCloseKey hKey
End If
End Function
Besides making it easy to determine the platform on which your application is
running, GetVersionEx makes determining the operating system's
version number very simple: you just have to evaluate the
OSVERSIONINFO's dwMajorVersion and dwMinorVersion
members. Table 3 indicates the version number returned by calling
GetVersionEx under existing 32-bit Windows operating systems.
Note that, when reporting the version number of Win32s, GetVersionEx
returns the version number of Win32s itself; GetVersion, on the other hand, returns 3.10 as the Windows version.
Table 3. Version numbers of 32-bit operating systems reported by GetVersionEx
| Operating System | Major Version | Minor Version |
| Win32s | 1 | 0 |
| 1 | 10 | |
| 1 | 15 | |
| 1 | 20 | |
| 1 | 25 | |
| 1 | 30 | |
| Windows 95 | 4 | 0 |
| Windows 98 | 4 | 10 |
| Windows NT | 3 | 10 |
| 3 | 50 | |
| 3 | 51 |
If you do need to retrieve the version number to make sure that your application is running on an operating system with the appropriate feature set, it's important, unless there's a pressing reason to do otherwise (like the feature that you need is present in one release but discontinued in later releases), to make sure that the operating system version is greater than or equal to some base version. In the case of an application that's expected to run under any of the Windows 9x operating systems (or the .0 release of any operating system), you don't even have to bother with the minor version:
If osVer.dwMajorVersion >= 4
// version is OK
In other cases, like if you require that your program run under Windows NT 3.51 or greater, you do have to look at the minor version:
If (osVer.dwMajorVersion = 3 And osVer.dwMinorVersion >= 51) Or
_
osVer.dwMajorVersion >= 4 Then
// version is OK
*Under DOS, the version table that was activated by including the command DEVICE=SETVER.EXE in Config.Sys listed applications that incorrectly queried the version number. Each entry in the table consisted of the application's filename and the version of DOS it thought it required in order to run. When the application queried the DOS version, it was given this number, rather than the actual DOS version number. Windows 3.1 supported compatibility bits, a variety of hexadecimal bit settings stored in the [Compatibility] section of Win.Ini that instructed Windows to provide special handling so that a Win30 application could run under Win31; the bulk of these settings were added to Win.Ini during installation.
Windows 9x continues to use these compatibility bits defined in the [Compatibility] section of Win.Ini. But it goes a bit further, indicating the pervasiveness of these problems. Win95 includes a special utility, MKCOMPAT.EXE, that allows any user to modify the compatibility bits for a particular application. One of its standard options is to (in the program's own words) "lie about Windows' version number"! Surely it says something about the real world of software development when two major operating systems must provide options to lie about something simple like the operating system version number on an application-by-application basis.
Copyright © 2009 O'Reilly Media, Inc.