Downloads
142571.zip

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 12.1.42.0. 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:

  1. Enter the number 3, for the major version.
  2. 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.
  3. 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
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 3.7.5.1 would be returned as an array containing the values 196615 (30007 hexadecimal for 3.7) and 327681 (50001 hexadecimal for 5.1).

 

Step 2: Compare the Versions

So far, we've seen that we can represent a 64-bit value as a pair of 32-bit values. The question now is how do we compare those values? Given two version numbers a.b.c.d and w.x.y.z, Table 1 shows the possible comparison results.

Table 1: Comparing Version Numbers

Step 3: Writing the Code

Now that you understand the nature of file-version numbers and how to compare them, we're ready to take a look at the VBScript and PowerShell code that does the work. Listing 3 contains the VBScript functions, and Listing 4 contains the same functions written in PowerShell. These functions are described in Table 2.

Table 2: Functions Needed to Compare Versions

Take care to note the syntax difference between VBScript and PowerShell when using these functions in your own code. In VBScript, function calls require parentheses with a comma between parameters, whereas in PowerShell the parentheses and commas must be omitted. For example, in VBScript, you would write

Result = CompareVersions(Ver1, Ver2)

whereas in PowerShell, the equivalent code statement is

$Result = CompareVersions $Ver1 $Ver2

To see these functions in action, download the code. Versions.vbs and Versions.ps1 are standalone scripts that use these functions to let you compare file versions from the command line. The scripts contain some additional functions as well: ValidateVersionString returns whether a specified version string is valid, and GetVersionArrayAsString is the inverse of GetVersionStringAsArray. I've also included a JScript version of the script, Versions.js, just in case JScript is your preferred language.

 

Listing 1: Retrieving a File's Version in VBScript
Dim FSO, FileName
Set FSO = CreateObject("Scripting.FileSystemObject")
FileName = "C:\Windows\system32\notepad.exe"
WScript.Echo FSO.GetFileVersion(FileName)

 

Listing 2: Retrieving a File's Version in PowerShell
$fileName = "C:\Windows\system32\notepad.exe"

function get-fileversion {
  param([System.IO.FileInfo] $fileItem)
  $verInfo = $fileItem.VersionInfo
  "{0}.{1}.{2}.{3}" -f
    $verInfo.FileMajorPart,
    $verInfo.FileMinorPart,
    $verInfo.FileBuildPart,
    $verInfo.FilePrivatePart
}

# Outputs the file's version
get-fileversion $fileName

# You can also use the FileSystemObject object's GetFileVersion method.
$fso = new-object -comobject "Scripting.FileSystemObject"

# Same output as previous get-fileversion function.
$fso.GetFileVersion($fileName)

 

 

Listing 3: VBScript Functions
' Bitwise left shift
Function Lsh(ByVal N, ByVal Bits)
  Lsh = N * (2 ^ Bits)
End Function

' Returns a version string "a.b.c.d" as a two-element numeric
' array. The first array element is the most significant 32 bits,
' and the second element is the least significant 32 bits.
Function GetVersionStringAsArray(ByVal Version)
  Dim VersionAll, VersionParts, N
  VersionAll = Array(0, 0, 0, 0)
  VersionParts = Split(Version, ".")
  For N = 0 To UBound(VersionParts)
    VersionAll(N) = CLng(VersionParts(N))
  Next

  Dim Hi, Lo
  Hi = Lsh(VersionAll(0), 16) + VersionAll(1)
  Lo = Lsh(VersionAll(2), 16) + VersionAll(3)

  GetVersionStringAsArray = Array(Hi, Lo)
End Function

' Compares two versions "a.b.c.d". If Version1 < Version2,
' returns -1. If Version1 = Version2, returns 0.
' If Version1 > Version2, returns 1.
Function CompareVersions(ByVal Version1, ByVal Version2)
  Dim Ver1, Ver2, Result
  Ver1 = GetVersionStringAsArray(Version1)
  Ver2 = GetVersionStringAsArray(Version2)
  If Ver1(0) < Ver2(0) Then
    Result = -1
  ElseIf Ver1(0) = Ver2(0) Then
    If Ver1(1) < Ver2(1) Then
      Result = -1
    ElseIf Ver1(1) = Ver2(1) Then
      Result = 0
    Else
      Result = 1
    End If
  Else
    Result = 1
  End If
  CompareVersions = Result
End Function

 

 

Listing 4: PowerShell Functions
# Bitwise left shift
function Lsh([UInt32] $n, [Byte] $bits) {
  $n * [Math]::Pow(2, $bits)
}

# Returns a version number "a.b.c.d" as a two-element numeric
# array. The first array element is the most significant 32 bits,
# and the second element is the least significant 32 bits.
function GetVersionStringAsArray([String] $version) {
  $parts = $version.Split(".")
  if ($parts.Count -lt 4) {
    for ($n = $parts.Count; $n -lt 4; $n++) {
      $parts += "0"
    }
  }
  [UInt32] ((Lsh $parts[0] 16) + $parts[1])
  [UInt32] ((Lsh $parts[2] 16) + $parts[3])
}

# Compares two version numbers "a.b.c.d". If $version1 < $version2,
# returns -1. If $version1 = $version2, returns 0. If
# $version1 > $version2, returns 1.
function CompareVersions([String] $version1, [String] $version2) {
  $ver1 = GetVersionStringAsArray $version1
  $ver2 = GetVersionStringAsArray $version2
  if ($ver1[0] -lt $ver2[0]) {
    return -1
  }
  elseif ($ver1[0] -eq $ver2[0]) {
    if ($ver1[1] -lt $ver2[1]) {
      return -1
    }
    elseif ($ver1[1] -eq $ver2[1]) {
      return 0
    }
    else {
      return 1
    }
  }
  else {
    return 1
  }
}