[ Go to September 1997 Table of Contents ]

Web Programming /
Martin Heller

Take a Windows Census

As the bead curtains clattered behind me, the shopkeeper gestured toward a back door with his eyes. Slipping past the dusty stacks of arcana and into the storeroom, I found and played the tape. By the time it self-destructed, I had my mission: Seek out and identify hidden Windows versions.

Okay, it wasn't quite that glamorous. WINDOWS Magazine wanted to build a Web page to help identify exactly what Windows components are installed on a system, and asked me to help out. I built an ActiveX control to do the job. Meanwhile, Optimizing Windows columnist John Woram has been working on identifying file versions from different Windows upgrades and components, and Power Windows columnist Karen Kenworthy has been using my ActiveX control and John's lists to build the actual Web page.

I did my part in Visual C++ using the ActiveX Template Library (ATL). I briefly considered Visual Basic 5.0, but I didn't think too many people would want to put up with a megabyte of component downloads when 25KB would do the job. I also considered Java, but realized that a Java applet couldn't get at the necessary system information because of restrictions on Java applets (for example, they can't read or write local files, talk to any server except the one they came from, or call native code to use system functions)

As always, I started by designing the methods and properties of the ActiveX object. I wound up with three methods: one to return a string describing the installed operating system (GetOS), one to return the operating system version as a string in <major>. <minor>. <build>form (GetSystemVersion) and one to return the version information resource from an executable file in the Windows directory or one of its subdirectories (GetFileVersion)

First, we needed an interface for these. To call the methods from VBScript (assuming the object is called ver), we'd write OS=ver.GetOS(), OSVer=ver.GetSystemVersion() and FileVer=ver.

GetFileVersion

(filename,folder), where folder is the Windows subdirectory that holds the file.

Each function's last parameter contains the attributes [out,retval] and is a pointer to a BSTR, which is the way to return a string from an ActiveX object. The BSTR is a Unicode string with a character count; OLE strings are always transmitted in Unicode.

Of course, Windows 95 programs use the 8-bit ANSI character set for string representations, not the 16-bit Unicode character set. Windows NT programs can use Unicode internally, but they don't have to. Fortunately, we can use system functions to convert Unicode strings to (WideCharToMultiByte) and from (MultiByteToWideChar) ANSI strings, to construct a BSTR from a Unicode string (SysAllocString) and to determine the size of a BSTR (SysStringLen). ATL also offers a helper class for BSTR manipulation, called CComBSTR.

Two kernel functions, GetVersion and GetVersionEx, tell us the operating system we're running and its version. GetVersion is universally supported; GetVersionEx exists in Windows NT 3.5 and later, and in all versions of Windows 95. It's just wonderful having a version-dependent function to determine an operating system's version.

We implement GetOS using GetVersion. If the desktop bit isn't set, the OS is Windows NT. If it is set, the OS is Windows 95 for a major version of 4 or greater and Windows 3.x with Win32s for a major version of 3. We use SysAllocString to create a BSTR that can be marshaled back to the calling program; the calling program is responsible for freeing the string later.

Finding out the system version is a little more complicated. Remember, Windows 95 doesn't return a build number to GetVersion, but it does return a build number to GetVersionEx. (A great design decision, huh?) And we can't call GetVersionEx until we know it exists in the current Windows version.

As you can see in the sidebar "Verify the Version," once we have the major version, minor version and build, we can format the return string with a combination of _itow, and the CComBSTR = and += operators. The _itow command converts an integer to a Unicode string, and the CComBSTR = and += operators copy and append a Unicode string to the CComBSTR object's internal BSTR member. When the string is complete, we use the CComBSTR copy method to return a BSTR for the calling program to use. Our CComBSTR object will automatically release its internal BSTR when it goes out of scope and destructs.

We can get the version resource from a Win32 executable file or DLL using three functions from the version-checking DLL, which is normally used during program installations. GetFileVersionInfoSize(filename,pHandle) returns the size of the version resource in filename and sets the handle pointed at by pHandle to zero. This gives you an opportunity to dynamically allocate the correct amount of memory for the version resource. GetFileVersionInfo fills a memory with the version resource. Finally, VerQueryValue returns a pointer to a named part of the version resource, such as the file version.

In the implementation of the GetFileVersion method we start by checking the filename parameter. If it's a null string, we return an error message. Then we convert the filename from a BSTR to ANSI, using WideCharToMultiByte. The constant CP_ACP in this call specifies the conversion to the ANSI code page.

Then we check and convert the winfolder parameter. If it's a null string, we'll be searching the Windows directory; otherwise, we'll be searching a subdirectory, perhaps System or System32. In any case, we have to determine the name of the Windows directory for the current installation, using the GetWindowsDirectory function. We use the Win32 string function lstrcat to append the subdirectory and filename to the Windows directory string.

Once the full path has been constructed, we can look for the file. GetFileVersionInfoSize returns zero for the size if the file is not found, so we don't have to explicitly test for the file's presence. As before, we use a CComBSTR object to construct and hold our return string.

We create a buffer to hold the version information resource with malloc and free it later. We call GetFileVersionInfo and VerQueryValue, and return the file version resource string if it's found or an error message if it's not found.

That's basically all there is to it. Of course, I also had to mark the control safe for scripting and sign it, but we talked about how that's done in May. You can find the code for all three methods at http://www.winmag.com/people/heller. If you'd like to try out the control in the context of the finished application, you'll find it at http://www.winmag.com/windows.

Enjoy!

When he's not watching reruns of "Mission: Impossible," Martin Heller writes about and does Windows and Web programming from Andover, Mass. View Martin's Web site at http://www.winmag.com/people/mheller/, or contact him care of the editor at the addresses on page 20.

SIDEBAR: Verify the Version

STDMETHODIMP CVer::GetSystemVersion(BSTR * Version)

{

DWORD dwVersion = GetVersion()

DWORD dwWindowsMajorVersion =

(DWORD)(LOBYTE(LOWORD(dwVersion)))

DWORD dwWindowsMinorVersion =

(DWORD)(HIBYTE(LOWORD(dwVersion)))

DWORD dwBuild = 0;

if (dwVersion < 0x80000000&RPAR; // Windows NT</P>

dwBuild = (DWORD)(HIWORD(dwVersion))

else if (dwWindowsMajorVersion < 4&RPAR; // Win32s</P>

dwBuild = (DWORD)(HIWORD(dwVersion) & ~0x8000)

else { // Windows 95 - No build numbers provided

//need GetVersionEx

OSVERSIONINFO vi;

vi.dwOSVersionInfoSize=sizeof(vi)

GetVersionEx(&vi)

dwBuild = LOWORD(vi.dwBuildNumber)

}

//format return string as major.minor.build

wchar_t szTemp[40]=L"";

wchar_t dot[]=L".";

CComBSTR bst;

_itow(dwWindowsMajorVersion,szTemp,10)

bst=szTemp;

bst+=dot;

_itow(dwWindowsMinorVersion,szTemp,10)

bst+=szTemp;

bst+=dot;

_itow(dwBuild,szTemp,10)

bst+=szTemp;

*Version=bst.Copy()

return S_OK;

}

Here is an implementation of GetSystemVersion. For the complete source code, visit http://www.winmag.com/people/heller.


Windows Magazine, September 1997, page 279.

[ Go to September 1997 Table of Contents ]