Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

VerCheck: Discovering Component Version Numbers


Dr. Dobb's Journal March 1998: VerCheck: Discovering Component Version Numbers

John, director of product architecture for Optimal Networks, can be contacted at [email protected].


Software version numbers are often the bane of a programmer's life -- they must be checked for compatibility purposes, incremented for new releases, matched against information stored in source-control systems, and frequently changed on the whims of the marketing department.

Most programmers shipping commercial software for 32-bit Windows are faced with the task of installing common DLLs and OCXes that form parts of their projects. I've worked on software where up to 25 different components (none of which I created) had to be installed and upgraded after matching version numbers with the customer's machine.

In addition, Microsoft frequently updates these common components as new features are added and revisions are made to operating systems. Many of these versions appear in OEM Service Releases (OSR) and Service Packs for Windows 95/NT, forcing us to figure out which set of DLLs and OCXes is most recent and compatible. And with the introduction of Internet Explorer, Microsoft has so accelerated the pace of new common control versions that I have to check Microsoft's online Knowledgebase on a daily basis.

To help us, Microsoft creates a REDIST directory (on the Visual C++ 5.0 disk, the full path is \DEVSTUDIO\VC\REDIST) containing copies of the components that may need to be redistributed and that constitute a compatible set. Programmers using Visual Basic can find a similar collection of DLLs and OCXes on the Visual Basic CD, and anyone using the Data Access Objects (DAO) can also find information about the latest versions in the Knowledgebase.

All this works well until the software is released -- then you're suddenly faced with customers who have varying levels of service packs and OSRs, not to mention versions of Office 97 or Internet Explorer that have updates to some of the DLLs and OCXes on which their program depends. More than once, I've seen problems caused by an incompatible set of components.

The VerCheck Program

I'm often faced with the task of walking customers through their WINDOWS\ SYSTEM directory and asking them to read to me version numbers of things like COMCTL32.DLL (as opposed to COMCT232.DLL and COMCTL32.OCX). I've also noticed that once you've gone through about three such version strings over the phone, users get a little tired. Hence, the program I present here, VerCheck, provides you with a list of the versions of all the components of relevance to your program. It is also convenient for both technical support (the program requires no configuration or command-line arguments) and customers (it is quick and easy to use).

I created VerCheck so that it was flexible (the list of components to check is provided in a text file), simple (users simply type VERCHECK), and easy to deliver (technical support need only send users an archive containing two files). Since I wanted it to run under Win32 without any supporting DLLs (which would have put me into a loop trying to check DLL versions for the version checker!), I opted to implement VerCheck as a Win32 Console Application. The complete source code and executables for VerCheck are available electronically; see "Resource Center," page 3.

How it Works

How do you retrieve the version number of a component of Windows? The answer lies in the resources attached to the component and a couple of Win32 API functions that are simple once you've read the documentation carefully.

The version number for an EXE, DLL, or OCX is stored in a special file resource called the VERSIONINFO that stores more than one version number for the program. Table 1 shows the VERSIONINFO resource for OLEAUT32.DLL (a common component that needs to be shipped with many third-party OCX controls -- it's worth checking its version and that of OLEPRO32.DLL if you are experiencing difficulties with an OCX). You can examine the version resource of a component by opening it in Developer Studio with the Resources option selected on the File Open dialog.

The first group of elements are binary resources, and the second are text-based resources and are retrieved as strings. You'll notice that there are FileVersion and ProductVersion numbers in both sections. FileVersion, what I generally think of as the version number, represents the version of this individual component and should be checked when deciding whether a DLL or OCX needs to be upgraded. ProductVersion defines the version of the software product for which this component is designed. With system DLLs, this tends to be the version of the OS that the component was released with, or, as in the case of OLEAUT32.DLL, it is the same as the FileVersion.

The binary equivalent of the FileVersion is FILEVERSION and is stored as a two 32-bit integers (unsigned!); as is the PRODUCTVERSION. All of these fields are described in the VERSIONINFO resource entry in the Developer Studio documentation. To provide simple information for version number checking, VerCheck reads the FileVersion entry for each DLL, OCX, or EXE that it is checking and writes it to a CSV file (use of the comma-separated file format means that the output of VerCheck can be read into programs like Microsoft Access or Excel for comparison). Since the FileVersion is a mandatory string, you can be guaranteed its existence (if the file has a VERSIONINFO resource).

VerCheck functions by reading a file called VERCHECK.LST (the list of files to be checked), reads the VERSIONINFO resource for each file, and writes the result to VERCHECK.CSV. To make the program easy to use, I've forced VerCheck to find VERCHECK.LST in the current directory and to write VERCHECK.CSV to the same place. While this means you don't have great flexibility, it also means that a technical support representative does not have to explain as much of the operation of the program to a customer.

To read the FileVersion, VerCheck makes three different calls to the Win32 API: GetFileVersionInfoSize, GetFileVersionInfo, and VerQueryValue. GetFileVersionInfoSize retrieves the size of the VERSIONINFO block in the file being checked. Since the VERSIONINFO block is of variable length, and its entries are also of variable length, there is no C structure that defines the block (although Microsoft supplies some pseudo-C to help explain the outline): Therefore, it is necessary to dynamically assign space for the structure.

Once the space is malloc'd, you call GetFileVersionInfo, which returns a copy of the VERSIONINFO structure from the executable. GetFileVersionInfoSize and GetFileVersionInfo do not need the path of any DLLs and OCXes that are installed in the \WINDOWS\SYSTEM directory, they automatically search for them. Hence, you only need supply the name of any common components to retrieve the version information, Windows takes care of finding it.

Once you have the structure in memory, you need to find the FileVersion entry. Win32 conveniently provides the VerQueryValue function for this, alleviating the need to traverse this variable-size structure and return the string value. VerQueryValue can also be used to get CompanyName, FileDescription, InternalName, LegalCopyright, OriginalFilename, ProductName, and ProductVersion. The most important entry it returns is the Translation table that contains an array of language and char-set identifiers.

To retrieve the FileVersion, you need to specify the language and char-set that are to be retrieved; so, you make two calls to VerQueryValue to retrieve the FileVersion; see Listing One.

The rest of the program checks return codes and allows users to type "VERCHECK /?" to get a help screen. A simple command-line parser lets users enter a different LST and CSV filename, but without any parameters, the program uses the defaults.

Figure 1 shows an excerpted VERCHECK.LST file containing a list of common components to be check (the last entry shows the use of an explicit path to check a piece of the DAO libraries) and Figure 2 is an excerpt of output from my machine.

Conclusion

VerCheck is not a complex program, nor is its algorithm revolutionary, but it is an example of a utility that satisfies the programmer's need for accurate information, the customer's desire to be interrupted as little as possible, and technical support's need for simple solutions.

DDJ

Listing One

#include "stdio.h"              // We indicate progress with printf#include "windows.h"            // Needed for standard Windows definition
#include "winver.h"             // Needed for the version checking API


</p>
// Input and output files with defaults in case the user does not specify any
char sourceFile[      MAX_PATH ] = "VERCHECK.LST";
char destinationFile[ MAX_PATH ] = "VERCHECK.CSV";


</p>
/*--------------------------------------------------------------------------
| procedure: printHelp
| purpose:   Prints program help
--------------------------------------------------------------------------*/
void printHelp( void )
{
        printf( "Usage - vercheck [ ]\n" );
        printf( "                   Source LST file\n" );
        printf( "              Destination CSV file\n" );
}
/*-------------------------------------------------------------------------
| procedure: parceCL
| purpose:   Parses the command line for information
--------------------------------------------------------------------------*/
BOOL parseCL( int argc, char * argv[] )
{
        // Check to see if there are any parameters, if any is /? then
        // we will print help and return FALSE, otherwise read the file names
        if ( argc == 2 )
        {
                strlwr( argv[1] );
                if ( strcmp( argv[1], "/?" ) == 0 )
                {
                        printHelp();
                        return FALSE;
                }
        }
        if ( argc == 3 ) 
        {
                strlwr( argv[1] );
                strlwr( argv[2] );
                strcpy( sourceFile,     argv[1] );
                strcpy( destinationFile, argv[2] );
        }
        return TRUE;
}
/*--------------------------------------------------------------------------
| procedure: main
| purpose:   This is where the program begins...
--------------------------------------------------------------------------*/
void main( int argc, char * argv[] )
{
  FILE * versionLST;      // File handle for the list of files
  // Print the banner
  printf( "\nVersion Check v1.0\n\n" );
  // Check parameters and return if /?
  if ( !parseCL( argc, argv ) )
     return;      
  // Check for the file VERCHECK.LST which we use to work out what
  // we are version checking
  versionLST = fopen( sourceFile, "r" );
  // If the file is open then create the output file
  if ( versionLST )
  {
     FILE * versionOUT;   // File handle for the output file
     // Create the output file
     versionOUT = fopen( destinationFile, "w" );
     if ( versionOUT )
     {
       // Parse LST file and read version number of each file mentioned
       while ( !feof( versionLST ) )
       {
          // The file that we are going to check
          char versionFILE[ MAX_PATH ];
          // Get the file name to be version checked

          if ( fgets( versionFILE, MAX_PATH, versionLST ) )
          {
            LPVOID version;     // Storage for the version resource
            DWORD  versionSIZE; // The size of the version structure
            DWORD  dummy;
            DWORD  c;
            // Tidy up the line
            c = strlen( versionFILE );
            if ( c > 3 ) 
            {
               versionFILE[ c - 1 ] = '\0';
               printf( "Reading version of %s\n", versionFILE );
               // Get the size of the files version information
               versionSIZE = GetFileVersionInfoSize( versionFILE, &dummy );
               if ( versionSIZE > 0 ) 
               {
                  // Create space to receive the version number
                  version = malloc( versionSIZE );


</p>
                  if ( version )
                  {
                     DWORD  versionLENGTH;  // Length of actual version
                     LPVOID versionINFO;    // Version string
                     DWORD  languageLENGTH; // Length of actual version
                     LPWORD languageINFO;   // Version string


</p>
                     // Now read the version number of the file
                   if (GetFileVersionInfo(versionFILE,0,versionSIZE,version))
                     {
                        // Get actual version information
                        if (VerQueryValue(version,
                                "\\VarFileInfo\\Translation",
                                &languageINFO,&languageLENGTH) !=0)
                        {
                          char versionSTRING[ MAX_PATH ];
                          // Create string to retrieve resource, first 
                          // entry in language table is used
                          sprintf(versionSTRING,
                              "\\StringFileInfo\\%4.4X%4.4X\\FileVersion",
                              languageINFO[0],languageINFO[1]);
                          if (VerQueryValue(version,versionSTRING,
                              &versionINFO, &versionLENGTH) != 0)
                             fprintf(versionOUT,"\"%s\",\"%s\"\n",
                                             versionFILE,versionINFO);
                          else
                             fprintf(versionOUT,"\"%s\",
                                   \"Failed (file version)\"\n",versionFILE);
                        }
                        else
                           fprintf(versionOUT,"\"%s\",
                                   \"Failed (language)\"\n",versionFILE);
                     }
                     else 
                        fprintf(versionOUT, 
                                "\"%s\",\"Failed (version)\"\n",versionFILE);
                     free( version );
                  }
                  else
                     printf( "Failed to allocate memory\n" );
               }
               else
               {
               printf("Failed to get version info for %s\n", versionFILE);
                 fprintf(versionOUT,"\"%s\",\"Not found\"\n", versionFILE );
               }
            }
            else
               break;
          }
       }
       fclose( versionOUT );
     }
     else
        printf( "Failed to create output file %s\n", destinationFile );


</p>
     fclose( versionLST );
  }
  else
     printf( "Failed to find %s\n", sourceFile );
}

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.