Билл Стюарт (bill.stewart@frenchmortuary.com) – системный и сетевой администратор компании French Mortuary, Нью-Мехико
Системным администраторам часто бывает нужна информация о версии файла, например для того, чтобы определить, применено ли программное исправление. Эта информация — метаданные, встроенные в исполняемый файл Windows (например,. exe,. dll) в виде набора чисел, разделенных точками, например 12.1.42.0. Слева направо эти числа указывают основной номер версии, дополнительный номер версии, номер сборки и номер редакции. Каждое число это 16-разрядное целое число без знака. Первые два числа (основной номер версии и дополнительный) представляют наиболее значимые 32 разряда номера версии. Последние два числа (номер сборки и номер редакции) представляют менее значимые 32 разряда номера версии.
В сценариях среды сценариев Windows Script Host (WSH) версию файла можно получить с помощью метода GetFileVersion объекта FileSystemObject. Например, код в листинге 1 это небольшой сценарий на VBScript, который выводит версию файла Notepad.exe.
В Windows PowerShell можно использовать свойство VersionInfo объекта «файл», это свойство является объектом. NET FileVersionInfo. У данного объекта четыре свойства — FileMajorPart, FileMinorPart, FileBuildPart и FilePrivatePart, которые соответствуют четырем частям версии файла. В коде в листинге 2 приведена небольшая функция Powershell, которая использует оператор –f для вывода версии Notepad.exe. В этом листинге также показано, как в Powershell можно задействовать FileSystemObject для получения этой же информации.
Неплохо для начала. Но получение версии файла — это только полдела. Как сравнить полученные числа? В VBScript нет типа для 64-разрядных целых чисел без знака. В Powershell есть тип UInt64, но не все операторы могут работать с ним. Чтобы решить эти проблемы, я написал несколько функций VBScript и PowerShell, которые облегчают сравнение версий. Прежде чем продемонстрировать их, я хочу объяснить принцип, по которому они работают.
Шаг 1. Разделяем разряды
Мы не можем использовать 64-разрядные числа в VBScript напрямую, также как не можем использовать эти числа в Powershell без потери поддержки части операторов. Поэтому мы начнем с разделения числа, определяющего версию файла, на пару 32-разрядных чисел. Поскольку мы не можем сравнить два 64-разрядных числа, мы вместо этого сравним две пары 32-разрядных чисел.
Для начала вам нужно знать, как конвертировать строку a.b, где a — более значимые 16 разрядов, а b — менее значимые 16 разрядов, в одну 32-разрядную величину. Мы не можем просто работать со строкой как с числом с плавающей точкой, потому что сравнение не будет производиться корректно. Например, в случае с числом с плавающей точкой, 5,20 равно 5,2. В данном случае 5,15 будет большим числом (потому что 15 больше 2). В случае с номером версии это не так: 5,20 больше, чем 5,15.
Чтобы корректно сконвертировать строку a.b в 32-разрядное число, мы сделаем сдвиг влево для первого числа и затем добавим к нему второе число. Чтобы проиллюстрировать это, воспользуемся калькулятором Windows 7 в режиме программиста.
Откройте калькулятор, выберите «Вид», «Программист», а затем переключатели Hex и Dword, расположенные вдоль левого края (эффект проще заметить в шестнадцатеричном режиме, чем в десятичном). В этом примере мы создадим 32-разрядное число, которое представляет номер версии 3,7. Выполните в калькуляторе следующие действия:
- Введите число 3 для основного номера версии
- Нажмите кнопку Lsh, введите значение 10 (шестнадцатеричное для 16) и нажмите «Ввод». Это действие передвинет разряды числа 16 влево.
- Нажмите +, затем 7 и после этого нажмите «Ввод». Это действие добавит дополнительный номер версии.
Результатом будет шестнадцатеричное 30007, как показано на экране.
![]() |
| Экран. Шестнадцатеричный пример конвертированной строки |
Таким образом, формула для конвертирования строки вида a.b в 32-разрядное число следующая:
(a Lsh 16) + b
Поскольку в VBScript и PowerShell нет оператора Lsh, мы будем использовать следующий эквивалент данной формулы:
(a * 216) + b
Выше я упоминал, что собираюсь разделить 64 разряда номера версии на два 32-разрядных числа. В сценарии я решил представить два 32-разрядных числа как массив из двух элементов. Например, номер версии 3.7.5.1 был бы представлен как массив, содержащий значения 196615 (30007 шестнадцатеричное для 3,7) и 327681 (50001 шестнадцатеричное для 5.1).
Шаг 2. Сравнение версий
Пока мы разобрали, как представить 64-разрядное значение в виде пары двух 32-разрядных значений. Вопрос в том, как сравнить эти значения? Предположим, что у нас есть два номера версии a.b.c.d и w.x.y.z. В таблице 1 показаны возможные результаты сравнения.
![]() |
| Таблица 1. Сравнение номеров версий |
Шаг 3. Написание кода
Теперь, когда вы понимаете природу номеров версий и то, как их сравнивать, мы можем взглянуть на код, который выполняет работу. В листинге 3 показаны функции VBScript, а в листинге 4 — те же функции, написанные на Powershell. Эти функции описаны в таблице 2.
![]() |
| Таблица 2. Функции, необходимые для сравнения версий |
Обратите внимание на разницу в синтаксисе VBScript и Powershell, когда будете использовать функции в своих сценариях. В VBScript вызов функции требует наличия круглых скобок и запятой между параметрами, тогда как в Poweshell скобки и запятая должны быть опущены. Например, в VBScript нужно написать:
Result = CompareVersions(Ver1, Ver2)
Тогда как в Powershell эквивалентное выражение будет выглядеть так:
$Result = CompareVersions $Ver1 $Ver2
Чтобы увидеть эти функции в действии, просмотрите код в листинге 5 и листинге 6. Versions.vbs и Versions.ps1 — самостоятельные сценарии, которые используют эти функции, для сравнения версий файлов из командной строки. Сценарии также включают дополнительные возможности: ValidateVersionStrin проверяет, корректна ли указанная строка версии, а GetVersionArrayAsString является обратной для GetVersionStringAsArray. Кроме того, я включил JScript версию сценария, Versions.js, в листинг 7 на тот случай, если Jscript — ваш любимый язык.
Листинг 1. Получение версии файла в VBScript
Dim FSO, FileName Set FSO = CreateObject(«Scripting.FileSystemObject») FileName = «C:\Windows\system32\notepad.exe» WScript.Echo FSO.GetFileVersion(FileName)
Листинг 2. Получение версии файла в 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)
' 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
# 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
}
}
Листинг 5. Код сценария Versions.ps1
param($version1, $version2)
# 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
}
}
# Bitwise right shift.
function Rsh([UInt32] $n, [Byte] $bits) {
[Math]::Truncate($n / [Math]::Pow(2, $bits))
}
# Returns the specified two-element array containing a version
# number as a string»a.b.c.d«.
function GetVersionArrayAsString([UInt32[]] $version) {
$hi = $version[0]
$lo = $version[1]
»{0}.{1}.{2}.{3}«-f ((Rsh $hi 16) -band 65535),
($hi -band 65535), ((Rsh $lo 16) -band 65535),
($lo -band 65535)
}
# Returns $TRUE if the version string»a.b.c.d«is valid,
# or $FALSE if not.
function ValidateVersionString([String] $version) {
$ok = $FALSE
$parts = $version.Split(».«)
if ($parts.Count -le 4) {
for ($n = 0; $n -lt $parts.Count; $n++) {
$ok = ($parts[$n] -as [UInt16]) -ne $NULL
if (-not $ok) { break }
}
}
return $ok
}
if (-not ($version1 -and $version2)) {
write-host»Specify two versions numbers (a.b.c.d) to compare«
exit
}
if (-not (ValidateVersionString $version1)) {
throw»Invalid version string — $version1«
}
if (-not (ValidateVersionString $version2)) {
throw»Invalid version string — $version2«
}
$OFS =»,«
$ver1Array = GetVersionStringAsArray $version1
$ver1String = GetVersionArrayAsString $ver1Array
»$ver1String = $ver1Array«
$ver2Array = GetVersionStringAsArray $version2
$ver2String = GetVersionArrayAsString $ver2Array
»$ver2String = $ver2Array«
$result = CompareVersions $ver1String $ver2String
if ($result -eq -1) {
»$ver1String < $ver2String«
}
elseif ($result -eq 0) {
»$ver1String = $ver2String«
}
else {
»$ver1String > $ver2String«
}
Листинг 6. Код сценария Versions.vbs
' 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 ' Bitwise right shift. Function Rsh(ByVal N, ByVal Bits) Rsh = N \ (2 ^ Bits) End Function ' Returns the specified two-element array containing a version ' number as a string»a.b.c.d«. Function GetVersionArrayAsString(ByVal Version) Dim Hi, Lo Hi = Version(0) Lo = Version(1) GetVersionArrayAsString = CStr(Rsh(Hi, 16) And 65535) &».«_ & CStr(Hi And 65535) &».«_ & CStr(Rsh(Lo, 16) And 65535) &».«_ & CStr(Lo And 65535) End Function ' Returns True if the version string»a.b.c.d«is valid, ' or False if not. Function ValidateVersionString(ByVal Version) Dim OK, VersionParts OK = False VersionParts = Split(Version,».«) If UBound(VersionParts) <= 3 Then Dim N, Part For N = 0 To UBound(VersionParts) On Error Resume Next Part = CLng(VersionParts(N)) OK = Err.Number = 0 On Error GoTo 0 If OK Then OK = (Part >= 0) And (Part <= 65535) End If If Not OK Then Exit For End If Next End If ValidateVersionString = OK End Function Dim Args Set Args = WScript.Arguments If Args.Unnamed.Count < 2 Then WScript.Echo»Specify two version numbers (a.b.c.d) to compare« WScript.Quit End If Dim Version1 Version1 = WScript.Arguments.Item(0) If Not ValidateVersionString(Version1) Then WScript.Echo»Invalid version string — '«& Version1 &»'« WScript.Quit End If Dim Version2 Version2 = WScript.Arguments.Item(1) If Not ValidateVersionString(Version2) Then WScript.Echo»Invalid version string — '«& Version2 &»'« WScript.Quit End If Dim Ver1Array, Ver1String Ver1Array = GetVersionStringAsArray(Version1) Ver1String = GetVersionArrayAsString(Ver1Array) WScript.Echo Ver1String &» = «& Ver1Array(0) &»,«& Ver1Array(1) Dim Ver2Array, Ver2String Ver2Array = GetVersionStringAsArray(Version2) Ver2String = GetVersionArrayAsString(Ver2Array) WScript.Echo Ver2String &» = «& Ver2Array(0) &»,«& Ver2Array(1) Dim Result Result = CompareVersions(Ver1String, Ver2String) If Result = -1 Then WScript.Echo Ver1String &» < «& Ver2String ElseIf Result = 0 Then WScript.Echo Ver1String &» = «& Ver2String Else WScript.Echo Ver1String &» > «& Ver2String End If
Листинг 7. Код сценария Versions.js
function getVersionStringAsArray(version) {
var parts = version.split(».«);
if (parts.length < 4) {
for (var n = parts.length; n < 4; n++)
parts.push(»0«);
}
var hi = (parseInt(parts[0], 10) << 16) + parseInt(parts[1], 10);
var lo = (parseInt(parts[2], 10) << 16) + parseInt(parts[3], 10);
return [hi, lo];
}
function getVersionArrayAsString(version) {
var hi = version[0];
var lo = version[1];
return ((hi >> 16) & 65535).toString() +».«+
(hi & 65535).toString() +».«+
((lo >> 16) & 65535).toString() +».«+
(lo & 65535).toString();
}
function compareVersions(version1, version2) {
var ver1 = getVersionStringAsArray(version1);
var ver2 = getVersionStringAsArray(version2);
if (ver1[0] < ver2[0])
return -1;
else if (ver1[0] == ver2[0]) {
if (ver1[1] < ver2[1])
return -1;
else if (ver1[1] == ver2[1])
return 0;
else
return 1;
}
else
return 1;
}
function validateVersionString(version) {
var ok = false;
var parts = version.split(».«);
if (parts.length <= 4) {
for (var n = 0; n < parts.length; n++) {
var part = parseInt(parts[n], 10);
ok = (part >= 0) && (part <= 65535);
if (! ok) break;
}
}
return ok;
}
var args = WScript.Arguments;
if (args.Unnamed.Count < 2) {
WScript.Echo(»Specify two version numbers (a.b.c.d) to compare«);
WScript.Quit();
}
var version1 = args.Unnamed.Item(0);
if (! validateVersionString(version1)) {
WScript.Echo(»Invalid version string — '«+ version1 +»'«);
WScript.Quit();
}
var version2 = args.Unnamed.Item(1);
if (! validateVersionString(version2)) {
WScript.Echo(»Invalid version string — '«+ version2 +»'«);
WScript.Quit();
}
var ver1Array = getVersionStringAsArray(version1);
var ver1String = getVersionArrayAsString(ver1Array);
WScript.Echo(ver1String +» = «+ ver1Array);
var ver2Array = getVersionStringAsArray(version2);
var ver2String = getVersionArrayAsString(ver2Array);
WScript.Echo(ver2String +» = «+ ver2Array);
var result = compareVersions(ver1String, ver2String);
if (result == -1)
WScript.Echo(ver1String +» < «+ ver2String);
else if (result == 0)
WScript.Echo(ver1String +» = «+ ver2String);
else
WScript.Echo(ver1String +» > " + ver2String);
.jpg)
.jpg)
.jpg)