Systems administrators often need to know file-version information—for example, to determine whether a software patch has been applied. The information is metadata, embedded in a Windows executable (e.g., .exe, .dll) file as a set of dot-delimited numbers, such as 22.214.171.124. From left to right, these numbers indicate major version, minor version, build, and revision. Each number is a 16-bit, unsigned integer. The first two numbers (i.e., major and minor version) represent the most significant 32 bits of the version number. The last two numbers (i.e., build and revision) represent the least significant 32 bits of the version number.
In Windows Script Host (WSH) scripts, you can retrieve a file's version by using the FileSystemObject object's GetFileVersion method. For example, the code in Listing 1 is a short VBScript script that outputs the file version of Notepad.exe.
In Windows PowerShell, you can use the VersionInfo property of a file object, which returns a .NET FileVersionInfo object. This object has four properties—FileMajorPart, FileMinorPart, FileBuildPart, and FilePrivatePart—that correspond to the four parts of the version number. The code in Listing 2 shows a short PowerShell function that uses the -f operator to output the version of Notepad.exe. This listing also shows how you can use the FileSystemObject in PowerShell to get the same information.
So far so good. But retrieving file versions is one thing; how do we compare those version numbers? We've already seen that a file-version number is actually a 64-bit, unsigned integer. VBScript doesn't have a 64-bit unsigned integer type. PowerShell has the UInt64 type, but not all PowerShell operators work with this type. To solve these problems, I wrote some VBScript and PowerShell script functions that take the pain out of comparing version numbers. Before presenting these functions, though, I need to give you a bit of background about how they work.
Step 1: Break Down the Bits
We can't use 64-bit numbers directly in VBScript, and we can't use these numbers in PowerShell without some missing operator support. So we'll begin by breaking down the version number into a pair of 32-bit numbers. Because we can't easily compare two 64-bit values, we'll compare two pairs of 32-bit values instead.
First, you need to know how to convert a string a.b, where a is the most significant 16 bits and b is the least significant 16 bits, into one 32-bit value. We can't simply interpret the string as a floating-point number, or the comparison won't work properly. For example, as floating-point numbers, 5.20 is equal to 5.2. In that scenario, 5.15 is the greater number (because 15 is greater than 2). In a version-number situation, that's obviously not the case: 5.20 is greater than 5.15.
To correctly convert the string a.b into a single 32-bit number, we perform a left shift on the first number and then add the second number to it. To illustrate, let's use the programmer mode in the Windows 7 calculator.
Open the calculator, Select View, Programmer, and then select the Hex and Dword radio buttons along the left edge of the window. (The effects are easier to observe in hexadecimal than in decimal.) In this example, we'll create a 32-bit number that represents the version number 3.7. Follow these steps in the calculator:
- Enter the number 3, for the major version.
- Click the Lsh button, enter the value 10 (i.e., hexadecimal for 16), and press Enter. This action shifts the number 16 bits to the left.
- Press +, then press 7, and then press Enter. This action adds the minor version.
The resulting value is hexadecimal 30007, as shown in Figure 1.
Figure 1: Sample Hexadecimal of a Converted String
Thus, the formula for converting the string a.b to a single 32-bit value is as follows:
(a Lsh 16) + b
Because VBScript and PowerShell don't have an Lsh operator, we'll use the following equivalent formula instead:
(a * 216) + b
I previously mentioned that I decided to break the 64 bits of a version number into two 32-bit numbers. In the script code, I decided to return the two 32-bit numbers as a two-element array. For example, the version number 126.96.36.199 would be returned as an array containing the values 196615 (30007 hexadecimal for 3.7) and 327681 (50001 hexadecimal for 5.1).