'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