Extra character added using {#emit SetupSetting("AppId")} in Inno Setup - inno-setup

I'm having problems using the following code, it's adding an extra "{".
For example:
[Setup]
AppID={{E643099E-1ECE-474F-B043-1E7A7CE405AA}
[Code]
const
INSTALL_KEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1';
Returns the following:
INSTALL_KEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{{E643099E-1ECE-474F-B043-1E7A7CE405AA}_is1';
Which obviously won't work detecting that key with RegKeyExists(HKLM, INSTALL_KEY) because of the extra "{" that isn't in the real path and the script won't compile if you remove the extra character in [Setup] because then it thinks it's a constant.

The issue looks to be resolved on later versions of Inno Setup (5.5.5). Following code works just fine:
[Setup]
AppId={{********-****-****-****-********}
...
[code]
sAppId := ExpandConstant('{#emit SetupSetting("AppId")}_is1');
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\') + sAppId;

I think that parser doesn't allow to use the custom constants enclosed by the {} characters, because they are used as introductors for the constants, so I think you will have to workaround it. Here is one of the options:
[Setup]
AppID=E643099E-1ECE-474F-B043-1E7A7CE405AA
[code]
const
INSTALL_KEY = '...\Uninstall\{{#emit SetupSetting("AppId")}}_is1';
My guess is that case you've described is just an unexpected bug, because the compiler prompts you to use double bracket in the beginning of your constant though but when you emit such constant you get it with the same double bracket back.

Use StringChange() to remove the extra {:
[Setup]
AppID={{E643099E-1ECE-474F-B043-1E7A7CE405AA}
[Code]
const INSTALL_KEY = '...\Uninstall\{#emit StringChange(SetupSetting("AppId"),"{{","{")}_is1';

The solution is to use ExpandConstant function as already mentioned by others.
[Setup]
AppId={{E643099E-1ECE-474F-B043-1E7A7CE405AA}
[Code]
var
INSTALL_KEY: String;
function InitializeSetup(): Boolean;
begin
INSTALL_KEY := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1');
MsgBox(INSTALL_KEY, mbInformation, MB_OK);
end;
This is the expected behavior, not a bug. In [Code] section, constants and scripted constants are not expanded while emit preprocessor directives {#emit ...}/{#...} are replaced with their values by ISPP. So you need to use ExpandConstant function to convert {{ to {.
In this case, you can't define INSTALL_KEY as a Pascal constant as ExpandConstant function is used.

You could also define your application ID as a constant and use it like this:
#define MyAppID "{{E643099E-1ECE-474F-B043-1E7A7CE405AA}"
[Setup]
AppId={#MyAppID}
[Code]
const INSTALL_KEY = '...\Uninstall\' + ExpandConstant('{#MyAppID}') + '_is1';

Related

Array variables and dynamic access in [Code] section

My installer has Components which come associated with downloadable files. These things are changing from build to build, so I'm using #insert to create the [Components] section as well as the appropriate entries in the [Files] section.
Some of these components rely on common downloadable files.
To now include the correct urls in the downloads page, I'm currently defining array variables that are named like the component and have as values the names of the required downloadable files, for example:
#dim myfeature[2] {"01aed27862e2087bd117e9b677a8685aebb0be09744723b4a948ba78d6011bac", "677756ac5969f814fd01ae677dbb832ab2e642513fea44ea0a529430d5ec1fdc"}
In the code for the download page I'm checking which components where selected via WizardSelectedComponents() and after converting the string to an array of strings, I'm trying to get to the previously defined variable and that is where I'm failing:
function GetDownloads(): Array of String;
var
Downloads: Array of String;
SelectedComponents: String;
SelectedArray: Array of String;
begin
SelectedComponents := WizardSelectedComponents(False);
// a custom procedure to parse the comma seperated string
SelectedArray := ParseArray(SelectedComponents, SelectedArray);
// trying to get to the constant array now this works:
MsgBox(ExpandConstant('{#myfeature[0]}'), mbInformation, MB_OK);
// same but trying to use the selected component value returns this as a literal
// '+SelectedArray[0]+' instead the expanded value
MsgBox(ExpandConstant('{#' + SelectedArray[0] + '[0]}'), mbInformation, MB_OK);
end;
So I understand something is up with the # mark but I could not find a way to solve this properly.
Thank you!
Markus
ExpandConstant expands Inno Setup "constants", not preprocessor values. See also Evaluate preprocessor macro on run time in Inno Setup Pascal Script.
You cannot access elements of a preprocessor compile-time array using run-time indexes.
If you know C/C++, it's like if you were trying to do:
#define VALUE1 123
#define VALUE2 456
int index = 1;
int value = VALUE ## index
I'm not really sure I completely understand what are you doing. But it seems that you need to create an array on compile time from various sources and use it on runtime.
There are several approaches that can be used for that. But you definitely need runtime array initialized on run time. But the code that initializes it can be generated on compile time.
An example of the approach follows (and some links to other approaches are at the end).
At the beginning of your script, define these support functions:
[Code]
var
FeatureDownloads: TStrings;
function AddFeature(
Feature: Integer; CommaSeparatedListOfDownloads: string): Boolean;
begin
if not Assigned(FeatureDownloads) then
begin
FeatureDownloads := TStringList.Create();
end;
while FeatureDownloads.Count <= Feature do
FeatureDownloads.Add('');
if FeatureDownloads[Feature] <> '' then
RaiseException('Downloads for feature already defined');
FeatureDownloads[Feature] := CommaSeparatedListOfDownloads;
Result := True;
end;
#define AddFeature(Feature, CommaSeparatedListOfDownloads) \
"<event('InitializeSetup')>" + NewLine + \
"function InitializeSetupFeature" + Str(Feature) + "(): Boolean;" + NewLine + \
"begin" + NewLine + \
" Result := AddFeature(" + Str(Feature) + ", '" + CommaSeparatedListOfDownloads + "');" + NewLine + \
"end;"
In your components include files, do:
#emit AddFeature(2, "01aed27862e2087bd117e9b677a8685aebb0be09744723b4a948ba78d6011bac,677756ac5969f814fd01ae677dbb832ab2e642513fea44ea0a529430d5ec1fdc")
If you add:
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
to the end of your main script, you will see in the Preprocessed.iss generated by the preprocessor/compiler that the #emit directive expands to:
<event('InitializeSetup')>
function InitializeSetupFeature2(): Boolean;
begin
Result := AddFeature(2, '01aed27862e2087bd117e9b677a8685aebb0be09744723b4a948ba78d6011bac,677756ac5969f814fd01ae677dbb832ab2e642513fea44ea0a529430d5ec1fdc');
end;
Now you have FeatureDownloads Pascal Script runtime variable that you can access using FeatureDownloads[SelectedArray[0]] to get comma-separated string, which you can parse to the individual downloads.
This can be optimimized/improved a lot, but I do not know/understand the extent of your task. But I believe that once you grasp the concept (it might be difficult at the beginning), you will be able to do it yourself.
Another similar questions:
Evaluate a collection of data from preprocessor on run time in Inno Setup Pascal Script (simple example that be easier to grasp initially)
Scripting capabilities in the Registry section (slightly different approach from times event attributes were not available yet – and that's YOUR question)

Inno Setup Function - Invalid Prototype [duplicate]

This question already has an answer here:
"Identifier Expected" or "Invalid Prototype" when implementing a scripted constant in Inno Setup
(1 answer)
Closed 1 year ago.
I'm using Inno Setup for my installation and am adding the following function to the existing [Code] section:
function Get64Or32: String;
begin
if IsWin64 then
begin
Result := 'Program Files (x86)';
end
else
begin
Result := 'Program Files';
end;
end;
I keep getting the following error:
10:41:08 Invalid prototype for 'Get64Or32'
I have tried several permutations including having the parenthesis after the function name but get the same error each time.
I'm attempting to use this from the [Registry] section to pinpoint the Java location:
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType:string; ValueName:"JRE_HOME"; ValueData:"C:\{code:Get64or32}\Java\jre_1.8.0_151"; Flags: preservestringtype uninsdeletevalue uninsdeletekeyifempty; Permissions: users-modify;
If you look here Pascal Scripting: Scripted Constants it explains it all. To quote:
The syntax of a {code:...} constant is: {code:FunctionName|Param}
The Pascal script can contain several functions which are called when
Setup wants to know the value of a scripted {code:...} constant. The
called function must have 1 String parameter named Param, and must
return a String or a Boolean value depending on where the constant is
used.
The syntax of a {code:...} constant is: {code:FunctionName|Param}
It also provides:
An example function:
[Code]
function MyConst(Param: String): String;
begin
Result := ExpandConstant('{autopf}');
end;
An example useage:
[INI]
FileName: "{app}\MyIni.ini"; Section: "MySettings"; Key: "ShortApp"; String: "{code:GetShortName|{app}}"
You don't have to use the parameter but it must be part of the prototype.

Generating application arguments for Run section in scripted constant based on state of checkboxes and radio buttons on Finished page of Inno Setup

I want to modify the parameters of a FileName in the [Run] section according to the state of some radio and check buttons. In my code section I have added:
function GetParameterString:String;
var
I: Integer;
s1, s2: String;
begin
for I := 0 to WizardForm.RunList.Items.Count - 1 do
if (wizardform.runlist.items.checked[i] = true) then begin
if (wizardform.runlist.items.itemcaption[i] = 'View Whats''s New in ' + {#MyAppVersion}) then
s1 := GetShellFolderByCSIDL(CSIDL_APPDATA, True) + '\Positron Studio\What''s New.pdf';
if (wizardform.runlist.items.itemcaption[i] = 'Positron Studio Dark' then
s2 := '-d'
else if (wizardform.runlist.Items.itemcaption[i] = 'Positron Studio Light' then
s2 := '-l'
end;
end;
Result := s1 + s2
end;
and I am calling it from the [Run] section like this:
FileName:"{app}\{#MyAppExename}"; parameters: Code: GetParameterString; \
Flags: postinstall nowait
It fails on the wizardform.runlist.items.checked[i] = true with:
Unknown Identifier Checked
How do I get the Checked value of a checkbox or radiobutton?
Lookup Pascal Scripting: Scripted Constants where it states:
The Pascal script can contain several functions which are called when Setup wants to know the value of a scripted {code:...} constant. The called function must have 1 String parameter named Param, and must return a String or a Boolean value depending on where the constant is used.
So try:
FileName: "{app}\{#MyAppExename}"; Parameters: {code:GetParameterString}; Flags: postinstall nowait
The above has not been tested. It may not fix the underlaying issue with what you are actually doing in the GetParameterString method.
Your immediate problem is that there's indeed no wizardform.runlist.items.checked (nor wizardform.runlist.items.itemcaption).
You want WizardForm.RunList.Checked (and WizardForm.RunList.ItemCaption).
See the TNewCheckListBox documentation.
Your next problem will be the invalid syntax of the reference to your scripted constant in the Parameters parameter (as Andrew has already shown in his answer). It should be:
Parameters: {code:GetParameterString};
Your next problem will be "Invalid prototype":
"Identifier Expected" or "Invalid Prototype" when implementing a scripted constant in Inno Setup
It should be:
function GetParameterString(Param: string): string;
Depending on how you application handles the argument, you also might need to the path to the .pdf, as there are spaces in it.
It also bit strange, how you add the -d and -l to the path. Did you really intend to pass something like this to the application?
C:\Users\user\AppData\Roaming\Positron Studio\What's New.pdf-d
It also bit unclear to me, how are you combining multiple "run list" entries states into arguments of one particular entry. But I assume you know what you are doing.

How to write data to an installer on the server?

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.
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.
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.

Send a static text and a `ExpandConstant` in Http request

In relate to previous Question I asked,
In 'WinHttpReq.Send' I wish to send a static text and a ExpandConstant which comes from a Function as so :
function TakeToolParams (Param: String): String; begin if (HPCB.Checked = True) and (DSCB.Checked = True) then begin Result := ExpandConstant('true true true'); end end;' I tried this - 'WinHttpReq.Send('cool'+ '{code:TakeToolParams}'); but it's not working, I get this on wireshark - cool {code:TakeToolParams} I actually want to receive this - cool true true true. do you know how to call this function (TakeToolParams) inside 'WinHttpReq.Send'?
You forgot to expand the constant value. That's where you should use the ExpandConstant function:
WinHttpReq.Send('cool' + ExpandConstant('{code:TakeToolParams}'));
But note, that the primary purpose of the {code:...} constants is for script sections. If you'll be using your TakeToolParams function only in [Code] section, better write it as a standalone function, which you can call directly without constant expanding, e.g.:
function TakeToolParams: string;
begin
Result := 'Some text';
end;
In that case you could call it directly:
WinHttpReq.Send('cool' + TakeToolParams);

Resources