Change messages/texts at runtime (Inno Setup) - inno-setup

In an innosetup script it is possible to define messages like this:
[Messages]
WelcomeLabel2=This wizard will update [name] to version [name/ver]
Now I would like to change this message at runtime, like this:
procedure InitializeWizard;
begin
//this doesn't work
WelcomeLabel2=NEW MESSAGE
end;
What is the correct way to do this? I want to dynamically change the contents of the welcome page to display whether the setup is performing a new installation or update. Based on the existence of some executables in the installation directory.

One way;
[Languages]
Name: "en"; MessagesFile: "compiler:Default.isl"
[CustomMessages]
en.WelcomeLabel2_ForInstall=intstall {#SetupSetting("AppName")}, {#SetupSetting("AppVersion")}
en.WelcomeLabel2_ForUpdate=update {#SetupSetting("AppName")} to {#SetupSetting("AppVersion")}
[code]
procedure InitializeWizard();
var
message: string;
begin
//some logic
message := 'WelcomeLabel2_ForUpdate';
WizardForm.WelcomeLabel2.Caption := CustomMessage(message);
end;
procedure CurPageChanged(CurPageID: Integer);
begin
case CurPageID of
wpFinished : WizardForm.FinishedLabel.Caption := 'bla bla';
end;
end;

Hm, this won't work, I think. One way could be to create two pages for the installer - one with the layout for the installation process and one with the layout for the update process. Then, change the page sequence in a way that you manually decide which one to show.
An example for integrating a new page into the process can be found in my answer here.

Related

Handling and customizing errors and messages in Inno Setup

Inno Setup will show different message boxes to the user.
For example,
When it tries to install a file that is in use, it will show a message to abort/ignore/cancel.
When the setup is installing the files and the target path will have low space while installing, the setup will give an error.
I need to customize these message boxes on my own. (I do not want these native messages to be shown to the user) for example, I need to show another message box, or even totally do not show a specific message from Inno Setup, or changing a label when that message is going to fire.
All Inno Setup messages can be customized using the [Messages] section.
Some examples:
How to modify error message in Inno Setup?
Show a custom message for unsupported architectures
How can I show my own message and then exit setup if the current version of Windows is not supported?
As for the change of the message box layout/design. You cannot really change it. With some fancy implementation of Check and BeforeInstall parameters, you might be able to catch some problems before Inno Setup detects them and handle them your custom way. But that's lot of work with unreliable results.
If you tell us what are you trying to achieve more specifically, you may get more specific answer.
If you need a solution that allows everything that Inno Setup allows on error, including clean abort of the installation, Check or BeforeInstall won't help as they do not have a way to cleanly abort it.
You would have to do all the checks before the installation, e.g. in CurStepChanged(ssInstall).
[Files]
Source: "MyProg.exe"; DestDir: "{app}"; Check: ShouldInstallFile
[Code]
var
DontInstallFile: Boolean;
function ShouldInstallFile: Boolean;
begin
Result := not DontInstallFile;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
FileName: string;
Msg: string;
Response: Integer;
begin
if CurStep = ssInstall then
begin
FileName := ExpandConstant('{app}\MyProg.exe');
repeat
if FileExists(FileName) and (not DeleteFile(FileName)) then
begin
Msg := Format('File %s cannot be replaced', [FileName]);
Response := MsgBox(Msg, mbError, MB_ABORTRETRYIGNORE)
case Response of
IDABORT: Abort;
IDIGNORE: DontInstallFile := True;
end;
end;
until (Response <> IDRETRY);
end;
end;

Use Inno Setup UI as a self-extractor only - No installation

I use Inno Setup for many "standard" installers, but for this task I need to extract a bunch of temp files, run one of them, then remove them and exit the installer (without actually installing anything).
Basically I'm looking to make a self-extractor with without it being an "installer", and am after the best user experience possible with inno setup.
I have the following code which almost works fine:
[Files]
Source: "dist\*"; Flags: recursesubdirs ignoreversion dontcopy;
[Code]
function InitializeSetup(): Boolean;
var
ResultCode: Integer;
begin
Result := True;
MsgBox('Please wait a minute or two...', mbInformation, MB_OK);
ExtractTemporaryFiles('{tmp}\*');
Exec(ExpandConstant('{tmp}\MyScript.exe'), '', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Abort();
end;
The problem is that the best I can do here is show a message box "Please wait a minute or two...", the user clicks [Ok], then waits as nothing appears to happen with nothing on-screen at all, then MyScript.exe starts.
What I'd like instead is a Wizard page saying "Please wait as temporary files are extracted..." with a npbstMarquee style progress bar, then is disappears once the files are extracted and my script starts.
I don't think there's a way to tell Inno Setup to display a progress bar while ExtractTemporaryFiles() is going (which would be ideal) and working this into a custom wizard page has got me baffled.
"Install" the files to {tmp}, instead of using ExtractTemporaryFiles;
Execute the files extracted to {tmp} from Run section (or use AfterInstall parameter or CurStepChanged to trigger Pascal Script code after files are installed);
Set Uninstallable to no;
Set CreateAppDir to no;
Use [Messages] section to edit the wizard texts that are too installer-centric for your needs.
[Setup]
Uninstallable=no
CreateAppDir=no
[Files]
Source: "dist\*"; DestDir: {tmp}; Flags: recursesubdirs
[Run]
FileName: "{tmp}\MyScript.exe"
Notes:
The {tmp} folder gets automatically deleted, when the "installer" is closing;
No need for ignoreversion flag, when installing to new empty folder.
A related question: Inno Setup installer that only runs a set of embedded installers
For an answer your literal question, see Inno setup: ExtractTemporaryFile causes wizard freeze. Or a more generic question on the topic: Inno Setup: How to modify long running script so it will not freeze GUI?
It seems that ExtractTemporaryFiles() essentially locks up the UI until it's finished, so there's no way to get a progress bar (or Marquee) animated in here.
Also getting a message on the screen at all while ExtractTemporaryFiles() is in progress was difficult. I solved it like this:
const
WM_SYSCOMMAND = 274;
SC_MINIMIZE = $F020;
//-------------------------------------------------------------------
procedure MinimizeButtonClick();
begin
PostMessage(WizardForm.Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
end;
//-------------------------------------------------------------------
procedure CurPageChanged(CurPageID: Integer);
var
ResultCode: Integer;
begin
if CurPageID = wpPreparing then
begin
MinimizeButtonClick();
Exec(ExpandConstant('{tmp}\MyScript.exe'), '', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
//-------------------------------------------------------------------
function NextButtonClick(CurPageID: Integer): Boolean;
var
ProgressPage: TOutputProgressWizardPage;
begin
if CurPageID = wpReady then
begin
ProgressPage := CreateOutputProgressPage('Preparing files...', '');
ProgressPage.Show;
try
ProgressPage.Msg1Label.Caption := 'This process can take several minutes; please wait ...';
ExtractTemporaryFiles('{tmp}\*');
finally
ProgressPage.Hide;
end;
end;
Result := True;
end;
//-------------------------------------------------------------------
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
//MinimizeButtonClick() is called here as the Wizard flashes up for a second
// and minimizing it makes that 1/2 a second instead...
MinimizeButtonClick();
Abort();
end;
end;
I then changed the text on the "Ready" page to suit using the [Messages] section.
The result is:
one wizard page asking the user if they want to continue
one wizard page telling the user "please wait..." while the temp files are extracted
once the files are extracted the wizard is hidden and MyScript.exe from the temp folder is run
once MyScript.exe finishes the wizard exits cleanly and deletes the temp files

Inno Setup - prevent executing the installer multiple times simultaneously

I've got a bit of a pickle with Inno Setup: on a user machine, my installer was running slowly (something I've yet to diagnose, might be a problem specific with that computer, I still don't know). This lead to said user to run the installer again, while the first instance was still executing - and to my surprise, they both seemed to be running for a time, before crashing and burning...
I searched around but have not found any way to disable this behavior - most of my queries wound up on Inno Setup mutex feature, which is not really what I'm looking for. Anyone got tips on how to make sure there is only one instance / process of the installer executing? Thank you!
Since Inno Setup 5.5.6 you can use the SetupMutex directive:
[Setup]
AppId=MyProgram
SetupMutex=SetupMutex{#SetupSetting("AppId")}
If you want to change a text of the message, that displays when another installer is running already, use:
[Messages]
SetupAppRunningError=Setup has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
Before this version, there was no built-in mechanism available. But you could write your own pretty simply. Principle is that you create a unique mutex when the setup starts. But, as first you check if there is no such mutex already created. If so, you exit the setup, if not, you create the mutex:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
// this needs to be system-wide unique name of the mutex (up to MAX_PATH long),
// there is a discussion on this topic http://stackoverflow.com/q/464253/960757
// you can expand here e.g. the AppId directive and add it some 'salt'
MySetupMutex = 'My Program Setup 2336BF63-DF20-445F-AAE6-70FD7E2CE1CF';
function InitializeSetup: Boolean;
begin
// allow the setup to run only if there is no thread owning our mutex (in other
// words it means, there's no other instance of this process running), so allow
// the setup if there is no such mutex (there is no other instance)
Result := not CheckForMutexes(MySetupMutex);
// if this is the only instance of the setup, create our mutex
if Result then
CreateMutex(MySetupMutex)
// otherwise tell the user the setup will exit
else
MsgBox('Another instance is running. Setup will exit.', mbError, MB_OK);
end;
If your installer was called setup.exe for example, then you could use the following code to check if setup.exe is running and terminate the install.
[Code]
function IsAppRunning(const FileName : string): Boolean;
var
FSWbemLocator: Variant;
FWMIService : Variant;
FWbemObjectSet: Variant;
begin
Result := false;
FSWbemLocator := CreateOleObject('WBEMScripting.SWBEMLocator');
FWMIService := FSWbemLocator.ConnectServer('', 'root\CIMV2', '', '');
FWbemObjectSet := FWMIService.ExecQuery(Format('SELECT Name FROM Win32_Process Where Name="%s"',[FileName]));
Result := (FWbemObjectSet.Count > 0);
FWbemObjectSet := Unassigned;
FWMIService := Unassigned;
FSWbemLocator := Unassigned;
end;
function InitializeSetup: boolean;
begin
result := not IsAppRunning('setup.exe');
if not result then
MsgBox('setup.exe is already running', mbError, MB_OK);
end;

Inno Setup: Run program without showing a checkbox

I have the following lines:
[Run]
Filename: "{app}\MyApp.exe"; Flags: postinstall nowait
I would like my app to get started without showing a checkbox (which would disallow the user to do so).
Can somebody show me how, please?
Thank you.
There are few options I can think of. The first one is to run your application from the [Code] section of your script, the second one is to disable that check box for your [Run] section entry and the third one is to hide the RunList.
1. How to manually run an application when the wizard is finished ?
I would personally prefer this way, because it's more straightforward than adding a check box and hiding it later on. You will remove your current [Run] section entry and call one of the following functions from the NextButtonClick event method when its CurPageID parameter equals to wpFinished, which indicates the Finish button click:
Exec - executes the specified executable or batch file, using the same credentials as Setup/Uninstall.
ExecAsOriginalUser - executes the specified executable or batch file, using the (normally non-elevated) credentials of the user that started Setup initially
ShellExec - opens the specified file or performs another action specified by Verb, using the same credentials as Setup/Uninstall.
ShellExecAsOriginalUser - opens the specified file or performs another action specified by Verb, using the (normally non-elevated) credentials of the user that started Setup initially.
Because you haven't used the runascurrentuser nor shellexec flags, the setup internally calls a function similar to this:
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
Result := True;
if CurPageID = wpFinished then
ExecAsOriginalUser(ExpandConstant('{app}\MyApp.exe'), '', '',
SW_SHOWNORMAL, ewNoWait, ResultCode);
end;
One weakness of this solution is that the program would be executed even if the restart is requested by the setup. To workaround a missing possibility to determine this request we can check if the YesRadio is visible (it is the Yes, restart the computer now radio button) and selected, which means that the user was asked to restart the computer and confirmed it. Here is the version considering the restart request:
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
Result := True;
// if the "Finish" button was clicked and "Yes, restart the computer now"
// radio button was either not visible or not selected that time, then...
if (CurPageID = wpFinished) and ((not WizardForm.YesRadio.Visible) or
(not WizardForm.YesRadio.Checked))
then
ExecAsOriginalUser(ExpandConstant('{app}\MyApp.exe'), '', '',
SW_SHOWNORMAL, ewNoWait, ResultCode);
end;
2. How to disable the post install check box on the final page ?
Another option is to disable the check box. The user will see that the application is going to be executed, but won't be able to do anything against it (except killing the setup from Task Manager, of course). This time you will keep your [Run] section entry as it, but modify the RunList from the [Code] section:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName=My Program
[Files]
Source: "MyApp.exe"; DestDir: "{app}"
[Run]
Filename: "{app}\MyApp.exe"; Flags: postinstall nowait
[Code]
procedure CurPageChanged(CurPageID: Integer);
begin
// you must do this as late as possible, because the RunList is being modified
// after installation; so this will check if there's at least one item in the
// RunList and then set to the first item (indexing starts at 0) Enabled state
// to False
if (CurPageID = wpFinished) and (WizardForm.RunList.Items.Count > 0) then
WizardForm.RunList.ItemEnabled[0] := False;
end;
3. How to completely hide the RunList ?
This will, contrary to the second option, do what you asked for. It will keep the check box hidden, or to be more precise, it will hide the whole RunList, so if you were having more than one entry in the [Run] section with the postinstall flag specified, it won't be seen as well:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName=My Program
[Files]
Source: "MyApp.exe"; DestDir: "{app}"
[Run]
Filename: "{app}\MyApp.exe"; Flags: postinstall nowait
[Code]
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then
WizardForm.RunList.Visible := False;
end;
Works on version 5.5.7
Add the following code if you want to open a website automatically
[Code]
procedure CurPageChanged(CurPageID: Integer);
var
ErrorCode : Integer ;
begin
if CurPageID = wpFinished then
ShellExecAsOriginalUser('', ExpandConstant('http:\\www.google.pt'), '', '',SW_SHOWNORMAL, ewNoWait, ErrorCode);
end;

How to change order of pages in InnoSetup?

I would like to swap the SelectDir page with the Components page in my setup.
I found a solution where the content of the other page is assigned to the current page.
Procedure CurPageChanged(CurPageID: Integer);
Begin
Case CurPageID of
wpSelectDir:
begin
WizardForm.SelectDirPage.Notebook.ActivePage:= WizardForm.SelectComponentsPage;
WizardForm.PageNameLabel.Caption:= SetupMessage(msgWizardSelectComponents)
WizardForm.Hint:= WizardForm.PageDescriptionLabel.Caption;
WizardForm.PageDescriptionLabel.Caption:= SetupMessage(msgSelectComponentsDesc)
end;
wpSelectComponents:
begin
WizardForm.SelectComponentsPage.Notebook.ActivePage:= WizardForm.SelectDirPage;
WizardForm.DiskSpaceLabel.Caption:= WzardForm.ComponentsDiskSpaceLabel.Caption;
WizardForm.PageNameLabel.Caption:= SetupMessage(msgWizardSelectDir)
WizardForm.PageDescriptionLabel.Caption:= WizardForm.Hint
end;
end;
End;
The problem using this method is that only the content but not the actual page is changed. Message boxes and error messages are not affected. I wrote many lines of code to work around these problems but I encounter more and more problems...
Is there a better solution? I hope you can help me!
Edit: After experimenting a bit I came up with this:
procedure RedesignWizard;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(wpWelcome, 'bla', 'bla');
WizardForm.ComponentsList.Parent := Page.Surface;
//Here I am changing the layout of the pages...
end;
procedure InitializeWizard;
begin
RedesignWizard;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if (CurPageID = Page.ID) then
begin
//perform your actions specific to the Custom page here.
end;
end;
This way the components list appears before the SelctDirPage and I do not have any more problems with those message boxes.
There is absolutely no way to safely swap the order of any of the builtin pages. (And usually, the only time people ask is when they are trying to replicate the flow of a different installation system. Relax and let it go; Inno works differently, embrace it instead of fighting it.)
Having said that, it is possible to give the appearance of swapping the pages, by recreating one or the other of them as a custom page. However by doing this you will forfeit all of the built-in functionality associated with that page -- eg. if you replace the components page then you cannot use the [Components] section or parameters, and if you replace the directory page then you cannot use {app} (not even in those places that use it implicitly, such as the UninstallFilesDir).
If you're willing to put in a lot of time and effort (especially testing), it can be done. But everything is worse off as a result -- so normally you're better off not doing so.
To add to what Miral said:
[Setup]
DisableDirPage=yes // disable the built-in page
[Code]
var
ApacheDirPage: TInputDirWizardPage;
ApacheDir: AnsiString;
procedure InitializeWizard;
begin
{ Create the custom wizard pages }
ApacheDirPage :=
CreateInputDirPage( wpSelectComponents, // display AFTER select Type/Components page
'Select Apache Directory',
'Select the Apache x.x Directory' + #13#10 + '(the one that contains the BIN folder)',
'Select the Apache directory, then click Next.',
False, '' );
ApacheDirPage.Add( '');
ApacheDirPage.Values[0] := csApacheLocation;
end;

Resources