1

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;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Ulukai55
  • 11
  • 4
  • See [How to force Inno Setup setup to fail when Run command fails?](https://stackoverflow.com/q/1122588/8508480). – Martin Prikryl Jun 10 '20 at 11:50
  • Thank you for your feedback. While it might work, I think it is an overly complex way to do things. Writing return codes to files, reading them back, etc. I would rather work with a simpler piece of code, like suggested here: https://stackoverflow.com/a/31427076/13719177. However, it's also somewhat ugly to launch a dummy program first. Is there no cleaner way to call the Exec function from a [Run] line? – Ulukai55 Jun 10 '20 at 14:45
  • So use the answer by @TLama (with the improvement suggested by @Nyerguds). – Martin Prikryl Jun 10 '20 at 14:51
  • I assumse you mean to do it like this then: https://stackoverflow.com/revisions/9621406/2 Do I understand it correctly and do I only need a [Code] block for this which gets triggered on a certain page, without needing lines in another [Section]? Another idea I’m playing around with, is to make this an integral part of the installation, would be to run this in the [Files] section. Maybe I should copy a dummy file RunCommand1.txt to {tmp} and combine it with an AfterInstall flag which triggers the Exec function. Does this sound like a good idea? – Ulukai55 Jun 10 '20 at 15:06
  • Not a certain page (`NextButtonClick`), but a certain step (`CurStepChanged(ssPostInstall)`). – Using `[Files]` and `AfterInstall` can work too, but I prefer the `CurStepChanged`. – Martin Prikryl Jun 10 '20 at 15:11
  • Edited my original post to include the solution I ended up implementing. Thanks for pointing me in the right direction. – Ulukai55 Jun 16 '20 at 11:02
  • If this question is reopened you should move your answer to the Answer box below. After a day or so you can accept your own answer; doing that will mark your question as answered in the UI. – Dour High Arch Jun 16 '20 at 17:33

0 Answers0