1

I have a complicated situation.
There is a downloadable link to my installer on the web.
Before anyone downloads the installer they have to fill out a form in the browser.
I would like to save the information filled out in the form in the installer itself.
How could I accomplish this?
*Compiling the installer on the server each time anyone fills out the form is not an option because the installer is very heavy and this process would take a lot of time.
*Creating the form in the installer is not an option.
*I only need to save a single number in my executable.

yuval
  • 2,848
  • 4
  • 31
  • 51
  • Does it have to be a web form ? Couldn't that be form built from native controls that would form a POST request to the server ? – TLama Sep 07 '15 at 15:09
  • lol, I was just about to message you in private because I know your the best in this field. No having that form in the installer is not desirable. I need a way to do it with a form on the browser. – yuval Sep 07 '15 at 15:12
  • Some time ago [I've extended](http://stackoverflow.com/a/31413745/960757) my plugin that can embed the browser into the wizard page. It now publishes the `OleObject` and allows you to access DOM through it. But how are you planning to force the user to send the filled form (I would expect some sort of send button on the form, but what if they ignore this step) ? – TLama Sep 07 '15 at 15:21
  • This is not what I need. I do not need to imitate a web browser or form in my installer, in fact i'm planning to create a silent installer. The form must be on the web browser. I was thinking that I might need to change exe properties using a server side language like php, to afterwards obtain these properties in the installer. – yuval Sep 07 '15 at 15:28
  • Sorry, I missed the update. Now I see what you mean. You want to have a web server that will include into an existing setup the information the user filled in their browser. Well, then I would start thinking about [resources](https://msdn.microsoft.com/en-us/library/ff468900(v=vs.85).aspx). On the Inno Setup side I could help (maybe), but have no clue about the server side (if e.g. PHP can modify application resources). What type of data do you want to hold there (I presume only text values) ? – TLama Sep 07 '15 at 15:40
  • I need to save a single number – yuval Sep 07 '15 at 15:42
  • Will updating my resources ruin my code signing of the installer? – yuval Sep 08 '15 at 07:52
  • You will have to sign it after that modification, I think. Here is the script showing [how to read resource string](http://pastebin.com/5p8JpMSm) in Inno Setup. – TLama Sep 08 '15 at 09:13

2 Answers2

3

Its an interesting question. I'm with TLama on this one. When the installer is already build, your only chance is to modify the application / executable resources.

A tool like ResourceHacker might help in automating this task. http://angusj.com/resourcehacker/resource_hacker.zip

On the server-side you might use PHP to accept the form data and then forward them to ResourceHacker executed with wine and from PHP, e.g.:

exec("wine ResourceHacker.exe -script ScriptFile");.

You have some options on how to pass the data from the form to ResourceHacker: cli argument, from a file, etc. For automation on server-side, i suggest to use a ScriptFile.

A ScriptFile could start like...

[FILENAMES]
Exe=Installer.exe
SaveAs=ModifiedInstaller.exe
Log=file.log

[COMMANDS]
-modify       ResourceSrc, ResourceMask

In order to find the element to change, you can use the ResourceHacker GUI on a Windows system and play around until it works, then alter the script for automation on the server-side accordingly.


Ok, after explaining how to modify a resource in general lets go into details:

Like i pointed out, its also possible to use a language string for this and change it, but i will give step-by-step instructions for inserting a new field in the VERSION_INFO section of an executable. For testing purposes i work on \innosetup\Examples\MyProg.exe

Our goal is to add a new VALUE "PrivateBuild" with kind of a serial number.

(According to https://msdn.microsoft.com/de-de/library/windows/desktop/aa381049(v=vs.85).aspx, there is also "Comments" and "SpecialBuild" to enter information.)

1. Extract Version Info from MyProg.exe into VersionInfo.rc

ResourceHacker.exe -extract MyProg.exe, VersionInfo.rc, versioninfo,,

The content of VersionInfo.rc looks like this:

1 VERSIONINFO
FILEVERSION 1,5,0,0
PRODUCTVERSION 1,5,0,0
FILEOS 0x4
FILETYPE 0x0
{
BLOCK "StringFileInfo"
{
    BLOCK "040904b0"
    {
        VALUE "CompanyName", "My Company"
        VALUE "FileDescription", "My Program"
        VALUE "FileVersion", "1.5.0.0"
        VALUE "InternalName", "MyProg"
        VALUE "LegalCopyright", "Copyright (C) My Company"
        VALUE "OriginalFilename", "MyProg.exe"
        VALUE "ProductName", "My Program"
        VALUE "ProductVersion", "1.5"
    }
}

BLOCK "VarFileInfo"
{
    VALUE "Translation", 0x0409, 0x04B0
}
}

2. Modify Version Info

We add the following line to VersionInfo.rc

VALUE "PrivateBuild", "123-123-123"

(Later: modify the file with PHP. Probably preg_match for the line containing ProductVersion and append a new line followed by the value line.)

The new content of VersionInfo.rc looks like this:

1 VERSIONINFO
FILEVERSION 1,5,0,0
PRODUCTVERSION 1,5,0,0
FILEOS 0x4
FILETYPE 0x0
{
BLOCK "StringFileInfo"
{
    BLOCK "040904b0"
    {
        VALUE "CompanyName", "My Company"
        VALUE "FileDescription", "My Program"
        VALUE "FileVersion", "1.5.0.0"
        VALUE "InternalName", "MyProg"
        VALUE "LegalCopyright", "Copyright (C) My Company"
        VALUE "OriginalFilename", "MyProg.exe"
        VALUE "ProductName", "My Program"
        VALUE "ProductVersion", "1.5"
        VALUE "PrivateBuild", "123-123-123"
    }
}

BLOCK "VarFileInfo"
{
    VALUE "Translation", 0x0409, 0x04B0
}
}

3. Compile VersionInfo

windres -i VersionInfo.rc -o VersionInfo.res -O res

Now the VersionInfo text is a resource again.

4. Insert resource into exe

ResourceHacker.exe -script ScriptFile.rh

where ScriptFile.rh contains

[FileNames]
Exe=MyProg.exe
SaveAs=MyProgNew.exe
Log=MyProg.log

[Commands]
-delete VERSIONINFO,1,1033
-add VersionInfo.res, VERSIONINFO,1,1033

Let's check the log:

[08 Sep 2015, 11:21:33]

[FileNames]
Exe=MyProg.exe
SaveAs=MyProgNew.exe
Log=MyProg.log

[Commands]
-delete VERSIONINFO,1,1033
  Deleted: VERSIONINFO,1,1033
-add VersionInfo.res, VERSIONINFO,1,1033
  Added: VERSIONINFO,1,1033

Done.

Ok... new VERSIONINFO was inserted.

5. How to use or extract the value from InnoSetup?

InnoSetup provides only a preprocessor function called GetStringFileInfo() so one can not use

#define SERIAL GetStringFileInfo("path/to/MyProgNew.exe", "PrivateBuild")

And so we have to find a workaround to access the info and that works probably using the WinAPI. Here is one way to do it, which has some room for improvement. Its written by El Sanchez over at OsZone.net.

[Code]
#ifdef UNICODE
  #define A "W"
#else
  #define A "A"
#endif
function GetFileVersionInfoSize(lptstrFilename: String; lpdwHandle: Integer): Integer;
external 'GetFileVersionInfoSize{#A}@version.dll stdcall delayload';

function GetFileVersionInfo(lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external 'GetFileVersionInfo{#A}@version.dll stdcall delayload';

function VerQueryValue(var pBlock: Byte; lpSubBlock: String; var lplpBuffer: DWord; var puLen: Integer): Boolean;
external 'VerQueryValue{#A}@version.dll stdcall delayload';

function GetFileVerInfo(FileName, VerName: String): String;
//VerName:
//Comments, LegalCopyright, CompanyName, FileDescription, FileVersion, ProductVersion,
//InternalName, LegalTrademarks, OriginalFilename, ProductName, PrivateBuild, SpecialBuild
var
  dwLen, puLen, i: Integer;
  lpFileVerInfo: array of Byte;
  lplpBufferCP, lplpBufferVN: DWord;
  LangCodepage: String;
begin
  Result := '';
  if FileExists(FileName) then
  begin
    dwLen := GetFileVersionInfoSize(FileName, 0);
    if dwLen > 0 then
    begin
      SetArrayLength(lpFileVerInfo, dwLen);
      if GetFileVersionInfo(FileName, 0, dwLen, lpFileVerInfo[0]) then
      begin
        if VerQueryValue(lpFileVerInfo[0], '\VarFileInfo\Translation', lplpBufferCP, puLen) then
        begin
          LangCodepage := Format('%.2x%.2x%.2x%.2x', [lpFileVerInfo[(dwLen div 2)-5], lpFileVerInfo[(dwLen div 2)-6], lpFileVerInfo[(dwLen div 2)-3], lpFileVerInfo[(dwLen div 2)-4]]);
          if VerQueryValue(lpFileVerInfo[0], Format('\%s\%s\%s', ['StringFileInfo', LangCodepage, VerName]), lplpBufferVN, puLen) then
          begin
            i := (dwLen div 2) + lplpBufferVN - lplpBufferCP - 6;
            repeat
              if lpFileVerInfo[i] <> 0 then
              begin
                SetLength(Result, Length(Result)+1);
                Result[Length(Result)] := Chr(lpFileVerInfo[i]);
              end;
            i := i + 1;
            #ifdef UNICODE
            until i > (dwLen div 2) + lplpBufferVN - lplpBufferCP - 8 + puLen;
            #else
            until lpFileVerInfo[i] = 0;
            #endif
          end;
        end;
      end;
    end;
  end;
end;

then GetFileVerInfo(ExpandConstant('{srcexe}'), "PrivateBuild");

6. Sign the installer

Will updating my resources ruin my code signing of the installer?

Yes, the insert will change the executable. You will have to sign it after the modification. Use an unsigned installer beforehand, then insert, then sign (on the server).

--

You can execute all steps with PHP on server-side. You need ResourceHacker.exe and windres.exe on the server and wine for executing them.

Jens A. Koch
  • 39,862
  • 13
  • 113
  • 141
  • Where and how should I define the variable in Inno Setup so I could easily update it using Resource Hacker? – yuval Sep 08 '15 at 07:31
  • I will add a little step by step tutorial to my answer, which explains how to modify a resource. – Jens A. Koch Sep 08 '15 at 09:26
  • `GetStringFileInfo` is a preprocessor function. And I don't like the way of using version info for this. You can just add a string to the string table. – TLama Sep 08 '15 at 09:49
  • Yes, its a preprocessor function, my bad. Thats compile-time evaluated, so the info isn't there, yet. Need to find a workaround to access the info. / Yes, working with the Language or String table would work, too. – Jens A. Koch Sep 08 '15 at 09:56
  • Add a string to the string table and grab a [code to read it](http://stackoverflow.com/questions/32441680/how-to-write-data-to-an-installer-on-the-server#comment52769790_32441680). – TLama Sep 08 '15 at 09:57
  • That WinAPI prototype translation is horrible. – TLama Sep 08 '15 at 10:18
  • Yes, there is lots of room for improvement :) – Jens A. Koch Sep 08 '15 at 10:23
  • Very good answer thank you very much. It's a shame though that I will have to resign my installer. Doing this all on the server side might really effect the downloading speed of the installer for the clients downloading my app. – yuval Sep 08 '15 at 10:56
  • Also I don't quite understand why I would have to re-sign the Installer, If I only change the "VersionInfo" for example, which has no influence on the flow of the executable and thus has no security breach. – yuval Sep 08 '15 at 11:11
  • The filehash and the signature of the file will change. You are signing the executable to protect against modification. We are doing the opposite here. / One additional question: Your installer is a packaged offline installer, right? Would it be possible to make online access for the installer an requirement? Then you could insert the form into the installer and communicate with the server: send form content (POST) and get the number back. You could check the form data against your customer database. – Jens A. Koch Sep 08 '15 at 14:57
  • The whole purpose of this question was to avoid implementing the form in the installer, because I would like a silent installer – yuval Sep 09 '15 at 07:38
  • 1
    For more clean implementation of the `GetFileVerInfo` function, that does not rely on magic offsets, see [Querying Version Information from executable at runtime](http://stackoverflow.com/q/40615599/850848). – Martin Prikryl Nov 16 '16 at 13:45
0

Rather than trying to hack/modify the compiled executable, how about writing whatever values from the web form that you want to include in the installer to an INI file, which you then include with your Setup.exe download as Setup.ini and use the GetIniString function to read these in as strings in the installer?

Your INI file could be as simple as:

[SETUP]
Value=Number

Alternatively, you could use either the LoadStringFromFile or LoadStringsFromFile functions and any file format you want. Personally, I would go with the INI file and the GetIniString function if you decide on this method.

Robert Wigley
  • 1,917
  • 22
  • 42
  • The question is how would you include such file ? The OP cannot build the setup per request as said in the question. – TLama Sep 08 '15 at 08:38
  • 1
    ini strings can be read from external files; the installer could be provided as zip file with both files included. It's kind of unwieldy, though. Not sure if inno setup has some var for the path of its own installer file though. – Nyerguds Sep 08 '15 at 10:34
  • Yes, this is exactly what I was suggesting. `{src}` constant can be used to get the path of the installer and the `external` flag can be used to reference a file that is not compiled in the installer. We use this solution for one of our installers and it is pretty straightforward and works well. There is a big difference from building the installer each time and writing a couple of lines to an INI file. – Robert Wigley Sep 08 '15 at 11:29
  • I cannot afford to provide a zip to the clients that are downloading the installer. It has to be a signed executable that immediately executes once opened. Is there a way to use this technique with providing just a single installer? – yuval Sep 08 '15 at 11:57
  • The only thing I can think of at present would be to combine the Setup.exe and Setup.ini in a self-extracting EXE using something like WinZip Self-Extractor and signing it afterwards. – Robert Wigley Sep 08 '15 at 13:44
  • I would need to sign the Self-Extractor every time? – yuval Sep 08 '15 at 14:20
  • 1
    Yes, you will always need to sign anything after creation or changes are made. This will be the same with the method of modifying/hacking the actual Setup.exe above, it would need to be re-signed after the changes. Otherwise code signing would be pointless :-) – Robert Wigley Sep 08 '15 at 14:46
  • It's not hacking. Linkers do that. So why we couldn't :) ? – TLama Sep 08 '15 at 15:26