/* Copyright (c) 1993-1995, UK, John M.Howells CIS ID: 100031,353 A whole slew of terms such as Microsoft, MS, MS-DOS, Windows, Win32, Win32s, Windows NT, Visual C++, Borland, etc., are probably trademarks of someone or other. If you have ever read any disclaimer about using the software at your own risk, and such, then assume you read it here also. Program to display the version of Windows on which it is run. The program can be compiled for both 16-bit and 32-bit versions, and although most of the code is unique to one version or the other it's easier to keep it all together. And its operation is quite crude, not registering a Window, and such, but displaying the results in a Message Box. On its own the program is not very useful, and the source is presented here for the benefit of others writing programs that need to know the version of Windows their programs are running on, as it tries to collect together the many documents produced by Microsoft, and other techniques, into a single working example. You may think this is all way over the top, and so it is, but it just shows how many ways I have so far found to determine the version of Windows hosting a program. It will report: 16-bit version: either Windows 3.1 'proper' or WOW (i.e. Windows on Windows, the 16-bit emulation provided by Windows NT) and Windows 95. if running on WOW it also reports the version and build of the underlying OS. if running on a system with Win32s it also reports the version and build. On Windows NT it will try and report the service pack identity. On a 16-bit host it will report the file version of the USER.EXE file. or 32-bit version: either Windows NT 'proper' or Windows 95 (aka Chicago) or Win32s (the extension to allow 32-bit programs to run on the 16-bit version of Windows 3.1x). if Windows NT is the host OS whether it is the Workstation product or a Server. The source of the information is as follows: Detect Windows-on-Windows Knowledgebase Articles Q92395 (as at 95-02-03) and Q96404 Detect presence of Win32s Knowledgebase Articles Q92395 from a 32-bit program (as at 95-02-03) and Q96404 Detect the Version of Win32s Win32s Programmer's Reference from a 16-bit program Detect other Version information Knowledgebase Article Q92395 (as at 95-02-03) Detect version reported to a DOS program DPMI specification Detect type and version of 32-bit host on-line help for GetVersionEx() Detect version of underlying Windows NT 3.1 from information in NT 3.5 SDK CD from 16-bit program \SCT\SAMPLES\INTEROP\README.TXT & Knowledgebase Article Q114341 Detect version of underlying Windows NT 3.5 from information in NT 3.5 SDK CD from 16-bit program \DOC\MISC\GENTHUNK.TXT & Knowledgebase Article Q104009 Detect Windows for Workgroups is running Knowldegebase Article Q114470 Detect file version from USER.EXE for based on Knowledgebase Article WFWG (&Win) 3.11 Q113892 Distinguish Windows NT Workstation Note from John Lawniczak of from Windows NT Server Microsoft Developer Support in the MSWIN32 forum on CIS Determine Workgroups from WINVER.EXE My own guess (!) after seeing output from the 16-bit CONFIGSP that it called a "Windows Version String" Determine Windows NT 3.1 Service Pack My own code, and examining which key the WINMSD program uses Detect if Win32s is running All my own work Detect numeric Service Pack number All my own work (CSD Version) for Windows NT 3.5 Detect Windows 95 from 16-bit mode Programmer's Guide for Windows 95 states Generic Thunk available. According to Pietrek, in MSJ Extra (UK edition), October '94, Page 24, in the Beta for Windows 95 [aka Windows 4.x, aka Chicago] GetVersion() returns with the top two bits (31 and 30) set, so in a 32-bit program code of the form: DWORD version = GetVersion(); if( 0 == ( version & 0x80000000 ) ) // bit 31 not set { // it's NT } else { // it's not NT if( ( version & 0xC0000000 ) == 0xC0000000 ) // bits 31 & 30 set { // it's Chicago } else { // it's Win32s } } will detect the platform, but we do not use that here, because Pietrek notes that the meaning of bit 30 may change [even though he then uses this code to detect Win32s in the API Spy program in the December '94 issue (UK edition) of MSJ] and instead we use code recommeded by Robert Hess of MS in the March 1994 issue of the MSDN newsletter, but only after GetVersionEx() has failed. When the program is running on a 16-bit version of Windows 3.1x or later it uses the DOS interface to check the version of Windows. It does this because Windows for Workgroups 3.11 tells lies (see MS Knowledgebase articles Q106271 and Q113892), and reports that it is Windows 3.10, but the DOS interface returns the correct version number. Even this does not work on Windows 3.11 (*not* Windows for Workgroups 3.11), as both interfaces, Windows and DOS, report 3.10, so the string from USER.EXE is also obtained! Also, the recommended File Version from USER.EXE is now reported. Development of this mixed-mode program is easier under the Borland environment, in my opinion, because the GUI can be used under 16-bit and 32-bit Windows to compile both 16-bit and 32-bit versions of the program, whereas the 32-bit GUI from Microsoft only works under WIndows NT, and you have to resort to the command-line tools to build a 32-bit version under 16-bit Windows (though even that is not possible with Visual C++ 2) and you have to develop the program in effectively four modes, since the results are different from 16-bit and 32-bit versions of the program for the 16-bit and 32-bit OS's. Modification History: 1.0 94-03-31 JMH Initial Version 1.1 94-04-28 JMH There are only two significant corrections. The first is for a silly mistake in omitting to initialize the bld_id string, though the original worked by accident! The other correction is for a better check for Version 3.10 or later. The previous version checked for the major version greater than 3 and the minor version greater than 10, which is not *quite* correct. The other changes are cosmetic. Changed the check to set up bld_id so no 'else' is needed. Corrected the spelling of 'both'. Changed the protected mode check in the 16-bit function to check for 3.10 version (3.11?) from the use of a DPMI function to use of the GetWinFlags() function. Changed the 32-bit check for NT to use a macro, rather than an embedded constant. Defined a macro for the cases where the DOS version check returns an 'unknown' result. Added 'argsused' pragma for WinMain() when compiling with Borland C++ 4.0. Corrected spelling of 'Windows'! Added the mode (Enhanced/Standard/Real) to the output in 16-bit mode. 1.2 94-05-09 JMH Modified to report the underlying OS version number when running on Windows NT. On the Windows NT 3.5 (aka Daytona) Beta (build 612) the WOW GetVersion returns 3.10, presumably to kid 16-bit applications that they are running on 3.10, in case they might get confused if they saw 3.50 returned. Here the underlying 32-bit version of GetVersion is called, as described on the Daytona Beta 612 CD in the \SCT\SAMPLES\INTEROP directory, to get the true underlying Windows NT OS version and build. 1.3 94-08-10 JMH Modified to report the Build identity from USER.EXE on Windows 3.10 and later. The Windows 3.11 OS/2 kicker (*NOT* WFWG 3.11) still reports 3.10 to both Windows and DOS programs that ask what version, but the build string in USER.EXE actually says 3.11 correctly. Ain't it clever! The program cannot analyze the string and not print it out if it is the same as the version reported by GetVersion(), as it contains (incorrectly in my opinion) the string "3.1" and not "3.10". Also changed the string for the "True" version report to say "DOS reports ..." because who knows what may be going on in future versions! Are there any more places where a version number is made available? Rearranged the output. 1.4 94-10-27 JMH Added the 32-bit GetVersionEx() interface to both 16-bit and 32-bit versions. In each case the program does not assume that the function exists, so we have to use LoadLibrary(), and so on, to find the function and do run-time linking to it. The GetversionEx() function was not present in Windows NT 3.1 or in version 1.10 of Win32s, but is present in Windows NT 3.5, and in Version 1.15 of Win32s. Changed the 16-bit version so that it detects whether or not the underlying OS supports the 32-bit thunk interface when trying to work out whether we are on a 32-bit OS for getting underlying version information, rather than relying on any flags. so that we *should* be independent of any particular OS, though probably it will only be valid on Windows NT. Changed the flow of the code slightly so that for each of the versions all output goes through a single call to MessageBox(). Used conditional compilation to allow the 'basic' version of the interfaces to be tested on systems that support the Extended interface. Changed so that the 16-bit version always reports the DOS version, if available, when running on 16-bit host. Altered the Base Address of the Microsoft-built 32-bit version to 400000 (the Borland tools already have this address as the default) to make the program a bit more Chicago compatible. Who would believe 'they' could invent so many ways of finding a simple thing like a version number! Must remember to get dates right this time! 1.5 94-11-06 JMH Slight change for compatibility with Visual C++ 2.0 (and presumably later versions of other tool suites), to only define the OSVERSIONINFOA structure and its associated VER_PLATFORM_WIN32... #defines when the VER_PLATFORM_WIN32_NT macro is not already defined. Added version number to Dialog Box text. Added a header file for common version information. 1.6 95-01-01 JMH Changed name in output from Chicago to Windows 95. Should have done this for previous version! Silly me! Added code in 16-bit version to try and detect Windows for Workgroups (see below), even when NT is the host, though on NT the network always appears to be running to a 16-bit program. Changed the method used to report the 32-bit platform name when GetVersionEx() exists so that cases other than those recognized at the time of writing are reported. Minor changes to a couple of comments to try and clarify the way things are done. Corrected comment for origin of detection of underlying version of NT 3.5, and alter both underlying detection comments to reflect the final SDK issue. Changed the name of the function pointer in the 16-bit program that holds the address of the function that gets the Win32s version information to more closely match that of the actual function name, and slightly simplified its use, as modern compilers don't have to be told exactly when to use a pointer to call a function. Changed field names in the WIN32SINFO structure to those used in the Win32s Programmer's Reference. Deleted pointer to extended version informnation in 16-bit and 32-bit code. Added File Version information, which is the method that is recommended by Microsoft to get the true version in Knowledgebase article Q113892, to all the other stuff, from USER.EXE for 16-bit and USER32.DLL for 32-bit. Corrected a good many format strings to use %u for version info not %d, as pedantically the numbers are (D)WORD's, and therefore unsigned. Added report of debug or retail version of Win32s to 16-bit version. changed the name bld_id to dos_bld_id for clarity. Corrected an error in the name of the Windows 95 definition for the platform. Added a check for which version of Windows NT, Workstation or Server, and made the 32-bit version UNICODE/ASCII portable. NOTE, a UNICODE build does not make any sense as a released version, since the result would only run on Windows NT (and hardly therefore qualifies as a portable 32-bit version detector!) but it is done as a guide to those who know their programs are going to run only on NT and want to use UNICODE. Changed slightly the local definition of OSVERSIONINFO to make it UNICODE/ASCII portable. I don't know the difference between the _UNICODE and UNICODE defines, and when to use each, so if only _UNICODE is defined the program defines UNICODE. *NOTE* that I have only tested this code on Windows NT Workstation 3.5 release version, which does *not* have a string in the szCSDVersion field, so I *hope* I have got that right, and the correct strings for a Server. Note that the program does not distinguish the names used for NT 3.1 (nothing and Advanced Server) from those for NT 3.5 (Workstation and Server), but uses the NT 3.5 names only. I cannot find the Workstation/Server key and its value in the NT 3.1 Resoruce Kit documentation to confirm the values used here. The 16-bit version does *not* try and get at the registry to determine if the NT host is a Workstation or a Server because the master key needed is not readily available to a 16-bit program. The projects do not use pre-compiled headers only because several sets for Borland and MS gets rather large. Changed the output format for 32-bit versions so that all now go consistently. Added the reasons for failure to the 16-bit Win32s report if the GetWin32sInfo function is found but returns a non-zero result. Changed the name of WV_WINNT to WV_NO_WINNT to properly reflect the meaning of the TRUE state of the flag. 1.7 95-02-07 JMH Changed the 'wfwg_....' string pointers to 'far' to overcome an apparent bug in the Borland C++ 4.5 compiler, which gets something wrong in the output from the wsprintf() statement noted. However, it does not take much to put it right, as almost any reference to a string corrects the problem. The point of change and the point where it went wrong are noted by comments starting ++++. Fortunately the Microsoft Visual C++ 1.51 compiler does not mind if they are declared far or not. In the 16-bit version when not running under Windows NT add the code to extract a version string (but *NOT* a part of the version resource) from the WINVER.EXE file. Even for versions of WINVER.EXE for Windows NT 3.1 and 3.5 (though they are not used here) this always appears to be at offset 0x0207 in the WINVER.EXE file, though this is only determined by inspection! The program does a quick confidence test, just in case!! This *appears* to be what the 16-bit version of MS' CONFIGSP program from the MSDN CD does, but I have not tried to trace this through to confirm it! However, on Windows 3.11 this reports the version as 3.10, so while it may seem to be good way of working out whether this is a Workgroups installation it is *NO* good for the version. Who is the twit in MS who decides these things? There is no need to use this code when Windows NT is running (though that could be said about getting the file version from USER.EXE as well) as the program generic thunks through to get the true underlying version. Now that I have Service Pack 1 for Windows NT 3.5 I can see that the szCSDVersion string was being handled properly for both ASCII and UNICODE. For 32-bit version leave the code to determine the File Version behind but conditionally compile it out of the build (but with a correction to report the USER32.DLL file and not USER.EXE as before). Corrected a minor, and extermely unlikely, bug in the 16-bit code if the GetSystemDirectory were to fail on the second call after the first call had succeeded. In the 16-bit version moved USER.EXE file version detection so that it is not executed when Windows NT is the host. Added a pointer to the OSVERSIONINFO definition and a function type for GetVersionEx in the 32-bit code. Made the UNICODE definition only apply to 32-bit programs. In the 16-bit output moved the DOS version up to just after the version reported by GetVersion(). Output the Service Pack [aka CSD=Corrective Service Diskette] number in the 32-bit version of the program for the basic GetVersion interface. This is the nearest that Windows NT 3.1 has to the szCSDVersion string returned on Windows NT 3.5 in the GetVersionEx() data structure. This should only be needed on Windows NT 3.1, because the extended interface does the job on NT 3.5, and while this item does exist on NT 3.5 it is a string when we are looking for the number used on NT 3.1, so we allow for it being either type, and for its absence, when the basic interface is tested. Note that this entry in the registry is not *quite* where GetVersionEx() gets its string from, but this string in the registry is updated to reflect the GetVersionEx() string when the system boots up. In the DPMI code that gets the version of Windows that a DOS application sees clear the register set structure to zeroes, just in case. Changed 'temp' name to 'win32s_ver' for clarity. Moved Windows for Workgroups check in the 16-bit version so that it is only executed when 16-bit Windows is the host as it always indicates that the Windows for Workgroups network is running, even on a stand-alone system with no network whatsoever, when Windows NT is the host OS. Corrected the code fragment in this comment, about detecting Win32s/Chicago/NT, etc. as there was one too few 0's in each constant. Succumbed to the tempptation, and added even more over the top code to get the type of Windows NT host for a 16-bit program, using a Generic Thunk to the underlying 32-bit functions that access the Registry, requiring a slight change to the 32-bit code. Note that for this we use the CallProc32W function, rather than CallProcEx32W, because the former works on both Windows NT 3.1, while only Windows NT 3.5 supports the latter. Because CallProc32W is a PASCAL function it is not easy to define an appropriate prototype, so there are actually three different versions of it defined here, each taking a different number of parameters, all of which are mapped by the DEF file to the single CallProc32W function! Added a function prototype for the Win32sInfo function that get the Win32s version. Changed 16-bit version to use STRICT compliance, which required two module handles to be changed from HANDLE to HINSTNACE. Slightly changed access to the Windows NT registry from the 16-bit version, and used it to add further over the top code to add the service pack data to the 16-bit version when Windows NT is the host. Split the change comments so that each change has its own paragraph. Changed WORKSTATION_... (KEY and VAL) to the more appropriate PRODUCTTYPE_... (KEY and VAL). Added a #define for the length of the string returned from the registry for the type of the Windows NT host. 1.8 95-03-15 JMH Cut definitions for OSVERSIONINFO as they are not needed. Added code to the 32-bit version to parse the version from the Win32s.INI file if the GetVersionEx function is not available. This is what happens on version 1.10 and before, though I have never seen version 1.0, and even Microsoft do not seem to know where a copy was distributed. To get this to compile for UNICODE (which is silly anyway!) required some fiddling about with the header files and translated function names, and the UNICODE version does not compile on Borland C++ 4.5, but only the Win32s bit which is silly as UNICODE anyway! Added code to the 16-bit version to detect whether Win32s is actually running. Clearly this makes no sense in the 32-bit version! Presumably a program that requires Win32s should only do an installation when Win32s is not actually running, though if the correct version of Win32s is already present the Win32s bit of the installation can, of course, be skipped. Added numeric CSD Version to 16-bit and 32-bit code when Windows NT is the host. This value does not exist on Windows NT 3.1, where the CSD Version under the CurrentVersion key is numeric. This should allow an application that depends on a particular service pack level to make sure that the system is already configured appropriately. minor rearrangement of code to always use reg32_access function for access to registry. a different form of the function is provided for each of 16-bit and 32-bit programs. 1.9 95-04-18 JMH Changed 32-bit Visual C++ 2 project settings for compatibility with the Visual C++ 2.1 subscription release by adding 3.10 to the /subsystem:windows flag so that Windows NT 3.1 can run the program. Added the Windows 95 definition if the header files have not already defined it, and removed conditional compilation tests for it. Used only bottom WORD of the GetVersionEx result for the build number, as on Windows 95 (I've got a preview copy at last - on 10th April 1995!) this appears to put the version number into the top word, as on build 347 it contains 0x0400015B ( 4.0.347 ?? ). Also, on Windows 95 the Generic Thunk interface works (see Article 32 in the Windows 95 Programmer's Guide from the Windows 95 SDK, though the GENTHUNK.TXT file on the same CD still states that only Windows NT will support Generic Thunks), so much to my surprise a 16-bit program on Windows 95 can get at the underlying 32-bit GetVersionEx and GetVersion functions. However, under Windows 95 the build number is not present in the GetVersion response, so modified the 16-bit output preperation when the Generic Thunk works, so that under Windows 95 the stuff that is exclusive to Windows NT is not produced. Changed the 16-bit program slightly so that it refers to WOW (Windows On Windows) only when running on Windows NT, otherwise just refer to Win16. Added the DOS version number to the 16-bit program running under Windows 95. Further notes: The results from the 16-bit version =================================== Windows Windows Windows WFWG Windows Windows Windows Version 3.10 3.11 3.11 NT 3.1 NT 3.5 95 File Version 3.10 3.11 3.11 3.10 3.10 GetVersion() 3.10 3.10 3.10 3.10 3.10 3.95 DOS 3.10 3.10 3.11 Build Id 3.10 3.11 3.11 WINVER.EXE string 3.10 3.10 3.11 Gen Thunk to GetVersion 3.10 3.50 4.00 Gen Thunk to GetVersionEx 3.10 3.50 4.00 It can be seen that (at the time of writing!) the most reliable method for getting the correct version number of the host OS as binary data (as opposed to the string provided by the Build ID) is to first check for the availability of Generic Thunk support and then use the File Version information from USER.EXE on the 16-bit versions of Windows, as recommended in Q113892, and the Thunk layer where available, where the GetVersion is simpler as it applies to all versions of Windows NT, whereas the GetVersionEx thunk is only available on Windows NT 3.5, so you have to be prepared to drop back to the GetVersion thunk if that fails. For the installation of Win32s programs should always use the 16-bit function from a 16-bit program, as Win32s may not yet be installed. The information returned appears to be a reliable indication of the version of Win32s, and is consistent with the information returned by the 32-bit family GerVersionEx function available in later versions. Windows 95 (aka Chicago) ======================== This information represents the Preview version, build 347. Note, that this is built with the commercially released tools and does not need anything from the Win95 SDK. In the 16-bit version of the program the Windows 95 check can use the Generic Thunk interface. The documentation on the Windows NT SDK indcated that the Generic Thunk interface would always be unique to Windows NT (and that same document on the Windows 95 SDK CD still indicates this). However, the Programmer's Guide on the Windows 95 SDK CD reports that the Generic Thunk interface is supported by Windows 95, so the 16-bit program can use that interface to access the 32-bit GetVersionEx or GetVersion functions. Note that if the GetVersion interface is used the program can distinguish Windows NT from Windows 95 by using the WF_WINNT bit in the value returned by GetWinFlags. In the 32-bit version the GetVersionEx function is available from the Windows 95 host, so there is no problem there. In both cases there is one minor problem with Windows 95, that the build identity field returned in the GetVersionEx structure contains (at least in the Preview build 347) 'rubbish' in the top word that looks like the version number (04.00). In the registry there is a DWORD version number that contains the same 'rubbish', so presumably that is copied straight into the build field without removing the version number part. Because of this the program uses only the low WORD of the field when displaying the build number. Maybe this will change for the final version. Detecting Windows for Workgroups ================================ As of Version 1.6 the program does its best to detect whether it is running under Windows for Workgroups. The program can easily detect Windows for Workgroups on a networked system when the network is running, as in Q114470, but this test is unreliable. The problem is that Windows for Workgroups does not require a network, and in such a mode there appears to be no obvious difference from a basic (i.e. not Workgroup) Windows installation. The program looks for the presence of WFWNET.DRV in the WINDOWS\SYSTEM directory as a further indication of Windows for Workgroups, but this in itself is still a very unreliable check. In the end we report what we find: Workgroups found and running (because net detect says so) Workgroups not found (net detect says no and no WFWNET.DRV found) Workgroups found but not running (net detect says no but WFWNET.DRV found) Note that the wording "Not found" is carefully chosen! It is intended to indicate that just because it was "not found" does not mean this is not a Windows for Workgroups system. But if anybody reading this knows a more reliable test for Windows for Workgroups ... other than the WINVER string, that is ... In version 1.7 a check on a string from the WINVER.EXE file is included, which contains the word "Workgroups" in the WFWG 3.11 version, but this seems somewhat dubious, and I have no idea what is in the Windows for Workgroups 3.10 string. Again, if anybody knows a better Workgroups check ... */ #if defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ ) /* #define UNICODE /* uncomment this or define UNICODE on the command line to create a UNICODE version of the 32-bit program, though it's not of any practical use! */ #endif #if defined( _UNICODE ) && !defined( UNICODE ) #define UNICODE /* just in case the alternate _UNICODE define is used */ #endif #if defined( UNICODE ) && !defined( _UNICODE ) #define _UNICODE /* and the other way round! */ #endif #define STRICT #include #include /* for _MAX_PATH */ #include /* for strncpmi() and strstr() and _fmemset */ #if defined( _MSC_VER ) /* Borland C++ 4.5 does not have tchar.h (yet?), so UNICODE version does not compile! */ #include #endif #if defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ ) /* to get the Borland version to compile when not using UNICODE define the translated name */ #if !defined( _tcstol ) #define _tcstol strtol #endif #endif /* of Borland translation */ #include "win_ver.h" /* #define TEST_BASIC_IF /* remove comment starter at beginning of line or define on command line to test basic 32-bit I/F's rather than the extended I/F's */ /* #define INCLUDE_USER32_DLL /* remove comment starter at beginning of line or define on command line to include the version of USER32.DLL in the 32-bit version output */ #if !defined( TEXT ) /* have to do this for 16-bit compiles */ #define TCHAR char #define PTCHAR char* #define TEXT( str ) str #endif #define PROG_NAME TEXT( "Windows Version Program : V" ) \ TEXT( PROG_MAJOR_VERS_STRING ) TEXT( "." ) \ TEXT( PROG_MINOR_VERS_STRING ) /* some of the things are defined here because they are not available in any of the header files that ship with some of the standard C/C++ development environments currently available, as presented here it will build with the retail versions of Microsoft VC++ 1.5 or 1.51 or 1.52 (from the VC++ 2.x packages) for 16-bit and VC++ 1.10 (aka 1.0) 32-bit edition or VC++ 2.0 or VC++ 2.1, and the Borland C++ 4.02 or C++ 4.5 packages for both 16-bit and 32-bit. */ #define WF_WINNT 0x4000 /* flag for WOW in 16-bit Windows, 1 == WOW == NT */ #define WV_NO_WINNT 0x80000000 /* flag for not NT in 32-bit GetVersion(), 1 == not NT */ #define W32SYS "W32SYS.DLL" /* name of Win32s DLL */ #define WIN32SINF "GETWIN32SINFO" /* name of GetWin32sInfo function in W32SYS.DLL */ #define WIN32S_INI "\\SYSTEM\\WIN32S.INI" /* name of Win32s.INI file in the Windows directory */ #define WIN32S_SECT "Win32s" /* section in Win32s.INI file */ #define WIN32S_KEY "Version" /* key in Win32s.INI file for version data */ #define KRNL386 "KRNL386.EXE" /* name of Krnl386 EXE */ #define LDLIB32W "LOADLIBRARYEX32W" /* name of LoadLibraryEx32W function in KRNL386.EXE */ #define CALLPROCEX "_CALLPROCEX32W"/* name of CallProcEx32W function in Kernel32.DLL. note that this does *not* exist on Windows NT 3.1, only on Windows NT 3.5 and later. */ #define USER_EXE "USER.EXE" /* name of USER.EXE file */ #define BUILD_ID 516 /* number of Build string in USER.EXE */ #define USER32_DLL "USER32.DLL" /* name of USER32.DLL file on Windows NT */ #define KERNEL32 "KERNEL32.DLL" /* name of module that holds GetVersion(Ex) functions */ #define GETVERN "GetVersion" /* name of GerVersion function in Kernel32.DLL */ #define FILEINFO_KEY "\\" /* name of file info root block for getting file version information */ #define VER_UNKNOWN 0xFFFF /* value when can't get version from DOS */ /* data for Windows for Workgroups network detection */ #define WFWG_NOT_FOUND 0 /* value when WFWG not found */ #define WFWG_FOUND 1 /* value when WFWG found but not running */ #define WFWG_RUNNING 2 /* value when WFWG found and running */ /* data for Workgroups detection from WINVER.EXE */ #define WINVER_EXE "WINVER.EXE" /* name of WINVER.EXE file */ #define WINVER_OFFSET 0x0207 /* offset of windows version string in that file */ #define WINVER_LENGTH 64 /* number of chars to read */ /* data for Service Pack detection on Windows NT 3.1 */ #define REG_CSD_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" /* name of the registry entry for the CSD version */ #define REG_CSD_DATA "CSDVersion" /* name of CSD version data - only needed for NT 3.1 */ #define CSD_TO_SP( xx ) ( ( xx ) >> 8 ) /* to convert CSD Version number to the Service Pack number. I have seen no documentation for this, so maybe something like HIBYTE( LOWORD( xx ) ) should be used, but I have not traced through the WINVER from Windows NT 3.1 to see how it is worked out. */ /* data for the extended get version information */ #if !defined( UNICODE ) /* using ASCII version of extended interface */ # define GETVERNEX "GetVersionExA" /* name of GetVersionEx function in Kernel32.DLL, note that this is in W32sCOMB.DLL in Win32s, but asking for it in the KERNEL32.DLL module nevertheless still works properly. */ #else /* UNICODE */ /* using UNICODE version of extended interface */ # define GETVERNEX "GetVersionExW" /* only sensible in Windows NT */ #endif #define CSD_STR_SIZE 128 /* MS do not define a macro for this */ #if !defined( TEST_BASIC_IF ) /* declarations only relevent for new interface */ #if !defined( VER_PLATFORM_WIN32_NT ) /* if already defined don't do again. this is defined in the headers for MS Visual C++ 2 and Borland C++ 4.5 but not in the earlier versions, and not for 16-bit compilations. */ typedef struct _OSVERSIONINFO { /* Extended Vern Info from GetVersionEx() */ DWORD dwOSVersionInfoSize; /* set to size of structure */ DWORD dwMajorVersion; /* OS major version */ DWORD dwMinorVersion; /* OS minor version */ DWORD dwBuildNumber; /* OS build number */ DWORD dwPlatformId; /* OS platform type */ TCHAR szCSDVersion[ CSD_STR_SIZE ]; /* description string */ } OSVERSIONINFO, *LPOSVERSIONINFO; /* the values returned in the PlatformID field of the Extended Version information to indicate which version of the 32-bit platform is in use. */ #define VER_PLATFORM_WIN32s 0 /* Win32s on Windows 3.1x */ #define VER_PLATFORM_WIN32_WINDOWS 1 /* Win32 on Windows 95. */ #define VER_PLATFORM_WIN32_NT 2 /* Windows NT */ #endif /* of extended version info already defined */ #if !defined( VER_PLATFORM_WIN32_WINDOWS ) /* Windows 95 value for platform ID not defined in the header files VC++ 2.0 or 2.1 or BC++ 4.5, so define it now */ #define VER_PLATFORM_WIN32_WINDOWS 1 #endif #endif /* of testing full interface */ #if !defined( WIN32 ) && !defined( _WIN32 ) && !defined( __WIN32__ ) /* prototypes for functions that permit the program to thunk from this 16-bit world to a 32-bit module. the DEF file identifies these as in the Kernel, and it does not matter if they are not, as they will only be called if the earlier checks OK it */ DWORD FAR PASCAL LoadLibraryEx32W( LPCSTR, DWORD, DWORD ); BOOL FAR PASCAL FreeLibrary32W( DWORD ); DWORD FAR PASCAL GetProcAddress32W( DWORD, LPCSTR ); DWORD FAR PASCAL CallProc32W__0( DWORD, DWORD, DWORD ); /* NT 3.1, et seq */ #if !defined( TEST_BASIC_IF ) DWORD FAR CDECL CallProcEx32W( DWORD, DWORD, DWORD, ... ); /* NT 3.5, et seq */ #endif /* of testing only basic interface */ #endif /* of 16-bit Windows NT and Windows 95 generic thunk function prototypes */ #if __BORLANDC__ >= 0x0452 /* on Borland C++ 4.x exclude exception handling code */ void _ExceptInit( void ) { ; } #endif #ifdef __BORLANDC__ #pragma argsused #endif /* the function that drives the whole thing */ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { DWORD win_vers; DWORD maj_vers; DWORD min_vers; TCHAR file_ver[ 128 ] = TEXT( "" ); /* file version string created from USER.??? */ TCHAR buff[ 1024 ]; /* results buffer */ int chrs; /* characters in results buffer so far */ extern PTCHAR nt_type( void ); /* function that works out type of Windows NT */ extern PTCHAR nt_csd_num( void ); /* function to get numeric CSD number as string */ extern BOOL reg32_access( PTCHAR, PTCHAR, LPDWORD, LPBYTE, LPDWORD ); /* function to read value from the registry */ #if defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ ) /* ==================================== program is being compiled for 32-bit ==================================== */ #define WIN_BITS "32" /* by comparison with the 16-bit code the 32-bit code is relatively straightforward! There are only two functions GetVersion() and GetVersionEx(). The latter is available on version 3.5 of Windows NT but not on version 3.1 of Windows NT, and on version 1.15 build 103 of Win32s, but not on version 1.10 build 88 of Win32s. Note that when we find Win32s and report the underlying Windows version we do *not* take any steps to determine the correct version number!!! */ #if !defined( TEST_BASIC_IF ) /* normally included in build - define for testing */ typedef BOOL ( WINAPI *GVEX_PTR )( LPOSVERSIONINFO ); /* type of ptr to GetVersionEx function */ HINSTANCE hKernel32; /* handle to 32-bit KERNEL32.DLL */ GVEX_PTR Get_Version_Ex; /* function ptr to GetVersionEx */ OSVERSIONINFO os_vn_inf = { sizeof( OSVERSIONINFO ) }; /* initialized Extended Version information */ #endif #if defined( INCLUDE_USER32_DLL ) /* chop this out of the build for version 1.7 but leave the code behind */ #include /* not included by windows.h - different name for 16-bit and 32-bit windows */ TCHAR user_name[ _MAX_PATH ]; /* constructed name of USER32.DLL file */ /* first try for platform independent stuff (though this will silently fail on Win32s) this is for file version from the USER32.DLL file. here errors are checked but not reported. this is just to demonstrate the consistency, because unlike 16-bit Windows the 32-bit GetVersion() function always [so far on NT 3.1 and NT 3.5!] tells the truth. UNICODE versions of the functions uses wide characters. */ if( GetSystemDirectory( user_name, _MAX_PATH ) != 0 ) { DWORD dummy; /* ignored on 32-bit */ DWORD inf_size; /* how big is version info buffer */ PBYTE ver_buff; /* ptr to mem allocated for ver info buff */ VS_FIXEDFILEINFO *fixed_info; /* ptr to fixed info in ver info buff */ UINT fixed_size; /* number of bytes read for ver info */ /* add name of user32.dll to system directory. */ lstrcat( user_name, TEXT( "\\" ) TEXT( USER32_DLL ) ); if( ( inf_size = GetFileVersionInfoSize( user_name, &dummy ) ) != 0 ) { /* how big is version info buffer required */ ver_buff = GlobalAlloc( GPTR, inf_size ); if( ver_buff != NULL ) { /* and get the information */ if( GetFileVersionInfo( user_name, 0, inf_size, ver_buff ) != FALSE ) { /* get ptr to fixed version information in version info buffer */ if( VerQueryValue( ver_buff, TEXT( FILEINFO_KEY ), (PVOID *)&fixed_info, &fixed_size ) != 0 ) { /* okay - add file version to build information */ wsprintf( file_ver, TEXT( "\n" ) TEXT( USER32_DLL ) TEXT( " File Version %lu.%lu" ), HIWORD( fixed_info->dwFileVersionMS ), LOWORD( fixed_info->dwFileVersionMS ) ); } } /* free memory for version buffer */ GlobalFree( ver_buff ); } } } #endif /* of code chopped out of build for version 1.7 */ #if !defined( TEST_BASIC_IF ) /* code normally included in build - define for testing */ /* now try for extended version information - the function is run-time linked so that we can use simple information if the extended information is not available on this version of the OS. Here normal 32-bit functions are used. the name given to GetProcAddress *NEVER* uses wide characters, but the name given to GetModuleHandle does - go figure. */ if( ( hKernel32 = GetModuleHandle( TEXT( KERNEL32 ) ) ) != NULL && ( Get_Version_Ex = (GVEX_PTR)GetProcAddress( hKernel32, GETVERNEX ) ) != NULL ) { /* got Extended Version function ptr so use it */ PTCHAR platform_type; TCHAR unknown_str[ 32 ]; /* and get data */ Get_Version_Ex( &os_vn_inf ); /* set up name of 32-bit platform */ switch( os_vn_inf.dwPlatformId ) { case VER_PLATFORM_WIN32_NT: platform_type = TEXT( "Windows NT" ); break; case VER_PLATFORM_WIN32s: platform_type = TEXT( "Win32s" ); break; case VER_PLATFORM_WIN32_WINDOWS: platform_type = TEXT( "Windows 95" ); break; default: /* cater for unknown platform types */ wsprintf( unknown_str, TEXT( "Unknown type %lu" ), os_vn_inf.dwPlatformId ); platform_type = unknown_str; } /* here use only low word of build identity for Windows 95 compatiblity, as on build 347 it contains 0x0400015B */ chrs = wsprintf( buff, TEXT( "%s Version %u.%02u Build %u\n" ) TEXT( "%s" ), platform_type, os_vn_inf.dwMajorVersion, os_vn_inf.dwMinorVersion, LOWORD( os_vn_inf.dwBuildNumber ), os_vn_inf.szCSDVersion ); switch( os_vn_inf.dwPlatformId ) { case VER_PLATFORM_WIN32s: /* on Win32s append version of underlying 16-bit Windows */ win_vers = GetVersion(); chrs += wsprintf( buff + chrs, TEXT( "\n" ) TEXT( "running on\n" ) TEXT( "Windows Version %u.%02u" ), LOBYTE( LOWORD( win_vers ) ), HIBYTE( LOWORD( win_vers ) ) ); break; case VER_PLATFORM_WIN32_NT: /* on Windows NT report if workstation or some kind of server and add the numeric Service Pack information */ chrs += wsprintf( buff + chrs, TEXT( "\n" ) TEXT( "Windows NT %s" ) TEXT( "%s" ), nt_type(), nt_csd_num() ); break; } /* end of extended version code */ } else { /* we could not get extended version information so go with basic */ #endif /* GetVersion() always exists so it is compile-time linked */ win_vers = GetVersion(); /* save version information */ maj_vers = LOBYTE( LOWORD( win_vers ) ); min_vers = HIBYTE( LOWORD( win_vers ) ); if( ( win_vers & WV_NO_WINNT ) == 0 ) { /* this is real Windows NT */ union { DWORD csd_no; /* CSD as number in NT 3.1 */ TCHAR csd_str[ CSD_STR_SIZE + 1 ]; /* CSD as string in NT 3.5 */ } csd_data; /* CSD as number or string */ DWORD csd_type; /* CSD value type */ DWORD csd_size = sizeof( csd_data ); /* CSD size in bytes - even for UNICODE */ /* try and get the CSD Version (Service Pack) value from the Registry */ if( reg32_access( TEXT( REG_CSD_KEY ), TEXT( REG_CSD_DATA ), &csd_type, (LPBYTE)&csd_data, &csd_size ) ) { /* display depends on the data type from the Registry */ switch( csd_type ) { case REG_DWORD: /* this is Windows NT with numeric CSD version - this is the code used on Windows NT 3.1 by the basic interface - form a service pack string */ wsprintf( csd_data.csd_str, TEXT( "Service Pack %lu (CSD Version 0x%lX)\n" ), CSD_TO_SP( csd_data.csd_no ), csd_data.csd_no ); break; case REG_SZ: /* this should only be used when testing the basic interface on Windows NT 3.5 - add line terminator */ lstrcat( csd_data.csd_str, TEXT( "\n" ) ); break; default: /* this should never happen! */ lstrcpy( csd_data.csd_str, TEXT( "??\n" ) ); /* drop through and out */ } /* form full Windows NT version string */ chrs = wsprintf( buff, TEXT( "Windows NT Version %u.%02u Build %u\n" ) TEXT( "%s" ) TEXT( "Windows NT %s" ) TEXT( "%s" ), maj_vers, min_vers, HIWORD( win_vers ) & 0x7FFF, csd_data.csd_str, nt_type(), nt_csd_num() ); } } else { /* yes - I know this bit is idiotic as UNICODE! and it does cause some grief. */ if( maj_vers < 4 ) { /* this is Win32s running on 16-bit Windows 3.1x, the program is running in 32-bit mode and the extended information is not available but although we are getting desperate before we bottle out we try for information from the Win32s.INI file */ TCHAR file_name[ _MAX_PATH ]; /* space for full INI file name */ TCHAR ver_buff[ 32 ]; /* space for string from INI file */ PTCHAR ver_ptr = ver_buff; /* ptr to string from INI file */ LONG win32s_maj; /* Win32s major version */ LONG win32s_min; /* Win32s minor version */ LONG win32s_bld; /* Win32s build number */ /* the default string is empty so we can recognize the case where it is returned - the documentation says that if we do not supply a full path name then the GetPrivateProfileString function should look in the Windows directory, but it appears not to work here, so we make the full name ourselves. the string is parsed as we go along. the various parts of the version number are handled as longs. note that we use strtol (portably _tcstol) rather than atoi (portably _ttoi) as it serves three purposes at once, (1) it gets the number (2) it steps the pointer to the next character of the string, (3) it allows us to check [following from (2)] that exactly all the characters expected were read as part of the number. all three numbers must be followed by a full stop. */ if( 0 != GetWindowsDirectory( file_name, _MAX_PATH ) && 0 != GetPrivateProfileString( TEXT( WIN32S_SECT ), TEXT( WIN32S_KEY ), TEXT( "" ), ver_ptr, sizeof( ver_buff ) / sizeof( TCHAR ), lstrcat( file_name, TEXT( WIN32S_INI ) ) ) && ( win32s_maj = _tcstol( ver_ptr, &ver_ptr, 10 ), TEXT( '.' ) == *ver_ptr ) && ( win32s_min = _tcstol( ++ver_ptr, &ver_ptr, 10 ), TEXT( '.' ) == *ver_ptr ) && ( win32s_bld = _tcstol( ++ver_ptr, &ver_ptr, 10 ), TEXT( '.' ) == *ver_ptr ) ) { /* we cannot tell if this is the debug or retail version, but we warn the user the data has come from the INI file! note that the version number is formatted as %ld.%ld rather than %ld.%02ld because the version 1.10 INI file contains version 1.1 and we must not report the identity as 1.01. */ chrs = wsprintf( buff, TEXT( "Win32s Version %ld.%ld Build %ld\n" ) TEXT( "(with data from WIN32S.INI file)\n" ) TEXT( "running on\n" ) TEXT( "Windows Version %u.%02u" ), win32s_maj, win32s_min, win32s_bld, maj_vers, min_vers ); } else { /* the version number from the Win32s.INI file is no good so we cannot easily determine the version of Win32s as that info would have to come from a 16-bit DLL - good innit. the program *could* work out the version, but it would require a 16-bit DLL to thunk to, and that just is not worth the effort. Bottle out and tell the user to run the 16-bit version. */ chrs = wsprintf( buff, TEXT( " Win32s running on Version %u.%02u\n" ) TEXT( "\t of Windows\n" ) TEXT( "\n" ) TEXT( " Run 16-bit version for information\n" ) TEXT( " on Version of Win32s\n" ) TEXT( "and for additional 16-bit build information" ), maj_vers, min_vers ); } } else { /* this is Windows 95 - aka Chicago - how did we ever get here as GetVersionEx (or GetVersion!) should have already done its thing. */ chrs = wsprintf( buff, TEXT( "??? Windows 95 Version %u.%02u" ), maj_vers, min_vers ); } } #if !defined( TEST_BASIC_IF ) } #endif /* however we got here add possibly empty file version string */ wsprintf( buff + chrs, TEXT( "%s" ), file_ver ); /* ========================== end of 32-bit Windows code ========================== */ #else /* ============================ start of 16-bit Windows code ============================ */ #if defined( UNICODE ) #error UNICODE for a 16-bit program ??? #endif #include /* not included by windows.h - different name for 16-bit and 32-bit windows */ #define WIN_BITS "16" /* compared with the 32-bit code this is horrible, though ironically it is tidier when Windows NT is the host. It tries for a host 32-bit OS. If that fails it proceeds with 16-bit detection, looking for Win32s as well as checking for version numbers known to DOS and the USER.EXE module, the file version from USER.EXE, and the Windows string from the WINVER.EXE file. */ #if !defined( REG_DWORD ) /* we have to define these for 16-bit version */ #define REG_SZ 1 #define REG_DWORD 4 #endif DWORD win_flags; HINSTANCE hKrnl386; /* instance handle for Krnl386.EXE */ /* first do the bits that are host independent - simple GetVersion */ win_vers = GetVersion(); /* save basic version number */ maj_vers = LOBYTE( LOWORD( win_vers ) ); min_vers = HIBYTE( LOWORD( win_vers ) ); /* OK - done system independent stuff - now system dependent */ win_flags = GetWinFlags(); /* detect whether the underlying OS can support the functions that would allow a 16-bit program to thunk to that underlying 32-bit OS. we don't need the function address, as this is only to check that the function we need is present, so don't bother to save it. the WF_WINNT check could be used, but by ignoring that and doing it this way we are not dependant on any particular flags to tell us which version of the OS we are on. If the LoadLibraryEx32W function is present we use it, [even though that almost certainly means only on Windows NT]. Well, on 10th April 1995 the previous statement in [ ] was proved wrong when I finally got my hands on a Preview copy of Windows 95, which *does* support the Generic Thunk stuff, so not using the WF_WINNT flag proved to be the correct choice way back in May 1995! Sheesh! note that neither the value from the 16-bit GetVersion nor from the 16-bit GetWinFlags function appears to indicate if this is running on Windows 95. */ if( /* win_flags & WF_WINNT - *NO* - the following works on Windows 95 where that flag is not set - testing this longer way is better */ ( hKrnl386 = GetModuleHandle( KRNL386 ) ) != NULL && GetProcAddress( hKrnl386, LDLIB32W ) != NULL ) { /* Program is running on Windows NT, or on some other system (Windows 95? - I thought maybe not - until I found out to the contrary on 10th April 1995) that supports LoadLibraryEx32W so use it to get version data from the underlying 32-bit OS */ DWORD hKernel32; /* handle to 32-bit DLL while thunking */ DWORD Get_Version; /* ptr to 32-bit function that gets version. */ #if !defined( TEST_BASIC_IF ) OSVERSIONINFO os_vn_inf = { sizeof( OSVERSIONINFO ) }; /* initialized extended version information */ DWORD Get_Version_Ex; /* ptr to Extended Version function */ /* first try for extended version information - we go through this so that we can use simple information if the extended information is not available on this version of the OS, as Windows NT 3.1 does not provide Extended Version information. Here the program uses the thunk interface to call from the 16-bit version of the program 'through' to the 32-bit world. the KRNL386 check is because the CallProcEx32W function did not appear till Windows NT 3.5, so we can't use the extended functions on Windows NT 3.1, and we don't want an unresolved Dynalink message to stop the program, but we don't need to save the address of the function. So, do checks and get data. We assume that if the CallProcEx32W() function does not exist then nor does the GetVersionEx() function. The CallProcEx32W function passes 1 parameter that requires address translation to the GetVersionEx() function. if any check fails, or we fail to get data, we go on to try for basic data. Note, that it is *VITAL* that hKernel32 is obtained *FIRST* so that is is *ALWAYS* done (the rest may not be!), as it is assumed to be set up when getting the basic version. */ if( ( hKernel32 = LoadLibraryEx32W( KERNEL32, NULL, 0 ) ) != NULL && ( Get_Version_Ex = GetProcAddress32W( hKernel32, GETVERNEX ) ) != NULL && GetProcAddress( hKrnl386, CALLPROCEX ) != NULL && CallProcEx32W( 1, 1, Get_Version_Ex, (OSVERSIONINFO FAR *)&os_vn_inf ) == TRUE ) { char *platform_type; char unknown_str[ 32 ]; /* free library when done. */ FreeLibrary32W( hKernel32 ); /* set up name of 32-bit platform though unlikely to be other than Windows NT */ switch( os_vn_inf.dwPlatformId ) { case VER_PLATFORM_WIN32_NT: platform_type = "Windows NT"; break; case VER_PLATFORM_WIN32s: /* this will never happen, as Win32s does not support Generic Thunks */ platform_type = "Win32s"; break; case VER_PLATFORM_WIN32_WINDOWS: platform_type = "Windows 95"; break; default: /* cater for unknown platform types */ wsprintf( unknown_str, "Unknown type %lu", os_vn_inf.dwPlatformId ); platform_type = unknown_str; } /* prepare output buffer and give user information */ chrs = wsprintf( buff, "%s reports OS Version %u.%2u\n" "running on\n" "%s Version %u.%02u Build %u\n" "%s\n", (LPCSTR)( win_flags & WF_WINNT ? "WOW (Win16)" : "Win16" ), LOBYTE( win_vers ), HIBYTE( win_vers ), (LPCSTR)platform_type, (WORD)os_vn_inf.dwMajorVersion, (WORD)os_vn_inf.dwMinorVersion, (WORD)os_vn_inf.dwBuildNumber, (LPCSTR)os_vn_inf.szCSDVersion ); switch( os_vn_inf.dwPlatformId ) { case VER_PLATFORM_WIN32_NT: /* if Windows NT is the host add the type and CSD version */ wsprintf( buff + chrs, "Windows NT %s" "%s", (LPCSTR)nt_type(), (LPCSTR)nt_csd_num() ); break; case VER_PLATFORM_WIN32s: /* if Wind32s add the DOS version number - should never happen */ case VER_PLATFORM_WIN32_WINDOWS: /* if Windows 95 add the DOS version number */ wsprintf( buff + chrs, "with DOS version %d.%02d", HIBYTE( HIWORD( win_vers ) ), LOBYTE( HIWORD( win_vers ) ) ); break; } } else { #else /* if testing basic interfaces get library handle */ hKernel32 = LoadLibraryEx32W( KERNEL32, NULL, 0 ); #endif /* failed to get extended information - try for basic. under normal circumstances (i.e. when not testing) this should only run if Windows NT 3.1 is the host. */ if( hKernel32 == NULL || ( Get_Version = GetProcAddress32W( hKernel32, GETVERN ) ) == NULL ) { /* somehow we failed to get a handle to KERNEL32.DLL, or the address of the GetVersion function - this should never happen!!! */ wsprintf( buff, "Error\n" "\n" "No %s" KERNEL32 " module\n" "\n" "???? - How can this be - ????", hKernel32 != NULL ? GETVERN " function in" : "" ); if( hKernel32 != NULL ) { FreeLibrary32W( hKernel32 ); } } else { DWORD nt_vers; /* underlying OS version and build */ /* get OS version number reported by the 32-bit OS. no parameters, so no translation. note that here the CallProc32W function is used, as the CallProcEx32W function does not exist on Windows NT 3.1, and we want this program to work on all Windows NT versions. */ nt_vers = CallProc32W__0( Get_Version, 0, 0 ); /*free the library when done */ FreeLibrary32W( hKernel32 ); /* prepare output buffer and give user information */ chrs = wsprintf( buff, "%s reports OS Version %u.%2u\n" "running on\n" "Windows %s Version %u.%02u", (LPCSTR)( win_flags & WF_WINNT ? "WOW (Win16)" : "Win16" ), (WORD)maj_vers, (WORD)min_vers, (LPCSTR)( win_flags & WF_WINNT ? "NT" : "95" ), LOBYTE( LOWORD( nt_vers ) ), HIBYTE( LOWORD( nt_vers ) ) ); if( win_flags & WF_WINNT ) { /* if Windows NT is the host add the build number (which is not present in the GetVersion response on Windows 95) and the type and CSD version */ /* data for access to registry for Service Pack level */ union { DWORD csd_no; /* CSD as number in NT 3.1 */ char csd_str[ CSD_STR_SIZE + 1 ]; /* CSD as string in NT 3.5 */ } csd_data; /* CSD as number or string */ DWORD csd_type; /* CSD value type */ DWORD csd_size = sizeof( csd_data ); /* CSD size in bytes */ /* get service pack info from the Windows NT registry */ if( reg32_access( REG_CSD_KEY, REG_CSD_DATA, &csd_type, (LPBYTE)&csd_data, &csd_size ) == TRUE ) { /* display depends on the data type from the Registry */ switch( csd_type ) { case REG_DWORD: /* this is Windows NT with numeric CSD version - this is the code used on Windows NT 3.1 by the basic interface - form a service pack string */ wsprintf( csd_data.csd_str, "Service Pack %lu (CSD Version 0x%lX)\n", CSD_TO_SP( csd_data.csd_no ) , csd_data.csd_no ); break; case REG_SZ: /* this should only be used when testing the basic interface on Windows NT 3.5 - add line terminator */ lstrcat( csd_data.csd_str, "\n" ); break; default: /* this should never happen! */ lstrcpy( csd_data.csd_str, "??\n" ); /* drop through and out */ } } wsprintf( buff + chrs, " Build %u\n" "%s" "Windows NT %s" "%s", HIWORD( nt_vers ) & 0x7FFF, (LPCSTR)csd_data.csd_str, (LPCSTR)nt_type(), (LPCSTR)nt_csd_num() ); } else { /* on Windows 95 (but never on Win32s!!) add the DOS version number */ wsprintf( buff + chrs, "\n" "with DOS version %d.%02d", HIBYTE( HIWORD( win_vers ) ), LOBYTE( HIWORD( win_vers ) ) ); } } #if !defined( TEST_BASIC_IF ) } #endif /* end of code for Windows NT as host */ } else { /* Program is running on real 16-bit Windows 3.1x - here the program can find out which version, if any, of Win32s is available, and which particular build of Windows 3.1x this really is */ char file_name[ _MAX_PATH ]; /* constructed name of a file */ /* space for WINVER.EXE data */ char winver_buff[ WINVER_LENGTH + 1 ] = ""; /* string from WINVER.EXE file, also used for result string */ /* data for Win32s */ typedef struct { BYTE bMajor; /* Win32s major version number */ BYTE bMinor; /* Win32s minor version number */ WORD wBuildNumber; /* Win32s build number */ BOOL fDebug; /* Win32s retail/debug flag */ } WIN32SINFO, far *LPWIN32SINFO; typedef int ( WINAPI *GW32S_PTR )( LPWIN32SINFO ); /* type of ptr to GetWin32sInfo function */ HINSTANCE h_win32s; /* handle to Win32s library */ GW32S_PTR Get_Win32s_Info; /* address of Win32s version function */ WIN32SINFO win32s_info; /* the Win32s version information */ char win32s_ver[ 128 ]; /* filled with Win32s version - if any */ /* data for USER.EXE build string */ HINSTANCE h_userexe; /* handle to USER.EXE library */ char bld_temp[ 64 ] = ""; /* space for build string */ char bld_str[ 64 ] = ""; /* space for build result */ /* data for DOS build string */ char dos_bld_id[ 64 ] = ""; /* for DOS build information */ /* ++++ these are only declared 'far' to keep the code working when the Borland C++ 4.5 compiler is used */ char far *wfwg_string; char far *wfwg_spacer; WORD win_for_wkg_check( void ); /* our function to check if this is Windows for Workgroups */ WORD get_dos_win_ver( void ); /* our function to ask DOS what version of Windows it thinks */ BOOL win32s_running( void ); /* our function to detect if Win32s is actually running */ /* get file version information from USER.EXE file - the only way of getting the true version number! */ if( GetSystemDirectory( file_name, _MAX_PATH ) != 0 ) { /* this is slightly simplified since V1.6 since now that it is not excecuted under Windows NT we do not have to worry about the possible alternate directory for the file */ DWORD inf_handle; /* handle to version information in USER.EXE */ DWORD inf_size; /* how big is version info buffer */ LPBYTE ver_buff; /* ptr to mem allocated for ver info buff */ VS_FIXEDFILEINFO FAR *fixed_info; /* ptr to fixed info in ver info buff */ UINT fixed_size; /* number of bytes read for ver info */ /* add file name to directory */ lstrcat( file_name, "\\" USER_EXE ); /* see if there is a file with constructed name that contains version info */ if( ( inf_size = GetFileVersionInfoSize( file_name, &inf_handle ) ) != 0 ) { HGLOBAL h_mem; /* we have an appropriate file name and the size of version info buffer - allocate space for it */ if( NULL != ( ver_buff = (LPBYTE)GlobalLock( h_mem = GlobalAlloc( GPTR, inf_size ) ) ) ) { /* get version information */ if( GetFileVersionInfo( file_name, inf_handle, inf_size, ver_buff ) != 0 ) { /* get ptr to version info in buffer */ if( VerQueryValue( ver_buff, FILEINFO_KEY, (LPVOID FAR*)&fixed_info, &fixed_size ) != 0 ) { wsprintf( file_ver, USER_EXE " File Version %u.%u\n", HIWORD( fixed_info->dwFileVersionMS ), LOWORD( fixed_info->dwFileVersionMS ) ); } } GlobalUnlock( h_mem ); GlobalFree( h_mem ); } } } /* set up WFWG information */ switch( win_for_wkg_check() ) { case WFWG_NOT_FOUND: wfwg_string = "Not Found"; wfwg_spacer = " "; break; case WFWG_FOUND: wfwg_string = "Found\n but Network not Running"; wfwg_spacer = " "; break; case WFWG_RUNNING: wfwg_string = "Found\n and Network Running"; wfwg_spacer = " "; break; default: wfwg_string = wfwg_spacer = ""; } /* get Windows version string from the WINVER.EXE file which should be in the main Windows directory */ if( GetWindowsDirectory( file_name, _MAX_PATH ) != 0 ) { HFILE h_winver; /* add the file name to the Windows directory name */ lstrcat( file_name, "\\" WINVER_EXE ); /* see if we can open the file */ if( ( h_winver = _lopen( file_name, READ ) ) != HFILE_ERROR ) { /* seek to start of string and read it in */ if( _llseek( h_winver, WINVER_OFFSET, SEEK_SET ) != HFILE_ERROR && _lread( h_winver, winver_buff, WINVER_LENGTH ) == WINVER_LENGTH ) { /* make sure string is terminated */ winver_buff[ WINVER_LENGTH ] = '\0'; /* check that the string looks sensible - i.e. it starts with "Windows" - using the standard C function for the compare as the Windows function does not allow the definition of the length, and see if the string contains "Workgroup" within it */ if( strncmp( winver_buff, "Windows", sizeof( "Windows" ) - 1 ) == 0 && strstr( winver_buff, "Workgroup" ) != NULL ) { /* yup - string looks OK and Workgroup found in string */ lstrcpy( winver_buff, WINVER_EXE " indicates Workgroups\n" ); } else { /* nope - blank out string */ winver_buff[ 0 ] = '\0'; } } /* tidy up */ _lclose( h_winver ); } } if( ( ( maj_vers << 8 ) + min_vers ) >= 0x030A ) { /* reported version number is 3.10 or greater so double check because Windows for Workgroups 3.11 and Windows 3.11 both report the version as 3.10 */ WORD dos_win_vers; dos_win_vers = get_dos_win_ver(); if( dos_win_vers != VER_UNKNOWN ) { /* setup DOS information */ wsprintf( dos_bld_id, " DOS reports Windows %u.%u\n", HIBYTE( dos_win_vers ), LOBYTE( dos_win_vers ) ); } /* don't disable No File Found error box - we should *always* find USER.EXE! */ h_userexe = LoadLibrary( USER_EXE ); if( h_userexe > HINSTANCE_ERROR ) { if( LoadString( h_userexe, BUILD_ID, bld_temp, sizeof( bld_temp ) ) != 0 ) { wsprintf( bld_str, "USER.EXE build id Windows %s\n", (LPCSTR)bld_temp ); } FreeLibrary( h_userexe ); } } /* disable "File Not Found" error box for the case where Win32s is not installed */ SetErrorMode( SEM_NOOPENFILEERRORBOX ); /* now try and load Win32s library to find out what (if any) version of Win32s is available */ h_win32s = LoadLibrary( W32SYS ); if( h_win32s > HINSTANCE_ERROR ) { /* Win32s library loaded so Win32s does exist, find out if it is running. note that though we have loaded the W32SYS library this does *not* cause Win32s to run so we do get a correct result, a 32-bit Win32s application has to be actually running. When all Win32s programs have closed the test will return false.*/ BOOL win32s_state = win32s_running(); /* get address of Win32s info function */ Get_Win32s_Info = (GW32S_PTR)GetProcAddress( h_win32s, WIN32SINF ); if( Get_Win32s_Info ) { int win32_stat; /* Win32s Version 1.1 or later installed. there is no need for the slightly more complex (*Get_Win32s_Info)( ... ) form of call used in the Win32s Programmer's Reference as modern compilers know to use a function ptr to call a function when the construct is appropriate. */ if( ( win32_stat = Get_Win32s_Info( &win32s_info ) ) == 0 ) { /* Win32s OK - report information */ wsprintf( win32s_ver, "\n Supporting Version %u.%u Build %u\n" " of Win32s - %s version\n" " Win32s %s running", win32s_info.bMajor, win32s_info.bMinor, win32s_info.wBuildNumber, (LPCSTR)( win32s_info.fDebug ? "Debug" : "Retail" ), (LPCSTR)( win32s_state ? "is" : "not" ) ); } else { /* Win32s failure */ static char *fmt = "\n Win32s VXD not %s"; switch( win32_stat ) { case 1: wsprintf( win32s_ver, fmt, (LPCSTR)"present" ); break; case 2: wsprintf( win32s_ver, fmt, (LPCSTR)"loaded\n because paging not enabled" ); break; case 3: wsprintf( win32s_ver, fmt, (LPCSTR)"loaded\n because running in Standard Mode" ); break; default: wsprintf( win32s_ver, "\n Win32s fail reason %d", win32_stat ); } } } else { /* if no function then Win32s is version 1.0 */ wsprintf( win32s_ver, "\nSupporting Version 1.0\n" "\tof Win32s" " Win32s %s running", (LPCSTR)( win32s_state ? "is" : "not" ) ); } FreeLibrary( h_win32s ); } else { /* no Win32s library */ wsprintf( win32s_ver, " Win32s not available" ); } /* ++++ this compiles badly on BC++ 4.5 unless the wfwg_... strings are declared far. */ wsprintf( buff, " Windows reports Version %u.%02u\n" "%s in %s mode\n" " on Version %u.%02u of DOS\n" "%sWin for Workgroups %s\n" "%s" /* WINVER.EXE Workgroups */ "%s\n" /* Win32s string */ "\n" "%s" /* USER.EXE build id */ "%s" /* USER.EXE file version */ "%s", /* DOS build */ LOBYTE( LOWORD( win_vers ) ), HIBYTE( LOWORD( win_vers ) ), (LPCSTR)( ( win_flags & ( WF_ENHANCED | WF_STANDARD ) ) ? "" : " " ), (LPCSTR)( win_flags & WF_ENHANCED ? "Enhanced" : win_flags & WF_STANDARD ? "Standard" : "Real" ), HIBYTE( HIWORD( win_vers ) ), LOBYTE( HIWORD( win_vers ) ), (LPCSTR)wfwg_spacer, (LPCSTR)wfwg_string, (LPCSTR)winver_buff, (LPCSTR)win32s_ver, (LPCSTR)bld_str, (LPCSTR)file_ver, (LPCSTR)dos_bld_id ); } /* ========================== end of 16-bit Windows code ========================== */ #endif MessageBox( NULL, buff, PROG_NAME TEXT( " : " ) TEXT( WIN_BITS ) TEXT( "-bit" ), MB_OK | MB_ICONINFORMATION ); return( FALSE ); } #if defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ ) /* ========================================= support function only used in 32-bit mode ========================================= */ BOOL reg32_access( PTCHAR key, PTCHAR val, LPDWORD data_type_ptr, LPBYTE data_ptr, LPDWORD data_size_ptr ) { /* this function reads a single value held under the HKEY_LOCAL_MACHINE key in the Windows NT registry */ HKEY reg_key; BOOL ret_val = FALSE; if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, key, 0, KEY_READ, ®_key ) ) { if( ERROR_SUCCESS == RegQueryValueEx( reg_key, val, 0, data_type_ptr, data_ptr, data_size_ptr ) ) { /* all OK */ ret_val = TRUE; } /* tidy up if we got key */ RegCloseKey( reg_key ); } return( ret_val ); } #else /* ========================================== support functions only used in 16-bit mode ========================================== */ #define MPX_INT 0x2f /* DOS Multiplex Interrupt */ #define MPX_WIN_VER 0x160A /* Get Windows Version */ #define DPMI_INT 0x31 /* DPMI Interrupt */ #define DPMI_REAL_INT 0x0300 /* DPMI call real-mode int */ #include /* this uses the DPMI interface to call real-mode INT 0x2F to get Windows version as seen by a DOS program. If the program directly uses INT 0x2F it gets a protected mode interupt that does not support the required interface. Paul A LeBlanc of Borland's TeamB on CIS's BCPPWIN forum suggested the technuique of using a DPMI real-mode interrupt for getting at the Windows version reported to a DOS program. */ WORD get_dos_win_ver( void ) { /* DPMI Function 0x0300 (real-mode interrupt) register set */ struct real_regs { DWORD reg_edi; DWORD reg_esi; DWORD reg_ebp; DWORD reg_reserved; DWORD reg_ebx; DWORD reg_edx; DWORD reg_ecx; DWORD reg_eax; WORD reg_status; WORD reg_es; WORD reg_ds; WORD reg_fs; WORD reg_gs; WORD reg_ip; WORD reg_cs; WORD reg_sp; WORD reg_ss; } real_regs; static struct real_regs far *real_reg_ptr; /* just make sure program is running in protected mode */ if( ( GetWinFlags() & WF_PMODE ) == 0 ) { /* real mode - this should never happen! */ return( VER_UNKNOWN ); } /* program is already running in protected mode, so it is okay to make DPMI (INT 0x31) calls */ real_reg_ptr = &real_regs; /* init ptr to register set */ _fmemset( real_reg_ptr, 0, sizeof( real_regs ) ); /* clear register set */ real_regs.reg_eax = MPX_WIN_VER; /* MPX fn to get WIN version */ _asm { les di, real_reg_ptr /* ptr to register set */ mov bx, MPX_INT /* INT 0x2f - flags zero */ mov cx, 0 /* 0 words to real-mode stack */ mov ax, DPMI_REAL_INT /* DPMI call real-mode int */ int DPMI_INT /* go to it */ jc dpmi_bad /* carry set on error */ } return( LOWORD( real_regs.reg_ebx ) ); /* return word with WIN vers */ dpmi_bad: return( VER_UNKNOWN ); /* version reported to DOS not known */ } /* the next function tried to detect whether this is a Windows for Workgroups installation */ /* things unique to the next function. These are included here so that special Win for Workgroups SDK stuff is not needed. the function prototype and the WNNC_... defines are also in the Workgroups SDK WINNET.H file */ #include #include #if !defined( WNNC_NET_TYPE ) /* if already defined don't do again */ /* the DEF file identifies this as in the User module */ WORD WINAPI WNetGetCaps( WORD ); #define WNNC_NET_TYPE 0x0002 #define WNNC_NET_MultiNet 0x8000 #define WNNC_SUBNET_WinWorkgroups 0x0004 #endif #define ACC_EXIST 0 /* value for 'file exists' in access() */ WORD win_for_wkg_check( void ) { WORD net_flags; char wfwnet_name[ _MAX_PATH ]; /* check for network type */ net_flags = WNetGetCaps( WNNC_NET_TYPE ); /* check for mutiple-network bit */ if( net_flags & WNNC_NET_MultiNet ) { /* check low byte for WFWG bit */ if( ( LOBYTE( net_flags ) ) & WNNC_SUBNET_WinWorkgroups ) { /* net detect report Windows for Workgroups */ return( WFWG_RUNNING ); } } /* network not running - but this does not mean this is not Windows for Workgroups. construct name and check it exists */ if( GetSystemDirectory( wfwnet_name, sizeof( wfwnet_name ) ) ) { lstrcat( wfwnet_name, "\\wfwnet.drv" ); if( access( wfwnet_name, ACC_EXIST ) == 0 ) { /* if file found this system is Windows for Workgroups */ return( WFWG_FOUND ); } /* if file not found this is not Windows for Workgroups (probably!) */ } return( WFWG_NOT_FOUND ); } /* the next function detects if Win32s is actully running, as opposed to being installed but not running */ #include #define WIN32S_MODULE "WIN32S16" /* name of Win32s module loaded when Win32s is actually running */ BOOL win32s_running( void ) { MODULEENTRY module_entry = { sizeof( MODULEENTRY ) }; /* if we can find module data then Win32s is running. no need to dynamically link to the ModuleFindName function as TOOLHELP.DLL should be installed on a Windows 3.1 (or later) system supporting Win32s. */ if( ModuleFindName( &module_entry, WIN32S_MODULE ) != NULL ) { return( TRUE ); } /* if we get here no Win32s application is running */ return( FALSE ); } /* the next function accesses the Windows NT Registry from a 16-bit program */ /* handle to Registry key pre-defined in 32-bit environment but not in 16-bit */ #define HKEY_LOCAL_MACHINE ( (DWORD)0x80000002UL ) /* and error return value - defined here to make sure we get correct 32-bit value */ #define ERROR_SUCCESS 0 /* more variations of the CallProc32W function. these are all mapped to CallProc32W by the DEF file */ DWORD FAR PASCAL CallProc32W__1( DWORD, DWORD, DWORD, DWORD ); /* 1 param */ DWORD FAR PASCAL CallProc32W__5( DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD ); /* 5 params */ DWORD FAR PASCAL CallProc32W__6( DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD ); /* 6 params */ /* to define the number of params to the function called - the last parameter to CallProc32W */ #define NUM_PARAMS( xx ) ( (DWORD)( xx ) ) /* a macro to set an address translation bit for the CallProc32W function so that we can use the normal param number even though the bits are used 'in reverse'. the bits form the penultimate parameter to CallProc32W */ #define ADDR( parno, numpars ) ( (DWORD)1 << ( numpars - parno ) ) /* used as penultimate param if no parameters to function called by CallProc32W require address translation */ #define NO_ADDR ( (DWORD)0 ) #define ADVAPI32 "ADVAPI32.DLL" /* module with 32-bit Reg... functions */ #define REGOPENKEYEX "RegOpenKeyExA" /* name of 32-bit ASCII RegOpenKeyEx() */ #define REGQUERYVALUEEX "RegQueryValueExA" /* name of 32-bit ASCII RegQueryValueEx() */ #define REGCLOSEKEY "RegCloseKey" /* name of 32-bit RegCloseKey */ #define REG_QUERY_VALUE 0x0001 /* manifest constant for ReqOpenKeyEx */ BOOL reg32_access( char *key, char *val, LPDWORD data_type, LPBYTE data, LPDWORD data_size ) { /* this function reads a single value held under the HKEY_LOCAL_MACHINE key in the Windows NT registry */ DWORD hAdvAPI32; /* handle to 32-bit ADVAPI32 module */ DWORD Reg_Open_Key_Ex; /* address in that of 32-bit RegOpenKeyEx function */ DWORD Reg_Query_Value_Ex; /* and address of 32-bit RegQueryValueEx function */ DWORD Reg_Close_Key; /* and address of 32-bit RegCloseKey function */ DWORD hKey; /* 32-bit key */ BOOL ret_val = FALSE; /* default return value */ if( ( hAdvAPI32 = LoadLibraryEx32W( ADVAPI32, NULL, 0 ) ) != NULL ) { /* got 32-bit module handle OK - get 32-bit function addresses */ if( ( Reg_Open_Key_Ex = GetProcAddress32W( hAdvAPI32, REGOPENKEYEX ) ) != NULL && ( Reg_Query_Value_Ex = GetProcAddress32W( hAdvAPI32, REGQUERYVALUEEX ) ) != NULL && ( Reg_Close_Key = GetProcAddress32W( hAdvAPI32, REGCLOSEKEY ) ) != NULL && /* got 32-bit function addresses - access registry */ ERROR_SUCCESS == CallProc32W__5( /* open key */ HKEY_LOCAL_MACHINE, (DWORD)(LPSTR)key, 0, REG_QUERY_VALUE, (DWORD)(LPDWORD)&hKey, Reg_Open_Key_Ex, ADDR( 2, 5 ) | ADDR( 5, 5 ), NUM_PARAMS( 5 ) ) && ERROR_SUCCESS == CallProc32W__6( /* query value */ hKey, (DWORD)(LPSTR)val, 0, (DWORD)data_type, (DWORD)data, (DWORD)data_size, Reg_Query_Value_Ex, ADDR( 2, 6 ) | ADDR( 4, 6 ) | ADDR( 5, 6 ) | ADDR( 6, 6 ), NUM_PARAMS( 6 ) ) && ERROR_SUCCESS == CallProc32W__1( /* close key */ hKey, Reg_Close_Key, NO_ADDR, NUM_PARAMS( 1 ) ) ) { /* access OK */ ret_val = TRUE; } /* tidy up whether or not we got value */ FreeLibrary32W( hAdvAPI32 ); } return( ret_val ); } #endif /* ====================================================== support functions used in 16-bit mode and 32-bit modes ====================================================== */ /* note that these functions use TCHAR to compile properly for UNICODE or not */ #define PRODUCTTYPE_KEY "System\\CurrentControlSet\\Control\\ProductOptions" /* Registry key under HKEY_LOCAL_MACHINE that tells us whether host OS is NT Workstaion or NT Server */ #define PRODUCTTYPE_VAL "ProductType" /* data item under that key with useful info. */ #define PRODUCTTYPE_LEN 16 /* space to allow for product type string */ #define NT_WORKSTATION "WinNT" /* value in Windows NT Registry that indicates if this is Workstation, other entries (either SERVERNT or LANMANNT) indicate server. */ #define NT_SERVER "ServerNT" /* value ... for NT Server */ #define NT_LANMAN "LanManNT" /* value ... for Lan Manager NT */ #define PRODUCTTYPE_CSD "CSDVersion" /* value in the Registry under the ProductOptions key for numeric CSD Version. exists on Windows NT 3.5 when a service pack is loaded but not on Windows NT 3.1 where the CSD Version under the CurrentVersion key is numeric. */ /* determine Windows NT type from string */ PTCHAR winnt_type( PTCHAR val_buff ) { /* start compares with value for Workstation */ if( lstrcmpi( val_buff, TEXT( NT_WORKSTATION ) ) == 0 ) { /* yes - workstation */ return( TEXT( "Workstation" ) ); } /* no - must be a server of some kind */ if( lstrcmpi( val_buff, TEXT( NT_SERVER ) ) == 0 ) { /* this is NT Server */ return( TEXT( "Server" ) ); } /* not NT server - try LAN Manager */ if( lstrcmpi( val_buff, TEXT( NT_LANMAN ) ) == 0 ) { /* this is LAN Manager on NT */ return( TEXT( "Lan Manager" ) ); } /* oops - don't recognize the type */ return( TEXT( "( Unknown Type [Server?] )" ) ); } /* the next two functions use reg32_access to be 16-bit/32-bit portable */ /* determines whether this is Windows NT Workstation or Server, works on Windows NT 3.1 and Windows NT 3.5. */ PTCHAR nt_type( void ) { TCHAR val_buff[ PRODUCTTYPE_LEN ]; DWORD val_buff_size = sizeof( val_buff ); /* even with UNICODE size is *ALWAYS* bytes */ DWORD val_type; PTCHAR retstr = TEXT( "( Type Not Known )" ); /* default return string */ /* open the master key for the product type information - read value and check type */ if( reg32_access( TEXT( PRODUCTTYPE_KEY ), TEXT( PRODUCTTYPE_VAL ), &val_type, (LPBYTE)val_buff, &val_buff_size ) && REG_SZ == val_type ) { /* get type of Windows NT */ retstr = winnt_type( val_buff ); } return( retstr ); } /* function gets numeric service pack identity */ PTCHAR nt_csd_num( void ) { DWORD csd_val; DWORD csd_val_size = sizeof( csd_val ); DWORD csd_type; static TCHAR retstr[ 64 ] = TEXT( "" ); /* default return string */ /* open the master key for the product type information - read value and check type */ if( reg32_access( TEXT( PRODUCTTYPE_KEY ), TEXT( PRODUCTTYPE_CSD ), &csd_type, (LPBYTE)&csd_val, &csd_val_size ) && REG_DWORD == csd_type ) { /* get type of Windows NT */ wsprintf( retstr, TEXT( "\nService Pack %lu (CSD Version 0x%lX)" ), CSD_TO_SP( csd_val ), csd_val ); } return( retstr ); }