File Version functions
Inno SetupのV6.1からFile Version functionsというカテゴリの関数が追加されました。What's Newの文章から引用します。
Added new GetPackedVersion, PackVersionNumbers, PackVersionComponents, ComparePackedVersion, SamePackedVersion, UnpackVersionNumbers, UnpackVersionComponents, and VersionToStr support functions.
ヘルプにある関数のリファレンスを見ると説明がほとんどないので使い方を調べてみました。
ファイルのバージョニング・ルール
マイクロソフトによるファイルのバージョニング・ルールを知っていると、これら関数への理解が早いです。
VERSIONINFO resource
https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
一般にファイルのバージョンは、a.b.c.d
のような4つの数値の並びで示されますが、各数値は16bit整数で表されます。ここから各数値の範囲は0~65535であることが分かります。すなわちバージョンとして表現できるのは、0.0.0.0
~65535.65535.65535.65535
の範囲、ということになります。また上記のドキュメントを読むと、32bit整数を2つ使って表記する方式もあることが分かります。これを踏まえて、関数名に使われている単語の意味を定義します。
単語 | 意味 |
---|---|
VersionComponents | 16bit整数4つの形式 |
VersionNumbers | 32bit整数2つの形式 |
PackedVersion | 64bit整数1つの形式 |
Get | ファイルからバージョンを取得する |
Compare | 2つのバージョンを比較して、大小関係を取得する |
same | 2つのバージョンを比較して、同じかどうかを取得する |
Pack | PackedVersionに変換する |
Unpack | PackedVersionから別の形式に変換する |
ここまで分かってしまえば、各関数の使い方も分かってきます。また、ファイルバージョンは64bitあれば表現できるのだから、64bit整数に変換してバージョン比較できる、というロジックも見えてきます。
EXE/DLLファイルとMSIファイルのバージョニング・ルールは異なる
誤解してはいけないのは、EXE/DLLファイルとMSIファイルのバージョニング・ルールは異なる、ということです。MSIファイルのバージョニングは、以下に解説があります。
ProductVersion property
https://docs.microsoft.com/en-us/windows/win32/msi/productversion
基本的に3つの数値を「.」で区切って並べますが、4つの数値を使うこともできます。しかし、4番目の数値が付いていても、Windows Installerはこの数値を無視します。使える番号の範囲も異なります。バージョンとして使える範囲は0.0.0
~255.255.65535
となります。
とはいえ、ソースを見れば分かるように、新設された関数は単純に数値演算しているだけなので、MSIファイルのバージョン比較にも使えそうです。
実際に使ってみる
実験用に下記の3つのEXEファイルを作り、インストーラーと同じディレクトリに置きます。
ファイル名 | ファイルバージョン |
---|---|
f8_4_2_1.exe | 8.4.2.1 |
f1_1_1_1.exe | 1.1.1.1 |
f65535_65535_65535_65535.exe | 65535.65535.65535.65535 |
各関数の動作を確認します。各関数のエラーをチェックしていないので、実際に使用するときは適宜追加してください。
GetVersionNumbersString()
var
FilenameA, VersionA: String;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetVersionNumbersString(FilenameA, VersionA);
MsgBox('GetVersionNumbersString(FilenameA, VersionA)'+#13#10+#13#10
+'VersionA : '+VersionA
, mbInformation, MB_OK);
end;
GetVersionComponents()
var
FilenameA: String;
MajorA, MinorA, RevisionA, BuildA: Word;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA);
MsgBox('GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA)'+#13#10+#13#10
+'MajorA : '+IntToStr(MajorA)+#13#10
+'MinorA : '+IntToStr(MinorA)+#13#10
+'RevisionA : '+IntToStr(RevisionA)+#13#10
+'BuildA : '+IntToStr(BuildA)
, mbInformation, MB_OK);
end;
GetVersionNumbers()
var
FilenameA: String;
VersionMSA, VersionLSA: Cardinal;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetVersionNumbers(FilenameA, VersionMSA, VersionLSA);
MsgBox('GetVersionNumbers(FilenameA, VersionMSA, VersionLSA)'+#13#10+#13#10
+'VersionMSA : '+IntToStr(VersionMSA)+#13#10
+'VersionLSA : '+IntToStr(VersionLSA)
, mbInformation, MB_OK);
end;
このような数字になるのは、以下の計算がされているからです。
\begin{align}
VersionMSA&=MajorA * 2^{16} + MinorA\\
&=8 * 2^{16} + 4\\
&=524292
\end{align}
\begin{align}
VersionLSA&=RevisionA * 2^{16} + BuildA\\
&=2 * 2^{16} + 1\\
&=131073
\end{align}
検算してみます1。
var
Major32, Minor32, Revision32, Build32: Cardinal;
begin
Major32 := 8;
Minor32 := 4;
Revision32 := 2;
Build32 := 1;
MsgBox('Verify VersionNumbers'+#13#10+#13#10
+'(MajorA shl 16)+MinorA : '+IntToStr((Major32 shl 16)+Minor32)+#13#10
+'(RevisionA shl 16)+BuildA : '+IntToStr((Revision32 shl 16)+Build32)+#13#10
, mbInformation, MB_OK);
end;
GetPackedVersion()
var
FilenameA: String;
VersionA64: Int64;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetPackedVersion(FilenameA, VersionA64);
MsgBox('GetPackedVersion(FilenameA, VersionA64)'+#13#10+#13#10
+'VersionA64 : '+IntToStr(VersionA64)
, mbInformation, MB_OK);
end;
このような数字になるのは、以下の計算がされているからです。
\begin{align}
VersionA64&=MajorA * 2^{48} + MinorA * 2^{32} + RevisionA * 2^{16} + BuildA\\
&=8 * 2^{48} + 4 * 2^{32} + 2 * 2^{16} + 1\\
&=2251816993685505
\end{align}
検算してみます1。
var
Major64, Minor64, Revision64, Build64: Int64;
begin
Major64 := 8;
Minor64 := 4;
Revision64 := 2;
Build64 := 1;
MsgBox('Verify PackedVersion'+#13#10+#13#10
+'(MajorA shl 48)+(MinorA shl 32)+(RevisionA shl 16)+BuildA : '
+IntToStr((Major64 shl 48)+(Minor64 shl 32)+(Revision64 shl 16)+Build64)
, mbInformation, MB_OK);
end;
ComparePackedVersion()
var
FilenameA, FilenameB, FilenameC, VersionA, VersionB, VersionC: String;
VersionA64, VersionB64, VersionC64: Int64;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
FilenameB := ExpandConstant('{src}\f1_1_1_1.exe');
FilenameC := ExpandConstant('{src}\f65535_65535_65535_65535.exe');
GetVersionNumbersString(FilenameA, VersionA);
GetVersionNumbersString(FilenameB, VersionB);
GetVersionNumbersString(FilenameC, VersionC);
GetPackedVersion(FilenameA, VersionA64);
GetPackedVersion(FilenameB, VersionB64);
GetPackedVersion(FilenameC, VersionC64);
MsgBox('VersionA : '+VersionA+#13#10
+'VersionB : '+VersionB+#13#10
+'VersionC : '+VersionC+#13#10+#13#10
+'ComparePackedVersion(VersionA64, VersionB64)'+#13#10
+IntToStr(ComparePackedVersion(VersionA64, VersionB64))+#13#10+#13#10
+'ComparePackedVersion(VersionA64, VersionC64)'+#13#10
+IntToStr(ComparePackedVersion(VersionA64, VersionC64))+#13#10+#13#10
+'ComparePackedVersion(VersionA64, VersionA64)'+#13#10
+IntToStr(ComparePackedVersion(VersionA64, VersionA64))
, mbInformation, MB_OK);
end;
SamePackedVersion
var
FilenameA, FilenameB, CompResultA, CompResultB: String;
VersionA64, VersionB64: Int64;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
FilenameB := ExpandConstant('{src}\f1_1_1_1.exe');
GetPackedVersion(FilenameA, VersionA64);
GetPackedVersion(FilenameB, VersionB64);
if SamePackedVersion(VersionA64, VersionB64) then begin
CompResultA := 'True';
end else begin
CompResultA := 'False';
end;
if SamePackedVersion(VersionA64, VersionA64) then begin
CompResultB := 'True';
end else begin
CompResultB := 'False';
end;
MsgBox('VersionA : '+VersionA+#13#10
+'VersionB : '+VersionB+#13#10+#13#10
+'SamePackedVersion(VersionA64, VersionB64)'+#13#10
+CompResultA+#13#10+#13#10
+'SamePackedVersion(VersionA64, VersionA64)'+#13#10
+CompResultB+#13#10
, mbInformation, MB_OK);
end;
PackVersionComponents()/PackVersionNumbers()
var
FilenameA: String;
MajorA, MinorA, RevisionA, BuildA: Word;
VersionMSA, VersionLSA: Cardinal;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA);
GetVersionNumbers(FilenameA, VersionMSA, VersionLSA);
MsgBox('PackVersionComponents(MajorA, MinorA, RevisionA, BuildA)'+#13#10
+IntToStr(PackVersionComponents(MajorA, MinorA, RevisionA, BuildA))+#13#10+#13#10
+'PackVersionNumbers(VersionMSA, VersionLSA)'+#13#10
+IntToStr(PackVersionNumbers(VersionMSA, VersionLSA))
, mbInformation, MB_OK);
end;
UnpackVersionComponents()/UnpackVersionNumbers()
var
FilenameA: String;
MajorB, MinorB, RevisionB, BuildB: Word;
VersionMSB, VersionLSB: Cardinal;
VersionA64: Int64;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetPackedVersion(FilenameA, VersionA64);
UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB);
UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB);
MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
+'UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB)'+#13#10
+'MajorB : '+IntToStr(MajorB)+#13#10
+'MinorB : '+IntToStr(MinorB)+#13#10
+'RevisionB : '+IntToStr(RevisionB)+#13#10
+'BuildB : '+IntToStr(BuildB)+#13#10+#13#10
+'UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB)'+#13#10
+'VersionMSB : '+IntToStr(VersionMSB)+#13#10
+'VersionLSB : '+IntToStr(VersionLSB)
, mbInformation, MB_OK);
end;
VersionToStr()
var
FilenameA: String;
VersionA64: Int64;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
GetPackedVersion(FilenameA, VersionA64);
MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
+'VersionToStr(VersionA64)'+#13#10
+VersionToStr(VersionA64)
, mbInformation, MB_OK);
end;
実験用にEXEファイルにバージョンを入れる
もちろんVisual Studioを使ったやり方もありますが、私はリソースコンパイラを使った方法がシンプルなのでよく使います。
先ほど紹介したVERSIONINFO resourceの下の方にリソースファイルの例があるので、マネして書いていきます。
#include <winver.h>
#define VER_FILEVERSION 8,4,2,1
#define VER_FILEVERSION_STR "8.4.2.1\0"
#define VER_PRODUCTVERSION 8,4,2,1
#define VER_PRODUCTVERSION_STR "8.4.2\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0x00000000L
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1252
END
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "tohshima"
VALUE "FileDescription", "for File Version Test"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "<InternalName>\0"
VALUE "LegalCopyright", "Copyright (C) 2021 tohshima.\0"
VALUE "OriginalFilename", "fx_x_x_x.exe\0"
VALUE "ProductName", "fx_x_x_x\0"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
END
x86 Native Tools Command Prompt
を起動して、リソースコンパイラで.res
ファイルに変換します。
rc f8_4_2_1.rc
これで、f8_4_2_1.res
が得られます。適当にCのプログラムを作ります。
int main(){
return 0;
}
コンパイルします。
cl f8_4_2_1.c f8_4_2_1.res
これで、バージョン8.4.2.1
のf8_4_2_1.exe
が得られます。
実験用のインストーラーを作る
今回の実験で使用したインストーラーのソースを以下に示します。Uninstallable=no
とすることで実行後にテスト環境に何も残さないようにできます。DisableReadyPage=True
を有効にするために、InitializeWizard()
の最後でWizardForm.Show();
を実行します。今回は管理者権限で実行する必要がないので、User Account Controlダイアログの表示を抑止するためにPrivilegesRequired=lowest
とします。
[Setup]
DisableReadyPage=True
DisableReadyMemo=True
DisableFinishedPage=True
AppName=testFileVersionFunctions
CreateAppDir=False
Uninstallable=no
AppVersion=1.0.0
PrivilegesRequired=lowest
OutputDir=.
[Code]
procedure InitializeWizard();
var
FilenameA, FilenameB, FilenameC, VersionA, VersionB, VersionC, CompResultA, CompResultB: String;
MajorA, MinorA, RevisionA, BuildA, MajorB, MinorB, RevisionB, BuildB: Word;
VersionMSA, VersionLSA, VersionMSB, VersionLSB, Major32, Minor32, Revision32, Build32: Cardinal;
VersionA64, VersionB64, VersionC64, Major64, Minor64, Revision64, Build64: Int64;
begin
FilenameA := ExpandConstant('{src}\f8_4_2_1.exe');
FilenameB := ExpandConstant('{src}\f1_1_1_1.exe');
FilenameC := ExpandConstant('{src}\f65535_65535_65535_65535.exe');
//
GetVersionNumbersString(FilenameA, VersionA);
MsgBox('GetVersionNumbersString(FilenameA, VersionA)'+#13#10+#13#10
+'VersionA : '+VersionA
, mbInformation, MB_OK);
//
GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA);
MsgBox('GetVersionComponents(FilenameA, MajorA, MinorA, RevisionA, BuildA)'+#13#10+#13#10
+'MajorA : '+IntToStr(MajorA)+#13#10
+'MinorA : '+IntToStr(MinorA)+#13#10
+'RevisionA : '+IntToStr(RevisionA)+#13#10
+'BuildA : '+IntToStr(BuildA)
, mbInformation, MB_OK);
//
GetVersionNumbers(FilenameA, VersionMSA, VersionLSA);
MsgBox('GetVersionNumbers(FilenameA, VersionMSA, VersionLSA)'+#13#10+#13#10
+'VersionMSA : '+IntToStr(VersionMSA)+#13#10
+'VersionLSA : '+IntToStr(VersionLSA)
, mbInformation, MB_OK);
//
Major32 := MajorA;
Minor32 := MinorA;
Revision32 := RevisionA;
Build32 := BuildA;
MsgBox('Verify VersionNumbers'+#13#10+#13#10
+'(MajorA shl 16)+MinorA : '+IntToStr((Major32 shl 16)+Minor32)+#13#10
+'(RevisionA shl 16)+BuildA : '+IntToStr((Revision32 shl 16)+Build32)+#13#10
, mbInformation, MB_OK);
//
GetPackedVersion(FilenameA, VersionA64);
MsgBox('GetPackedVersion(FilenameA, VersionA64)'+#13#10+#13#10
+'VersionA64 : '+IntToStr(VersionA64)
, mbInformation, MB_OK);
//
Major64 := MajorA;
Minor64 := MinorA;
Revision64 := RevisionA;
Build64 := BuildA;
MsgBox('Verify PackedVersion'+#13#10+#13#10
+'(MajorA shl 48)+(MinorA shl 32)+(RevisionA shl 16)+BuildA : '
+IntToStr((Major64 shl 48)+(Minor64 shl 32)+(Revision64 shl 16)+Build64)
, mbInformation, MB_OK);
//
GetVersionNumbersString(FilenameB, VersionB);
GetVersionNumbersString(FilenameC, VersionC);
GetPackedVersion(FilenameB, VersionB64);
GetPackedVersion(FilenameC, VersionC64);
MsgBox('VersionA : '+VersionA+#13#10
+'VersionB : '+VersionB+#13#10
+'VersionC : '+VersionC+#13#10+#13#10
+'ComparePackedVersion(VersionA64, VersionB64)'+#13#10
+IntToStr(ComparePackedVersion(VersionA64, VersionB64))+#13#10+#13#10
+'ComparePackedVersion(VersionA64, VersionC64)'+#13#10
+IntToStr(ComparePackedVersion(VersionA64, VersionC64))+#13#10+#13#10
+'ComparePackedVersion(VersionA64, VersionA64)'+#13#10
+IntToStr(ComparePackedVersion(VersionA64, VersionA64))
, mbInformation, MB_OK);
//
if SamePackedVersion(VersionA64, VersionB64) then begin
CompResultA := 'True';
end else begin
CompResultA := 'False';
end;
if SamePackedVersion(VersionA64, VersionA64) then begin
CompResultB := 'True';
end else begin
CompResultB := 'False';
end;
MsgBox('VersionA : '+VersionA+#13#10
+'VersionB : '+VersionB+#13#10+#13#10
+'SamePackedVersion(VersionA64, VersionB64)'+#13#10
+CompResultA+#13#10+#13#10
+'SamePackedVersion(VersionA64, VersionA64)'+#13#10
+CompResultB+#13#10
, mbInformation, MB_OK);
//
MsgBox('PackVersionComponents(MajorA, MinorA, RevisionA, BuildA)'+#13#10
+IntToStr(PackVersionComponents(MajorA, MinorA, RevisionA, BuildA))+#13#10+#13#10
+'PackVersionNumbers(VersionMSA, VersionLSA)'+#13#10
+IntToStr(PackVersionNumbers(VersionMSA, VersionLSA))
, mbInformation, MB_OK);
//
UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB);
UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB);
MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
+'UnpackVersionComponents(VersionA64, MajorB, MinorB, RevisionB, BuildB)'+#13#10
+'MajorB : '+IntToStr(MajorB)+#13#10
+'MinorB : '+IntToStr(MinorB)+#13#10
+'RevisionB : '+IntToStr(RevisionB)+#13#10
+'BuildB : '+IntToStr(BuildB)+#13#10+#13#10
+'UnpackVersionNumbers(VersionA64, VersionMSB, VersionLSB)'+#13#10
+'VersionMSB : '+IntToStr(VersionMSB)+#13#10
+'VersionLSB : '+IntToStr(VersionLSB)
, mbInformation, MB_OK);
//
MsgBox('VersionA64 : '+IntToStr(VersionA64)+#13#10+#13#10
+'VersionToStr(VersionA64)'+#13#10
+VersionToStr(VersionA64)
, mbInformation, MB_OK);
//
WizardForm.Show(); // wpReady画面を出さない
end;