7

An installer I'm working on does most of its work in the PrepareToInstall function, as everything I need to do might fail, and therefore this is the appropriate place to handle these things in case they do fail. Any failure can be automatically reported by passing the error message in the function's result. There are only 3 small files which the installer actually copies over.

The problem is that the wizard seems to freeze (or not respond rather) during this function, just showing a blank page titled "Preparing to install..." while in reality, it's going through my install process.

I would like to show the progress to the user with a simple procedure ShowProgress(const S: String); which shows the user what it's actually doing. How can I do this?

This is how I'm doing my install, where I'd like to wrap each call to Log()...

function PrepareToInstall(var NeedsRestart: Boolean): String;
var
  R: Integer;
begin
  Result:= '';
  try
    Log('Doing this...');
    R:= DoThis;
    case R of
      0: begin
        Result:= '';
      end;
      1: begin
        Result:= 'Error message 1 was raised while doing this.';
      end;
      else begin
        Result:= 'Unexpected error doing this: ' + IntToStr(R);
      end;
    end;

    if Result = '' then begin
      Log ('Doing that...');
      R:= DoThat;
      case R of
        0: begin
          Result:= '';
        end;
        1: begin
          Result:= 'Error message 1 was raised while doing that.';
        end;
        else begin
          Result:= 'Unexpected error doing that: ' + IntToStr(R);
        end;
      end;
    end;

    if Result = '' then begin
      Log ('Doing something else...');
      R:= DoSomethingElse;
      case R of
        0: begin
          Result:= '';
        end;
        1: begin
          Result:= 'Error message 1 was raised while doing something else.';
        end;
        else begin
          Result:= 'Unexpected error doing something else: ' + IntToStr(R);
        end;
      end;
    end;

    //A few more blocks like above

    //Error logging
    if Result <> '' then begin
      Log('FAILURE: '+Result);
    end;

  except
    Result:= 'EXCEPTION';
    Log('EXCEPTION');
  end;
end;
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327

3 Answers3

12

I mixed all your answers guys and I found another possible solution to this question.

I know this is not a so called 'elegant solution'. Anyway it is a quick solution that can avoid you to develop a separated DLL with custom controls managing the communication between the installer and the DLL.

The following approach describe how to create a custom Page that is a copy of the Page Inno Setup shows during PrepareToInstall setup phase. This copy must have the same look and feel of the original one plus a progress bar and a label that developer can use to indicate the current step of the PrepareToInstall setup phase.

Let's see some code.
First of all, prepare the custom Page:

[Code]
var
  PrepareToInstallWithProgressPage : TOutputProgressWizardPage;

Now define PrepareToInstall function to add our custom stuff:

function PrepareToInstall(var NeedsRestart: Boolean): String;
var
  ResultCode:   Integer;
begin
  PrepareToInstallWithProgressPage.SetProgress(0, 0);
  PrepareToInstallWithProgressPage.Show;

  try
    // First preinstallation step: suppose to kill currently app you are going to update 
    PrepareToInstallWithProgressPage.SetText('Exiting MyApp Running Instances'), '');

    ExecuteCmd('taskkill', '/F /IM MyApp');
    // Set progress bar to 10%
    PrepareToInstallWithProgressPage.SetProgress(1, 10);

    // Second preinstallation step
    // DoSomething usefull...
    // Set progress bar to 20% after done
    PrepareToInstallWithProgressPage.SetProgress(2, 10);

    // ...do other preinstallation steps till the end
    PrepareToInstallWithProgressPage.SetProgress(10, 10);
  finally
    PrepareToInstallWithProgressPage.Hide;
  end;
end;

At this installation phase we completed the steps for prepare to install phase so the default PrepareToInstall Page of Inno Setup is shown for a while.

Now, the user probably cannot understand that the page changes because our PrepareToInstallWithProgressPage has the same look and feel of the original one.

In order to let our page has the same look and feel we can use SetupMessage function to get the 2 strings of the original Inno Setup PrepareToInstall page.
Using SetupMessage we can avoid to duplicate and localize strings for our custom page to copy the originals.

Just like this:

procedure InitializeWizard;
var
  A: AnsiString;
  S: String;
begin
  // The string msgWizardPreparing has the macro '[name]' inside that I have to replace with the name of my app, stored in a define constant of my script.
  S := SetupMessage(msgPreparingDesc); 
  StringChange(S, '[name]', '{#MY_APPNAME}');
  A := S;
  PrepareToInstallWithProgressPage := CreateOutputProgressPage(SetupMessage(msgWizardPreparing), A);
end;

I hope this help.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
trix
  • 878
  • 6
  • 14
2

Have a look at the CodeDlg.iss example script included with Inno, in particular at the code that uses CreateOutputProgressPage.

This page type allows you to show a status message and/or a progress bar while you are performing other actions. And it automatically pumps messages whenever you change the progress label/value to ensure that the user sees the correct status.

It is intended for precisely this kind of code.

Miral
  • 12,637
  • 4
  • 53
  • 93
0

The code written in the PrepareToInstall function blocks the Windows message pump from processing, rendering the wizard form non-responsive. If many controls are needed to be visible in this screen, showing perhaps a progress bar and list of steps to take, this can be done with a form inside of a DLL. Make sure there is a thread in this DLL which does the actual install process, and only update the GUI to display the status to the user. This DLL form may overlay the wizard, or may even be embedded inside of the wizard in a custom wizard page, for example. The idea is to move your install code from the PrepareToInstall function to within this thread, and only then will you be able to achieve a fully responsive GUI during this process. From the PrepareToInstall function, there should be just one DLL call to initiate the install process, which in turn shows the form from the DLL.

The result may look something like this (in the works):

enter image description here

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • You can update controls immediately by calling `Repaint`, or `Refresh` as I said. And though the external library with a worker thread is better, it is not necessary. And finally, you don't need to build a form in a DLL. You can build the UI inside the wizard page and update it with Windows API functions from that worker thread. – TLama May 04 '14 at 19:38
  • @TLama Yes, that's one solution. But trying to replicate a grid, for example, within Inno Setup, is near impossible. I'd rather have a rich grid displaying the steps being taken and their current status, etc. which is extremely easy to do in Delphi. I can also embed this form inside of a custom wizard page. – Jerry Dodge May 04 '14 at 19:41
  • You were talking about progress so I assumed you're going to show a progress bar and some label. For those is creating a form in an external library just overkill. – TLama May 04 '14 at 19:43
  • @TLama Actually I never mentioned anything about a progress bar in my question, although that will be one thing I end up implementing :P I just meant how can I let the user see that the installer's actually doing something than think that it's frozen. By "show progress" I mean let the user see what the installer's doing. – Jerry Dodge May 04 '14 at 19:44
  • @TLama And exception handling is much easier in Delphi, and returning a universal message/exit code back to the installer – Jerry Dodge May 04 '14 at 20:06
  • If you feel it so... Well, an external library with a worker thread is a good choice. An embedded form is not, since it is unecessary (I bet it is) and can only break the visual look of the whole wizard. – TLama May 04 '14 at 20:16
  • @TLama I was actually inspired to design a new form for this DLL which can be universally used in different applications, adding a list of "steps" to perform, where the thread reports back the status and progress of each of these steps. – Jerry Dodge May 04 '14 at 20:32