Where Am I Running?by Ron Petrusha
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_DATAin Windows NT and
HKEY_DYN_DATAin 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
member be assigned an accurate value beforehand.
||DWORD||The size of the |
||DWORD||The major version of the operating system|
||DWORD||The minor version of the operating system|
||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.|
||DWORD||The platform supported by the
operating system. It can have any of the following constants:|
||TCHAR||A zero-terminated string containing additional information about the operating system.|
For Visual Basic programmers, this data structure can be defined by the
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
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.
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
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
|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.)
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&
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
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
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
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.
|Operating System||Major Version||Minor Version|
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.