How to get the current process list in windows - inno-setup

I'm trying to show all processes which are currently running on my system.
Can any one guide me to do that.

I would use WMI for this task since WMI can list also 64-bit processes which I think the Windows API way cannot. Here is an example which uses the Win32_Process class to query list of running processes:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
WM_SETREDRAW = $000B;
var
ProcessList: TNewListBox;
// this is a substitution for missing BeginUpdate method
// of TStrings class
procedure WinControlBeginUpdate(Control: TWinControl);
begin
SendMessage(Control.Handle, WM_SETREDRAW, 0, 0);
end;
// this is a substitution for missing EndUpdate method
// of TStrings class
procedure WinControlEndUpdate(Control: TWinControl);
begin
SendMessage(Control.Handle, WM_SETREDRAW, 1, 0);
Control.Refresh;
end;
function GetProcessNames(Items: TStrings): Integer;
var
I: Integer;
WQLQuery: string;
WbemLocator: Variant;
WbemServices: Variant;
WbemObject: Variant;
WbemObjectSet: Variant;
begin
Result := 0;
WbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
WbemServices := WbemLocator.ConnectServer('localhost', 'root\CIMV2');
WQLQuery := 'SELECT Name FROM Win32_Process';
WbemObjectSet := WbemServices.ExecQuery(WQLQuery);
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Items.Clear;
for I := 0 to WbemObjectSet.Count - 1 do
begin
WbemObject := WbemObjectSet.ItemIndex(I);
if not VarIsNull(WbemObject) then
Items.Add(WbemObject.Name);
end;
Result := Items.Count;
end;
end;
procedure RefreshButtonClick(Sender: TObject);
begin
// this try..finally block is used to reduce annoying visual effects
// when filling the list box; Inno Setup doesn't publish BeginUpdate,
// EndUpdate method pair, hence this home brewn solution
WinControlBeginUpdate(ProcessList);
try
GetProcessNames(ProcessList.Items);
finally
WinControlEndUpdate(ProcessList);
end;
end;
procedure InitializeWizard;
var
RefreshBtn: TNewButton;
ProcessPage: TWizardPage;
begin
ProcessPage := CreateCustomPage(wpWelcome, 'Caption', 'Description');
ProcessList := TNewListBox.Create(WizardForm);
ProcessList.Parent := ProcessPage.Surface;
ProcessList.SetBounds(0, 0, ProcessPage.SurfaceWidth,
ProcessPage.SurfaceHeight - 33);
RefreshBtn := TNewButton.Create(WizardForm);
RefreshBtn.Parent := ProcessPage.Surface;
RefreshBtn.Left := 0;
RefreshBtn.Top := ProcessPage.SurfaceHeight - RefreshBtn.Height;
RefreshBtn.Caption := 'Refresh';
RefreshBtn.OnClick := #RefreshButtonClick;
end;

Related

Installation steps on left side - Inno Setup

Inno Setup - I want to create an installer similar to the screenshot.
The installer should highlight the current step on the left side.
How to achieve the same?
#Bill_Stewart is correct, vanilla Inno Setup does not offer such complex functionality.
However it is possible to do that using Graphical Installer which is a 3rd party extension for Inno Setup (see www.graphical-installer.com for more info).
The implemented functionality (about 150 lines in Pascal):
; This identifier is used for compiling script as Graphical Installer powered installer. Comment it out for regular compiling.
#define GRAPHICAL_INSTALLER_PROJECT
#ifdef GRAPHICAL_INSTALLER_PROJECT
; File with setting for graphical interface
#include "script.graphics.iss"
#else
; Default UI file
#define public GraphicalInstallerUI ""
#endif
[Setup]
AppName=InnoSetupProject
AppVersion=1.5
LicenseFile=readme.txt
DefaultDirName={autoappdata}\InnoSetupProject
DefaultGroupName=My Program
UninstallDisplayIcon={app}\MyProg.exe
Compression=lzma2
SolidCompression=yes
OutputDir=Output
OutputBaseFilename=InnoSetupProject
PrivilegesRequired=lowest
; Directive "WizardSmallImageBackColor" was modified for purposes of Graphical Installer.
WizardSmallImageBackColor={#GraphicalInstallerUI}
DisableWelcomePage=no
[Code]
type
TInnoPageStep = record
PageID: Integer;
Caption: String;
Index: Integer;
IsActive: Boolean;
end;
var
AllPagesList: array of TInnoPageStep;
AllPagesCount: Integer;
AllPagesCheckListBox: TNewCheckListBox;
I: Integer;
procedure SetPagesCount(pages: Integer);
begin
AllPagesCount := 0;
SetLength(AllPagesList, pages);
end;
procedure AddPage(pageID: Integer; caption: String);
var
page: TInnoPageStep;
begin
page.Caption := caption;
page.PageID := pageID;
page.Index := AllPagesCount + 1;
AllPagesList[AllPagesCount] := page;
AllPagesCount := AllPagesCount + 1;
end;
procedure CreatePagesListBox();
begin
AllPagesCheckListBox := TNewCheckListBox.Create(WizardForm);
AllPagesCheckListBox.Top := ScaleX(160);
AllPagesCheckListBox.Left := ScaleY(20);
AllPagesCheckListBox.Width := 260;
AllPagesCheckListBox.Height := ScaleY(300);
AllPagesCheckListBox.BorderStyle := bsNone;
AllPagesCheckListBox.ParentColor := True;
AllPagesCheckListBox.MinItemHeight := 30;
AllPagesCheckListBox.ShowLines := False;
AllPagesCheckListBox.WantTabs := True;
AllPagesCheckListBox.Parent := WizardForm;
for I := 0 to AllPagesCount-1 do
begin
AllPagesList[I].Index := AllPagesCheckListBox.AddRadioButton(AllPagesList[I].Caption, '', 0, False, True, nil);
end;
end;
procedure UpdateActivePage(pageID: Integer);
begin
for I := 0 to AllPagesCount-1 do
begin
if (AllPagesList[I].PageID = pageID) then
AllPagesList[I].IsActive := True
else
AllPagesList[I].IsActive := False;
end;
for I := 0 to AllPagesCount-1 do
begin
AllPagesCheckListBox.Checked[AllPagesList[I].Index] := AllPagesList[I].IsActive;
if (AllPagesList[I].IsActive) then
begin
AllPagesCheckListBox.ItemFontStyle[AllPagesList[I].Index] := [fsBold];
AllPagesCheckListBox.ItemFontColor[AllPagesList[I].Index] := clBlack;
end
else
begin
AllPagesCheckListBox.ItemFontStyle[AllPagesList[I].Index] := [];
AllPagesCheckListBox.ItemFontColor[AllPagesList[I].Index] := clWhite;
end;
end;
AllPagesCheckListBox.Invalidate;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
#ifdef GRAPHICAL_INSTALLER_PROJECT
PageChangedGraphicalInstaller(CurPageID);
#endif
UpdateActivePage(CurPageID);
end;
// Next functions are used for proper working of Graphical Installer powered installer
procedure InitializeWizard();
var
pageIDs: array of Integer;
pageCaptions: array of String;
begin
#ifdef GRAPHICAL_INSTALLER_PROJECT
InitGraphicalInstaller();
#endif
// This is a list of all standard pages in Inno Setup.
pageIDs := [wpWelcome, wpLicense, wpPassword, wpInfoBefore, wpUserInfo, wpSelectDir, wpSelectComponents, wpSelectProgramGroup, wpSelectTasks, wpReady, wpPreparing, wpInstalling, wpInfoAfter, wpFinished];
pageCaptions := ['wpWelcome', 'wpLicense', 'wpPassword', 'wpInfoBefore', 'wpUserInfo', 'wpSelectDir', 'wpSelectComponents', 'wpSelectProgramGroup', 'wpSelectTasks', 'wpReady', 'wpPreparing', 'wpInstalling', 'wpInfoAfter', 'wpFinished'];
// The setup will have 5 pages
SetPagesCount(5);
// If you are using custom pages you need to add them too
AddPage(pageIDs[0], pageCaptions[0]);
AddPage(pageIDs[1], pageCaptions[1]);
AddPage(pageIDs[5], pageCaptions[5]);
AddPage(pageIDs[11], pageCaptions[11]);
AddPage(pageIDs[13], pageCaptions[13]);
// Init the UI
CreatePagesListBox();
end;
procedure DeInitializeSetup();
begin
#ifdef GRAPHICAL_INSTALLER_PROJECT
DeInitGraphicalInstaller();
#endif
end;
// End of file (EOF)
Please notice: To compile this script you need to install the Graphical Installer. Feel free to ask any other questions (I am developer of GI).

Inno Setup - How to add cancel button to decompressing page?

I am using this code: How to add .arc decompression to Inno Setup? (answer of Martin Prikryl). I want to add a cancel button at decompressing page and active this page for others functions (when the decompression is active, this page is inactive and, for example, i can not press on/off button of my music implementation).
How to add a cancel button to decompression page? and how to active this page for others functions?
I have reimplemented the solution from How to add .arc decompression to Inno Setup? using unarc.dll (from FreeArc+InnoSetup package ISFreeArcExtract v.4.0.rar).
It greatly simplifies the code and also makes it easier to add the ability to cancel the decompression.
#define ArcArchive "test.arc"
[Files]
Source: unarc.dll; Flags: dontcopy
[Code]
const
ArcCancelCode = -10;
function FreeArcExtract(
Callback: LongWord;
Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: PAnsiChar
): Integer;
external 'FreeArcExtract#files:unarc.dll cdecl';
const
CP_UTF8 = 65001;
function WideCharToMultiByte(CodePage: UINT; dwFlags: DWORD;
lpWideCharStr: string; cchWideChar: Integer; lpMultiByteStr: AnsiString;
cchMultiByte: Integer; lpDefaultCharFake: Integer;
lpUsedDefaultCharFake: Integer): Integer;
external 'WideCharToMultiByte#kernel32.dll stdcall';
function GetStringAsUtf8(S: string): AnsiString;
var
Len: Integer;
begin
Len := WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, 0, 0, 0);
SetLength(Result, Len);
WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, Len, 0, 0);
end;
var
ArcTotalSize: Integer;
ArcExtracted: Integer;
ArcCancel: Boolean;
ArcProgressPage: TOutputProgressWizardPage;
function FreeArcCallback(
AWhat: PAnsiChar; Int1, Int2: Integer; Str: PAnsiChar): Integer;
var
What: string;
begin
What := AWhat;
if What = 'origsize' then
begin
ArcTotalSize := Int1;
Log(Format('Total size of files to be extracted is %d MB', [ArcTotalSize]));
end
else
if What = 'write' then
begin
if ArcTotalSize > 0 then
begin
ArcProgressPage.SetProgress(Int1, ArcTotalSize);
end;
ArcExtracted := Int1;
end
else
begin
// Just to pump message queue more often (particularly for 'read' callbacks),
// to get more smooth progress bar
if (ArcExtracted > 0) and (ArcTotalSize > 0) then
begin
ArcProgressPage.SetProgress(ArcExtracted, ArcTotalSize);
end;
end;
if ArcCancel then Result := ArcCancelCode
else Result := 0;
end;
function FreeArcCmd(
Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: string): Integer;
begin
ArcCancel := False;
try
Result :=
FreeArcExtract(
CreateCallback(#FreeArcCallback),
GetStringAsUtf8(Cmd1), GetStringAsUtf8(Cmd2), GetStringAsUtf8(Cmd3),
GetStringAsUtf8(Cmd4), GetStringAsUtf8(Cmd5), GetStringAsUtf8(Cmd6),
GetStringAsUtf8(Cmd7), GetStringAsUtf8(Cmd8), GetStringAsUtf8(Cmd9),
GetStringAsUtf8(Cmd10));
Log(Format('Arc command "%s" result %d', [Cmd1, Result]));
except
Result := -63;
end;
end;
function UnPackArchive(ArchivePath: string; DestPath: string): Integer;
begin
{ Find out length of files to be extracted - origsize }
Result := FreeArcCmd('l', '--', ArchivePath, '', '', '', '', '', '', '');
if Result = 0 then
begin
// Actually extract
Result :=
FreeArcCmd('x', '-o+', '-dp' + DestPath, '-w' + DestPath, '--', ArchivePath,
'', '', '', '');
end;
end;
procedure UnpackCancelButtonClick(Sender: TObject);
begin
ArcCancel := True;
end;
procedure ExtractArc;
var
ArcArchivePath: string;
UnpackResult: Integer;
PrevCancelButtonClick: TNotifyEvent;
Error: string;
begin
ArcProgressPage :=
CreateOutputProgressPage('Decompression', 'Decompressing archive...');
ArcProgressPage.SetProgress(0, 100);
ArcProgressPage.Show;
try
WizardForm.CancelButton.Visible := True;
WizardForm.CancelButton.Enabled := True;
PrevCancelButtonClick := WizardForm.CancelButton.OnClick;
WizardForm.CancelButton.OnClick := #UnpackCancelButtonClick;
ArcArchivePath := ExpandConstant('{src}\{#ArcArchive}');
Log(Format('Arc extraction starting - %s', [ArcArchivePath]));
ArcExtracted := 0;
UnpackResult := UnPackArchive(ArcArchivePath, ExpandConstant('{app}'));
if UnpackResult <> 0 then
begin
if ArcCancel then
begin
Error := 'Extraction cancelled';
end
else
begin
Error := Format('Extraction failed with code %d', [UnpackResult]);
end;
MsgBox(Error, mbError, MB_OK);
end;
finally
Log('Arc extraction cleanup');
ArcProgressPage.Hide;
WizardForm.CancelButton.OnClick := PrevCancelButtonClick;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
ExtractArc;
end;
end;
For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library.
The code extracts a separate .arc file. If you want to embed the archive to the installer, you can use
[Files]
Source: {#ArcArchive}; DestDir: "{tmp}"; Flags: nocompression deleteafterinstall
And extract the archive from the {tmp}:
ArcArchivePath := ExpandConstant('{tmp}\{#ArcArchive}');
Note that the unarc.dll from ISFreeArcExtract v.4.0.rar does not seem to support password protected archives. The version from ISFreeArcExtract v.4.2.rar does, but I'm not aware of trustworthy download link.
If you want to extract multiple archives, see Inno Setup - How to add multiple arc files to decompress?
All you need is this
http://fileforums.com/showthread.php?t=96619
This program have the latest compatibility with inno setup and also supports Password based file with multiple extensions.

Merging event function (InitializeWizard) implementations from different sources

I am now combining the script that I want but it has an error.
When I put a period, it will run but missing other feature.
Here is my code:
procedure InitializeWizard;
begin
MessageBoxTimeout(WizardForm.Handle, 'MsgBox ' +
Timeout 'Setup', MB_OK or MB_ICONINFORMATION, 0, 2000);
end;
var
TuneLabel: TLabel;
begin
ExtractTemporaryFile('tune.xm');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
SoundCtrlButton := TNewButton.Create(WizardForm);
Music := BASS_MusicLoad(False,
ExpandConstant('{tmp}\tune.xm'), 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP, 0);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 10000);
BASS_ChannelPlay(Music, False);
SoundCtrlButton := TNewButton.Create(WizardForm);
SoundCtrlButton.Parent := WizardForm;
SoundCtrlButton.Left := 10;
SoundCtrlButton.TabStop := False;
SoundCtrlButton.Top := WizardForm.ClientHeight -
SoundCtrlButton.Height - 9;
SoundCtrlButton.Width := 40;
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
SoundCtrlButton.OnClick := #SoundCtrlButtonClick;
TuneLabel := TLabel.Create(WizardForm);
TuneLabel.Parent := WizardForm;
TuneLabel.Caption := 'Tune';
TuneLabel.Left := SoundCtrlButton.Left + SoundCtrlButton.Width + ScaleX(5);
TuneLabel.Top :=
SoundCtrlButton.Top + ((SoundCtrlButton.Height - TuneLabel.Height) div 2);
end;
end;
The error refers to a line after the last end;.
Please help me through.
When you are reusing various feature implementations from different sources, those commonly implement the same Inno Setup event functions (like the InitializeWizard).
The solution for Inno Setup 6 is very simple, as shown below. In older versions it's more complicated. See lower.
Inno Setup 6
Inno Setup 6 has event attributes features that helps solving this problem.
Just make sure that each of your event implementation have an unique name, e.g. appending unique suffix. And add event attribute with the name of the implemented event.
[Code]
procedure InitializeWizard;
begin
Log('InitializeWizard called');
end;
<event('InitializeWizard')>
procedure InitializeWizard2;
begin
Log('InitializeWizard2 called');
end;
Inno Setup 5
In old versions of Inno Setup that does not support the event attributes, you have to merge these event functions as there can be just one function implementation.
You can do that by appending unique suffix to the different implementation and than calling them from a main implementation.
The main implementation have to be below the other implementations.
For example, if one source has InitializeWizard event function implemented as:
var
GlobalVariable1: Integer;
procedure SubProcedure1;
begin
{ blah }
end;
procedure InitializeWizard;
var
Variable1: Integer;
Variable2: Integer;
begin
Variable1 := GlobalVariable1;
SubProcedure1;
end;
And the other source as:
var
GlobalVariableA: Integer;
procedure SubProcedureA;
begin
{ blah }
end;
procedure InitializeWizard;
var
VariableA: Integer;
begin
VariableA := GlobalVariableA;
SubProcedureA;
end;
Then merged code should be:
var
GlobalVariable1: Integer;
procedure SubProcedure1;
begin
{ blah }
end;
procedure InitializeWizard1;
var
Variable1: Integer;
Variable2: Integer;
begin
Variable1 := GlobalVariable1;
SubProcedure1;
end;
var
GlobalVariableA: Integer;
procedure SubProcedureA;
begin
{ blah }
end;
procedure InitializeWizard2;
var
VariableA: Integer;
begin
VariableA := GlobalVariableA;
SubProcedureA;
end;
procedure InitializeWizard;
begin
InitializeWizard1;
InitializeWizard2;
end;
See also Inno Setup - Merging implementations of event functions that return boolean (like InitializeSetup).
So, in your specific case the code should be:
procedure InitializeWizard1;
begin
MessageBoxTimeout(WizardForm.Handle, 'MsgBox ' +
Timeout 'Setup', MB_OK or MB_ICONINFORMATION, 0, 2000);
end;
procedure InitializeWizard2;
var
TuneLabel: TLabel;
begin
ExtractTemporaryFile('tune.xm');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
SoundCtrlButton := TNewButton.Create(WizardForm);
Music := BASS_MusicLoad(False,
ExpandConstant('{tmp}\tune.xm'), 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP, 0);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 10000);
BASS_ChannelPlay(Music, False);
SoundCtrlButton := TNewButton.Create(WizardForm);
SoundCtrlButton.Parent := WizardForm;
SoundCtrlButton.Left := 10;
SoundCtrlButton.TabStop := False;
SoundCtrlButton.Top := WizardForm.ClientHeight -
SoundCtrlButton.Height - 9;
SoundCtrlButton.Width := 40;
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
SoundCtrlButton.OnClick := #SoundCtrlButtonClick;
TuneLabel := TLabel.Create(WizardForm);
TuneLabel.Parent := WizardForm;
TuneLabel.Caption := 'Tune';
TuneLabel.Left := SoundCtrlButton.Left + SoundCtrlButton.Width + ScaleX(5);
TuneLabel.Top :=
SoundCtrlButton.Top + ((SoundCtrlButton.Height - TuneLabel.Height) div 2);
end;
end;
procedure InitializeWizard;
begin
InitializeWizard1;
InitializeWizard2;
end;
If you are using Inno Setup Script #Includes (ISSI), see Implementing event functions InitializeWizard while using ISSI (to add background image) in Inno Setup: Duplicate identifier 'INITIALIZEWIZARD'.

Delphi TWSocket with Thread

When i used this code with button, it is ok... All sockets connected success. All events working. no problem (OnSessionClosed, OnSessionConnected, ...)
procedure TfrmMain.btnConnectClick(Sender: TObject);
var
I: Integer;
begin
for I := 0 to query.RecordCount - 1 do
begin
pUser[I] := TUser.Create();
pUser[I].Connect(frmMain.editEbenezerIP.Text);
pUser[I].run := False;
pUser[I].username := Trim(query.FieldByName('strAccountID').Text);
pUser[I].password := Trim(query.FieldByName('strPasswd').Text);
pUser[I].md5 := editMD5.Text;
pUser[I].Resume;
query.Next;
end;
end;
i created a thread to connect with sleep. (my thread TConnector).
All thread connected but OnSessionConnected event not working when i created with TConnector.
No problem with button to use create sockets.
procedure TUser.OnSessionConnected(Sender: TObject; ErrCode: Word);
begin
ShowMessage('Connection success!');
end;
procedure TUser.Connect(eip : string);
begin
Initialize;
socket := TWSocket.Create(nil);
socket.OnDataAvailable := OnDataAvailable;
socket.OnSessionConnected := OnSessionConnected;
socket.OnSessionClosed := OnSessionClosed;
socket.Connect;
end;
procedure TConnector.Execute;
var
I : Integer;
pUser : array [0..1500] of TUser;
begin
for I := 0 to frmMain.query.RecordCount - 1 do
begin
pUser[I] := TUser.Create();
pUser[I].run := False;
pUser[I].username := Trim(frmMain.query.FieldByName('strAccountID').Text);
pUser[I].password := Trim(frmMain.query.FieldByName('strPasswd').Text);
pUser[I].Connect(frmMain.editEbenezerIP.Text);
pUser[I].Resume;
frmMain.query.Next;
**Sleep(100);**
end;
end;
I fixed this problem with Synchronize(CreateUser);. Thanks for your answers
TConnector = class(TThread)
private
protected
procedure Execute; override;
public
strAccountID, strPasswd, MD5, eIP : string;
X : Integer;
constructor Create;
procedure CreateUser;
end;
procedure TConnector.CreateUser;
begin
Output(Format('Thread for %s',[strAccountID]));
frmMain.pUser[X] := TUser.Create();
frmMain.pUser[X].run := False;
frmMain.pUser[X].username := strAccountID;
frmMain.pUser[X].password := strPasswd;
frmMain.pUser[X].md5 := MD5;
frmMain.pUser[X].Connect(eIP, frmMain);
frmMain.pUser[X].Resume;
end;
procedure TConnector.Execute;
var
I : Integer;
begin
MD5 := frmMain.editMD5.Text;
eIP := frmMain.editEbenezerIP.Text;
for I := 0 to frmMain.query.RecordCount - 1 do
begin
X := I;
strAccountID := Trim(frmMain.query.FieldByName('strAccountID').Text);
strPasswd := Trim(frmMain.query.FieldByName('strPasswd').Text);
**Synchronize(CreateUser);**
Sleep(1000);
frmMain.query.Next;
end;
while(not Terminated)do
begin
Sleep(1000);
OutPut('test');
end;
end;
TWSocket uses a non-blocking socket and a hidden window for handling socket state updates asynchronously. As such, you need to give your thread a message loop. It works in a TButton.OnClick event because it is utilizing the main thread's existing message loop.
Edit: The simplest message loop involves calling Peek/GetMessage(), TranslateMessage(), and DispatchMessage() in a loop for the lifetime of the thread, so you need to add those function calls to your worker thread, eg:
procedure TConnector.Execute;
var
I : Integer;
pUser : array [0..1500] of TUser;
Msg: TMsg
begin
for I := 0 to frmMain.query.RecordCount - 1 do
begin
if Terminated then Break;
pUser[I] := TUser.Create();
pUser[I].run := False;
pUser[I].username := Trim(frmMain.query.FieldByName('strAccountID').Text);
pUser[I].password := Trim(frmMain.query.FieldByName('strPasswd').Text);
pUser[I].Connect(frmMain.editEbenezerIP.Text);
pUser[I].Resume;
frmMain.query.Next;
end;
while (GetMessage(Msg, 0, 0, 0) > 0) and (not Terminated) then
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
// perform cleanup here as needed...
end;
procedure TConnector.Stop;
begin
Terminate;
PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
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