How to handle DotNet prerequisite with Inno Setup Install / DswinDs system? - inno-setup

I have an understanding now of how to use this DswinsHs for downloading a file (as we have it working for my help documentation).
But now I need to migrate some old code that optionally downloaded and installed Dot Net Framework.
Old Code
I have this code (used ISTool DLL):
const
// Changed to 4.6.2 download link (see: http://msdn.microsoft.com/en-us/library/ee942965%28v=vs.110%29.aspx#redist)
dotnetRedistURL = 'http://go.microsoft.com/fwlink/?LinkId=780600';
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
IsInstalled: Cardinal;
begin
Result := '';
dotNetNeeded := true;
// Check for required netfx installation
// http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx#net_b
if(Is64BitInstallMode()) then begin
if (RegValueExists(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
RegQueryDWordValue(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
if(IsInstalled >= 378675) then begin
dotNetNeeded := false;
downloadNeeded := false;
end;
end;
end
else begin
if (RegValueExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
if(IsInstalled >= 378675) then begin
dotNetNeeded := false;
downloadNeeded := false;
end;
end;
end;
if(dotNetNeeded) then begin
if (not IsAdminLoggedOn()) then begin
Result := ExpandConstant('{cm:DotNet_NeedAdminRights}');
end
else begin
dotnetRedistPath := ExpandConstant('{src}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
if not FileExists(dotnetRedistPath) then begin
dotnetRedistPath := ExpandConstant('{tmp}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
if not FileExists(dotnetRedistPath) then begin
isxdl_AddFile(dotnetRedistURL, dotnetRedistPath);
downloadNeeded := true;
end;
end;
if (downloadNeeded) then begin
if (MsgBox(ExpandConstant('{cm:DotNet_NeedToDownload}'), mbConfirmation, MB_OKCANCEL) = IDCANCEL) then begin
Result := ExpandConstant('{cm:DotNet_InstallAborted}');
end;
end;
end;
end;
// AJT v19.0.0 We always delete the existing local help file if it exists.
// The new version will be downloaded on the next wizard form if
// the user still wants the local help. ("downloadhelp" task selected).
if (bDownloadHelpDocSetup) then DoDeleteFile(ExpandConstant('{app}\CommunityTalks.chm'));
end;
Then I have:
procedure CurStepChanged(CurStep: TSetupStep);
var
hWnd: Integer;
ResultCode: Integer;
begin
if (CurStep = ssInstall) then
begin
hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));
// Don't try to init isxdl if it's not needed because it will error on < ie 3
if (downloadNeeded) then begin
isxdl_SetOption('label', ExpandConstant('{cm:Downloading}'));
isxdl_SetOption('description', ExpandConstant('{cm:DownloadingInfo}'));
if (isxdl_DownloadFiles(hWnd) = 1) then begin
if (dotNetNeeded = true) then begin
if Exec(ExpandConstant(dotnetRedistPath), '/quiet', '',
SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
// handle success if necessary; ResultCode contains the exit code
if not (ResultCode = 0) then begin
// Microsoft present an array of options for this. But since
// The interface was visible I think it is safe to just say
// that the installation was not completed.
MsgBox(ExpandConstant('{cm:DotNet_InstallFailed}'), mbInformation, MB_OK);
Abort();
end;
end
else begin
// The execution failed for some reason
MsgBox(SysErrorMessage(ResultCode), mbInformation, MB_OK);
Abort();
end;
end;
end
else begin
// The user most likely cancelled the download of the file
MsgBox(ExpandConstant('{cm:DotNet_DownloadFailed}'), mbInformation, MB_OK);
Abort();
end;
end;
end;
end;
That needs changing.
How it is done with DswinsHs
For my download that does work with DwinsHs I basically have two bits as follows:
[Files] section:
; AJT v19.0.0 Download Help Documentation Setup file.
; This is associated with the "downloadhelp" task.
; It will be downloaded from the internet and deleted after install.
Source: "{tmp}\HelpDocSetup.exe"; \
DestDir: "{app}"; \
Flags: external deleteafterinstall; \
Tasks: downloadhelp; \
Check: DwinsHs_Check( ExpandConstant('{tmp}\HelpDocSetup.exe'), '{#HelpDocSetupURL}', \
'My_Setup', 'Get', {#HelpDocSetupFileSize}, 0 )
[Run] section:
; AJT v19.0.0 Installed the downloaded help documentation.
; This is only done if the "downloadhelp" task was selected.
Filename: "{app}\HelpDocSetup.exe"; \
Parameters: "/SP- /VERYSILENT /InstallPath=""{app}"""; \
WorkingDir: "{app}"; \
Flags: waituntilterminated runhidden; \
Description: "{cm:InstallingHelpDescription}"; \
StatusMsg: "{cm:InstallingHelpStatusMessage}"; \
Tasks: downloadhelp
The issue
I need to convert my previous code (DotNet prerequisite) into suitable file / run script lines (except this time I have to pass 0 for the file size as I do not know the size).
In short, my setup requires admin privileges, and technically, we need it to download and install dotnet (if it is not there) before continuing with the setup. The reason being we have these run entries:
Filename: "{dotnet40}\regasm.exe"; \
Parameters: "PTSTools_x86.dll /codebase"; \
WorkingDir: "{app}"; \
Flags: runhidden
Filename: "{dotnet4064}\regasm.exe"; \
Parameters: "PTSTools_x64.dll /codebase"; \
WorkingDir: "{app}"; \
Flags: runhidden; \
Check: IsWin64
Filename: "{dotnet40}\regasm.exe"; \
Parameters: "/u PTSTools_x86.dll"; \
WorkingDir: "{app}"; \
Flags: runhidden; \
Check: FileExists(ExpandConstant('{app}\PTSTools.dll')); \
AfterInstall: DoDeleteFile(ExpandConstant('{app}\PTSTools.dll'))
Filename: "{dotnet4064}\regasm.exe"; \
Parameters: "/u PTSTools.dll"; \
WorkingDir: "{app}"; \
Flags: runhidden; \
Check: IsWin64 and FileExists(ExpandConstant('{app}\PTSTools.dll')); \
AfterInstall: DoDeleteFile(ExpandConstant('{app}\PTSTools.dll'))
So having DotNet is a prerequisite for the installer to work. Should I be dealing with this differently?
Am I Doing This Right?
Based on the answer provided and my understanding of the documentation ...
Step 1
In PrepareToInstall we check to see if DotNet is needed and cache the result:
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
IsInstalled: Cardinal;
begin
Result := '';
dotNetNeeded := true;
// Check for required netfx installation
// http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx#net_b
if(Is64BitInstallMode()) then begin
if (RegValueExists(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
RegQueryDWordValue(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
if(IsInstalled >= 378675) then begin
dotNetNeeded := false;
end;
end;
end
else begin
if (RegValueExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
if(IsInstalled >= 378675) then begin
dotNetNeeded := false;
end;
end;
end;
if(dotNetNeeded) then begin
if (MsgBox(ExpandConstant('{cm:DotNet_NeedToDownload}'), mbConfirmation, MB_OKCANCEL) = IDCANCEL) then begin
Result := ExpandConstant('{cm:DotNet_InstallAborted}');
end;
end;
end;
Step 2
We add a BeforeDownload handler. This is where we get a chance to add the file we need to download to the list:
function BeforeDownload(): Boolean;
begin
if(dotNetNeeded) then
begin
dotNetRedistPath := ExpandConstant('{tmp}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
DwinsHs_AppendRemoteFile( dotNetRedistPath, \
dotnetRedistURL, 'My_Setup', rmGet, FILESIZE_QUERY_SERVER );
end;
Result := True;
end;
Step 3
We add a AfterDownload handler. This is where we perform the install of DotNet.
procedure AfterDownload(State: Integer);
var
hWnd: Integer;
ResultCode: Integer;
begin
if (State = READ_OK) then
begin
if(dotNetNeeded) then
begin
if Exec(ExpandConstant(dotnetRedistPath), '/quiet', '',
SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
// handle success if necessary; ResultCode contains the exit code
if not (ResultCode = 0) then begin
// Microsoft present an array of options for this. But since
// The interface was visible I think it is safe to just say
// that the installation was not completed.
MsgBox(ExpandConstant('{cm:DotNet_InstallFailed}'), mbInformation, MB_OK);
Abort();
end;
end
else begin
// The execution failed for some reason
MsgBox(SysErrorMessage(ResultCode), mbInformation, MB_OK);
Abort();
end;
end;
end;
end;
I am not sure if "quiet" is the right way to go now ...
Step 4
We adjust the CurPageChanged handler:
procedure CurPageChanged(CurPage: Integer);
begin
DwinsHs_CurPageChanged(CurPage, #BeforeDownload, #AfterDownload);
end;

Just call DwinsHs_AppendRemoteFile when dotNetNeeded.
DwinsHs_AppendRemoteFile has basically the same arguments as DwinsHs_Check (DwinsHs_Check is actually only a Check-compatible wrapper around DwinsHs_AppendRemoteFile).
I believe that's all you need. The [Run] happens only after the download.

Related

Inno Setup: Check if extracted file exists, if not download zip file and extract

I have large file ( needs to be extracted ) in App Directory.
[Files]
Source: "Installer Files\out\abc.data"; DestDir: "{userappdata}\App"; Flags: ignoreversion onlyifdoesntexist
Zip file on server is abc.zip (contains abc.data)
Looked at answers here -
Download file - https://stackoverflow.com/a/66100456/2323607
Unzip https://stackoverflow.com/a/44382324/2323607
Not sure how to integrate them together
Q1 - Only download zip abc.zip, if file abc.data doesn't exist at "{userappdata}\App" location
Q2 - Once zip file is downloaded, extract abc.data it to location "{userappdata}\App"
By default InnoSetup will check if a file exists. You can however, change the behavior by adding a function that does the check for you. If you don't do that, it seems InnoSetup will always check for the presence of the abc.data file in the source directory.
[Files]
Source: "{tmp}\abc.data"; DestDir: "{userappdata}\App"; Flags: external; Check: ExtractedFileNeedsInstallation
[Code]
function ExtractedFileNeedsInstallation: Boolean;
var
TargetPath: String;
begin
TargetPath := ExpandConstant('{userappdata}')+'\App\abc.data';
Result := not FileExists(TargetPath);
Log(Format('ExtractedFileNeedsInstallation: %d', [Result]));
end;
For the download function, you can first check if the file exists, in which case you skip the download:
if CurPageID = wpReady then begin
if (not ExtractedFileNeedsInstallation) then
begin
Result := True;
end
else
and if the file is downloaded, then unzip the file when the download is complete:
try
DownloadPage.Download;
Temp := ExpandConstant('{tmp}');
UnZip(Temp+'\abc.zip', 'abc.data', Temp);
Result := True;
except
Full InnoSetup example with most parts taken from the linked download code and unzip code:
[Setup]
AppName=DownloadExample
AppVersion=1.0
DefaultDirName=DownloadTest
[Files]
Source: "{tmp}\abc.data"; DestDir: "{userappdata}\App"; Flags: external; Check: ExtractedFileNeedsInstallation
[Code]
const
NO_PROGRESS_BOX = 4;
RESPOND_YES_TO_ALL = 16;
procedure UnZip(ZipPath, FileName, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
Item: Variant;
TargetFolder: Variant;
begin
Shell := CreateOleObject('Shell.Application');
ZipFile := Shell.NameSpace(ZipPath);
if VarIsClear(ZipFile) then
RaiseException(Format('Cannot open ZIP file "%s" or does not exist', [ZipPath]));
Item := ZipFile.ParseName(FileName);
if VarIsClear(Item) then
RaiseException(Format('Cannot find "%s" in "%s" ZIP file', [FileName, ZipPath]));
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsClear(TargetFolder) then
RaiseException(Format('Target path "%s" does not exist', [TargetPath]));
TargetFolder.CopyHere(Item, NO_PROGRESS_BOX or RESPOND_YES_TO_ALL);
end;
function ExtractedFileNeedsInstallation: Boolean;
var
TargetPath: String;
begin
TargetPath := ExpandConstant('{userappdata}')+'\App\abc.data';
Result := not FileExists(TargetPath);
Log(Format('ExtractedFileNeedsInstallation: %d', [Result]));
end;
var
DownloadPage: TDownloadWizardPage;
function OnDownloadProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean;
begin
if Progress = ProgressMax then
begin
Log(Format('Successfully downloaded file to {tmp}: %s', [FileName]));
end;
Result := True;
end;
procedure InitializeWizard;
begin
DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), #OnDownloadProgress);
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
Temp: String;
begin
if CurPageID = wpReady then begin
if (not ExtractedFileNeedsInstallation) then
begin
Result := True;
end
else
begin
DownloadPage.Clear;
DownloadPage.Add('http://37.120.179.6/test/thomas/upload/abc.zip', 'abc.zip', '');
DownloadPage.Show;
try
try
DownloadPage.Download;
Temp := ExpandConstant('{tmp}');
UnZip(Temp+'\abc.zip', 'abc.data', Temp);
Result := True;
except
SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
end;
end else
Result := True;
end;

Conditional reboot in innosetup installer [duplicate]

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;

Detect 64bit or 32bit OS and install VC10 vcredist file with Inno Setup Installer [duplicate]

This question already has an answer here:
Inno-setup 32bit and 64bit in one
(1 answer)
Closed 4 years ago.
I want to add vcredist_x64.exe and vcredist_x86.exe with my Inno Setup installer. How my installer will detect the OS whether it is 64bit or 32bit and it will install the file vcredist file as per OS.
Try this:
in the [Files] section add
Source: "vcredist_x86.exe"; DestDir: {tmp}; Flags: IgnoreVersion replacesameversion; Check: "not IsWin64";
Source: "vcredist_x64.exe"; DestDir: {tmp}; Flags: IgnoreVersion replacesameversion; Check:IsWin64;
and in your [Code] section do:
function Launch_VCRedist(svDir:String) : Boolean;
var
svTargetApplication: String;
svParameter: String;
workingDir: String;
showCmd: Integer;
wait: TExecWait;
resultCode: Integer;
VersionMS, VersionLS : Cardinal;
Major, Minor, Rev, Build: Cardinal;
Version:String;
begin
Result := True;
//Optional: if you want to execute silently your redist.exe, add this. This is for vc_redist version from 2005 to 2012
GetVersionNumbers(svDir + '\vcredist_x86.exe', VersionMS, VersionLS);
Major := VersionMS shr 16;
case Major of
11: //2012
begin
svParameter := '/install /passive';
end
10: //2010
begin
svParameter := '/passive /showfinalerror';
end
6: //2005
begin
svParameter := '/q';
end
9: //2008
begin
svParameter := '/Q';
end
end;
workingDir := '';
showCmd := SW_SHOW;
wait := ewWaitUntilTerminated;
retVal := Exec(svDir + '\vcredist_x86.exe', svParameter, workingDir, showCmd, wait, resultCode)
if retVal then
begin
//handle success if necessary; resultCode contains the exit code
end
else begin
//handle failure if necessary; resultCode contains the error code
Result := False;
end;
end;
And in CurStepChanged procedure add:
procedure CurStepChanged(CurStep: TSetupStep);
begin
case CurStep of
ssPostInstall:
b_ret := Launch_VCRedist(ExpandConstant('{tmp}'));
if b_ret Then
begin
//Handle success if necessary
end
else begin
//Handle failure if necessary
end;
end;
end;

Inno UninstallRun How To get global variable by executing

My target is to read a registry entry at [UninstallRun].
The issue is, if Inno Setup is running in an install process the Function GetRegistryVar() is called here ? It should be called when the UnInstall process is running. If I start the UnInstall process the Function GetRegistryVar() will not called here ? Only the CurUninstallStepChanged() and PGetRegistryVal(). So I can't receive my global variable gStrPrnName ?
[UninstallRun]
Filename: "{#dInst64bitDir}\{#dUnInstDrvExeName}"; Parameters: "-f {code:GetGlobalRegistryVar}"; WorkingDir: "{#dInst64bitDir}"; Flags: runhidden runascurrentuser;
[Code]
var
gStrPrnName: string;
function GetGlobalRegistryVar(Value: string): string;
begin
Result := gStrPrnName;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
case CurUninstallStep of
usUninstall:
begin
// ...insert code to perform pre-uninstall tasks here...
PGetRegistryVal(gStrPrnName);
end;
usPostUninstall:
begin
// ...insert code to perform post-uninstall tasks here...
end;
end;
end;
function PGetRegistryVal(Value: String): String;
var
strPrnName: string;
i: Integer;
begin
for i:=0 to 2 do
begin
if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\GetDevice_1', 'Name', strPrnName) then
Result := strPrnName;
if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\GetDevice_2', 'Name', strPrnName) then
Result := strPrnName;
...
end;
end;
Has somebody a solution for this issue ?
Thanks

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