Exit from Inno Setup installation from [Code]
Is it possible to exit the installation from a function in the [Code]
section of an installer created with Inno Setup?
I'm not interested in setting the exit code, what I want to do is perform a custom check for a requirement, and exit the installation if that requirement was not previously installed.
Solution 1:
To prevent the installer from running, when prerequisites test fails, just return False
from the InitializeSetup
. This will exit the installer even before the wizard shows.
function InitializeSetup(): Boolean;
begin
Result := True;
if not PrerequisitesTest then
begin
SuppressibleMsgBox('Prerequisites test failed', mbError, MB_OK, IDOK);
Result := False;
end;
end;
If you need to test prerequisites right before the installation starts only (i.e. the InitializeSetup
is too early), you can call the Abort
function from the CurStepChanged(ssInstall)
:
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if not PrerequisitesTest then
begin
SuppressibleMsgBox('Prerequisites test failed', mbError, MB_OK, IDOK);
Abort;
end;
end;
end;
Though for this scenario, consider using the PrepareToInstall
event function mechanism, instead of exiting the setup.
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
Result := '';
if not PrerequisitesTest then
begin
Result := 'Prerequisites test failed';
end;
end;
If you need to force terminate the installer any other time, use the ExitProcess
WinAPI call:
procedure ExitProcess(uExitCode: Integer);
external '[email protected] stdcall';
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpReady then
begin
if not PrerequisitesTest then
begin
SuppressibleMsgBox('Prerequisites test failed', mbError, MB_OK, IDOK);
ExitProcess(1);
end;
end;
Result := True;
end;
Though this is rather unsafe exit, so use it only as the last resort approach. If you have any external DLL loaded, you might need to unload it first, to avoid crashes. This also does not cleanup the temporary directory.
Solution 2:
You can use Abort() if you are in these events:
InitializeSetup
InitializeWizard
CurStepChanged(ssInstall)
InitializeUninstall
CurUninstallStepChanged(usAppMutexCheck)
CurUninstallStepChanged(usUninstall)
Solution 3:
The way I do it is:
procedure ExitProcess(exitCode:integer);
external '[email protected] stdcall';
And the way of using it is:
[Code]
if .... then begin
ExitProcess(0);
end;
Solution 4:
Take a look at InitializeSetup and Abort in the InnoSetup help. As Cody said, it is possible. If you're having problems, post what you've done and the problem you're having.
Solution 5:
This is a write up of what I fiddled out of my Inno 5.6.1 today and the sources you can find at https://github.com/jrsoftware/issrc [ref1]
A possibly useful catch-all solution to "Exit from [Code]"
TL;DR example:
[Code]
var _ImmediateInnoExit_was_invoked_flag: Boolean; // Inno/Pascal Script initializes all Boolean to False.
procedure ImmediateInnoExit();
var MainFormRef: TForm;
begin
_ImmediateInnoExit_was_invoked_flag := True;
try
MainFormRef := MainForm(); // calls GetMainForm() in Inno pascal code, which will raise an internal exception if the form is not yet initialized.
Log('INFO: ImmediateInnoExit: Calling MainForm.Close()!');
Log('NOTE: If the Event Fn CancelButtonClick is not coded to auto-Confirm, this will display the cancel dialog in the GUI case!');
Log('NOTE: Code will stall inside the Close() function while the Cancel confirmation dialog is displayed.');
MainFormRef.Close(); // this is only effective if the Wizard is visible, but we cann call it even when running siently (as long as the Wizard is initialized)
Log('NOTE: MainForm.Close() invoked. (If confirmed, setup will exit.)');
except
Log('INFO: ImmediateInnoExit did not resolve MainForm -> assuming we were call in an InitializeSetup() context before the Main form has been created!');
end;
Log('INFO: ImmediateInnoExit: Calling Abort() -> EAbort!');
Log('NOTE: Will exit the current scope.');
Log('NOTE: In GUI mode, it will just jump up to the Delphi event loop (and be ignored there). (But the WizardForm.Close() call should lead to exit!)');
Log('NOTE: In Silent Mode, it will be caught and exit the setup.');
Abort(); // Raise EAbort
end;
// This is called when the user clicks the cancel button or the [x] Close button
// the close/cancel can be invoked from code via WizardForm.Close!
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
Log(Format('IN: CancelButtonClick(%d <- Cancel=[%d], Confirm=[%d])', [CurPageID, Cancel, Confirm]));
Confirm := not _ImmediateInnoExit_was_invoked_flag; // if Confirm==False we don't get the dialog prompt.
Log(Format('IN: CancelButtonClick(%d -> [%d], [%d])', [CurPageID, Cancel, Confirm]));
end;
And now to what the point of the above code is:
Anatomy of Inno Setup Exit/Cancel and Abort
Abort
The Inno docs for Abort
state:
Description: Escapes from the current execution path without reporting an error.
Abort raises a special "silent exception" which operates like any other exception, but does not display an error message to the end user.
Remarks:
Abort does not cause Setup or Uninstall to exit unless it's called from one of these event functions (or another function invoked by them):
InitializeSetup InitializeWizard CurStepChanged(ssInstall) InitializeUninstall CurUninstallStepChanged(usAppMutexCheck) CurUninstallStepChanged(usUninstall)
Abort() behaviour explained
The reason the Abort function bevahes in this way is because, internally, Inno raises an EAbort
exception, and that exception is treated specially by the Delphi UI loop. Only in the functions listed, the Inno devs have either added special treatment for EAbort
(like in the case of CurStepChanged(ssInstall)
[ref2]), --
-- or the function os not called via the UI loop, like in the case of InitializeSetup
, which is called from the main program in Setup.dpr
, and any direct EAbort
is handled there specifically in the except
block there.
In all other Inno event function (e.g. NextButtonClick
etc.) the EAbort
exception will reach the main program/UI loop and be ignored there.
Which leads us nicely to:
Abort() behaviour, when running /SILENT
(or /VERSILENT
)
When Inno runs silently, it does not display the wizard form UI. The "Wizard" / Inno's progress is then not driven by the UI loop, but by WizardForm.ClickThroughPages
, which is invoked under same toplevel try/except
block as e.g. InitializeSetup
. [ref3]
Because of this, if Inno is being called silently, Abort()
will exit setup from every most [Code]
functions, and the list given in the docs for Abort
becomes moot if setup is being run silently.
Cancel
To cancel the setup, the user can click the [Cancel]
button or the [X]
close button of the Setup Wizard.
In this case, Inno will invoke the callback function CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean)
(if defined) and terminates setup, possible with an escape hatch dialog:
Called when the user clicks the Cancel button or clicks the window's Close button. The
Cancel
parameter specifies whether normal cancel processing should occur; it defaults toTrue
. TheConfirm
parameter specifies whether an "Exit Setup?" message box should be displayed;
User [Code]
can invoke the Cancel Button mechinism via calling WizardForm.Close()
, but this only works if Setup is displaying the Wizard Form, and doesn't work in silent mode.
Cancel Details
WizardForm.Close
[ref4], or the click on the actual button, will eventually call TMainForm.FormCloseQuery
(in Main.pas), which will call CancelButtonClick
callbacks[ref5] and dependeing on Confirm
value, either call TerminateApp();
directly or first call the helper function ExitSetupMsgBox()
that will display the message box to the user.
Footnotes:
- [ref1] : For non-delphi folks: If you search over the sources in you text editor, make sure to include at least
.iss .pas
and.dpr
- [ref2] : ssInstall exception handling is located at
issrc\Projects\Main.pas
:TMainForm.Install
viaSetStep(ssInstall, False);
and theexcept
block at the end ofTMainForm.Install
whereTerminateApp
is called. - [ref3] : See
Setup.dpr
callingMainForm.InitializeWizard
fromMain.pas
which callsWizardForm.ClickThroughPages
iffnot InstallMode = imNormal
, i.e. in the silent case. - [ref4] :
WizardForm.Close()
internally callsMainForm.Close()
(see:TWizardForm.FormClose
) - [ref5] : There are actually two kinds of cancel button click callbacks defineable in Inno
[Code]
: The globalCancelButtonClick
procedure, and each Wizard page also has aOnCancelButtonClick: TWizardPageCancelEvent
that can be set.