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

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.

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;

How do I perform a move folder and delete operation using Innosetup [Windows Platform]

I am a beginner in Pascal coding and Innosetup. Would like to request some advice on how to use the procedure to execute a delete folder and transfer folder operation.
I was trying to move files from the release folder in 'C:\Program Files (x86)\winpxe1\Apache24\htdocs\current\Storygame*'
to the current folder instead.
'C:\Program Files (x86)\winpxe1\Apache24\htdocs\current\"
Next, I wanted to delete another release folder of the same name in 'C:\Program Files (x86)\winpxe1\Apache24\Storygame'.
The following is an extract of my code:
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}";GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "C:\Program Files (x86)\Inno Setup 5\Examples\MyProg.exe";
DestDir: "{app}"; Flags: ignoreversion
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram, {#StringChange(MyAppName, '&', '&&')}}";AfterInstall: DirectoryCopy DeleteTransferFolder Flags: nowait postinstall skipifsilent
[Code]
procedure DirectoryCopy(SourcePath, DestPath: string);
var
FindRec: TFindRec;
SourceFilePath: string;
DestFilePath: string;
begin
if FindFirst(SourcePath + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
SourceFilePath := SourcePath + '\' + FindRec.Name;
DestFilePath := DestPath + '\' + FindRec.Name;
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
begin
if FileCopy(SourceFilePath, DestFilePath, False) then
begin
Log(Format('Copied %s to %s', [SourceFilePath, DestFilePath]));
end
else
begin
Log(Format('Failed to copy %s to %s', [SourceFilePath, DestFilePath]));
end;
end
else
begin
if DirExists(DestFilePath) or CreateDir(DestFilePath) then
begin Log(Format('Created %s', [DestFilePath]));
DirectoryCopy(SourceFilePath, DestFilePath);
end
else
begin
Log(Format('Failed to create %s', [DestFilePath]));
end;
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Log(Format('Failed to list %s', [SourcePath]));
end;
end;
procedure DirectoryCopy(SourcePath, DestPath: string);
begin
Result := DirectoryCopy( 'C:\Program Files (x86)\winpxe1\Apache24 \htdocs\current\Storygame\*','C:\Program Files (x86)\winpxe1\Apache24\htdocs\current': string);
end;
procedure DeleteTransferFolder();
begin
MsgBox('DeleteTransferFolder 1', mbInformation, MB_OK);
if (FileExists (ExpandConstant( 'C:\Program Files (x86)\winpxe1\Apache24 \Storygame'))) then
begin DelTree(ExpandConstant('C:\Program Files (x86)\winpxe1 \Apache24 \Storygame'), True, True, True);
MsgBox('DeleteTransferFolder 2', mbInformation, MB_OK); end;
After running this script, I encountered the "Compiler error": "Directive or Parameter "AfterInstall" expression error: Invalid token "DeleteTransferFolder" found."
I would like to also give credit to the authors of the original code for the directorycopy and deletetransferfolder as it was extracted from other questions in this forum.
Any advice appreciated on how I can integrate these codes with the existing support class references of innosetup.
Regards

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;

Inno Setup check if a machine is joined to a Domain

Is there a way of checking whether the machine you are installing on is joined to a Domain or in a Workgroup?
I found this article about how to do this in Delphi, but I am unable to get this working within Inno Setup. Can anyone assist with this? Is this even possible?
http://delphi.about.com/od/delphitips2009/qt/computer-in-a-domain.htm
I would translate (and shorten) it this way:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
NERR_BASE = 2100;
NERR_SetupNotJoined = NERR_BASE + 592;
type
NET_API_STATUS = DWORD;
function NetRenameMachineInDomain(lpServer: WideString;
lpNewMachineName: WideString; lpAccount: WideString;
lpPassword: WideString; fRenameOptions: DWORD): NET_API_STATUS;
external 'NetRenameMachineInDomain#netapi32.dll stdcall';
function IsInDomain: Boolean;
begin
Result := NetRenameMachineInDomain('', '', '', '', 0) <> NERR_SetupNotJoined;
end;
procedure InitializeWizard;
begin
if IsInDomain then
MsgBox('Is in domain.', mbInformation, MB_OK)
else
MsgBox('Is not in domain.', mbInformation, MB_OK);
end;

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