Inno Setup: How to run a code procedure in Run section or before Run section? - inno-setup

I want to remove the old database before installing the new one, to update it for the user.
I have the following scenario:
In my Components section I have an option for the user:
[Components]
Name: "updateDatabase"; Description: "Update Database"; Types: custom; \
Flags: checkablealone disablenouninstallwarning
And I have in Code section, a procedure to execute, if the user selects this option, in the run section, before installing the new one.
[Code]
procedure RemoveOldDatabase();
begin
...
end;
[Run]
**--> Here I want to call RemoveOldDatabase if Components: updateDatabase is checked**
Filename: "database.exe"; StatusMsg: "Installing new database..."; Components: updateDatabase
The installation of the new database works fine. The problem is I want to remove the old one before installing the new one, calling the procedure RemoveOldDatabase.
Is it possible by only using Inno Setup?
Thanks.

One way, in my view really simple and still descriptive, is to execute your procedure as a BeforeInstall parameter function of your [Run] section entry. A BeforeInstall parameter function is executed once right before an entry is processed (and only if it's processed, which in your case is when the component is selected). You would write just this:
[Run]
Filename: "database.exe"; Components: UpdateDatabase; BeforeInstall: RemoveOldDatabase
[Code]
procedure RemoveOldDatabase;
begin
{ ... }
end;

Related

InnoSetup Rename Install Folder and Run Exe

I am creating a patch installer using InnoSetup.
After the installation, I want InnoSetup to rename the folder in Program Files (as you can see in the [Code] section below.
This works fine. But then, at the end of the installation, it prompts if I want to run the app. When yes is selected - it looks for the exe file in the old path. It makes sense because that is what is in the {app} variable.
So my question, is how can I make the [Run] section look at the new renamed path?
Please note that I have UsePreviousAppDir=yes set to true and should stay set to true.
Here are related parameters for my InnoSetup:
[Setup]
UsePreviousAppDir=yes
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Code]
{ ///////////////////////////////////////////////////////////////////// }
procedure InitializeWizard();
begin
RenameFile(ExpandConstant('{app}'), ExpandConstant('..\{app}') + ExpandConstant('{#MyAppName}') + ' ' + ExpandConstant('{#MyAppVersion}'))
end;
UPDATE:
Was able to make it work now. Seems my path above is incorrect. Answer has been posted below.
Managed to make it work now.
[Run]
Filename: "{app}\..\{#MyAppName} {#MyAppVersion}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[Code]
{ ///////////////////////////////////////////////////////////////////// }
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssPostInstall) then
begin
RenameFile(ExpandConstant('{app}'), ExpandConstant('{app}\..\{#MyAppName} {#MyAppVersion}'))
end;
end;

Inno Setup: Error handling in [Run] section [duplicate]

This question already has answers here:
How to force Inno Setup setup to fail when Run command fails?
(4 answers)
Using Process Exit code to show error message for a specific File in [Run]
(2 answers)
Closed 2 years ago.
I've been creating my first installers with Inno Setup and while they work, I want to look into some more profound error handling in case things go south during installation.
Here are the script snippets containing what I have so far. All of this works, questions follow below.
[Components]
Name: "base"; Description: "Install base files needed for this application"; Types: full compact custom; Flags: fixed
Name: "fonts"; Description: "Install Spartan fonts for a correct rendering of the application"; Types: full custom
Name: "updater"; Description: "Create a scheduled task to run the component updater on a daily basis"; Types: full custom
[Tasks]
Name: "update"; Description: "Run the updater after the installation to bring all application files up to date"
[Files]
; Application files
Source: "{#MyAppSourceNetwork1}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion; Components: base
Source: "{#MyAppSourceNetwork1}\{#MyAppcfgName}"; DestDir: "{app}"; Flags: ignoreversion; Components: base
Source: "{#MyAppSourceNetwork1}\Source\Componenten_COM\*"; DestDir: "{app}\Componenten_COM"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: base
Source: "{#MyAppSourceNetwork1}\Source\Componenten_RegAsm\*"; DestDir: "{app}\Componenten_RegAsm"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: base
Source: "{#MyAppSourceNetwork2}\*"; DestDir: "{#MyAppLocalFolderLib}\OCXinstaller"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: base
; Updater
Source: "{#MyAppSource}\Updater\*"; DestDir: "{#MyAppLocalFolderLib}\Updater"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: base
[Run]
; Create scheduled task for updater
Filename: "powershell"; \
Parameters: "-Executionpolicy Bypass -NoProfile -WindowStyle Hidden -Command ""Register-ScheduledTask -TaskName '{#MyAppTSUpdaterName}' -Description 'Smartclient component updater' -User System -Action (New-ScheduledTaskAction -Execute wscript -Argument '{#MyAppLocalFolderLib}\Updater\{#MyAppUpdateScript}') -Trigger (New-ScheduledTaskTrigger -Daily -At 07:00) -Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit (New-TimeSpan -Hours 2) -MultipleInstances IgnoreNew)"""; \
Description: "Create scheduled task for updater"; \
StatusMsg: "Creating scheduled task ""{#MyAppTSUpdaterName}""..."; \
Flags: runhidden; \
Components: updater
; Run scheduled task for updater
Filename: "wscript"; \
Parameters: """{#MyAppLocalFolderLib}\Updater\{#MyAppUpdateScript}"""; \
Description: "Run Smartclient component updater"; \
StatusMsg: "Running Smartclient updater. Please wait, this will take a few minutes..."; \
Flags: runhidden; \
BeforeInstall: SetMarqueeProgress(True); \
AfterInstall: SetMarqueeProgress(False); \
Tasks: update
; Offer option to launch application after installation
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent unchecked
[Code]
// Show infinite progress bar while waiting
procedure SetMarqueeProgress(Marquee: Boolean);
begin
if Marquee then
begin
WizardForm.ProgressGauge.Style := npbstMarquee;
end
else
begin
WizardForm.ProgressGauge.Style := npbstNormal;
end;
end;
For the purpose of testing, I deliberatly wrote some errors in the commands (RRRegister-ScheduledTask / UUUpdater / NNNotepad) and in the log file below, you can clearly see that the commands do not return code 0.
During the installation I got message boxes for the wscript and notepad commands, making clear that something went wrong, with the return code and an OK button. However, the Powershell command doesn't give a message at all. It only returns an error when I write the command wrong (PPPowershell.exe) but not when there is an error in the Parameters flag. Any idea why? Because I'm sure Powershell returns some nice red lines indicating an error occured.
Additionally, I notice that Inno Setup writes in the log that the installation succeeded (7th line in my snippet) although it even still needs to start with the [Run] section. When I write the commands without typos, the setup does everything correctly, but it doesn't give me much confidence in how my installers might indicate success even when some actions didn't succeed. Why isn't the [Run] block considered to be an integral part if the installation?
2020-06-10 08:52:15.025 Saving uninstall information.
2020-06-10 08:52:15.025 Deleting uninstall key left over from previous administrative 32-bit install.
2020-06-10 08:52:15.027 Creating new uninstall key: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\{C3DAA6E8-7152-40C7-8487-F4A1BDC3EB92}_is1
2020-06-10 08:52:15.027 Writing uninstall key values.
2020-06-10 08:52:15.030 Detected previous non administrative install? No
2020-06-10 08:52:15.030 Detected previous administrative 64-bit install? No
2020-06-10 08:52:15.047 Installation process succeeded.
2020-06-10 08:52:15.052 -- Run entry --
2020-06-10 08:52:15.052 Run as: Current user
2020-06-10 08:52:15.053 Type: Exec
2020-06-10 08:52:15.053 Filename: powershell
2020-06-10 08:52:15.053 Parameters: -Executionpolicy Bypass -NoProfile -WindowStyle Hidden -Command "RRRegister-ScheduledTask -TaskName 'Smartclient Updater' -Description 'Smartclient component updater' -User System -Action (New-ScheduledTaskAction -Execute wscript -Argument 'C:\Smartclient_MH_LIB\Updater\Mediahuis_Smartclient_Components_Updater.vbs') -Trigger (New-ScheduledTaskTrigger -Daily -At 07:00) -Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -ExecutionTimeLimit (New-TimeSpan -Hours 2) -MultipleInstances IgnoreNew)"
2020-06-10 08:52:19.509 Process exit code: 1
2020-06-10 08:52:19.511 -- Run entry --
2020-06-10 08:52:19.511 Run as: Current user
2020-06-10 08:52:19.511 Type: Exec
2020-06-10 08:52:19.511 Filename: wscript
2020-06-10 08:52:19.511 Parameters: "C:\Smartclient_MH_LIB\UUUpdater\Mediahuis_Smartclient_Components_Updater.vbs"
2020-06-10 08:57:47.994 Process exit code: 1
2020-06-10 08:57:47.996 -- Run entry --
2020-06-10 08:57:47.996 Run as: Current user
2020-06-10 08:57:47.996 Type: Exec
2020-06-10 08:57:47.996 Filename: NNNotepad
2020-06-10 08:57:47.997 Exception message:
2020-06-10 08:57:47.997 Message box (OK):
Unable to execute file:
NNNotepad
CreateProcess failed; code 2.
The system cannot find the file specified.
2020-06-10 08:57:50.009 User chose OK.
So where should I take this from here to be able to handle the return codes of these commands?
How can I specify which return codes from a [Run] line should be considered fatal, and which not? How do I catch them? I understand I will probably need to write [Code] blocks, maybe using Exec? But how do I trigger them from a [Run] line? Using Check or something else?
Can someone give an example how to catch the return code from the Powershell or Wscript command and how to throw a message box which manipulates the behaviour of the installer (e.g. quit if you click Abort, continue if you click Ignore)?
How do you manipulate the exit code from the setup itself in case things go wrong? Or is this a big no-no?
I read things about Windows process exit codes that are different than return codes in Pascal scripting. This is confusing the hell out of me. Can someone explain?
I've been looking into many examples and similar topics, but couldn't really find a useful case which I could edit to suit my needs.
Thanks for reading this far and for any help / insight you can offer.
EDIT 2020/06/16
Sorry for the delay. Meanwhile the topic has been closed, but I wanted to let you know how I solved it eventually and that I'm grateful for the help you offered. With the suggestions provided by Martin Prikryl and some additional searching I was able to reach my goals: write a [Code] block which lets me perform additional tasks, catch the return code and override the Setup exitcode if I wish to. I will post my generic code example below for anyone who needs a similar solution.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; // Example - Run additional actions purely in the [Code] block during the ssPostInstall step (executed after the [Run] block) and use ExitCode to modify the default exit code of the setup
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[Tasks]
Name: "runscript"; Description: "Run a script"
[Code]
// Start with exmpty exit code, can be used to override setup exit code with GetCustomSetupExitCode
var
ExitCode: Integer;
// Show infinite progress bar while waiting
procedure SetMarqueeProgress(Marquee: Boolean);
begin
if Marquee then
begin
WizardForm.ProgressGauge.Style := npbstMarquee;
end
else
begin
WizardForm.ProgressGauge.Style := npbstNormal;
end;
end;
// Run post-installation commands
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: Integer;
ProgramNAme: String;
ProgramArguments: String;
begin
if CurStep = ssPostInstall then
begin
// Change progressbar
SetMarqueeProgress(True);
// Run script if task is selected
if IsTaskSelected('runscript') then
begin
WizardForm.StatusLabel.Caption := 'Running script. This may take a few minutes...';
ProgramName := 'wscript';
ProgramArguments := ExpandConstant('{sd}\Smartclient_MH_LIB') + '\script\{#MyAppUpdateScript}';
Log('Running ' + ProgramName + ' ' + ProgramArguments);
if Exec(ProgramName, ProgramArguments, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
Log('ResultCode = ' + IntToStr(ResultCode) + '.');
if ResultCode = 0 then
begin
Log('Setup successfully ran the script.')
end
else
begin
Log('Setup failed to run the script.');
ExitCode := ExitCode + 1;
end;
end;
end
else
begin
Log('Skipping update because task "update" is not selected.');
end;
// Change progressbar
SetMarqueeProgress(False);
end;
end;
// Override setup exit code
function GetCustomSetupExitCode: Integer;
begin
if ExitCode <> 0 then
begin
Log('Returning custom exit code ' + IntToStr(ExitCode));
end;
Result := ExitCode;
end;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Perform DeleteFile only on NEW installation [duplicate]

My InnoSetup script opens a web page (with the user's default browser) at the end of the install process:
[Run]
Filename: http://example.com; Flags: shellexec
However, I'd like the web page to not be opened if the app already exists, i.e., if the user is installing a new version of the program. The web page should only be opened after the initial install. (I assume it's worth mentioning that the install includes an AppID, obviously, and enters values in the registry beside installing files.)
Thank you, as always -- Al C.
Yes, this is easy to do with scripting.
Just write
[Run]
Filename: "http://example.com"; Flags: shellexec; Check: NotAnUpdate
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpInstalling then
IsUpdate := FileExists(ExpandConstant('{app}\TheFileNameOfMyApp.exe'));
end;
function NotAnUpdate: Boolean;
begin
result := not IsUpdate;
end;
The answer by #AndreasRejbrand won't work, if user chooses to install the executable to a different location than the last time.
You can query installer-specific Inno Setup registry keys:
#define AppId "your-app-id"
#define SetupReg \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\" + AppId + "_is1"
#define SetupAppPathReg "Inno Setup: App Path"
[Setup]
AppId={#AppId}
...
[Run]
Filename: "https://www.example.com/"; Flags: shellexec; Check: not IsUpgrade
...
[Code]
function IsUpgrade: Boolean;
var
S: string;
begin
Result :=
RegQueryStringValue(HKLM, '{#SetupReg}', '{#SetupAppPathReg}', S) or
RegQueryStringValue(HKCU, '{#SetupReg}', '{#SetupAppPathReg}', S);
end;
For an example how to use IsUpgrade in [Code] section, see
Excludes part of Code section in ssPostInstall step if installation is update in Inno Setup
Check this if your "AppId" contains a left-curly-bracket:
Checking if installation is fresh or upgrade does not work when AppId contains a curly bracket

Inno Setup: Asking for directory page if a task checked

Under the task section I have
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; \
GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "installFolder"; Description: "Install project folder."; \
GroupDescription:" folder";
and in the Files section is this particular folder
Source: "C:\\Output\LEA\*.*"; DestDir: {code:GetDataDir}; \
Flags: createallsubdirs recursesubdirs ignoreversion;
My aim is to test the for the checked button and then have a window to ask for the directory to install the folder to.
if WizardForm.TasksList.Checked[3] then
GetDataDir;
Can this be done without the need to create pages or the one page to get the directory?
Also, is this a good way to handle extra files that are optional and will be installed to a different location than the default {app} location?
The confusing part for me thus far is when it's all compiled, the GetDataDir is being called before the page to select Tasks. So I choose my directory and then I'm asked whether I want to install it or not. I don't know how to go about getting the GetDataDir to occur afterwards.
The wizard model in Inno Setup means you should always create the wizard pages, but you can skip the ones that don't need to be shown.
This can be done in the ShouldSkipPage() event function by calling IsTaskSelected():
function ShouldSkipPage(PageID: Integer): Boolean;
begin
if (PageID = InstallFolderPage.ID) and not IsTaskSelected('installFolder') then
Result := True
else
Result := False
end;
In this case, with only a single check, it can be shortened to:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := (PageID = InstallFolderPage.ID) and not IsTaskSelected('installFolder')
end;
As TLama said, you don't need to do anything special in the {code:...} functions, just return the appropriate value directly.
You simply need to add '; Tasks: installFolder' at the end of your Source... line, then it won't be called unless the task as been selected.
Source: "C:\\Output\LEA\*.*"; DestDir: {code:GetDataDir}; Flags: createallsubdirs recursesubdirs ignoreversion; Tasks: installFolder

Can Inno Setup respond differently to a new install and an update?

My InnoSetup script opens a web page (with the user's default browser) at the end of the install process:
[Run]
Filename: http://example.com; Flags: shellexec
However, I'd like the web page to not be opened if the app already exists, i.e., if the user is installing a new version of the program. The web page should only be opened after the initial install. (I assume it's worth mentioning that the install includes an AppID, obviously, and enters values in the registry beside installing files.)
Thank you, as always -- Al C.
Yes, this is easy to do with scripting.
Just write
[Run]
Filename: "http://example.com"; Flags: shellexec; Check: NotAnUpdate
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpInstalling then
IsUpdate := FileExists(ExpandConstant('{app}\TheFileNameOfMyApp.exe'));
end;
function NotAnUpdate: Boolean;
begin
result := not IsUpdate;
end;
The answer by #AndreasRejbrand won't work, if user chooses to install the executable to a different location than the last time.
You can query installer-specific Inno Setup registry keys:
#define AppId "your-app-id"
#define SetupReg \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\" + AppId + "_is1"
#define SetupAppPathReg "Inno Setup: App Path"
[Setup]
AppId={#AppId}
...
[Run]
Filename: "https://www.example.com/"; Flags: shellexec; Check: not IsUpgrade
...
[Code]
function IsUpgrade: Boolean;
var
S: string;
begin
Result :=
RegQueryStringValue(HKLM, '{#SetupReg}', '{#SetupAppPathReg}', S) or
RegQueryStringValue(HKCU, '{#SetupReg}', '{#SetupAppPathReg}', S);
end;
For an example how to use IsUpgrade in [Code] section, see
Excludes part of Code section in ssPostInstall step if installation is update in Inno Setup
Check this if your "AppId" contains a left-curly-bracket:
Checking if installation is fresh or upgrade does not work when AppId contains a curly bracket

Resources