How to force a reboot in post-install in Inno Setup - installation

In my setup I have to install an external driver.
In some rare cases the installation fails and I have to remove the old driver and reboot before I can try again.
I install the external driver in the ssPostInstall.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
if Exec(ExpandConstant('{app}\external.exe'), '-install', '', SW_SHOW,
ewWaitUntilTerminated, ResultCode) then
begin
{ handle success if necessary; ResultCode contains the exit code }
end
else begin
{ handle failure if necessary; ResultCode contains the error code }
bReboot := true;
end;
end;
function NeedRestart(): Boolean;
begin
Result := bReboot;
end;
Unfortunately this does not work since NeedRestart is called before ssPostInstall.
Is there any other way to trigger a reboot?
I don't want to set AlwaysRestart = yes
I could let pop up a MsgBox to inform the user and tell them what to do. But it would be much nicer if it could be handled within of the setup automatically.

You can do the installation sooner. For example immediately after the external.exe is installed using AfterInstall:
[Files]
Source: "external.exe"; DestDir: "{app}"; AfterInstall: InstallDriver
[Code]
procedure InstallDriver;
begin
if Exec(ExpandConstant('{app}\external.exe'), '-install', '', SW_SHOW,
ewWaitUntilTerminated, ResultCode) then
begin
{ handle success if necessary; ResultCode contains the exit code }
end
else
begin
{ handle failure if necessary; ResultCode contains the error code }
bReboot := true;
end;
end;
Another option is to use ssInstall step (or even PrepareToInstall event) and extract the file programmatically using ExtractTemporaryFile.
Btw, if external.exe is only an installer, you may want to "install" it to {tmp} (to get it automatically deleted).

Related

Setting task value based on active file type associations when upgrading

I have the following tasks as part of my installer:
[Tasks]
Name: register32; Description: "Meeting Schedule Assistant (32 bit)"; \
GroupDescription: "{cm:FileAssociations}"; flags: unchecked exclusive;
Name: register64; Description: "Meeting Schedule Assistant (64 bit)"; \
GroupDescription: "{cm:FileAssociations}"; Check: IsWin64; Flags: exclusive;
By design, Inno Setup has UsePreviousTasks set to Yes. However, my software installs both bit editions and the user may subsequently override the installer default via application settings.
Therefore, when my installer is upgrading, can it determine which bit edition is actively registered and leave it set as that value?
Based on your previous question, I know that your registrations look like:
[HKEY_CLASSES_ROOT\MeetSchedAssist.MWB\Shell\Open\Command]
#="\"C:\\Program Files (x86)\MeetSchedAssist\MeetSchedAssist.exe\" \"%1\""
[HKEY_CLASSES_ROOT\MeetSchedAssist.MWB\Shell\Open\Command]
#="\"C:\\Program Files\MeetSchedAssist\MeetSchedAssist_x64.exe\" \"%1\""
So you can query the registered command and look for a respective executable name in the command.
procedure InitDefaultFileAssociationsTaskValue;
var
SubKeyName, Command: string;
begin
SubKeyName := 'MeetSchedAssist.MWB\Shell\Open\Command';
if not RegQueryStringValue(HKCR, SubKeyName, '', Command) then
begin
Log('MWB registration not found');
end
else
begin
Log(Format('Command registered for MWB is [%s]', [Command]));
Command := Lowercase(Command);
if Pos('meetschedassist_x64.exe', Command) > 0 then
begin
Log('Detected 64-bit registration');
WizardSelectTasks('register64');
end
else
if Pos('meetschedassist.exe', Command) > 0 then
begin
Log('Detected 32-bit registration');
WizardSelectTasks('register32');
end
else
begin
Log('Registration not recognised');
end;
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectTasks then
begin
{ Only now is the task list initialized. }
InitDefaultFileAssociationsTaskValue;
end;
end;
You may want to modify this to change the task selection only the first time the user enters the tasks page.
var
SelectTasksVisited: Boolean;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectTasks then
begin
{ Only now is the task list initialized. }
if not SelectTasksVisited then
begin
InitDefaultFileAssociationsTaskValue;
SelectTasksVisited := True;
end;
end;
end;

Waiting for uninstaller prompt in usPostUninstall before proceeding with installation in Inno Setup

Is there any way to pause the execution of Inno Setup until user makes some interactions with the message box. I am using a message box to confirm whether or not to keep user data. I want to stop all other executions in setup until the user selects yes or no.
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
begin
if DirExists(ExpandConstant('{localappdata}\{#MyBuildId}\storage')) then
if MsgBox('Do you want to delete the saved user data?',
mbConfirmation, MB_YESNO) = IDYES
then
DelTree(ExpandConstant('{localappdata}\{#MyBuildId}\storage'), True, True, True);
end;
end;
I am using a separate procedure to uninstall the previous version in the beginning of the install.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
So when starting to install a new setup first it uninstalls the old version. The user data deletion message box is also shown. But the execution is not pausing. It uninstalls and reinstalls the app while the message box is showing
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath :=
ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppId}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
{ Return Values: }
{ 1 - uninstall string is empty }
{ 2 - error executing the UnInstallString }
{ 3 - successfully executed the UnInstallString }
// default return value
Result := 0;
{ get the uninstall string of the old app }
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART','', SW_HIDE,
ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
When you execute the Inno Setup uninstaller .exe, it clones itself to a temporary folder and runs the clone internally. The main process waits for the clone to (almost) finish, before it terminates itself. The clone can then delete the main uninstaller .exe (as it is not locked anymore). The main process is terminated just after the actual uninstallation completes. But before CurUninstallStepChanged(usPostUninstall). So if you display your message box there, the main uninstaller process is terminated already and so is the Exec in UnInstallOldVersion.
If possible, do the data deletion on usUninstall, not on usPostUninstall.

Inno Setup conditional restart based on result of executable call

My Inno Setup script is used to install a driver. It runs my InstallDriver.exe after this file was copied during step ssInstall.
I need to ask the user to restart in some cases according to the value returned by InstallDriver.exe.
This means that I cannot put InstallDriver.exe in section [Run] because there's no way to monitor it's return value.
So I put it in function CurStepChanged() as follows:
procedure CurStepChanged(CurStep: TSetupStep);
var
TmpFileName, ExecStdout, msg: string;
ResultCode: Integer;
begin
if (CurStep=ssPostInstall) then
begin
Log('CurStepChanged(ssPostInstall)');
TmpFileName := ExpandConstant('{app}') + '\InstallDriver.exe';
if Exec(TmpFileName, 'I', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then .......
However, I can't find a way to make my script restart at this stage.
I thought of using function NeedRestart() to monitor the output of the driver installer, but it is called earlier in the process.
Does it make sense to call the driver installer from within NeedRestart()?
NeedRestart does not look like the right place to install anything. But it would work, as it's fortunately called only once. You will probably want to present a progress somehow though, as the wizard form is almost empty during a call to NeedRestart.
An alternative is to use AfterInstall parameter of the InstallDriver.exe or the driver binary itself (whichever is installed later).
#define InstallDriverName "InstallDriver.exe"
[Files]
Source: "driver.sys"; DestDir: ".."
Source: "{#InstallDriverName}"; DestDir: "{app}"; AfterInstall: InstallDriver
[Code]
var
NeedRestartFlag: Boolean;
const
NeedRestartResultCode = 1;
procedure InstallDriver();
var
InstallDriverPath: string;
ResultCode: Integer;
begin
Log('Installing driver');
InstallDriverPath := ExpandConstant('{app}') + '\{#InstallDriverName}';
if not Exec(InstallDriverPath, 'I', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
Log('Failed to execute driver installation');
end
else
begin
Log(Format('Driver installation finished with code %d', [ResultCode]))
if ResultCode = NeedRestartResultCode then
begin
Log('Need to restart to finish driver installation');
NeedRestartFlag := True;
end;
end;
end;
function NeedRestart(): Boolean;
begin
if NeedRestartFlag then
begin
Log('Need restart');
Result := True;
end
else
begin
Log('Do not need restart');
Result := False;
end;
end;

Disk caching issue with inno-setup?

I will try to be as brief as possible without attaching all the related source files. I have tracked down the issue as much as my Pascal knowledge allows me...
I thing I found a disk caching problem that occurs, for my case, at step ssInstall. I have an installer for an app that, if it finds an older app version installed, it will invoke an uninstallation like this:
procedure CurStepChanged(CurStep: TSetupStep);
var
uninstallStr: String;
ResultCode: Integer;
begin
if (CurStep = ssInstall) and IsUpdatableApplicationInstalled() then
begin
uninstallStr := GetUninstallString();
uninstallStr := RemoveQuotes(uninstallStr);
Result := Exec(uninstallStr, '/SILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if Result and (ResultCode = 0) then
Log('CurStepChanged = ssInstall; uninstall OK');
//-------------
//Sleep(30000);
//-------------
end;
Also the folders/files are defined like this:
[Dirs]
Name: "{app}\db"; Flags: uninsalwaysuninstall
[Files]
Source: "..\bin\*"; DestDir: "{app}\bin"; Flags: ignoreversion createallsubdirs recursesubdirs
Source: "..\java\jre\*"; DestDir: "{app}\jre"; Flags: ignoreversion recursesubdirs createallsubdirs
blah...
Test case1; Normal installation: Everything goes smoothly.
Log file part:
Starting the installation process.
Creating directory: C:\Program Files <---
Creating directory: C:\Program Files\MyApp <---
Creating directory: C:\Program Files\MyApp\db <---
Creating directory: C:\Program Files\MyApp\jre <---
Creating directory: C:\Program Files\MyApp\jre\lib
Creating directory: C:\Program Files\MyApp\jre\lib\applet
Directory for uninstall files: C:\Program Files\MyApp
Creating new uninstall log: C:\Program Files\MyApp\unins000.dat <--- !!!
-- File entry --
Dest filename: C:\Program Files\MyApp\unins000.exe <--- !!!
blah...
Test case2; Update old version: When getting to step ssInstall, the uninstaller launches, it finishes then the installation begins.
Log file part:
CurStepChanged = ssInstall; uninstall OK
Starting the installation process.
Creating directory: C:\Program Files\MyApp\jre\lib
Creating directory: C:\Program Files\MyApp\jre\lib\applet
Directory for uninstall files: C:\Program Files\MyApp
Creating new uninstall log: C:\Program Files\MyApp\unins001.dat <--- !!!
-- File entry --
Dest filename: C:\Program Files\MyApp\unins001.exe <--- !!!
blah...
As you can see some folders do not get created and my app fails later on when it tries to write to 'db' folder.
If I uncomment the Sleep() command, everything runs smoothly and both the log files are identical.
It seems the disk has enough time to flush the changes! Somehow there must be a flush() command missing in inno-setup.
Can any of the inno-gurus comment or help somehow?
Is there a flush() i could call, instead of the sleep()?
Any help is appreciated. I just want to be sure before I file a bug request.
Just to sum up the comment trail on the question:
Don't Uninstall
The best solution is to not run the uninstaller at all. You can remove redundant files via the [InstallDelete] section; eg. to remove a "jre" subfolder completely (to be replaced as part of your installation) then do this:
[InstallDelete]
Type: filesandordirs; Name: "{app}\jre"
(Only use this for subfolders like this and only sparingly; you can get yourself in trouble if you delete too much stuff.)
For normal individual application files installed by a previous version that are now redundant, you can remove them like so:
[InstallDelete]
Type: files; Name: "{app}\redundant.dll"
(You'll need to do something slightly fancier if they had "regserver" or "sharedfile" when installed.)
If you Uninstall regardless, wait longer
The uninstaller cannot delete itself or the folder where it is located while it is still running. While Inno does take care of this in such a way that it is able to delete the uninstaller and folder, it does mean that your Exec call to the uninstaller will return before this deletion has occurred and before the uninstall process actually finishes.
You will need to wait longer after the Exec for the uninstall to actually finish before you let it continue with the installation. Using a Sleep is simple enough and will work in most cases but if you want the best results you'll need to call into the WinAPI to check the running processes list.
Additionally, you should use the PrepareToInstall event function to perform the actual uninstallation. This will better allow you to handle cases such as uninstall errors or when a reboot is required between uninstall and reinstall. (And because it executes at the "right" time in the installation process.)
This is how I actually worked around this issue. I am posting my solution in hope to help others with the same problem.
Big thanks to all that helped out and especially to Miral for pointing me in the right direction!
The solution is rather simple; wait until the uninstaller exe is deleted!
const
DELAY_MILLIS = 250;
MAX_DELAY_MILLIS = 30000;
function GetUninstallString(): String;
var
uninstallPath: String;
uninstallStr: String;
begin
uninstallPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
uninstallStr := '';
if not RegQueryStringValue(HKLM, uninstallPath, 'UninstallString', uninstallStr) then
RegQueryStringValue(HKCU, uninstallPath, 'UninstallString', uninstallStr);
Result := RemoveQuotes(uninstallStr);
end;
function ForceUninstallApplication(): Boolean;
var
ResultCode: Integer;
uninstallStr: String;
delayCounter: Integer;
begin
// 1) Uninstall the application
Log('forcing uninstall of application);
uninstallStr := GetUninstallString();
Result := Exec(uninstallStr, '/SILENT /NORESTART /SUPPRESSMSGBOXES /LOG', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
if not Result then
begin
Log('application uninstall failed!');
Exit
end;
Log('application uninstalled!');
// 2) Be sure to wait a while, until the actual uninstaller is deleted!
Log('waiting a while until uninstaller changes are flushed in the filesystem...');
delayCounter := 0;
repeat
Sleep(DELAY_MILLIS);
delayCounter := delayCounter + DELAY_MILLIS;
until not FileExists(uninstallStr) or (delayCounter >= MAX_DELAY_MILLIS);
if (delayCounter >= MAX_DELAY_MILLIS) then
RaiseException('Timeout exceeded trying to delete uninstaller: ' + uninstallStr);
Log('waited ' + IntToStr(delayCounter) + ' milliseconds');
end;
Function ForceUninstallApplication() can be successfuly called from either PrepareToInstall or CurStepChanged(ssInstall). For my case it takes about 500 millis when I use my SSD hard disk and maybe a couple of seconds when I use my external usb hard disk.
Thank you to fubar for this answer ! I just made a little modification in order to avoid the same problem with the install folder which is delete during uninstall process. It could be deleted after the call to ForceUninstallApplication() and avoid the creation of the install folder.
To avoid this problem, I just create a temp file in the install folder and remove when installation in finish. When a unknow file is present folder, unins000.exe don't delete the folder.
Here the fubar code updated:
const
DELAY_MILLIS = 250;
MAX_DELAY_MILLIS = 30000;
var
tempUninstallFilename: String;
function GetUninstallString(): String;
var
uninstallPath: String;
uninstallStr: String;
begin
uninstallPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
uninstallStr := '';
if not RegQueryStringValue(HKLM, uninstallPath, 'UninstallString', uninstallStr) then
RegQueryStringValue(HKCU, uninstallPath, 'UninstallString', uninstallStr);
Result := RemoveQuotes(uninstallStr);
end;
function ForceUninstallApplication(): Boolean;
var
ResultCode: Integer;
uninstallStr: String;
delayCounter: Integer;
begin
// 1) Create a temporary file to avoid destruction of install folder during uninstall.
uninstallStr := RemoveQuotes(GetUninstallString());
tempUninstallFilename := ExtractFileDir(uninstallStr) + '\uninstall.log';
SaveStringToFile(tempUninstallFilename, '...', False);
Log('Create temp file: ' + tempUninstallFilename);
// 2) Uninstall the application
Log('forcing uninstall of application);
uninstallStr := GetUninstallString();
Result := Exec(uninstallStr, '/SILENT /NORESTART /SUPPRESSMSGBOXES /LOG', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
if not Result then
begin
Log('application uninstall failed!');
Exit
end;
Log('application uninstalled!');
// 2) Be sure to wait a while, until the actual uninstaller is deleted!
Log('waiting a while until uninstaller changes are flushed in the filesystem...');
delayCounter := 0;
repeat
Sleep(DELAY_MILLIS);
delayCounter := delayCounter + DELAY_MILLIS;
until not FileExists(uninstallStr) or (delayCounter >= MAX_DELAY_MILLIS);
if (delayCounter >= MAX_DELAY_MILLIS) then
RaiseException('Timeout exceeded trying to delete uninstaller: ' + uninstallStr);
Log('waited ' + IntToStr(delayCounter) + ' milliseconds');
end;
//============================================================================================================
// SUMMARY
// You can use this event function to perform your own pre-install and post-install tasks.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep = ssInstall) then
begin
ForceUninstallApplication();
end ;
if (CurStep = ssPostInstall) then
begin
DeleteFile(tempUninstallFilename);
end;
end;

How to install DirectX redistributable from Inno-setup?

I didn't find any tip about DirectX installation at Inno-Setup web site. So, is there any sample installation script? I know that I have to add to [Run] sction something like this:
Filename: "{src}\DirectX\DXSETUP.exe"; WorkingDir: "{src}\DirectX"; Parameters: "/silent"; Check: DirectX; Flags: waituntilterminated; BeforeInstall: DirectXProgress;
But how to include it into setup file (temp folder?), how to extract it, ect?
To include it in the setup, you can install it to {tmp} and then [Run] it from there.
The correct way to install this sort of requirement is to extract in code and call Exec() on it in the PrepareToInstall() event function:
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
InstallerResult: integer;
begin
//Check if .Net is available already
if NeedsDirectX() then begin
ExtractTemporaryFile('DXSETUP.exe');
if Exec(ExpandConstant('{tmp}\DXSETUP.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, InstallerResult) then begin
case InstallerResult of
0: begin
//It installed successfully (Or already was), we can continue
end;
else begin
//Some other error
result := 'DirectX installation failed. Exit code ' + IntToStr(InstallerResult);
end;
end;
end else begin
result := 'DirectX installation failed. ' + SysErrorMessage(InstallerResult);
end;
end;
end;
The ISXKB has an article on how to detect the versions installed.

Resources