Run post install file replace before creating service - inno-setup

I have configured the below script to ask the user for an IP address as part of the install wizard, this address gets written to a config file which the application will reference to know where to communicate with.
However currently, the service is created before the configuration file is updated (Run section happens before CurStep = ssPostInstall), so the default value has already been read from the file before it is updated.
This means that a further manual service restart is currently required to get the application to communicate.
I understand it is possible to have the Run section happen after the ssPostInstall.
I have read the article Inno Setup: How to run a code procedure in Run section or before Run section? which suggests I can use the BeforeInstall: parameter as part of the run command to perform the CurStepChanged procedure first. However, this leads to an error of
Required function or procedure 'CurStepChanged' found but not with a compatible prototype.
I also tried moving the run line and command above the CurStepChanged section (to match the article mentioned as closely as possible), but this still returned the same error.
Can anyone offer some guidance as to where I am going wrong with the configuration?
[Code]
var
PrimaryServerPage: TInputQueryWizardPage;
function FileReplaceString(ReplaceString: string):boolean;
var
MyFile : TStrings;
MyText : string;
begin
Log('Replacing in file');
MyFile := TStringList.Create;
try
Result := true;
try
MyFile.LoadFromFile(ExpandConstant('{app}' + '\providers\print\win\print-provider.conf'));
Log('File loaded');
MyText := MyFile.Text;
{ Only save if text has been changed. }
if StringChangeEx(MyText, 'REPLACE_WITH_CUSTOMER_IP', ReplaceString, True) > 0 then
begin;
Log('IP address inserted');
MyFile.Text := MyText;
MyFile.SaveToFile(ExpandConstant('{app}' + '\providers\print\win\print-provider.conf'));
Log('File saved');
end;
except
Result := false;
end;
finally
MyFile.Free;
end;
Result := True;
end;
procedure InitializeWizard;
begin
PrimaryServerPage :=
CreateInputQueryPage(
wpWelcome, 'Application Server Details', 'Where is installed?',
'Please specify the IP address or hostname of your ' +
'Primary Application Server, then click Next.');
PrimaryServerPage.Add('Primary Application Server IP/Hostname:', False);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
Log('File installed, replacing IP address');
FileReplaceString(PrimaryServerPage.Values[0]);
end;
end;
[run]
Filename: {sys}\sc.exe; Parameters: "create PCPrintProvider start= auto binPath= ""{app}\providers\print\win\pc-print.exe PCPrintProvider"" depend= Spooler" ; Flags: runhidden ; BeforeInstall: CurStepChanged
Filename: {sys}\sc.exe; Parameters: "start PCPrintProvider" ; Flags: runhidden ; BeforeInstall: CurStepChanged

You have to define your own procedure for the BeforeInstall parameter:
[Run]
Filename: {sys}\sc.exe; \
Parameters: "create PCPrintProvider start= auto binPath= ""{app}\providers\print\win\pc-print.exe PCPrintProvider"" depend= Spooler"; \
Flags: runhidden; BeforeInstall: ReplaceIPAddress
[Code]
procedure ReplaceIPAddress;
begin
FileReplaceString(PrimaryServerPage.Values[0]);
end;

Try using Check param
[run]
Filename: {sys}\sc.exe; Parameters: "create PCPrintProvider start= auto binPath= ""{app}\providers\print\win\pc-print.exe PCPrintProvider"" depend= Spooler" ; Flags: runhidden ; Check: IsIpChanged;
[code]
function IsIpChanged: Boolean;
begin
FileReplaceString(PrimaryServerPage.Values[0]);
Result := True;
end;

Related

How do I copy the Inno Setup log file to a certain destination after setup is done?

I have asked chat.openai how to copy the log path to a certain path after it's done setupping.
It proposed this, but I think it's wrong. It expects the log file to be in {app}\SetupLog.log", but I don't think it's there.
Here is what it proposed.
Can somebody correct it?
I do not set the log path from outside. It will be the default path.
I can not change that unfortunately. And when the installation is done, the temp installation folder is deleted, so I don't have a chance to view the log file.
Thank you!
#define LogFileName "SetupLog.log"
#define LogDestination "C:\MyLogs"
[Setup]
DefaultDirName={pf}\My Application
DefaultGroupName=My Application
[Files]
Source: "MyApp.exe"; DestDir: "{app}"
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
LogPath: string;
DestinationPath: string;
begin
if CurStep = ssPostInstall then
begin
LogPath := ExpandConstant('{app}\SetupLog.log');
DestinationPath := ExpandConstant(LogDestination + '\SetupLog.log');
if FileCopy(LogPath, DestinationPath, false) then
MsgBox('Log file was copied successfully to ' + DestinationPath, mbInformation, MB_OK)
else
MsgBox('Failed to copy log file to ' + DestinationPath, mbError, MB_OK);
end;
end;
There is a Q & A about this because I asked at one time. I can't find it.
But, this is the code I am now using:
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: integer;
strLogFilePathName, strLogFileName, strNewFilePathName: string;
begin
if (CurStep = ssDone) then
begin
strLogFilePathName := ExpandConstant('{log}');
strLogFileName := ExtractFileName(strLogFilePathName);
strNewFilePathName := ExpandConstant('{#CommonDataDir}\Installation Logs\') + strLogFileName;
FileCopy(strLogFilePathName, strNewFilePathName, false);
end;
end;
Change the folder to copy the log to as required. Notice that I am using ssDone.

How do you prompt a user to read a guide and restart/log off from their computer on the finished page using Inno Setup?

I am building an installer and would like to ask the user to restart using a radio button. I would also like to include an option to open the user guide if the user selects "No, I will restart later". My current method for asking the user to open the user guide is putting it in the [Run] section like this:
[Run]
Filename: "{app}\userguide.pdf"; Description: "View the User Guide"; Flags: shellexec runasoriginaluser postinstall nowait unchecked
This works perfectly, it even opens in the default PDF viewer. However, whenever I try to include a restart option, it overrides the user guide option and completely removes it. So, trying:
[Code]
function NeedRestart(): Boolean;
begin
Result := True;
end;
as well as:
[Setup]
AlwaysRestart=yes
work in the sense that they include an option for restart but they also override the user guide button. Is there a way to make a custom page that, upon checking the "No I will restart later" radio button, will show an option on opening the user guide? I am not too familiar with using Inno Setup and Delphi/Pascal.
You have to code that. For example you can add your own checkbox for starting the user guide:
[Setup]
AlwaysRestart=yes
[Files]
Source: "userguide.pdf"; DestDir: "{app}"
[Code]
var
LaunchCheckbox: TCheckbox;
procedure YesNoRadioClicked(Sender: TObject);
begin
// Disable the user guide checkbox when "restart" is selected
LaunchCheckbox.Enabled := WizardForm.NoRadio.Checked;
end;
procedure InitializeWizard();
begin
LaunchCheckbox := TCheckbox.Create(WizardForm.FinishedPage);
LaunchCheckbox.Caption := 'View the User Guide';
LaunchCheckbox.Checked := False;
LaunchCheckbox.Left := WizardForm.YesRadio.Left;
LaunchCheckbox.Width := WizardForm.YesRadio.Width;
LaunchCheckbox.Height := ScaleY(LaunchCheckbox.Height);
LaunchCheckbox.Parent := WizardForm.FinishedPage;
if (WizardForm.YesRadio.OnClick <> nil) or (WizardForm.NoRadio.OnClick <> nil) then
begin
Log('Restart radio button event handler unexpectedly set');
end
else
begin
WizardForm.YesRadio.OnClick := #YesNoRadioClicked;
WizardForm.NoRadio.OnClick := #YesNoRadioClicked;
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then
begin
// Adjust to the initial start of restart selection
YesNoRadioClicked(nil);
// Only now the restart radio buttons have their definitive vertical position
LaunchCheckbox.Top :=
WizardForm.NoRadio.Top + WizardForm.NoRadio.Height + ScaleY(16);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
ErrorCode: Integer;
begin
if CurStep = ssDone then
begin
if (not WizardSilent) and
(not WizardForm.YesRadio.Checked) and
LaunchCheckbox.Checked then
begin
Log('Opening user guide');
ShellExecAsOriginalUser(
'open', ExpandConstant('{app}\userguide.pdf'), '', '', SW_SHOWNORMAL,
ewNoWait, ErrorCode);
end;
end;
end;
The code assumes AlwaysRestart. Were the restart conditional, the code will need an update to adjust to a different layout of the Finished page, when restart is not needed. For a full solution see my installer for WinSCP:
https://github.com/winscp/winscp/blob/master/deployment/winscpsetup.iss

How to set a short delay using Inno Setup? [duplicate]

I have a license.exe file that I call in my setup code at the end,
The code needs the environment variable to be set before working correctly,
The code is as follows:
[Registry]
; set PATH
Root: HKLM; \
Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: string; ValueName: "PATH"; ValueData: "{app}"
[Setup]
; Tell Windows Explorer to reload the environment
ChangesEnvironment=yes
[Run]
Filename: "{app}\temp\installation_files\license.exe";
Here the code executes, but does not find the correct path.
When I check the system environment variable, it is set correctly,
When I run the license.exe code afterwards manually, it works correctly and sees the environment variable.
Can someone tell me how to fix this?
Or how to delay the [Run] section until the system recognizes the environment variable?
The processes created for executing entries from the [Run] section inherits the environment block of its parent process, which is the installer itself. So you have to set the environment variable to the installer and let it inherit to your executed application. How to do that is shown in the below script:
[Run]
Filename: "{app}\temp\installation_files\license.exe"; BeforeInstall: SetEnvPath
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function SetEnvironmentVariable(lpName: string; lpValue: string): BOOL;
external 'SetEnvironmentVariable{#AW}#kernel32.dll stdcall';
procedure SetEnvPath;
begin
if not SetEnvironmentVariable('PATH', ExpandConstant('{app}')) then
MsgBox(SysErrorMessage(DLLGetLastError), mbError, MB_OK);
end;
Previous answer for notifying rest of the system about variable change:
As #Jerry pointed out in his comment, a notification about the environment changes is performed after the [Run] section is processed. Actually, it is one of the last things executed by the installer.
So, to notify the system about environment changes before processing the [Run] section, you'll need to have a workaround. I rewrote the RefreshEnvironment procedure from Inno Setup code to script. It's the same function as it's executed if you have ChangesEnvironment directive set to yes.
In the following script I have removed the ChangesEnvironment directive and added execution of the RefreshEnvironment procedure from the AfterInstall parameter function of your registry entry:
[Registry]
Root: HKLM; \
Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: string; ValueName: "PATH"; ValueData: "{app}"; \
AfterInstall: RefreshEnvironment;
[Run]
Filename: "{app}\temp\installation_files\license.exe";
[Code]
const
SMTO_ABORTIFHUNG = 2;
WM_WININICHANGE = $001A;
WM_SETTINGCHANGE = WM_WININICHANGE;
type
WPARAM = UINT_PTR;
LPARAM = INT_PTR;
LRESULT = INT_PTR;
function SendTextMessageTimeout(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: PAnsiChar; fuFlags: UINT;
uTimeout: UINT; out lpdwResult: DWORD): LRESULT;
external 'SendMessageTimeoutA#user32.dll stdcall';
procedure RefreshEnvironment;
var
S: AnsiString;
MsgResult: DWORD;
begin
S := 'Environment';
SendTextMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;
The solution with SetEnvironmentVariable in TLama's answer is correct for many situations.
But it won't work for [Run] tasks with runasoriginaluser flag (what is implied by postinstall flag). I.e. the variable won't be propagated to an application run with common "Run My Program" check box on the "Finished" page.
The reason is that the tasks with runasoriginaluser are executed by a un-elevated hidden parent process of the Inno Setup installer. The SetEnvironmentVariable will change environment for the installer, but not for its parent process. Unfortunately, the parent process of the installer cannot be controlled (imo).
As a workaround, to set the variable for the runasoriginaluser tasks, you have to inject an intermediate process between the installer parent process and the task, and have the intermediate process set the variable.
Such an intermediate process can easily be the cmd.exe with its set command:
[Run]
Filename: "{cmd}"; Parameters: "/C set PATH=%PATH%;{app} & ""{app}\MyProg.exe"""; \
Description: "Run My Program"; Flags: postinstall runhidden
The runhidden flag hides the cmd.exe console window, not the application (assuming it's a GUI application). If it's a console application and you want the output to be visible, remove the runhidden flag. Alternatively, you can use start command to start the application in its own console window.
after some modifications below, worked perfectly:
[Run]
Filename: "{app}\{#MyAppExeName}"; BeforeInstall: AppendToPathAndRefresh;Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}";Flags: nowait postinstall shellexec skipifsilent
[Code]
////////////////////////////////////////////////////////////
const
SMTO_ABORTIFHUNG = 2;
WM_WININICHANGE = $001A;
type
WPARAM = UINT_PTR;
LPARAM = INT_PTR;
LRESULT = INT_PTR;
function SendTextMessageTimeout(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: PAnsiChar; fuFlags: UINT;
uTimeout: UINT; out lpdwResult: DWORD): LRESULT;
external 'SendMessageTimeoutA#user32.dll stdcall';
procedure RefreshEnvironment;
var
S: AnsiString;
MsgResult: DWORD;
begin
S := 'Environment';
SendTextMessageTimeout(HWND_BROADCAST, WM_WININICHANGE, 0,
PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;
///PATH ENVINRONMENT//////////////////////////////////////////
function Replace(Dest, SubStr, Str: string): string;
var
Position: Integer;
Ok: Integer;
begin
Ok := 1;
while Ok > 0 do
begin
Position:=Pos(SubStr, Dest);
if Position > 0 then
begin
Delete(Dest, Position, Length(SubStr));
Insert(Str, Dest, Position);
end else
Ok := 0;
end;
Result:=Dest;
end;
procedure AppendToPath();
var
V: string;
Str: string;
begin
RegQueryStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
Str := ExpandConstant('{app}\libav');
V := Replace(V, Str, '');
V := V + ';' + Str;
V := Replace(V,';;',';');
RegWriteStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
// MsgBox(V, mbInformation, MB_OK);
end;
procedure RemoveFromPath();
var
V: string;
Str: string;
begin
RegQueryStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
Str := ExpandConstant('{app}\dlls');
V := Replace(V, Str, '');
V := Replace(V,';;',';');
RegWriteStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
//MsgBox(V, mbInformation, MB_OK);
end;
procedure AppendToPathAndRefresh;
begin
AppendToPath;
RefreshEnvironment;
end;
procedure DeinitializeUninstall();
begin
RemoveFromPath();
end;
///END OF PATH ENVIRONMENT ///////////////////////////////////

Using Process Exit code to show error message for a specific File in [Run]

Using innosetup and want to show error/msgbox if one of the [RUN] process does not return process code 0. I'm using it for authorization process, if authorization is not successful, i want to notify the user.
I have following:
Filename: "{pf32}\Common Files\Authorization.exe"; Parameters: " "{code:GetAuthorizationFilePath}" /s"; WorkingDir: "{tmp}"; Flags: skipifdoesntexist hidewizard; StatusMsg: "Authorizing License";
Returns me:
Process exit code:0
0 of course is successful, but if its not 0 i want to notify the user.
Is there a way to do that?
Thanks and Regards,
Kev84
I think there's no way to accomplish this from the [Run] section. What you can do is:
use the Pascal Script for this task
or display the modal error message from your executed application Authorization.exe and terminate it only after the user confirms the error message (setup will then continue e.g. with the execution of the other files in the [Run] section)
Here is the code sample of the Pascal Script; you can check also the commented version of this code:
[Code]
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
Result := True;
if CurPageID = wpWelcome then
begin
Result := False;
if Exec(ExpandConstant('{pf32}\Common Files\Authorization.exe'), '', '',
SW_SHOW, ewWaitUntilTerminated, ResultCode) then
begin
if ResultCode = 0 then
Result := True
else
MsgBox('The authorization failed!', mbCriticalError, MB_OK);
end;
end;
end;
I had the same requirements: to run an external program and display an error message if the return code is not 0. It was very important for me to run the program in the Run section as I needed to display a status message and the progress bar is nice to have.
I found that you can use AfterInstall in the Run section to trigger the execution of your program and check the result code (see this link for more info about AfterInstall.)
So, my idea was to run a dummy program like change and to use the procedure specified in AfterInstall to run the real program and catch its result code.
[Code]
procedure ExecuteRealProgram();
var
ResultCode: Integer;
begin
if Exec(ExpandConstant('{pf32}\Common Files\Authorization.exe'), '', '', SW_SHOW,
ewWaitUntilTerminated, ResultCode)
then begin
if not (ResultCode = 0) then
MsgBox('Error! ResultCode is ' + IntToStr(ResultCode), mbCriticalError, MB_OK);
end
else
MsgBox('Exec failed! Error: ' + SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
end;
end;
[Run]
Filename: "change.exe"; WorkingDir: "{tmp}"; \
StatusMsg: "Running external program. Please wait."; AfterInstall: ExecuteRealProgram

How to force Inno Setup setup to fail when Run command fails?

I have some commands in the [Run] section of my Inno Setup script. Right now, if any of them returns a failure code (non-zero return value), the setup continues without any warning to the user. The desired behavior is to have the setup fail and roll back.
How do I enable this? I couldn't find any flag for the Run entry that would force this behavior. Am I missing something?
As far as I'm concerned, you have to use [Code] section for that, run the files with Exec function, check ResultCode upon return and run your uninstall script.
I did it this way:
Write error message (either abort confirmation message or just notification message) to temporary file {tmp}\install.error using Inno Setup's BeforeInstall parameter with SaveStringToUTF8File procedure. You can use Inno Setup's constants, such as {cm:YourCustomMessage}.
Use Windows command shell cmd.exe /s /c to run desired program. Also use conditional execution of del command with && - https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx. So error message file would be deleted if command succeed (exit code 0). Please be aware of special quotes handling in cmd.exe /s /c. Use code below as example.
Check existence of error message file {tmp}\install.error using Inno Setup's AfterInstall parameter with either ConfirmInstallAbortOnError or NotifyInstallAbortOnError procedures depending on error severity. They will abort install with proper notification or confirmation (and optional presenting of log file) and perform rollback using Exec(ExpandConstant('{uninstallexe}'), ...
ShouldAbortInstallation global variable is used to keep status. Inno Setup's ShouldSkipPage(PageID: Integer) function is used to hide final page. All commands in [Run] section should use Check parameter with CheckInstallationIsNotAborted function. It will prevent their execution after failure at some point.
See example below. Hope this helps.
[CustomMessages]
InstallAbortOnErrorConfirmationMessage=An error has occurred during setup.%nAbort installation?
InstallAbortOnErrorNotificationMessage=An error has occurred during setup.%nInstallation will be aborted.
RunProgram1ErrorMsg=Post installation phase 1 failed. Should abort install?
RunProgram2ErrorMsg=Post installation phase 2 failed. Installation will be aborted. Please, contact tech support.
RunProgram1StatusMsg=Post installation phase 1 is in progress
RunProgram2StatusMsg=Post installation phase 2 is in progress
[Run]
; Write error text to file. Delete file on succeed. Abort installation if file exists after command execution.
Filename: "cmd.exe"; Parameters: "/s /c "" ""{app}\program1.exe"" /param1 /param2:""val2"" && del /F /Q ""{tmp}\install.error"" """; \
WorkingDir:"{app}"; Flags: runhidden; \
BeforeInstall: SaveStringToUTF8File('{tmp}\install.error', '{cm:RunProgram1ErrorMsg}', False); \
AfterInstall: ConfirmInstallAbortOnError('{tmp}\install.error', '{app}\logs\setup.log'); \
StatusMsg: "{cm:RunProgram1StatusMsg}"; \
Check: CheckInstallationIsNotAborted;
Filename: "cmd.exe"; Parameters: "/s /c "" ""{app}\program2.exe"" && del /F /Q ""{tmp}\install.error"" """; \
WorkingDir:"{app}"; Flags: runhidden; \
BeforeInstall: SaveStringToUTF8File('{tmp}\install.error', '{cm:RunProgram2ErrorMsg}', False); \
AfterInstall: NotifyInstallAbortOnError('{tmp}\install.error', '{app}\logs\setup.log'); \
StatusMsg: "{cm:RunProgram2StatusMsg}"; \
Check: CheckInstallationIsNotAborted;
[Code]
var
ShouldAbortInstallation: Boolean;
procedure SaveStringToUTF8File(const FileName, Content: String; const Append: Boolean);
var
Text: array [1..1] of String;
begin
Text[1] := Content;
SaveStringsToUTF8File(ExpandConstant(FileName), Text, Append);
end;
function LoadAndConcatStringsFromFile(const FileName: String): String;
var
Strings: TArrayOfString;
i: Integer;
begin
LoadStringsFromFile(FileName, Strings);
Result := '';
if High(Strings) >= Low(Strings) then
Result := Strings[Low(Strings)];
for i := Low(Strings) + 1 to High(Strings) do
if Length(Strings[i]) > 0 then
Result := Result + #13#10 + Strings[i];
end;
procedure ConfirmInstallAbortOnError(ErrorMessageFile, LogFileToShow: String);
var
ErrorCode: Integer;
ErrorMessage: String;
begin
ErrorMessageFile := ExpandConstant(ErrorMessageFile);
LogFileToShow := ExpandConstant(LogFileToShow);
Log('ConfirmInstallAbortOnError is examining file: ' + ErrorMessageFile);
if FileExists(ErrorMessageFile) then
begin
Log('ConfirmInstallAbortOnError: error file exists');
{ Show log file to the user }
if Length(LogFileToShow) > 0 then
ShellExec('', LogFileToShow, '', '', SW_SHOW, ewNoWait, ErrorCode);
ErrorMessage := LoadAndConcatStringsFromFile(ErrorMessageFile);
if Length(ErrorMessage) = 0 then
ErrorMessage := '{cm:InstallAbortOnErrorConfirmationMessage}';
if MsgBox(ExpandConstant(ErrorMessage), mbConfirmation, MB_YESNO) = IDYES then
begin
Log('ConfirmInstallAbortOnError: should abort');
ShouldAbortInstallation := True;
WizardForm.Hide;
MainForm.Hide;
Exec(ExpandConstant('{uninstallexe}'), '/SILENT', '', SW_HIDE,
ewWaitUntilTerminated, ErrorCode);
MainForm.Close;
end;
end;
Log('ConfirmInstallAbortOnError finish');
end;
procedure NotifyInstallAbortOnError(ErrorMessageFile, LogFileToShow: String);
var
ErrorCode: Integer;
ErrorMessage: String;
begin
ErrorMessageFile := ExpandConstant(ErrorMessageFile);
LogFileToShow := ExpandConstant(LogFileToShow);
Log('NotifyInstallAbortOnError is examining file: ' + ErrorMessageFile);
if FileExists(ErrorMessageFile) then
begin
Log('NotifyInstallAbortOnError: error file exists');
{ Show log file to the user }
if Length(LogFileToShow) > 0 then
ShellExec('', LogFileToShow, '', '', SW_SHOW, ewNoWait, ErrorCode);
ErrorMessage := LoadAndConcatStringsFromFile(ErrorMessageFile);
if Length(ErrorMessage) = 0 then
ErrorMessage := '{cm:InstallAbortOnErrorNotificationMessage}';
MsgBox(ExpandConstant(ErrorMessage), mbError, MB_OK);
Log('NotifyInstallAbortOnError: should abort');
ShouldAbortInstallation := True;
WizardForm.Hide;
MainForm.Hide;
Exec(ExpandConstant('{uninstallexe}'), '/SILENT', '', SW_HIDE,
ewWaitUntilTerminated, ErrorCode);
MainForm.Close;
end;
Log('NotifyInstallAbortOnError finish');
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := ShouldAbortInstallation;
end;
function CheckInstallationIsNotAborted(): Boolean;
begin
Result := not ShouldAbortInstallation;
end;
The [Run] section happens after installation is complete, so there's no rollback possible at that point, because it's already finalized.
However, what you can do is use AfterInstall in the [Files] section, after your .exe or whatever is required to execute your method. This runs before finalizing the installation, so canceling at this point does a rollback that removes all files.
If you combine that with the "CancelWithoutPrompt" from this answer you can do a rollback when running in interactive mode. Unfortunately, there doesn't seem to be a rollback for silent mode.
[Files]
Source: src\myapp.exe; DestDir: "{app}"; AfterInstall: RunMyAppCheck
[Code]
var CancelWithoutPrompt: boolean;
function InitializeSetup(): Boolean;
begin
CancelWithoutPrompt := false;
result := true;
end;
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
if CancelWithoutPrompt then
Confirm := false; { hide confirmation prompt }
end;
procedure RunMyAppCheck();
var
resultCode: Integer;
begin
Exec(ExpandConstant('{app}\myapp.exe'), '--verify --example-other-params',
'', SW_HIDE, ewWaitUntilTerminated, resultCode);
if resultCode <> 0 then
begin
SuppressibleMsgBox(
'MyApp failed, exit code ' + IntToStr(resultCode) + '. ' +
'Aborting installation.',
mbCriticalError, MB_OK, 0);
CancelWithoutPrompt := true;
WizardForm.Close;
end;
end;
You can use the AfterInstall flag in the Run section to trigger the execution of your program and catch the result code.
See my answer here.
Then according to the result code you can cancel the installation.

Resources