Inno Setup Change AppName based on component(s) selected - inno-setup

I need the installer to show different AppName based on (un)selected components. I tried this:
[Setup]
AppName={code:GetAppName}
AppVersion=1.0
AppVerName=Dagon Video Tools
AppId=Dagon Video Tools
DefaultDirName={sd}\Games\Dagon Video Tools
[Code]
function GetAppName(Value: string): string;
var
CurPageID: Integer;
Begin
Result := 'Dagon Video Tools'
if (CurPageID=wpSelectComponents) and IsComponentSelected('Slasher') and not IsComponentSelected('Frankenstein') then
begin
Result := 'Dagon Slasher';
end;
if (CurPageID=wpSelectComponents) and IsComponentSelected('Frankenstein') and not IsComponentSelected('Slasher') then
begin
Result := 'Dagon Frankenstein';
end;
if (CurPageID=wpSelectComponents) and IsComponentSelected('Slasher') and IsComponentSelected('Frankenstein') then
begin
Result := 'Dagon Video Tools';
end;
End;
But, as you can guess, this doesn't work. Is this script incomplete or should it be done in a different way altogether?

The AppName directive value is resolved (= your GetAppName is called) immediately after the InitializeSetup (if any) finishes. That is a long before the user is able to change the components.
So you cannot make AppName depend on the selected components.
Some uses of the AppName could be overridden with a custom value though, but not all. See below.
Though, as I know that your question is actually about a setup type, you can do this:
Create custom "type" page (like a menu) as the very first one.
Once the user selects the "type", restart the installer with a custom switch (e.g. /APPTYPE=slasher) and exit.
Once the installer is (re-)run with the /APPTYPE, you know from the beginning, what component/type you are installing and hence you can set the AppName normally.
Of course, you skip the custom "type" page.
This is actually a way simpler to implement. The only drawback is that the setup window is "recreated" after the user selects the "type".
This is the original response in case you do not want to use the above solution.
First, your implementation of the GetAppName is wrong. You are using an uninitialized variable CurPageID. And anyway, as mentioned already, the GetAppName is called even before the wizard window is created, so "current page" is irrelevant here.
The correct implementation would be like:
function GetAppName(Value: string): string;
begin
if IsComponentSelected('Slasher') and not IsComponentSelected('Frankenstein') then
begin
Result := 'Dagon Slasher';
end
else
if IsComponentSelected('Frankenstein') and not IsComponentSelected('Slasher') then
begin
Result := 'Dagon Frankenstein';
end
else
begin
Result := 'Dagon Video Tools';
end;
end;
But this still won't make it working in the AppName directive. We will use it in other contexts though later.
Also note that for your specific installer, you should better use the WizardSetupType(false) function instead of the IsComponentSelected.
FinishedLabel
Just override the Inno Setup default text in CurPageChanged(wpFinished):
procedure CurPageChanged(CurPageID: Integer);
var
S: string;
begin
if CurPageID = wpFinished then
begin
S := SetupMessage(msgFinishedHeadingLabel);
StringChange(S, '[name]', GetAppName(''));
WizardForm.FinishedHeadingLabel.Caption := S;
WizardForm.AdjustLabelHeight(WizardForm.FinishedHeadingLabel);
{ Ideally we should shift the FinishedLabel up or down here, }
{ if the height of the header changed. }
{ Note that other messages (msgFinishedLabelNoIcons or msgFinishedRestartLabel) }
{ are used in special situations, so this is not a complete solution. }
S := SetupMessage(msgFinishedLabel);
StringChange(S, '[name]', GetAppName(''));
WizardForm.FinishedLabel.Caption := S;
WizardForm.AdjustLabelHeight(WizardForm.FinishedLabel);
end;
end;
Add/Remove Programs
That's easy. There's the UninstallDisplayName directive for this, which is resolved only during the actual installation, when we already know the selected components. So we can use your (fixed) GetAppName here:
[Setup]
UninstallDisplayName={code:GetAppName}
Are you sure you want to completely remove AppName and all of its components?
You cannot change that. You better use some generic name in the AppName so that this message works for any component.
Or make the message not mention the application name at all:
[Messages]
ConfirmUninstall=Are you sure you want to completely remove this game?
Alternatively remove the message completely:
Replace or customize modal uninstallation windows in Inno Setup
Please wait while AppName is removed from your computer
The same solution as for the WizardForm.FinishedLabel. Just use the UninstallProgressForm.PageDescriptionLabel from the InitializeUninstallProgressForm.
AppName was successfully removed from your computer
Similar as with the "Are you sure you want to completely remove AppName and all of its components?"
Either make the AppName generic. Or disable the message with "silent" mode and implement your own message in the CurUninstallStepChanged(usPostUninstall).
Again, see Replace or customize modal uninstallation windows in Inno Setup.
For a similar discussion, see also Changing AppName and AppDir depending on language in Inno Setup.

Related

Inno Setup customize FinishedLabel with Pascal Script

My goal with Inno Setup 6.x is to customize the FinishedLabel text in code, i.e., Pascal Script. The reason why I'm using Pascal Script is that I want to only customize/change the label if IsAdminMode() is true. How can I do that?
The following two approaches do not work:
Use a scripted constant:
[Messages]
FinishedLabel={code:GetFinishedLabel}
[Code]
function GetFinishedLabel(Param: String): String;
begin
Result := 'BLA';
end;
This shows "{code:GetFinishedLabel}" rather than "BLA".
Customize the wizard in InitializeWizard.
Complete (failing) example:
[Code]
procedure InitializeWizard();
begin
WizardForm.FinishedLabel.Caption := 'BLA';
end;
The FinishLabel still shows the original text from Default.isl
Any ideas?
The FinishedLabel is updated at the end of the installation according to various factors. So your value set in InitializeWizard is overridden. You have to set your custom message later, such as in CurPageChanged(wpFinished):
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then
begin
WizardForm.FinishedLabel.Caption := 'BLA';
end;
end;
You might consider to improve the code to do what Inno Setup would do, like:
Taking into account, if restart is needed (FinishedRestartLabel);
Taking into account, if icons were created (FinishedLabel vs. FinishedLabelNoIcons);
Adjusting the label height to fit the message;
Shifting RunList position according to the message height.

Inno Setup: Modify icon and title of specific error message

I have a program that should only be installed on 64-bit architectures. So I added:
ArchitecturesAllowed = x64
In my Inno Setup file to prevent this.
The problem is that the message displayed is a bit too "violent". It displays "error" in title and a big red cross.
I've found that I can change content of this message (with OnlyOnTheseArchitectures message), but no way to modify icon and title without impacting other error message.
And I do not see what step can match...
Is there a way to make it like an information box?
There's no generic way to modify any standard Inno Setup message box.
You generally have to re-implement the function on your own. What is not always possible.
For your specific case, you are lucky, as you can easily implement your own custom check for 64-bit system using the IsWin64 function from the InitializeSetup event function.
And display your own custom message box using the MsgBox function.
function InitializeSetup(): Boolean;
begin
Result := True;
if not IsWin64 then
begin
MsgBox('This cannot be installed on 32-bit system.', mbInformation, MB_OK);
Result := False;
end;
end;

Inno Setup: how to call custom functions from the InstallDelete section

I would need Inno Setup generated installer to delete certain files prior installation if the software is already installed with an older version.
I tried to do this by comparing version numbers (custom function below) but when compiling, Inno Setup generates an error:
[ISPP] Undeclared identifier: "GetInstalledVersion".
The Inno Setup script relevant extract is:
(...)
[Code]
function GetInstalledVersion(MandatoryButNotUsedParam: String): String;
var Version: String;
begin
if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\'+ExpandConstant('AppId')+'_is1', 'DisplayVersion') then
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\'+ExpandConstant('AppId')+'_is1', 'DisplayVersion', Version);
MsgBox(ExpandConstant('Existing version:'+Version+' New version:'+ExpandConstant('AppVersion')), mbInformation, MB_OK);
Result := Version;
end
else
begin
Result := '';
end
end;
(...)
[InstallDelete]
#define InstalledAppVersion GetInstalledVersion('')
#if "1.013" > InstalledAppVersion
Type: files; Name: {userappdata}\xxx\*.hhd
#endif
Being new to Inno Setup, this is certainly a trivial question but no answer found on forums. The question is thus: how can I properly call the function GetInstalledVersion from the [InstallDelete] section?
Is there an issue because [InstallDelete] section might be called before [code] section is read?
Many thanks for any help / hint!
Do you want to check for currently installed version and if it's below 1.013,
then remove user files from {userappdata}\xxx\*.hhd ?
then what you need is parameter Check http://www.jrsoftware.org/ishelp/index.php?topic=scriptcheck
[Code]
function isOldVersionInstalled: Boolean;
begin
// Result := <True|False>;
end;
[InstallDelete]
Type: files; Name: {userappdata}\xxx\*.hhd; Check:isOldVersionInstalled;
What is wrong with your example:
You are calling a Pascal function from the pre-processor.
Those are two different things.
You can define a macro in pre-processor - that's kind of like a function,
but that's not what you want because Pre-processor only runs on compile time and so it can't be used to check on state of User's files/environment.

Modify pre-defined wizard page

Is there a way to modify (or replace) a pre-defined Inno Setup wizard page from the script code? I would like to place some informative bitmap and text on the (otherwise unused) bottom part of the "Installing" wizard page, or do similar (mainly static, non-functional) modifications to other wizard pages.
Note: this question is not about creating a completely new custom pages - that's clear, widely used and documented.
I think I have found an answer myself. It is vaguely hinted at here: http://www.jrsoftware.org/ishelp/index.php?topic=scriptclasses .
So I plan to access the WizardForm.InstallingPage (or other page) from the InitializeWizard procedure. That should do it.
(after having tried it - e.g.:)
procedure InitializeBillboard;
var
Page: TNewNotebookPage;
begin
Page := WizardForm.InstallingPage;
BillboardBitmapImage := TBitmapImage.Create(Page);
BillboardBitmapImage.Left := 0;
BillboardBitmapImage.Width := 416;
BillboardBitmapImage.Top := WizardForm.ProgressGauge.Top + WizardForm.ProgressGauge.Height + 16;
BillboardBitmapImage.Height := 146;
BillboardBitmapImage.OnClick := #BillboardBitmapImage_OnClick;
BillboardBitmapImage.Parent := Page;
end;

Extending event functions via #Include directive

I have a base inno-setup script that I use as a template for several installers. As part of the base script I have a call to the event function, NextButtonClick.
I would now like to add some additional code to the NextButtonClick event that will only be executed by one of my installers. Is there some way to "extend" the NextButtonClick event? I'm thinking of something along the lines of Python's super() function.
Inno-setup uses Pascal as a scripting language, so perhaps a Pascal expert can offer some insight.
Not directly
Remember the #include directive is just a pre-compiler directive which makes the included file to appear in the place the directive is to the inno setup script compiler.
but
To avoid including individual installer code on the template script, you can create a convention to call a procedure in the template.
The only rule you have to follow is that every installer must declare the procedure, even blank. That way, you can customize as per-installer basis while maintaining a neutral template.
Your template may be something like:
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := BeforeNextButtonClick(CurPageID);
//if the per-installer code decides not to allow the change,
//this code prevents further execution, but you may want it to run anyway.
if not Result then
Exit;
//your template logic here
Result := Anything and More or Other;
//same here!
if not Result then
Exit;
//calling the per-installer code
Result := AfternextButtonClck(CurPageID);
end;
Then individual installers may look like this:
function BeforeNextButtonClick(CurPageID: Integer): Boolean;
begin
//specific logic here
Result := OtherThing;
end
function AfterNextButtonClick(CurPageID: Integer): Boolean;
begin
//and here, a blank implementation
Result := True;
end;
#include MyCodeTemplate.iss
Maybe it is possible to implement a complex approach, I just can't remember if PascalScript supports procedural types and no time to check with inno.
disclaimer all code written directly here to show you the idea, it may not compile.
I'm using the following workaround, which may make things hard to manage eventually, but using version control I'm able to keep a handle on it for now:
In my individual installers, I have a series of #define directives. For example:
#define IncludeSomeFeature
#define IncludeSomeOtherOption
Then in my base template, I use the #ifdef directives to optionally include different pieces of code within the Pascal scripting events:
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
Result := True;
if CurPageID = wpReady then begin
#ifdef IncludeSomeFeature
... some code ...
#endif
#ifdef IncludeSomeOtherOption
... some more code ...
#endif
... code to run for every installer ...
end;
end;
The one downside to this approach is that code that the base template will slowly fill up with code that really belongs with the individual installer. However, since these are compile time directives, the resulting setup executables should not get bloated.
Really, though, my biggest problem with this approach is that it just doesn't feel like The Right Way™.

Resources