'Querying Version Information from executable at runtime
I am trying to query the version details of a file that the installer installs and compare it against the version details of the same file present in the installer being executed. The details are not in the FileVersion
or ProductVersion
field but can be in other fields like InternalName
etc.
I see Win32 APIs for solving this and also some sample code like :
However, some of the data types used in those code samples do not work with Inno Setup. Further, some samples and description seem to indicate that the language and codepage itself will be an array but some samples use it assuming only one entry for language and codepage. I was stuck at trying to find the language and codepage and based on comments below, I hard coded it for en-us.
I do see this answer which has a code sample for Inno Setup Pascal but the language and codepage calculation not being based on the lplpBufferCP variable makes me doubt its correctness.
Is it possible to read generic version info properties from Inno Setup Pascal script ? If so, please help around how to find the language and code page values.
The code I have written based on the aforesaid solutions is listed below with in-line comments for the problematic portions.
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function GetFileVersionInfoSize(lptstrFilename: String; lpdwHandle: Integer): Integer;
external 'GetFileVersionInfoSize{#AW}@version.dll stdcall delayload';
function GetFileVersionInfo(lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external 'GetFileVersionInfo{#AW}@version.dll stdcall delayload';
function VerQueryValue(var pBlock: Byte; lpSubBlock: String; var lplpBuffer: Byte; var puLen: Integer): Boolean;
external 'VerQueryValue{#AW}@version.dll stdcall delayload';
function GetFileVersionProperty(const FileName, PropertyName: String): String;
var
VerSize: Integer;
VerInfo: array of Byte;
Dummy: Integer;
InternalNameArr: array of Byte;
begin
Result := '';
if not FileExists(FileName) then
begin
Log('File ' + FileName + ' does not exist');
Exit;
end;
VerSize := GetFileVersionInfoSize(FileName, 0);
if not VerSize > 0 then
begin
Log('File ' + FileName + ' has no version information');
Exit;
end;
SetArrayLength(VerInfo, VerSize);
if not GetFileVersionInfo(FileName, 0, VerSize, VerInfo[0]) then
begin
Log('Failed to get version info for ' + FileName);
Exit;
end;
if not GetFileVersionInfo(FileName, 0, VerSize, VerInfo[0]) then
begin
Log('Failed to get version info for ' + FileName);
Exit;
end;
{ Getting 'Version size = 2156' }
Log(Format('Version size = %d', [VerSize]));
{ Hard coded value just for testing }
SetArrayLength(InternalNameArr, 512);
{ 040904E4 hard coded for en-us }
{ Is this the correct way of querying the details ? }
{ If not, what needs to be done here }
{ TODO : InternalName hard coded. Use parameter PropertyName }
if VerQueryValue(VerInfo[0], '\StringFileInfo\040904E4\InternalName', InternalNameArr[0], Dummy) then
begin
Log('Failed to query internal name of ' + FileName);
Exit;
end
else
begin
{ What needs to be done here to convert an array of byte to string ? }
{ Do I need to iterate over the array and do the conversion ?}
{ The following does not work because of SetString() being unavailable : }
{ InternalName = SetString(AnsiStr, PAnsiChar(@InternalNameArr[0]), Len);}
{ Getting 'ProductName = 0000' and 'Dummy = 0' }
Log(Format('ProductName = %d%d', [InternalNameArr[0], InternalNameArr[1], InternalNameArr[2], InternalNameArr[3]]));
Log(Format('Dummy = %d', [Dummy]));
end;
{ TODO : Populate Result with appropriate value }
end;
An alternate approach could be to save the file properties of the installed file in registry (I am interested in 1 property of 1 of the files) and have the property available in the installer statically for the new file.
Solution 1:[1]
The correct code to retrieve a string from the first language of a file version info is below. The code builds on an answer by @Jens A. Koch to How to write data to an installer on the server?
The code requires Unicode version of Inno Setup.
function GetFileVersionInfoSize(
lptstrFilename: String; lpdwHandle: Integer): Integer;
external '[email protected] stdcall delayload';
function GetFileVersionInfo(
lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external '[email protected] stdcall delayload';
function VerQueryValue(
var pBlock: Byte; lpSubBlock: String; var lplpBuffer: DWord;
var Len: Integer): Boolean;
external '[email protected] stdcall delayload';
procedure RtlMoveMemoryAsString(Dest: string; Source: DWord; Len: Integer);
external '[email protected] stdcall';
procedure RtlMoveMemoryAsBytes(Dest: array of Byte; Source: DWord; Len: Integer);
external '[email protected] stdcall';
function GetFileVerInfo(FileName, VerName: String): String;
var
Len: Integer;
FileVerInfo: array of Byte;
Lang: array of Byte;
Buffer: DWord;
LangCodepage: string;
SubBlock: string;
begin
Result := '';
if FileExists(FileName) then
begin
Len := GetFileVersionInfoSize(FileName, 0);
if Len > 0 then
begin
SetArrayLength(FileVerInfo, Len);
if GetFileVersionInfo(FileName, 0, Len, FileVerInfo[0]) then
begin
if VerQueryValue(
FileVerInfo[0], '\VarFileInfo\Translation', Buffer, Len) then
begin
if Len >= 4 then
begin
SetArrayLength(Lang, 4);
RtlMoveMemoryAsBytes(Lang, Buffer, 4);
LangCodepage :=
Format('%.2x%.2x%.2x%.2x', [Lang[1], Lang[0], Lang[3], Lang[2]]);
SubBlock :=
Format('\%s\%s\%s', ['StringFileInfo', LangCodepage, VerName]);
if VerQueryValue(FileVerInfo[0], SubBlock, Buffer, Len) then
begin
SetLength(Result, Len - 1);
RtlMoveMemoryAsString(Result, Buffer, (Len - 1) * 2);
end;
end;
end;
end;
end;
end;
end;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |