Inno Setup: Avoid closing application/task, unless installing new version - inno-setup

My program has 4 services and the first small service controls the other three services (stops them on a file server for overwriting). The first one is very small and simple and normally it should not be closed (only when newer).
[Files]
Source: "ctrlserversvc3.exe"; DestDir: "{code:GetInstallDir|Program}"
It is in the Files section. Inno Setup asks me everytime for closing the service and restarting it at the end. But it should only ask me when my service in the setup has a newer version, not when it is the same version.
How can I tell Inno Setup to skip this file or question when nothing to change?

Inno Setup, unfortunately, does not check file version before checking if the file is locked. It should, imho. Because the file won't get installed in the end, if it has the same version.
Anyway, you can implement the check yourself, using the Check parameter;
[Files]
Source: "ctrlserversvc3.exe"; DestDir: "{code:GetInstallDir|Program}"; \
Check: IsNewer('{#GetFileVersion("ctrlserversvc3.exe")}')
[Code]
procedure CutVersionPart(var VersionString: string; var VersionPart: Word);
var
P: Integer;
begin
P := Pos('.', VersionString);
if P > 0 then
begin
VersionPart := StrToIntDef(Copy(VersionString, 1, P - 1), 0);
Delete(VersionString, 1, P);
end
else
begin
VersionPart := StrToIntDef(VersionString, 0);
VersionString := '';
end;
end;
function IsNewer(InstalledVersion: string): Boolean;
var
Filename: string;
ExistingMS, ExistingLS: Cardinal;
ExistingMajor, ExistingMinor, ExistingRev, ExistingBuild: Cardinal;
InstalledMajor, InstalledMinor, InstalledRev, InstalledBuild: Word;
begin
Filename := ExpandConstant(CurrentFilename);
Log(Format('Checking if %s should be installed', [Filename]));
if not FileExists(Filename) then
begin
Log(Format('File %s does not exist yet, allowing installation', [FileName]));
Result := True;
end
else
if not GetVersionNumbers(FileName, ExistingMS, ExistingLS) then
begin
Log(Format('Cannot retrieve version of existing file %s, allowing installation',
[FileName]));
Result := True;
end
else
begin
ExistingMajor := ExistingMS shr 16;
ExistingMinor := ExistingMS and $FFFF;
ExistingRev := ExistingLS shr 16;
ExistingBuild := ExistingLS and $FFFF;
Log(Format('Existing file %s version: %d.%d.%d.%d',
[FileName, ExistingMajor, ExistingMinor, ExistingRev, ExistingBuild]));
Log(Format('Installing version: %s', [InstalledVersion]));
CutVersionPart(InstalledVersion, InstalledMajor);
CutVersionPart(InstalledVersion, InstalledMinor);
CutVersionPart(InstalledVersion, InstalledRev);
CutVersionPart(InstalledVersion, InstalledBuild);
Log(Format('Installing version: %d.%d.%d.%d',
[InstalledMajor, InstalledMinor, InstalledRev, InstalledBuild]));
if (InstalledMajor > ExistingMajor) or
((InstalledMajor = ExistingMajor) and (InstalledMinor > ExistingMinor)) or
((InstalledMajor = ExistingMajor) and (InstalledMinor = ExistingMinor) and
(InstalledRev > ExistingRev)) then
begin
Log('Installing file that is newer than existing file, ' +
'allowing installation.');
Result := True;
end
else
begin
Log('Installing file that is same or older than existing file, ' +
'skipping installation.');
Result := False;
end;
end;
end;

Related

Parse key-value text file in Inno Setup for checking version number

I'm creating a Inno Setup installer/updater for my application. Now I need to find a way to check if a new version is available and if it is available it should be installed automatically over the already installed version.
The special case is that the version number is in a file with other data.
The file that Inno Setup need to read looks like:
#Eclipse Product File
#Fri Aug 18 08:20:35 CEST 2017
version=0.21.0
name=appName
id=appId
I already found a way to update the application using a script that only read a text file with the version number in it.
Inno setup: check for new updates
But in my case it contains more data that the installer does not need. Can someone help me to build a script that can parse the version number out of the file?
The code that I already have looks like:
function GetInstallDir(const FileName, Section: string): string;
var
S: string;
DirLine: Integer;
LineCount: Integer;
SectionLine: Integer;
Lines: TArrayOfString;
begin
Result := '';
Log('start');
if LoadStringsFromFile(FileName, Lines) then
begin
Log('Loaded file');
LineCount := GetArrayLength(Lines);
for SectionLine := 0 to LineCount - 1 do
Log('File line ' + lines[SectionLine]);
if (pos('version=', Lines[SectionLine]) <> 0) then
begin
Log('version found');
S := RemoveQuotes(Trim(Lines[SectionLine]));
StringChangeEx(S, '\\', '\', True);
Result := S;
Exit;
end;
end;
end;
But when running the script the check for checking if the version string is on the line does not work.
Your code is almost correct. You are only missing begin and end around the code, that you want to repeat in the for loop. So only the Log line repeats; and the if is executed for out-of-the-range LineCount index.
It becomes obvious, if you format the code better:
function GetInstallDir(const FileName, Section: string): string;
var
S: string;
DirLine: Integer;
LineCount: Integer;
SectionLine: Integer;
Lines: TArrayOfString;
begin
Result := '';
Log('start');
if LoadStringsFromFile(FileName, Lines) then
begin
Log('Loaded file');
LineCount := GetArrayLength(Lines);
for SectionLine := 0 to LineCount - 1 do
begin { <--- Missing }
Log('File line ' + lines[SectionLine] );
if (pos('version=', Lines[SectionLine]) <> 0) then
begin
Log('version found');
S := RemoveQuotes(Trim(Lines[SectionLine]));
StringChangeEx(S, '\\', '\', True);
Result := S;
Exit;
end;
end; { <--- Missing }
end;
end;

Is it possible to display the EULA and then run the setup with /SILENT or /VERYSILENT parameter?

Basically what I'm trying to achieve is the following:
If the user run the setup with the /SILENT or /VERYSILENT parameters, the setup will immediately present the EULA. If the user rejects, the install is canceled. If the user accepts, the rest of the install will happen in silent or verysilent mode.
Edit: both solutions presented by RobeN and TLama worked perfectly. The only problem is when the EULA is too big to fit a Message Box (that would be the most common situation). Anyway that's a good solution to at least display some warning or information before the install begins.
Simple solution - probably not the best, but quite fast.
Based on How to detect whether the setup runs in very silent mode?
[Files]
Source: "EULA_ANSI.txt"; DestDir: "{tmp}"; Flags: dontcopy nocompression
[Code]
var
isSilent: Boolean;
EULAText: AnsiString;
function InitializeSetup(): Boolean;
var
j: Integer;
begin
result := true;
isSilent := False;
for j := 1 to ParamCount do
if (CompareText(ParamStr(j), '/verysilent') = 0) or
(CompareText(ParamStr(j), '/silent') = 0) then
begin
isSilent := True;
Break;
end;
if isSilent then begin
ExtractTemporaryFile('EULA_ANSI.TXT');
if LoadStringFromFile(ExpandConstant('{tmp}\EULA_ANSI.txt'),
EULAText) then
begin
if MsgBox(EULAText, mbConfirmation, MB_YESNO) = IDNO then
result := false;
end
else begin
MsgBox('Unable to display EULA.' + #13#10 + #13#10 +
'Installation terminated!', mbCriticalError, MB_OK);
result := false;
end;
end
else begin
MsgBox(ExpandConstant('Standard Installation'), mbInformation,
MB_OK);
end;
end;
I do not think you can do that directly.
But you can introduce another command-line option, like /AUTOMATIC, that does what you need.
[Code]
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result :=
(Pos('/AUTOMATIC', Uppercase(GetCmdTail())) > 0) and
(PageID <> wpLicense);
end;

Inno Setup: Allowing the user to only choose the drive the software can be installed?

Can I allow the user to only choose the drive in which the software will be installed?
For example they can choose the C or D drive:
C:\Software
D:\Software
But the user can not specify anything else,
Like they can't choose to install the software under Downloads or MyDocumnets … etc.
Is this possible?
How to restrict users to select only drive on which the software will be installed ?
There are hundreds of ways to design this restriction. I chose the one which creates a combo box with available paths that user can choose from. This code as first lists all fixed drives on the machine, and if there's at least one, it creates the combo box which is placed instead of original dir selection controls. It is filled with drive names followed by a fixed directory taken from the DefaultDirName directive value which must not contain a drive portion since it is already concatenated with found fixed drive roots:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName=My Program
[Messages]
SelectDirBrowseLabel=To continue, click Next.
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
TDriveType = (
dtUnknown,
dtNoRootDir,
dtRemovable,
dtFixed,
dtRemote,
dtCDROM,
dtRAMDisk
);
TDriveTypes = set of TDriveType;
function GetDriveType(lpRootPathName: string): UINT;
external 'GetDriveType{#AW}#kernel32.dll stdcall';
function GetLogicalDriveStrings(nBufferLength: DWORD; lpBuffer: string): DWORD;
external 'GetLogicalDriveStrings{#AW}#kernel32.dll stdcall';
var
DirCombo: TNewComboBox;
#ifndef UNICODE
function IntToDriveType(Value: UINT): TDriveType;
begin
Result := dtUnknown;
case Value of
1: Result := dtNoRootDir;
2: Result := dtRemovable;
3: Result := dtFixed;
4: Result := dtRemote;
5: Result := dtCDROM;
6: Result := dtRAMDisk;
end;
end;
#endif
function GetLogicalDrives(Filter: TDriveTypes; Drives: TStrings): Integer;
var
S: string;
I: Integer;
DriveRoot: string;
begin
Result := 0;
I := GetLogicalDriveStrings(0, #0);
if I > 0 then
begin
SetLength(S, I);
if GetLogicalDriveStrings(Length(S), S) > 0 then
begin
S := TrimRight(S) + #0;
I := Pos(#0, S);
while I > 0 do
begin
DriveRoot := Copy(S, 1, I - 1);
#ifdef UNICODE
if (Filter = []) or
(TDriveType(GetDriveType(DriveRoot)) in Filter) then
#else
if (Filter = []) or
(IntToDriveType(GetDriveType(DriveRoot)) in Filter) then
#endif
begin
Drives.Add(DriveRoot);
end;
Delete(S, 1, I);
I := Pos(#0, S);
end;
Result := Drives.Count;
end;
end;
end;
procedure DriveComboChange(Sender: TObject);
begin
WizardForm.DirEdit.Text := DirCombo.Text;
end;
procedure InitializeWizard;
var
I: Integer;
StringList: TStringList;
begin
StringList := TStringList.Create;
try
if GetLogicalDrives([dtFixed], StringList) > 0 then
begin
WizardForm.DirEdit.Visible := False;
WizardForm.DirBrowseButton.Visible := False;
DirCombo := TNewComboBox.Create(WizardForm);
DirCombo.Parent := WizardForm.DirEdit.Parent;
DirCombo.SetBounds(WizardForm.DirEdit.Left, WizardForm.DirEdit.Top,
WizardForm.DirBrowseButton.Left + WizardForm.DirBrowseButton.Width -
WizardForm.DirEdit.Left, WizardForm.DirEdit.Height);
DirCombo.Style := csDropDownList;
DirCombo.OnChange := #DriveComboChange;
for I := 0 to StringList.Count - 1 do
DirCombo.Items.Add(StringList[I] + '{#SetupSetting('DefaultDirName')}');
DirCombo.ItemIndex := 0;
DirCombo.OnChange(nil);
end;
finally
StringList.Free;
end;
end;
And a screenshot:

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;

Inno Setup Search for specifc file on a CD, retrieve exact filepath and return value to [Files]-Section [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I want to make an installer for an old program which comes on 2 CDs and I want to install the files directly from the discs.
At start up the setup should check if a certain file exists which means the first CD is inserted into the cd rom drive.
This is the code for that task:
[Files]
Source: {code: ??? }; Destination: {app}; flags:external;
[Code]
procedure InitializeWizard();
begin
if not FileExists('A:\Resource\CD1.GOB') xor
FileExists('B:\Resource\CD1.GOB') xor
// and so on, for every drive letter...
FileExists('Z:\Resource\CD1.GOB') then
Repeat
if MsgBox('Insert the first CD!', mbInformation, MB_OKCANCEL) = IDCANCEL then
ExitProcess(0);
Until FileExists('A:\Resource\CD1.GOB') or
FileExists('B:\Resource\CD1.GOB') or
// going through all letters again...
FileExists('Z:\Resource\CD1.GOB') = true;
So this works as intended. If the CD is not inserted and thus the file cannot be found a message will be shown which asks the user to insert the CD.
But I am wondering if there is a better way to increment the drive letter, because this is quite a mess.
And second, how can I save the full filepath und pass it on to the [Files] section?
I hope you can help me with this!
UPDATE:
I tried it again and came up with this:
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageId = wpWelcome then
begin
WizardForm.NextButton.Enabled := False;
repeat
for i:=0 to 31 do
dstr := (Chr(Ord('A') + i) + ':\Resource\CD1.gob');
until FileExists(dstr);
WizardForm.NextButton.Enabled := True;
end;
end;
But using this code Setup freezes at the beginning and doesn't respond even if the CD is already inserted.
Something like this should do what you need:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Files]
Source: {code:GetFileSource}; DestDir: {app}; flags:external;
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
TDriveType = (
dtUnknown,
dtNoRootDir,
dtRemovable,
dtFixed,
dtRemote,
dtCDROM,
dtRAMDisk
);
TDriveTypes = set of TDriveType;
function GetDriveType(lpRootPathName: string): UINT;
external 'GetDriveType{#AW}#kernel32.dll stdcall';
function GetLogicalDriveStrings(nBufferLength: DWORD; lpBuffer: string): DWORD;
external 'GetLogicalDriveStrings{#AW}#kernel32.dll stdcall';
var
FileSource: string;
#ifndef UNICODE
function IntToDriveType(Value: UINT): TDriveType;
begin
Result := dtUnknown;
case Value of
1: Result := dtNoRootDir;
2: Result := dtRemovable;
3: Result := dtFixed;
4: Result := dtRemote;
5: Result := dtCDROM;
6: Result := dtRAMDisk;
end;
end;
#endif
function GetLogicalDrives(var ADrives: array of string;
AFilter: TDriveTypes): Integer;
var
S: string;
I: Integer;
DriveRoot: string;
begin
Result := 0;
SetArrayLength(ADrives, 0);
I := GetLogicalDriveStrings(0, #0);
if I > 0 then
begin
SetLength(S, I);
if GetLogicalDriveStrings(Length(S), S) > 0 then
begin
S := TrimRight(S);
I := Pos(#0, S);
while I > 0 do
begin
DriveRoot := Copy(S, 1, I - 1);
#ifdef UNICODE
if (AFilter = []) or
(TDriveType(GetDriveType(DriveRoot)) in AFilter) then
#else
if (AFilter = []) or
(IntToDriveType(GetDriveType(DriveRoot)) in AFilter) then
#endif
begin
SetArrayLength(ADrives, GetArrayLength(ADrives) + 1);
#ifdef UNICODE
ADrives[High(ADrives)] := DriveRoot;
#else
ADrives[GetArrayLength(ADrives) - 1] := DriveRoot;
#endif
end;
Delete(S, 1, I);
I := Pos(#0, S);
end;
Result := GetArrayLength(ADrives);
end;
end;
end;
function GetFileSource(Value: string): string;
begin
// file source path passed to the [Files] section
Result := FileSource;
end;
procedure InitializeWizard;
var
I: Integer;
DriveCount: Integer;
DriveArray: array of string;
begin
// the function will fill the DriveArray only with CDROM
// drives and returns the count of found drives
DriveCount := GetLogicalDrives(DriveArray, [dtCDROM]);
// here you have an array of CD-ROM drives so iterate it
// search for a file you need and when you find it, pass
// the path to the FileSource variable, which will later
// be queried to get the source to the file in [Files]
for I := 0 to DriveCount - 1 do
begin
if FileExists(DriveArray[I] + 'Resource\CD1.GOB') then
begin
FileSource := DriveArray[I] + 'Resource\CD1.GOB';
Break;
end;
end;
MsgBox('File was found on path: ' + FileSource, mbInformation, MB_OK);
end;

Resources