Innosetup return flag [duplicate] - inno-setup

This question already has an answer here:
Inno Setup Section [Run] with condition
(1 answer)
Closed 4 years ago.
I am working on compiling an Inno Setup project. What I am trying to do is check if a folder exists, and if the folder does not exist then I want to uncheck a checkbox in the [run] section.
I was trying to accomplish this through the [Code] section. However, I cannot figure out how to call the function in the flags of my [Run] section.
In my code section, I have the following function that checks to see if the directory exists, if it does not then I attempt to set the flag to be unchecked if the directory does exist I just return any flag.
[Code]
function VerifyDir(DirName: String): Flag;
begin
{Check if directory exists, if it does then set the check flag to unchecked}
if not DirExists(DirName) then
Result := unchecked
end;
{Directory Exists return a flag}
Result := nowait
end;
Then in my [Run] section, I attempt to pass back the flag from the function like so:
[Run]
Filename: C:\3S\LegacyAppFolder\Update.exe; Description: Blah Blah Blah; \
Flags: VerifyDir('C:\3S\LegacyAppFolder')
However, I get an error when I try to compile the installer
Parameter "Flags" includes an unknown flag.
I assume this is because either I cannot have an inline function and I need to go about this a different way, or this is not possible at all.

You don’t want to use the flags section to do the test.
If you look here you will see that the right thing to do is use:
Check: xxxxxxx
If the check function returns true the statement is processed.

Related

Can I turn this into a [Run] entry in Inno Setup? [duplicate]

At the end of an installation I need to run a Pascal function that updates a flight simulator .cfg file (called .ini file in Inno Setup). The Pascal function exists in its [Code] section and runs correctly. I would like to run this Pascal function in the [Run] section using StatusMsg to tell the user what is going on.
[Run]
Filename: {code:FsxEditSceneryFile|Add#<scenerySpec>}; StatusMsg: "Add scenery to FSX";
; <scenerySpec> is just a place holder of the actual scenery specification!
All works as expected with the exception that Inno Setup forces me to use a string as return value of the Pascal function. However the Filename statement requires a Boolean as return value to specify if the execution was successful (True) or failed (False). This type mismatch produces an error message box at the end of the execution of the Filename statement saying
CreateProcess failed; Code 87. Wrong Parameter.
Any proposal how this could be solved? I know that event functions exist that I could use e.g. CurStepChanged() but I find the StatusMsg mechanism very nice to tell the user what is done by the installation.
You are abusing Filename parameter resolution to execute some code. It's undocumented when the parameter value is resolved. This makes your approach unreliable. You cannot know that the value is resolved, while the StatusMsg is displaying. And moreover, the value must resolve to an executable path anyway. And Inno Setup will try to execute it (hence the error). What you probably do not want. Do not do that.
Instead, as you already suggested, use the CurStepChanged. You can display the status message from Pascal code by accessing the WizardForm.StatusLabel.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
WizardForm.StatusLabel.Caption := 'Installing something...';
{ Install something }
end;
end;
In case somebody else is stumbling across this problem, here's a solution that will allow you to run pascal script procedures "in place" of any statement in the [RUN] section.
I needed to move the execution of a powershell script to the [CODE] section, since the [RUN] section doesn't provide a way of reacting to the exit code of external cmd programs.
As I've got a bunch of sub installers that I need to execute in a particular order within my setup, it's really handy to have the option to control the position of the pascal script within the [RUN] section and not just when entering the [RUN] sections setup step.
It works by using a "dummy" program (like ping) as Filename for the [RUN] statement, then using the build-in BeforeInstall parameter to call the pascal script procedure containing the actual execution logic.
[RUN] section
Filename: "ping"; BeforeInstall: RunSQLSetupScript; Components: "thirds\db"; StatusMsg: "Installing SQL Server ..." ; Flags: runhidden
Pascal script procedure containing the actual execution logic
(Note that {tmp}\{#SQLServerInstallScript} would be the actual path to the script in this example, as it is generally advised to avoid hard coded paths and use constants + the temporary directory instead.)
// runs the SQL setup script and terminates the setup process, if the script returns an exit code that indicates an error
procedure RunSQLSetupScript();
var
runSuccess: Boolean;
retVar: Integer;
msgText: String;
begin
runSuccess := ShellExec('', 'powershell.exe', '-NoProfile -File ' + ExpandConstant('{tmp}\{#SQLServerInstallScript}'), '', SW_SHOW, ewWaitUntilTerminated, retVar);
// the external script will return an exit code > 0 if an error occurred
if (runSuccess = False) or (retVar > 0) then
begin
msgText := 'SQL Server setup script returned error code ' + IntToStr(retVar) + ', indicating an unsuccessful installation of SQL Server. Setup will now terminate.';
MsgBox(msgText, mbCriticalError, MB_OK);
// => further handle error case here, like cancelling the running setup or log the issue etc.
end;
end;

Inno Setup: Execute a Pascal function in [Run] section

At the end of an installation I need to run a Pascal function that updates a flight simulator .cfg file (called .ini file in Inno Setup). The Pascal function exists in its [Code] section and runs correctly. I would like to run this Pascal function in the [Run] section using StatusMsg to tell the user what is going on.
[Run]
Filename: {code:FsxEditSceneryFile|Add#<scenerySpec>}; StatusMsg: "Add scenery to FSX";
; <scenerySpec> is just a place holder of the actual scenery specification!
All works as expected with the exception that Inno Setup forces me to use a string as return value of the Pascal function. However the Filename statement requires a Boolean as return value to specify if the execution was successful (True) or failed (False). This type mismatch produces an error message box at the end of the execution of the Filename statement saying
CreateProcess failed; Code 87. Wrong Parameter.
Any proposal how this could be solved? I know that event functions exist that I could use e.g. CurStepChanged() but I find the StatusMsg mechanism very nice to tell the user what is done by the installation.
You are abusing Filename parameter resolution to execute some code. It's undocumented when the parameter value is resolved. This makes your approach unreliable. You cannot know that the value is resolved, while the StatusMsg is displaying. And moreover, the value must resolve to an executable path anyway. And Inno Setup will try to execute it (hence the error). What you probably do not want. Do not do that.
Instead, as you already suggested, use the CurStepChanged. You can display the status message from Pascal code by accessing the WizardForm.StatusLabel.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
WizardForm.StatusLabel.Caption := 'Installing something...';
{ Install something }
end;
end;
In case somebody else is stumbling across this problem, here's a solution that will allow you to run pascal script procedures "in place" of any statement in the [RUN] section.
I needed to move the execution of a powershell script to the [CODE] section, since the [RUN] section doesn't provide a way of reacting to the exit code of external cmd programs.
As I've got a bunch of sub installers that I need to execute in a particular order within my setup, it's really handy to have the option to control the position of the pascal script within the [RUN] section and not just when entering the [RUN] sections setup step.
It works by using a "dummy" program (like ping) as Filename for the [RUN] statement, then using the build-in BeforeInstall parameter to call the pascal script procedure containing the actual execution logic.
[RUN] section
Filename: "ping"; BeforeInstall: RunSQLSetupScript; Components: "thirds\db"; StatusMsg: "Installing SQL Server ..." ; Flags: runhidden
Pascal script procedure containing the actual execution logic
(Note that {tmp}\{#SQLServerInstallScript} would be the actual path to the script in this example, as it is generally advised to avoid hard coded paths and use constants + the temporary directory instead.)
// runs the SQL setup script and terminates the setup process, if the script returns an exit code that indicates an error
procedure RunSQLSetupScript();
var
runSuccess: Boolean;
retVar: Integer;
msgText: String;
begin
runSuccess := ShellExec('', 'powershell.exe', '-NoProfile -File ' + ExpandConstant('{tmp}\{#SQLServerInstallScript}'), '', SW_SHOW, ewWaitUntilTerminated, retVar);
// the external script will return an exit code > 0 if an error occurred
if (runSuccess = False) or (retVar > 0) then
begin
msgText := 'SQL Server setup script returned error code ' + IntToStr(retVar) + ', indicating an unsuccessful installation of SQL Server. Setup will now terminate.';
MsgBox(msgText, mbCriticalError, MB_OK);
// => further handle error case here, like cancelling the running setup or log the issue etc.
end;
end;

Can I put setup command line parameters in a file which is called during installation instead?

After creating my setup.exe I have to pack it for various software deployment tools. Therefore I can't call the setup.exe with parameters, instead I have placed my own parameters in a setup.ini file next to the setup.exe
[Code]
var
MyIniFile: String;
function InitializeSetup(): Boolean;
var
LoadFromIniFile: String;
begin
Result := true;
MyIniFile := ExpandConstant('{srcexe}'); //writes the full path of the setup.exe in "MyIniFile"
MyIniFile := Copy(MyIniFile, 1, Length(MyIniFile) - Length(ExtractFileExt(MyIniFile))) + '.ini'; //changes the ".exe" into ".ini"
if FileExists(MyIniFile) then LoadFromIniFile := MyIniFile; //checks wether there is a ini-file
if LoadFromIniFile <> '' then begin
MyLogFile := GetIniString('Setup', 'Log', MyLogFile , LoadFromIniFile);
ProductName := GetIniString('Setup', 'ProductName', ProductName, LoadFromIniFile);
end;
end;
Now I want to also place the so called "Setup Command Line Parameters" (listed on the Inno Setup Help site) in my ini-file. I think that there is a way for the /Dir="x:\dirname parameter, which I did not figure out yet. But I also want to have the /SILENT parameter in there, do you think there is a way to do this? If yes, how would you do this? If not, can you please give me a hint why not?
So customize your installer for different products, I'd recommend you to use a pre-processor and automatically build the installer for each product (with different "defines"), instead of using an external INI file.
For example to be able to change application name and resulting executable when building the installer, use a script like:
[Setup]
AppName={#AppName}
OutputBaseFilename={#BaseFilename}
Now you can create two different installers automatically using command-line:
ISCC.exe Example1.iss /dAppName=App1 /dBaseFilename=SetupApp1
ISCC.exe Example1.iss /dAppName=App2 /dBaseFilename=SetupApp2
Regarding the implicit silent installation:
There's no API other than the command-line /SILENT switch to trigger silent installation.
But you can create a near-silent installation by disabling most installer pages:
[Setup]
DisableWelcomePage=true
DisableDirPage=true
DisableProgramGroupPage=true
DisableReadyPage=true
DisableFinishedPage=true
Actually the above example disables all default pages. But Inno Setup compiler will ignore the DisableReadyPage=true, if all other previous pages are disabled.
You may want to choose a different page to show instead. For example a Welcome page (by omitting DisableWelcomePage=true, but keeping the DisableReadyPage=true).
If you do not mind about using external files (as you already use an external INI file), you can of course wrap the installer to a batch file and call the installer with the /SILENT switch.

Innosetup, best practice custom installation pass variables to external application

I've got an installation task using InnoSetup which I'm not quite sure how to do properly.
Situation is as follows:
1 innosetup custom page with some textboxes and checkboxes. Basically containing targeturl, and a windows servicename. Might be more later.
This custom information needs to be passed on to two different app.config files.
Question is now, how to do this?
My first intention was to use the [Run] segment with a bunch of parameters such as:
[Run]
Filename: {app}\MyApp.exe; Parameters: /install; Flags: runminimized
But I don't know how to pass the custom data to the application.
Perhaps one can create some environment variables and pass the data that way?
Perhaps one can create a temporary file with necessary values?
Perhaps one should do this in a loaded dll during the installation and not post install?
Any suggestions would be very much appreciated.
and thankyou. For "#DenverCoder9":
Basically what I ended up doing, which is almost line for line in the bundled examples from Inno-setup. Missed the fact that Inno-setup allows for XML manipuation (via MSXML), which allowed me to to:
include a sample configuration file (app.config.sample).
load the sample configuration file
modify it using the collected data from the custom form.
save it to the correct location.
[Files]
...
Source: ..\UpdateService\UpdateService\Server\bin\Release\UpdateService.exe.config; DestDir: {app}; Permissions: users-modify; Flags: comparetimestamp onlyifdoesntexist; AfterInstall: MyAfterInstall;
[Code]
procedure MyAfterInstall();
var XMLDoc : Variant;
var RootNode : Variant;
begin
// if(FLAG_UPDATE_SERVICE_CONFIG) then begin
XMLDoc := CreateOleObject('MSXML2.DOMDocument');
XMLDoc.async := False;
XMLDoc.resolveExternals := False;
XMLDoc.load(ExpandConstant(CurrentFilename));
RootNode := XMLDoc.documentElement;
...
end;
end;
This has the added benefit that I have a sample file to provide for anyone who needs to modify the application by hand.
Can you not use command-line arguments to pass values entered in your Inno Setup installer to an external application?
The external application (which I assume is .NET) can handle writing the values from the command-line to configuration files (i.e. 'App.config'). The Main(string[] args) method of the .NET application can examine the command-line argument values. Use the [Run] section as you suggested and pass these values as parameters (using Parameters).

How can I create a INI-file in the {commonappdata} which can be written to by any uses when using Innosetup

If I want to create files in the {commonappdata} folder, I need to add the Permissions:users-modify parameter on a [Files] Source:... line.
This works great ( I finally figured out how to make sure no roaming folder was used when changing one of the installed files from within the installed program ).
However , how can I make INI-entries from the [INI] Filename:... section also writeable by any user on the PC ? There are program values, not user values, after all.
As it is now, the ini-file is written by the Inno-setup installation, but if I later start the installed program and change the ini file grammatically, a roaming version of the ini file is written.
To be complete:
I know about the way to create the ini-file as a template when installing your application, and than , upon first run of the program , copy them over to the the {commonappdata} folder, but I am just wondering whether this can be achieved from within the Inno-Setup script.
I am running the latest version of Inno-Setup 5.4.2 under Windows 7 Prof 64 ( if this should make a difference ).
You'll be blamed for this design. You might have contradicting user settings if you keep them in application data folders. Refer to this question for more blame. ;)
Anyway, only [Files], [Dirs] and [Registry] sections allow 'Permissions' parameter. So it is not possible for an [Ini] section to create an ini file with modified permissions. One way to achieve your task can be to use the 'ini' section to collect necessary information during setup, then as a post-install action, transfer the contents of the ini file to one with modified permissions created through the 'files' section. Something like this:
[Files]
Source: MyProg.ini; DestDir: {commonappdata}\MyCompany; Permissions: users-modify;
;// ini file contents will be transferred to this file
[Ini]
filename: {commonappdata}\MyCompany\MyProg_Temp.ini; section: users; key: username; string: {username}; Flags: UninsDeleteEntry;
;// this is used in the installation, and will be deleted in post-install
...
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
s: string;
begin
if CurStep = ssPostInstall then begin
if LoadStringFromFile(ExpandConstant('{commonappdata}\MyCompany\MyProg_Temp.ini'), s) and
SaveStringToFile(ExpandConstant('{commonappdata}\MyCompany\MyProg.ini'), s, False) then
DeleteFile(ExpandConstant('{commonappdata}\MyCompany\MyProg_Temp.ini'));
end;
end;
Inno Setup allows to set permissions only in sections [Files] [Dirs] and [Registry] and you'll have to use those somehow. Here are 2 solution to this problem. Both are good but each with a slight disadvantage.
Solution #1: Set permission to the entire directory
[Dirs]
Name: {commonappdata}\MyCompany; Permissions:everyone-modify
[INI]
Filename: {commonappdata}\MyCompany\MyProg.ini; Section: "SomeSection"; Key: "SomeKey"; String: "SomeValue"
This is a great solution if you don't mind to have the entire directory's permissions modified. I did mind and came up with a second solution.
Solution #2: Create your .ini file in {tmp} and copy it in the [Files] section:
#define TargetIniDir "{commonappdata}\MyCompany"
#define TargetIniName "MyProg.ini"
....
[Files]
Source: {tmp}\{#TargetIniName}; DestDir: {#TargetIniDir}; Flags:external; Permissions: users-modify;
....
[Code]
procedure PrepareIniFileForCopy(section, key, value, iniFileTemp, iniFileTarget:String);
begin
if FileExists(iniFileTarget) then
FileCopy(iniFileTarget, iniFileTemp, False);
SetIniString(section, key, value, iniFileTemp);
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
iniFile, iniFileTemp:String;
begin
if CurStep=ssInstall then begin
PrepareIniFileForCopy('SomeSection', 'SomeKey', 'SomeValue', ExpandConstant('{tmp}\{#TargetIniName}'), ExpandConstant('{#TargetIniDir}\{#TargetIniName}'));
end;
end;
This will create your .ini file in the {tmp} directory (which will be deleted after install finishes), then copied in the [Files] section to the desired commondata directory with desired permissions.
Note the external flag in the [Files] section which means the file isn't packed to the setup at compile time, rather taken dynamically at installation time. Also note that the creation of the temporary file must be done before the install (CurStep=ssInstall means right before installation).
I think this solution is good, but not very pretty. You split your operation to two different places, that rely one one being done before the other.
Both solution can add values to existing .ini files, not just create new ones.

Resources