Make the uninstall registry key conditional via code - inno-setup

How do you make Inno Setup disable CreateUninstallRegKey via code?
My setup.exe file created in Inno Setup accepts parameters, e.g.:
setup.exe -a
or
setup.exe -b
If -a parameter is supplied, then enable CreateUninstallRegKey, or if -b parameter is supplied, then disable CreateUninstallRegKey.
Is there anyway to set CreateUninstallRegKey via code or do I have to make a function then call the function in script section?
This help page explains about using {code:...} constants, but unfortunately I got this error:
Thanks

Do not use the {code:} expression for passing values to Boolean type directives. Do it this way:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
CreateUninstallRegKey=NeedsUninstallRegKey
[Code]
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
function NeedsUninstallRegKey: Boolean;
begin
Result := CmdLineParamExists('-a');
end;

Related

Using Inno Setup preprocessor variable value with quote/apostrophe in Pascal Script code

When I define a preprocessor string variable (using the ISPP) that contains a quote/apostrophe, there will be a compiler error when I use the function ExpandConstant in the [Code] section to read this string.
Here's an example .iss script for demonstrating/testing:
#define _AppName "Uli's Program"
[Setup]
AppName={#_AppName}
AppVersion=1.2.3
DefaultDirName={pf}\{#_AppName}
[Code]
function InitializeSetup: Boolean;
begin
MsgBox(ExpandConstant('{#_AppName}'),
mbInformation,
MB_OK);
Result:=False;
end;
The exact compiler error message is:
comma (',') expected.
Update
This works when the apostrophe is doubled. But now the captions of the wizard pages show the app name with a double apostrophe (because of AppName={#_AppName}).
A similar issue occurs when the #define is removed and the script is altered this way:
[Setup]
AppName=Uli's Program
AppVersion=1.2.3
DefaultDirName={pf}\{#AppName}
[Code]
function InitializeSetup: Boolean;
begin
MsgBox('{#SetupSetting("AppName")}'),
mbInformation,
MB_OK);
Result:=False;
end;
Now the compiler error message is
Assignment expected.
You can use the macro to create an entry in CustomMessages section.
And then load the custom message in Pascal Script using CustomMessage function.
#define _AppName "Uli's Program"
[CustomMessages]
MyAppName={#_AppName}
[Code]
function InitializeSetup(): Boolean;
begin
MsgBox(CustomMessage('MyAppName'), mbInformation, MB_OK);
Result := False;
end;
Another (rather hackish) way is to use StringChange preprocessor function to double the quote in Pascal Script.
#define _AppName "Uli's Program"
[Code]
function InitializeSetup(): Boolean;
begin
MsgBox('{#StringChange(_AppName, "'", "''")}', mbInformation, MB_OK);
Result := True;
end;
Note that I do not use ExpandConstant to resolve the preprocessor variable/expression - It's nonsense. See Evaluate preprocessor macro on run time in Inno Setup Pascal Script.

Uncheck a task checkbox at setup initialisation

In my Inno Setup script there's a task that may be used under certain conditions which are determined by code. In no other conditions this task should be executed. In fact that entire Tasks page is skipped then. Unfortunately the task selection is remembered by Inno Setup and restored on every following update setup, even if the page isn't visible at all.
I now need to uncheck that task generally at every setup initialisation in order to forget the last selected state. But I can't get this to work. Here's my latest try:
[Tasks]
Name: DeleteConfig; Description: "{cm:Task_DeleteConfig}"; Flags: unchecked
#define Task_DeleteConfig_Index 0
[InstallDelete]
; Delete user configuration files if the task is selected
Type: files; Name: "{userappdata}\...\App.conf"; Tasks: DeleteConfig
[Code]
var
IsDowngradeSetup: Boolean;
function InitializeSetup: Boolean;
begin
// More code not shown here, but the following may be set under certain conditions
IsDowngradeSetup := true;
end;
procedure InitializeWizard;
begin
// Clear possibly remembered value from previous downgrade install
WizardForm.TasksList.Checked[{#Task_DeleteConfig_Index}] := false;
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
// Make upgrade install quicker
Result := ((PageID = wpSelectTasks) or ((PageID = wpReady) and (GetArrayLength(products) = 0))) and PrevInstallExists;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpWelcome then
begin
if PrevInstallExists then
begin
// Change "Next" button to "Upgrade" on the first page, because it won't ask any more
WizardForm.NextButton.Caption := ExpandConstant('{cm:Upgrade}');
WizardForm.FinishedHeadingLabel.Caption := ExpandConstant('{cm:UpdatedHeadingLabel}');
end;
end;
if CurPageID = wpSelectTasks then
begin
if IsDowngradeSetup then
begin
// Pre-select task to delete existing configuration on downgrading (user can deselect it again)
// (Use the zero-based index of all rows in the tasks list GUI)
// Source: http://stackoverflow.com/a/10490352/143684
WizardForm.TasksList.Checked[{#Task_DeleteConfig_Index}] := true;
end;
end;
end;
This gives me a
Runtime error (at 85:77): List index out of bounds (0).
I don't know where "85:77" is supposed to be but from the only recent changes it can only be the quoted code above.
I first had that in the InitializeSetup function but that didn't work either.
Where should I put this code so that it works and finds a fully initialised tasks list? The tasks page may not be shown so I think it's too late to wait for the page to become visible. In fact the code used to be there and wasn't called when the page was skipped.
I do not understand, why you need to reset the task. I have an impression that you have that conditional skip of the task implemented incorrectly.
It's just a guess, but I assume that you skip the task page using the ShouldSkipPage. So the task stays checked, if it was enabled in a previous installation.
Do not use the ShouldSkipPage for this, use the Check parameter instead. If there's a single task only that is conditionally disabled using the Check parameter, whole task page gets skipped.
[Tasks]
Name: DeleteConfig; Description: "{cm:Task_DeleteConfig}"; Flags: unchecked; \
Check: UseDeleteConfig
[Code]
function UseDeleteConfig: Boolean;
begin
Result := IsDowngradeSetup;
end;
To answer your actual question, you can do this:
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageId = wpReady then
begin
if PrevInstallExists then
begin
{ In Inno Setup 6, you can use WizardSelectTasks }
WizardForm.TasksList.Checked[0] := False;
end;
end;
end;
function UpdateReadyMemo(
Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo,
MemoGroupInfo, MemoTasksInfo: String): String;
begin
if PrevInstallExists then
begin
MemoTasksInfo := '';
end;
end;
Though again, I do not think, that this is a good solution.
Or even easier, use the UsePreviousTasks:
[Setup]
UsePreviousTasks=no
Or similarly using the checkedonce flag:
[Tasks]
Name: DeleteConfig; Description: "{cm:Task_DeleteConfig}"; Flags: unchecked checkedonce

How can I use values from the [Setup] section in code?

How can I use values from the [Setup] section in code?
I suspect I'm using the tool incorrectly; maybe I should be doing this in a different way entirely.
[Setup]
MyValue=some value
[code]
function InitializeSetup(): Boolean;
begin
// blank
MsgBox(GetEnv('MyValue'), mbError, MB_OK);
// no expansion occurs
MsgBox(ExpandConstant('MyValue'), mbError, MB_OK);
// unknown constant "MyValue".
MsgBox(ExpandConstant('{MyValue}'), mbError, MB_OK);
Result := true;
end;
thank you for your help!
You cannot declare variable in the [Setup] section. This section may contain only a set of predefined directives. If your aim was to define a constant which could be used in script section entries as well as in script coding [Code] section, then you were looking for preprocessor variables declared by #define directive. For example:
#define MyValue "some value"
[Setup]
AppName={#MyValue}
AppVersion=1.5
DefaultDirName={pf}\My Program
[INI]
Filename: "MyProg.ini"; Section: "InstallSettings"; Key: "InstallPath"; String: "{#MyValue}"
[Code]
function InitializeSetup: Boolean;
begin
Result := True;
MsgBox('{#MyValue}', mbInformation, MB_OK);
end;
What actually happens behind a {#MyValue} statement is that the preprocessor emits the value of the defined MyValue constant to the final script.

Inno how to add <Skip> button for optional wizard page?

I have an optional wizard page of TInputDirWizardPage type.
How to add button which will not validate data and skip if data were not entered?
Did you check the inno help, here you see the available functions on the TInputDirWizardPage
TInputDirWizardPage = class(TWizardPage)
function Add(const APrompt: String): Integer;
property Buttons[Index: Integer]: TNewButton; read;
property Edits[Index: Integer]: TEdit; read;
property PromptLabels[Index: Integer]: TNewStaticText; read;
property SubCaptionLabel: TNewStaticText; read;
property Values[Index: Integer]: String; read write;
end;
I used this way only if i needed a text input on the page. I recommend you to create a complete custom WizardPage, with that you are more flexible. For the creation of the page you can use a designer, I created all my custom pages with the InnoSetup Form Designer. Here you can see it in action http://www.cenadep.org/2012/02/09/innosetup-form-designer/
I've found simple and working solution. Instead of adding additional button to avoid validation of empty path. I've just added default dir creation. in [Dirs] section.
[Dirs]
Name: {code:WrkGetWorkingDir}; Flags: uninsneveruninstall
My application could accept working dir cmdline arg or create default one if it is not specified. So. I just always specifying that cmdline arg and creating that default dir (if user did not changed that path) in installation script.
Hope this will help
procedure OnClickMyButton(Sender: TObject);
begin
MsgBox('OnClickMyButton', mbInformation, MB_OK);
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = YourPageID.ID then
begin
MyButton := TButton.Create(YourPageID);
MyButton.Parent := YourPageID.Buttons[0].Parent;
MyButton.Top := YourPageID.Edits[0].Top + YourPageID.Edits[0].Height + 10;
MyButton.Left := YourPageID.Edits[0].Left;
MyButton.Width := 100;
MyButton.Caption := 'My Custom Button';
MyButton.OnClick := #OnClickMyButton;
end;
end;

Inno Setup: How do I create a 'check:' function for currently installed version of DirectX and whether MS VC++ 2005 is installed

I'm in the process of creating a custom installer and for the most part I have it set up the way I want it, except that it's missing two features I want to add to the Setup. I've done some extensive searching and while I've found plenty of similar questions, I haven't been able to take the responses to those and modify them for my specific needs with any success.
Basically what I need to do is create a custom function for 'Check:' that checks the version of DirectX that's currently installed. I know there is the 'RegQueryStringValue' function, and I know where the key is in the registry that contains the version (HKLM\SOFTWARE\Microsoft\DirectX, Version). I just don't know how to implement the code to check the version contained in the registry, and if it reports back a value less than 4.09.00.0904 to go ahead with the DXSETUP I have entered under [Files].
I also want to perform this same routine for a 'Check:' to use with Visual C++ 2005 (x86). This one I believe will be simpler as it only needs to check if an actual key exists (RegQueryKey?) and not a value. I believe the key for VC++ 2005 is HKLM\SOFTWARE\Microsoft\VisualStudio\8.0
If anyone can help me out I'd greatly appreciate it, as I've been messing with this for several hours trying to get something functional together without much success. If you require any further information from me I'd be more than happy to provide it.
There's an example for checking for prerequisites included in the Inno Setup Examples of doing this sort of thing in CodePrepareToInstall.iss. InitializeSetup shows how to check for the existence of a registry entry, and you can do so in DetectAndInstallPrerequisites. I added a CheckDXVersion function that you can pass the Version string from the DirectX registry entry that checks for 4.9 or higher (untested!) you can use as well.
; -- CodePrepareToInstall.iss --
;
; This script shows how the PrepareToInstall event function can be used to
; install prerequisites and handle any reboots in between, while remembering
; user selections across reboots.
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
DefaultGroupName=My Program
UninstallDisplayIcon={app}\MyProg.exe
OutputDir=userdocs:Inno Setup Examples Output
[Files]
Source: "MyProg.exe"; DestDir: "{app}";
Source: "MyProg.chm"; DestDir: "{app}";
Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme;
[Icons]
Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
[Code]
const
(*** Customize the following to your own name. ***)
RunOnceName = 'My Program Setup restart';
QuitMessageReboot = 'The installation of a prerequisite program was not completed. You will need to restart your computer to complete that installation.'#13#13'After restarting your computer, Setup will continue next time an administrator logs in.';
QuitMessageError = 'Error. Cannot continue.';
var
Restarted: Boolean;
function InitializeSetup(): Boolean;
begin
Restarted := ExpandConstant('{param:restart|0}') = '1';
if not Restarted then begin
Result := not RegValueExists(HKLM, 'Software\Microsoft\Windows\CurrentVersion\RunOnce', RunOnceName);
if not Result then
MsgBox(QuitMessageReboot, mbError, mb_Ok);
end else
Result := True;
end;
function CheckDXVersion(const VerString: String): Boolean;
var
MajorVer, MinorVer: Integer;
StartPos: Integer;
TempStr: string;
begin
(* Extract major version *)
StartPos := Pos('.', VerString);
MajorVer := StrToInt(Copy(VerString, 1, StartPos - 1);
(* Remove major version and decimal point that follows *)
TempStr := Copy(VerString, StartPos + 1, MaxInt);
(* Find next decimal point *)
StartPos := Pos('.', TempStr);
(* Extract minor version *)
MinorVer := Copy(TempStr, 1, StartPos - 1);
Result := (MajorVer > 4) or ((MajorVer = 4) and MinorVer >= 9));
end;
function DetectAndInstallPrerequisites: Boolean;
begin
(*** Place your prerequisite detection and installation code below. ***)
(*** Return False if missing prerequisites were detected but their installation failed, else return True. ***)
//<your code here>
Result := True;
(*** Remove the following block! Used by this demo to simulate a prerequisite install requiring a reboot. ***)
if not Restarted then
RestartReplace(ParamStr(0), '');
end;
function Quote(const S: String): String;
begin
Result := '"' + S + '"';
end;
function AddParam(const S, P, V: String): String;
begin
if V <> '""' then
Result := S + ' /' + P + '=' + V;
end;
function AddSimpleParam(const S, P: String): String;
begin
Result := S + ' /' + P;
end;
procedure CreateRunOnceEntry;
var
RunOnceData: String;
begin
RunOnceData := Quote(ExpandConstant('{srcexe}')) + ' /restart=1';
RunOnceData := AddParam(RunOnceData, 'LANG', ExpandConstant('{language}'));
RunOnceData := AddParam(RunOnceData, 'DIR', Quote(WizardDirValue));
RunOnceData := AddParam(RunOnceData, 'GROUP', Quote(WizardGroupValue));
if WizardNoIcons then
RunOnceData := AddSimpleParam(RunOnceData, 'NOICONS');
RunOnceData := AddParam(RunOnceData, 'TYPE', Quote(WizardSetupType(False)));
RunOnceData := AddParam(RunOnceData, 'COMPONENTS', Quote(WizardSelectedComponents(False)));
RunOnceData := AddParam(RunOnceData, 'TASKS', Quote(WizardSelectedTasks(False)));
(*** Place any custom user selection you want to remember below. ***)
//<your code here>
RegWriteStringValue(HKLM, 'Software\Microsoft\Windows\CurrentVersion\RunOnce', RunOnceName, RunOnceData);
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ChecksumBefore, ChecksumAfter: String;
begin
ChecksumBefore := MakePendingFileRenameOperationsChecksum;
if DetectAndInstallPrerequisites then begin
ChecksumAfter := MakePendingFileRenameOperationsChecksum;
if ChecksumBefore <> ChecksumAfter then begin
CreateRunOnceEntry;
NeedsRestart := True;
Result := QuitMessageReboot;
end;
end else
Result := QuitMessageError;
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := Restarted;
end;

Resources