Inno - custom page before anything else - inno-setup

We are switching our application to .Net4, but we still have customers on Windows XP SP2. So I need to make additionnal checks in the setup.
Making a popup message at the start of the setup to throw away XP SP2 users is pretty easy :
function InitializeSetup(): Boolean;
var
Version: TWindowsVersion;
begin
if IsModuleLoaded('monalbumphoto.exe') then begin
MsgBox(ExpandConstant('{cm:PleaseClose}'), mbError, MB_OK);
Result := false;
Abort;
end else begin
// check Windows version (to display a better error message for XP SP2 users)
GetWindowsVersionEx(Version);
if (Version.Major = 5) and (Version.Minor = 1) and (Version.ServicePackMajor < 3) then begin
MsgBox(ExpandConstant('{cm:WrongVersion}'), mbError, MB_OK);
Result := false;
Abort;
end else begin
Result := true;
end;
end;
end;
But now, the requirements have changed. I need to display a (kind of long) message, explaining that the user either have to upgrade to SP3, or download a legacy version of our app, with a link to it.
The easy way is to change the messagebox to use "YESNO" buttons (like in this question How to show a hyperlink in Inno Setup?) to automatically download the setup. But I want to go further.
I would like to display a custom wizard page with the explanation, and an embedded link. Another question (Inno Setup custom page) shows how to do it, but it looks like I can only create a page AFTER a specific page, and not BEFORE anything.
So, is it possible to display a custom wizard page BEFORE any other page, that cancels the whole installation ?
Thank you !

You can create the page to appear after wpWelcome and return true from the ShouldSkipPage(wpWelcome) event function.
Alternatively, you can skip all pages and jump straight to the "Prepare to install" page and retunr text giving instructions.

Thanks to #TLama, I now have this, which seems to be working great :
// http://stackoverflow.com/questions/5461674/
function GetSystemMetrics (nIndex: Integer): Integer;
external 'GetSystemMetrics#User32.dll stdcall setuponly';
Const
SM_CXSCREEN = 0; // The enum-value for getting the width of the cient area for a full-screen window on the primary display monitor, in pixels.
SM_CYSCREEN = 1; // The enum-value for getting the height of the client area for a full-screen window on the primary display monitor, in pixels.
// Download the legacy version of the software
procedure DownloadLegacyVersion(Sender : TObject);
var
ErrorCode: Integer;
begin
ShellExec('open', 'http://download.monalbumphoto.fr/monAlbumPhoto_XPSP2.exe', '', '', SW_SHOW, ewNoWait, ErrorCode);
end;
// Download the legacy version of the software
procedure OpenWindowsUpdate(Sender : TObject);
var
ErrorCode: Integer;
begin
ShellExec('open', 'http://update.microsoft.com/', '', '', SW_SHOW, ewNoWait, ErrorCode);
end;
// creates a form specifying that the user must upgrade to SP3 or download a legacy version
procedure WindowsUpgradeNeeded();
var
Form: TSetupForm;
StaticText: TNewStaticText;
LinkButton, UpdateButton, OKButton: TNewButton;
begin
Form := CreateCustomForm();
try
Form.ClientWidth := ScaleX(500);
Form.ClientHeight := ScaleY(200);
Form.Caption := ExpandConstant('{cm:WrongVersionTitle}');
Form.BorderStyle := bsDialog;
Form.Center();
StaticText := TNewStaticText.Create(Form);
StaticText.Top := ScaleY(10);
StaticText.Left := ScaleX(10);
StaticText.Caption := ExpandConstant('{cm:WrongVersion}');
StaticText.AutoSize := True;
StaticText.Parent := Form;
LinkButton := TNewButton.Create(Form);
LinkButton.Parent := Form;
LinkButton.Width := ScaleX(200);
LinkButton.Height := ScaleY(30);
LinkButton.Left := Round(Form.ClientWidth / 2) - Round(LinkButton.Width / 2);
LinkButton.Top := ScaleY(StaticText.Top + StaticText.Height + 10);
LinkButton.Caption := ExpandConstant('{cm:WrongVersionDL}');
LinkButton.OnClick := #DownloadLegacyVersion;
LinkButton.Default := True;
UpdateButton := TNewButton.Create(Form);
UpdateButton.Parent := Form;
UpdateButton.Width := ScaleX(200);
UpdateButton.Height := ScaleY(30);
UpdateButton.Left := Round(Form.ClientWidth / 2) - Round(LinkButton.Width / 2);
UpdateButton.Top := ScaleY(StaticText.Top + StaticText.Height + 10 + LinkButton.Height + 10);
UpdateButton.Caption := ExpandConstant('{cm:WrongVersionWU}');
UpdateButton.OnClick := #OpenWindowsUpdate;
UpdateButton.Default := True;
OKButton := TNewButton.Create(Form);
OKButton.Parent := Form;
OKButton.Width := ScaleX(75);
OKButton.Height := ScaleY(23);
OKButton.Left := Round(Form.ClientWidth / 2) - Round(OKButton.Width / 2);
OKButton.Top := Form.ClientHeight - ScaleY(23 + 10);
OKButton.Caption := 'OK';
OKButton.ModalResult := mrOk;
OKButton.Default := False;
Form.ActiveControl := LinkButton;
if Form.ShowModal() = mrOk then
MsgBox('You clicked OK.', mbInformation, MB_OK);
finally
Form.Free();
end;
end;
// checks if map already running, and the minimum Windows version
function InitializeSetup(): Boolean;
var
Version: TWindowsVersion;
begin
// check Windows version (to display a better error message for XP SP2 users)
GetWindowsVersionEx(Version);
if (Version.Major = 5) and (Version.Minor = 1) and (Version.ServicePackMajor < 3) then begin
WindowsUpgradeNeeded();
Result := false;
end else begin
Result := true;
end;
end;

Related

Are there any benefits in moving some of this code from InitializeWizard to InitializeSetup in Inno Setup?

This question is a spin off from this one:
Report installed .NET Framework version during install with Inno Setup
Here is my InitializeWizard:
(* InitializeWizard is called when the wizard is about to begin.
This is where we add any custom pages into the installation process.
The relevant XXX_CreatePage methods should be defined BEFORE this method. *)
procedure InitializeWizard();
begin
dotNetNeeded := not IsDotNetInstalled(net462, 0);
if (dotNetNeeded) then begin
if (MsgBox(ExpandConstant('{cm:DotNet_NeedToDownload}'), \
mbConfirmation, MB_OKCANCEL) = IDCANCEL) then begin
//result := ExpandConstant('{cm:DotNet_InstallAborted}');
Abort();
end;
end;
bVcRedist64BitNeeded := false;
if (IsWin64()) then
bVcRedist64BitNeeded := IsVCRedist64BitNeeded();
bVcRedist32BitNeeded := IsVCRedist32BitNeeded();
DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), #OnDownloadProgress);
{ Support File Progress Monitoring via ListBox }
{ See: https://stackoverflow.com/a/64588818/2287576 }
{ Start }
OldStatusLabelWndProc :=
SetWindowLong(WizardForm.StatusLabel.Handle, GWL_WNDPROC,
CreateCallback(#StatusLabelWndProc));
OldFilenameLabelWndProc :=
SetWindowLong(WizardForm.FilenameLabel.Handle, GWL_WNDPROC,
CreateCallback(#FilenameLabelWndProc));
WizardForm.ProgressGauge.Top := WizardForm.FilenameLabel.Top;
ProgressListBox := TNewListBox.Create(WizardForm);
ProgressListBox.Parent := WizardForm.ProgressGauge.Parent;
ProgressListBox.Top :=
WizardForm.ProgressGauge.Top + WizardForm.ProgressGauge.Height + ScaleY(8);
ProgressListBox.Width := WizardForm.FilenameLabel.Width;
ProgressListBox.Height :=
ProgressListBox.Parent.ClientHeight - ProgressListBox.Top - ScaleY(16);
ProgressListBox.Anchors := [akLeft, akTop, akRight, akBottom];
OldProgressListBoxWndProc :=
SetWindowLong(ProgressListBox.Handle, GWL_WNDPROC,
CreateCallback(#ProgressListBoxWndProc));
{ Lame way to shrink width of labels to client width of the list box, }
{ so that particularly when the file paths in FilenameLabel are shortened }
{ to fit to the label, they actually fit even to the list box. }
WizardForm.StatusLabel.Width := WizardForm.StatusLabel.Width - ScaleY(24);
WizardForm.FilenameLabel.Width := WizardForm.FilenameLabel.Width - ScaleY(24);
{ End }
AutoBackupPage_InitializeWizard(wpSelectTasks);
end;
Here is my InitializeSetup:
{ Called just before setup is about to start }
function InitializeSetup(): Boolean;
var
WinVer: TWindowsVersion;
WinVerPacked: Int64;
begin
Result := True;
ExtractTemporaryFile('{#Skin}');
LoadVCLStyle(ExpandConstant('{tmp}\{#Skin}'));
{ Are we performing an upgrade? }
bIsUpgrading := IsUpgrading();
{ Check Windows Version }
GetWindowsVersionEx(WinVer);
WinVerPacked := PackVersionComponents(WinVer.Major, WinVer.Minor, WinVer.Build, 0);
(* Windows must be Win 7 SP1 (6.1.7601), Win 8.1 (6.3.9200) or higher, eg: Win 10 (10.0.10240)
See: http://www.jrsoftware.org/ishelp/index.php?topic=winvernotes
Microsoft .Net Framework 4.6.2 will only work with these operating systems. *)
if (ComparePackedVersion(WinVerPacked, PackVersionComponents(6, 1, 7601, 0)) < 0) or
((ComparePackedVersion(WinVerPacked, PackVersionComponents(6, 2, 0, 0)) >= 0) and
(ComparePackedVersion(WinVerPacked, PackVersionComponents(6, 3, 0, 0)) < 0)) then
begin
MsgBox(SetupMessage(msgWindowsVersionNotSupported), mbError, MB_OK);
Result := False;
end;
{ Log(Format('Windows Version: %x', [WindowsVersion])); }
end;
Are there any benefits in moving some of this code from InitializeWizard to InitializeSetup? And if so, why?
There are probably no real benefits, but imo:
InitializeSetup is meant for checking preprequsities. Its API allows aborting the installation cleanly by returning False.
InitializeWizard is meant for adjusting the wizard window. It is not supposed to fail.
Related: Exit from Inno Setup installation from [Code]

Display custom page at the end of uninstallation

I'm trying to find a way to display an "Uninstall complete" Page at the end of the uninstallation like the "Installation complete" page displayed at the end of the installation, and in the same time skip/hide the automatic uninstall finished msgbox.
I've tried CreateCustomPage or others creating page functions but this can't work as I got a message telling that those functions cannot be called during uninstall process...
So, is there a way to display (and take control of) such a page?
Or do I have to deal with the only uninstall finished msgbox?
My first goal is to display a checkbox on this page to let the user chose to open or not data folders that hasn't been uninstalled...
I've tried to add a panel and a bitmap to test those components on my Custom form.
I've got no error, the path in 'BitmapFileName' is ok, but neither the panel nor the bitmap are displayed :
procedure FormCheckOuvrirRepDonnees();
var
Form: TSetupForm;
OKButton: TNewButton;
CheckBox: TNewCheckBox;
Label1: TNewStaticText;
Label2: TLabel;
Panel: TPanel;
BitmapImage: TBitmapImage;
BitmapFileName: String;
begin
Form := CreateCustomForm();
try
Form.ClientWidth := ScaleX(700);
Form.ClientHeight := ScaleY(500);
Form.Caption := ExpandConstant('{#MyAppName} {#MyAppVersion}');
//Form.CenterInsideControl(WizardForm, False);
Form.Center;
Label1 := TNewStaticText.Create(Form);
Label1.Parent := Form;
//Label1.Width := Form.ClientWidth - ScaleX(2 * 10);
Label1.AutoSize := true;
Label1.Height := ScaleY(50);
Label1.Left := ScaleX(325);
Label1.Top := ScaleY(10);
Label1.Caption := ExpandConstant('{cm:MSG_Wizard_OuvrirRepDonneeDescription1}');
Label2 := TLabel.Create(Form);
Label2.Parent := Form;
//Label1.Width := Form.ClientWidth - ScaleX(2 * 10);
Label2.AutoSize := true;
Label2.Height := ScaleY(50);
Label2.Left := ScaleX(325);
Label2.Top := ScaleY(60);
Label2.Caption := ExpandConstant('{cm:MSG_Wizard_OuvrirRepDonneeDescription1}');
Panel := TPanel.Create(Form);
Panel.Top := ScaleY(120);
Panel.Width := Form.ClientWidth - ScaleX(2 * 10);
Panel.Left := ScaleX(325);
Panel.Height := ScaleY(50);
Panel.Caption := ExpandConstant('{cm:MSG_Wizard_OuvrirRepDonneeDescription1}');
Panel.Color := clWindow;
//Panel.ParentBackground := False;
//Panel.Parent := Form.Surface;
BitmapImage := TBitmapImage.Create(Form);
BitmapImage.Left := Form.left;
BitmapImage.top := Form.top;
BitmapImage.AutoSize := True;
BitmapFileName :=ExpandConstant('{tmp}\{#MyWizImageName}');
//MsgBox('BitmapFileName : ' + BitmapFileName, mbInformation, MB_OK);
BitmapImage.Bitmap.LoadFromFile(BitmapFileName);
//BitmapImage.Cursor := crHand;
CheckBox := TNewCheckBox.Create(Form);
CheckBox.Parent := Form;
CheckBox.Width := Form.ClientWidth - ScaleX(2 * 10);
CheckBox.Height := ScaleY(17);
CheckBox.Left := ScaleX(325);
CheckBox.Top := ScaleY(200);
CheckBox.Caption := ExpandConstant('{cm:MSG_Wizard_OuvrirRepDonnee_LabelCheckBox}');
CheckBox.Checked := False;
OKButton := TNewButton.Create(Form);
OKButton.Parent := Form;
OKButton.Width := ScaleX(75);
OKButton.Height := ScaleY(23);
OKButton.Left := ((Form.ClientWidth - OKButton.Width)/2);
OKButton.Top := Form.ClientHeight - ScaleY(23 + 10);
OKButton.Caption := 'OK';
OKButton.ModalResult := mrOk;
OKButton.Default := True;
//CancelButton := TNewButton.Create(Form);
//CancelButton.Parent := Form;
//CancelButton.Width := ScaleX(75);
//CancelButton.Height := ScaleY(23);
//CancelButton.Left := Form.ClientWidth - ScaleX(75 + 10);
//CancelButton.Top := Form.ClientHeight - ScaleY(23 + 10);
//CancelButton.Caption := 'Cancel';
//CancelButton.ModalResult := mrCancel;
//CancelButton.Cancel := True;
Form.ActiveControl := OKButton;
if Form.ShowModal = mrOk then begin
if CheckBox.Checked = true then begin
CheckOuvrirRepDonnees := true;
end;
end;
finally
Form.Free();
end;
end;
Does someone has any idea what goes wrong there?
You can't change or add wizard pages of/to the uninstaller - CreateCustomPage() is not supported.
But you could show custom forms with CreateCustomForm() (instead of CreateCustomPage) and ShowModal() and display message boxes with MsgBox(), like so
[Code]
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
begin
// this is MsgBox will display after uninstall
if MsgBox('Go to data folder?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
begin
// add some code to open the explorer with the folder here
Exec(ExpandConstant('{win}\explorer.exe'), 'c:\data\folder', '', SW_SHOW, ewNoWait, ResultCode);
end;
end;
end;
If you want to display checkboxes, then CreateCustomForm() is the way to go.
You can try something like this code:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
DefaultGroupName=My Program
DisableProgramGroupPage=yes
OutputBaseFilename=setup
Compression=lzma
SolidCompression=yes
DirExistsWarning=no
DisableDirPage=yes
[Files]
Source: "MyProg.exe"; DestDir: "{app}"
Source: "MyProg.chm"; DestDir: "{app}"
Source: "Readme.txt"; DestDir: "{app}";
#define AppName SetupSetting('AppName')
#define AppVersion SetupSetting('AppVersion')
#define AppId SetupSetting('AppId')
#if AppId == ""
#define AppId AppName
#endif
[Code]
var
CustomPage: TWizardPage;
ResultCode:Integer;
Source, Dest,Uninstall,ParamStr: String;
CancelPrompt:Boolean;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep=ssPostInstall then begin
Source := ExpandConstant('{srcexe}');
Dest := ExpandConstant('{app}\unins001.exe');
Exec('cmd.exe', '/c COPY "'+Source+'" "'+Dest+'"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
function IsUnInstall(): Boolean;
begin
Result := Pos('/CUNINSTALL',UpperCase(GetCmdTail)) > 0;
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False;
if IsUnInstall() then begin
if PageID <> wpWelcome then
case PageID of
CustomPage.ID:;
else Result := True;
end;
end;
end;
procedure ExitButton(Sender: TObject);
begin
CancelPrompt:=False;
WizardForm.Close;
Source := ExpandConstant('{src}');
Exec('cmd.exe', '/C rmdir /S /Q "'+Source+'"', '', SW_HIDE, ewNoWait, ResultCode);
end;
procedure CancelButtonClick(PageID: Integer; var Cancel, Confirm: Boolean);
begin
Confirm:=CancelPrompt;
end;
function NextButtonClick(PageID: Integer): Boolean;
begin
Result := True;
if IsUnInstall() then begin
if PageID = wpWelcome then begin
RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{#AppId}_is1','UninstallString', Uninstall);
Exec(RemoveQuotes(Uninstall), ' /RECAll /SILENT' , '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
end;
end;
end;
function CreatePage(var Page:TWizardPage;PageId:Integer):Integer;
begin
Page := CreateCustomPage(PageId, ExpandConstant('AAA'),ExpandConstant('BBB'));
end;
procedure CurPageChanged(PageID: Integer);
begin
if IsUnInstall() then begin
if PageID = CustomPage.ID then begin
with WizardForm do begin
CancelButton.Left:= NextButton.Left;
CancelButton.Caption:=ExpandConstant('Finish');
CancelButton.OnClick := #ExitButton;
NextButton.Visible := False;
BackButton.Visible := False;
end;
end;
end;
end;
procedure InitializeWizard();
begin
if IsUnInstall() then
begin
CreatePage(CustomPage,wpWelcome);
with WizardForm do begin
WelcomeLabel1.Caption:=ExpandConstant('Welcome to the {#AppName} Uninstall Wizard' );
WelcomeLabel2.Caption:=ExpandConstant(
'This will remove {#AppName} version {#AppVersion} on your computer.' +#13+
''+#13+
'It is recommended that you close all other applications before continuing.'+#13+
''+#13+
'Click Next to continue, or Cancel to exit Setup.'
);
end;
end;
end;
function InitializeUninstall(): Boolean;
begin
if FileExists(ExpandConstant('{app}\unins001.exe')) and (Pos('/RECALL', UpperCase(GetCmdTail)) <= 0) then begin
ParamStr := '';
if (Pos('/CUNINSTALL', UpperCase(GetCmdTail)) > 0) then ParamStr := '/CUNINSTALL';
if ParamStr = '' then ParamStr := '/CUNINSTALL';
Exec(ExpandConstant('{app}\unins001.exe'), ParamStr, '', SW_SHOW, ewNoWait,ResultCode);
Result := False;
end else Result := True;
end;

Inno setup: connect to sql using windows authentication

can anyone tell me how to connect to SQL 2008 using windows authentication thru inno? Currently I'm using ado connection to connect to SQL from inno, the user needs windows authentication option too. Please suggest.
It's enough to remove the credential attributes from your connection string (User Id and Password) when we are talking about the example you've linked in your question and include one of the following:
Integrated Security=SSPI;
Trusted_Connection=True;
This answer is based just on this topic, I haven't tested it.
Update:
I don't know if that's what you want to do, but I'll post it here. The following script creates a custom page with radio buttons letting user choose authentication mode and optionally fill the credentials.
Important:
There is no protection against SQL injection for those credential fields!
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
LT_WindowsAuthentication = 0;
LT_SQLServerAuthentication = 1;
var
LoginType: Integer;
UsernameEdit: TNewEdit;
UsernameLabel: TLabel;
PasswordEdit: TNewEdit;
PasswordLabel: TLabel;
procedure OnLoginTypeChange(Sender: TObject);
var
EditColor: TColor;
LabelColor: TColor;
begin
LoginType := TNewRadioButton(Sender).Tag;
UsernameEdit.Enabled := LoginType = LT_SQLServerAuthentication;
PasswordEdit.Enabled := LoginType = LT_SQLServerAuthentication;
case LoginType of
LT_WindowsAuthentication:
begin
EditColor := clBtnFace;
LabelColor := clGray;
end;
LT_SQLServerAuthentication:
begin
EditColor := clWindow;
LabelColor := clBlack;
end;
end;
UsernameEdit.Color := EditColor;
PasswordEdit.Color := EditColor;
UsernameLabel.Font.Color := LabelColor;
PasswordLabel.Font.Color := LabelColor;
end;
procedure InitializeWizard;
var
LoginPage: TWizardPage;
begin
LoginPage := CreateCustomPage(wpWelcome, 'DB Login', 'Choose a login type to continue...');
with TNewRadioButton.Create(WizardForm) do
begin
Parent := LoginPage.Surface;
Left := 0;
Top := 0;
Width := LoginPage.Surface.ClientWidth;
Checked := True;
Tag := 0;
Caption := 'Windows authentication';
OnClick := #OnLoginTypeChange;
end;
with TNewRadioButton.Create(WizardForm) do
begin
Parent := LoginPage.Surface;
Left := 0;
Top := 20;
Width := LoginPage.Surface.ClientWidth;
Checked := False;
Tag := 1;
Caption := 'SQL Server authentication';
OnClick := #OnLoginTypeChange;
end;
UsernameLabel := TLabel.Create(WizardForm);
with UsernameLabel do
begin
Parent := LoginPage.Surface;
Left := 12;
Top := 44;
Width := 200;
Font.Color := clGray;
Font.Style := [fsBold];
Caption := 'Username';
end;
UsernameEdit := TNewEdit.Create(WizardForm);
with UsernameEdit do
begin
Parent := LoginPage.Surface;
Left := 12;
Top := UsernameLabel.Top + UsernameLabel.Height + 6;
Width := 200;
Color := clBtnFace;
Enabled := False;
end;
PasswordLabel := TLabel.Create(WizardForm);
with PasswordLabel do
begin
Parent := LoginPage.Surface;
Left := 12;
Top := UsernameEdit.Top + UsernameEdit.Height + 6;
Width := 200;
Font.Color := clGray;
Font.Style := [fsBold];
Caption := 'Password';
end;
PasswordEdit := TNewEdit.Create(WizardForm);
with PasswordEdit do
begin
Parent := LoginPage.Surface;
Left := 12;
Top := PasswordLabel.Top + PasswordLabel.Height + 6;
Width := 200;
Color := clBtnFace;
Enabled := False;
PasswordChar := '*';
end;
end;
// and in your connection event then use
case LoginType of
LT_WindowsAuthentication:
ADOConnection.ConnectionString :=
'Provider=SQLOLEDB;' + // provider
'Data Source=Default\SQLSERVER;' + // server name
'Initial Catalog=Northwind;' + // default database
'Integrated Security=SSPI;'; // trusted connection
LT_SQLServerAuthentication:
ADOConnection.ConnectionString :=
'Provider=SQLOLEDB;' + // provider
'Data Source=Default\SQLSERVER;' + // server name
'Initial Catalog=Northwind;' + // default database
'User Id=' + UsernameEdit.Text + ';' + // user name
'Password=' + PasswordEdit.Text + ';'; // password
end;

Delphi: FormStyle:fsStayOnTop.InputQuery is in thread and BEHIND a Form

How to fix it? FormStyle:fsStayOnTop. I call Input Query in a thread, but it appears behind a main form or is not visible!
I dynamically create ZipForge in a thread.
procedure StartUpdating.DoPassword;
var
S: String;
begin
if PassSkip then
FSkipFile := True
else if InputQuery('Pas', FFileName, S) then
FPassword := AnsiString(S)
else
begin
PassSkip := True;
FSkipFile := True;
Terminate;
end;
end;
procedure StartUpdating.ZipForgePassword(Sender: TObject; FileName: String;
var NewPassword: AnsiString; var SkipFile: Boolean);
begin
FFileName := FileName;
FPassword := NewPassword;
FSkipFile := SkipFile;
Synchronize(DoPassword);
FileName := FFileName;
NewPassword := FPassword;
SkipFile := FSkipFile;
end;
Even if I call this from a thread it does not help me:
Function TForm1.InQuery(cap1: string; cap2: string):bool;
var s:string;
begin
if InputQuery(cap1,cap2,s) then
begin
ThreadUpdating.MainPas:=s;
result:=true;
end else result:=false;
end;
Edit (2)
The form shown by InputQuery() is a modal form (shown using .ShowModal, and Delphi's VCL is smart enough to make sure no modal form is shown behind a top-most window. Essentially calls are made to DisableTaskWindows and NormalizeAllTopMosts, the interesting one being NormalizeAllTopMosts: this makes sure there are no top-most windows while the modal form is shown. Those calls aren't directly made, the VCL uses a number of tricks to make it happen. The details can be found by reading the code for TCustomForm.ShowModal, Forms.DisableTaskWindows, TApplication.WndProc - specifically the handling of the WM_ENABLE message.
What needs to be known is that there appears to a bug allowing a modal form to be shown behind the Main form of the application, if the main form is top-most. This is happening on both Delphi 2010 and Delphi XE, didn't test with older versions. The test is very simple: Create a new Delphi VCL application, on Form1 set FormStyle=fsStayOnTop, drop a button and in the button's OnClick do this:
procedure TForm2.Button1Click(Sender: TObject);
var s:string;
begin
s := '';
InputQuery('a', 'b', s);
end;
The fix
This should be considered a temporary fix, because clearly the design calls for top-most windows to be "degraded" before showing modal forms. If you know your main form is top-most, you should do something like this before calling ShowModal for anything:
MainForm.FormStyle := fsNormal;
try
YourDialog.ShowModal;
finally MainForm.FormStyle := fsStayOnTop;
end;
In the particular case of InputQuery one can use a function similar to the following one. Please note I included the ability to turn the text editor into a password-editor, based on what the OP requested in an other question:
function CustomInputQuery(const Caption, Prompt: string; const Password:Boolean; var Value:string): Boolean;
var F: TForm;
Ed: TEdit;
Lb: TLabel;
Bt: TButton;
WasStayOnTop:Boolean;
begin
F := TForm.Create(nil);
try
F.Caption := Caption;
Lb := TLabel.Create(F);
Lb.Parent := F;
Lb.Caption := Prompt;
Lb.Left := 8;
Lb.Top := 8;
Ed := TEdit.Create(F);
Ed.Parent := F;
Ed.Left := Lb.Left + Lb.Width + 8;
Ed.Top := 8;
Ed.Width := 150;
Ed.Text := Value;
if Password then
Ed.PasswordChar := '*';
Bt := TButton.Create(F);
Bt.Caption := 'Ok';
Bt.Default := True;
Bt.ModalResult := mrOk;
Bt.Left := 8;
Bt.Top := Ed.Top + Ed.Height + 8;
Bt.Parent := F;
Bt := TButton.Create(F);
Bt.Caption := 'Cancel';
Bt.Cancel := True;
Bt.ModalResult := mrCancel;
Bt.Left := 8 + Bt.Width + 8;
Bt.Top := Ed.Top + Ed.Height + 8;
Bt.Parent := F;
F.Width := F.Width - F.ClientWidth + Ed.Left + Ed.Width + 8;
F.Height := F.Height - F.ClientHeight + Bt.Top + Bt.Height + 8;
F.Position := poDesktopCenter;
WasStayOnTop := Assigned(Application.MainForm) and (Application.MainForm.FormStyle = fsStayOnTop);
if WasStayOnTop then Application.MainForm.FormStyle := fsNormal;
try
if F.ShowModal = mrOk then
begin
Value := Ed.Text;
Result := True;
end
else
Result := False;
finally if WasStayOnTop then Application.MainForm.FormStyle := fsStayOnTop;
end;
finally F.Free;
end;
end;

Add a custom input field to Inno Setup

I am making use of Inno Setup (it's amazing!). I was hoping to customise the installer so that I can accept a string from the user in the form of an input field and maybe add a message to it.
How can I do this? I had a look through the docs, google search and not much came up!
Thanks all for any help
You can use Pascal scripting in InnoSetup to create new pages for the installer. These pages can be integrated into the normal installation flow. This is well documented within the InnoSetup documentation (Google search should also come up with samples). Also the Samples folder within your Program Files\InnoSetup has some code examples.
Some time ago, there was a software called InnoSetup Form designer, which allowed you to visually design the page. The link is still there, but on the page I could not find the download. Maybe if you look around a bit you can find it?
EDIT
This is a sample for a page I made once. This is the code section of the ISS file.[Code]
var
EnableFolderPage: Boolean;
lblBlobFileFolder: TLabel;
lblBlobFileWarning1: TLabel;
lblBlobFileWarning2: TLabel;
tbBlobFileFolder: TEdit;
btnBlobFileFolder: TButton;
function GetBlobFolder(param: String): String;
begin
Result := Trim(tbBlobFileFolder.Text);
end;
{ BlobFileForm_Activate }
procedure BlobFileForm_Activate(Page: TWizardPage);
var
s: string;
begin
s := Trim(tbBlobFileFolder.Text);
if (s = '') then
begin
tbBlobFileFolder.Text := ExpandConstant('{sys}');
end;
end;
{ BlobFileForm_NextButtonClick }
function BlobFileForm_NextButtonClick(Page: TWizardPage): Boolean;
var
s: string;
begin
s := Trim(tbBlobFileFolder.Text);
if (s = '') then
begin
MsgBox(ExpandConstant('{cm:BlobFileForm_NoFolder}'), mbError, MB_OK);
Result := false;
end else
begin
if not DirExists(s) then
begin
MsgBox(ExpandConstant('{cm:BlobFileForm_DirDoesntExist}'), mbError, MB_OK);
Result := false;
end else
begin
Result := True;
end;
end;
end;
procedure btnBlobFileFolder_Click(sender: TObject);
var
directory: string;
begin
if BrowseForFolder('', directory, true) then
begin
tbBlobFileFolder.Text := directory;
end;
end;
{ BlobFileForm_CreatePage }
function BlobFileForm_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(
PreviousPageId,
ExpandConstant('{cm:BlobFileForm_Caption}'),
ExpandConstant('{cm:BlobFileForm_Description}')
);
{ lblBlobFileFolder }
lblBlobFileFolder := TLabel.Create(Page);
with lblBlobFileFolder do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:BlobFileForm_lblBlobFileFolder_Caption0}');
Left := ScaleX(8);
Top := ScaleY(8);
Width := ScaleX(167);
Height := ScaleY(13);
end;
{ lblBlobFileWarning1 }
lblBlobFileWarning1 := TLabel.Create(Page);
with lblBlobFileWarning1 do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning1_Caption0}');
Left := ScaleX(8);
Top := ScaleY(80);
Width := ScaleX(50);
Height := ScaleY(13);
Font.Color := -16777208;
Font.Height := ScaleY(-11);
Font.Name := 'Tahoma';
Font.Style := [fsBold];
end;
{ lblBlobFileWarning2 }
lblBlobFileWarning2 := TLabel.Create(Page);
with lblBlobFileWarning2 do
begin
Parent := Page.Surface;
Caption :=
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption0}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption1}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption2}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption3}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption4}');
Left := ScaleX(8);
Top := ScaleY(96);
Width := ScaleX(399);
Height := ScaleY(133);
AutoSize := False;
WordWrap := True;
end;
{ tbBlobFileFolder }
tbBlobFileFolder := TEdit.Create(Page);
with tbBlobFileFolder do
begin
Parent := Page.Surface;
Left := ScaleX(8);
Top := ScaleY(24);
Width := ScaleX(401);
Height := ScaleY(21);
TabOrder := 0;
end;
{ btnBlobFileFolder }
btnBlobFileFolder := TButton.Create(Page);
with btnBlobFileFolder do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:BlobFileForm_btnBlobFileFolder_Caption0}');
Left := ScaleX(320);
Top := ScaleY(48);
Width := ScaleX(91);
Height := ScaleY(23);
TabOrder := 1;
end;
with Page do
begin
OnActivate := #BlobFileForm_Activate;
OnNextButtonClick := #BlobFileForm_NextButtonClick;
end;
with btnBlobFileFolder do
begin
OnClick := #btnBlobFileFolder_Click;
end;
Result := Page.ID;
end;
procedure InitializeWizard();
begin
BlobFileForm_CreatePage(wpSelectDir);
end;
EDIT 2
To write the value the user entered to a registry key, create a new function:
function GetUserEnteredText(param: String): String;
begin
Result := Trim(tbTextBox.Text);
end;
This function simply returns what was entered in the text box. Please note that the function must take a string parameter - even though you ignore it!
In the [Registry] section of your script, declare the key that should be written like that:
Root: HKLM; Subkey: SOFTWARE\MyCompany\MyTool; ValueType: string; ValueName: MyValue; ValueData: {code:GetUserEnteredText}; Flags: createvalueifdoesntexist uninsdeletekeyifempty uninsdeletevalue
This creates a registry value named "MyValue" in HKLM\SOFTWARE\MyCompany\MyTool that contains what the user entered in the text box.
Here is shorter code to add a custom page to Inno Setup installer with an Input Field:
var
CustomQueryPage: TInputQueryWizardPage;
procedure AddCustomQueryPage();
begin
CustomQueryPage := CreateInputQueryPage(
wpWelcome,
'Custom message',
'Custom description',
'Custom instructions');
{ Add items (False means it's not a password edit) }
CustomQueryPage.Add('Custom Field:', False);
end;
procedure InitializeWizard();
begin
AddCustomQueryPage();
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
{ Read custom value }
MsgBox('Custom Value = ' + CustomQueryPage.Values[0], mbInformation, MB_OK);
end;
end;

Resources