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;

enter image description here


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;

enter image description here


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;

enter image description here


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.

enter image description here


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 EAbortis handled there specifically in the exceptblock there.

In all other Inno event function (e.g. NextButtonClick etc.) the EAbortexception 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 to True. The Confirm 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 via SetStep(ssInstall, False); and the except block at the end of TMainForm.Install where TerminateApp is called.
  • [ref3] : See Setup.dpr calling MainForm.InitializeWizard from Main.paswhich calls WizardForm.ClickThroughPages iff not InstallMode = imNormal, i.e. in the silent case.
  • [ref4] : WizardForm.Close() internally calls MainForm.Close() (see: TWizardForm.FormClose)
  • [ref5] : There are actually two kinds of cancel button click callbacks defineable in Inno [Code]: The global CancelButtonClickprocedure, and each Wizard page also has a OnCancelButtonClick: TWizardPageCancelEvent that can be set.