Inno Setup FileExists unable to find existing file - inno-setup

In my script I am checking whether a directory and two files in this directory exist. While the first returns the correct value, the second check does not. I've checked it multiple times that these files exist in the designated directory but Inno Setup will always tell me that they do not exist. This is happening on a virtual Windows Server, and can't be reproduced on my local machine. There it always returns the correct Value.
UpdatePath := ExpandConstant('{app}');
if DirExists(UpdatePath) then begin
ExePath := UpdatePath+'\Application.exe';
ConfigFilePath := UpdatePath+'\Application.exe.config';
if FileExists(ExePath) and FileExists(ConfigFilePath) then begin //this one returns incorrect values
//Do Stuff
end else begin
MsgBox(FmtMessage(CustomMessage('DirPageFileNotFound'), [ExePath, ConfigFilePath]),mbInformation,MB_OK);
Result := False;
end;
end else begin
MsgBox(FmtMessage(CustomMessage('DirPageDirectoryNotFound'), [UpdatePath]),mbInformation,MB_OK);
Result := False;
end;
As you can see, I'm checking for an executable which can also be executed when double-clicked. It is there but Inno Setup will always tell me that it's not there. Is the virtual environment messing with it? What is happening here?

To debug the issue, try adding following code. Then check the log file of the installer and the output of the dir command:
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function GetFileAttributes(lpFileName: string): DWORD;
external 'GetFileAttributes{#AW}#kernel32.dll stdcall';
function GetLastError() : LongInt;
external 'GetLastError#kernel32.dll stdcall';
const
INVALID_FILE_ATTRIBUTES = $FFFFFFFF;
procedure ...;
var
UpdatePath: string;
ExePath: string;
FindRec: TFindRec;
Attrs: DWORD;
LastError: LongInt;
ResultCode: Integer;
begin
Log('InitializeWizard');
UpdatePath := ExpandConstant('{app}');
ExePath := UpdatePath+'\Application.exe';
if FileExists(ExePath) then
begin
Log(ExePath + ' exists');
end
else
begin
LastError := GetLastError;
Log(ExePath + ' does not exist - ' +
Format('System Error. Code: %d. %s', [
LastError, SysErrorMessage(LastError)]));
end;
if not FindFirst(UpdatePath + '\*', FindRec) then
begin
LastError := GetLastError;
Log(UpdatePath + ' not found - ' +
Format('System Error. Code: %d. %s', [
LastError, SysErrorMessage(LastError)]));
end
else
begin
repeat
Log('Found file: ' + FindRec.Name + ' in ' + UpdatePath);
until not FindNext(FindRec);
end;
Attrs := GetFileAttributes(ExePath);
if Attrs <> INVALID_FILE_ATTRIBUTES then
begin
Log(ExePath + ' attributes = ' + IntToStr(Attrs));
end
else
begin
LastError := GetLastError;
Log(Format('Cannot get attributes of ' + ExePath +
': System Error. Code: %d. %s', [
LastError, SysErrorMessage(LastError)]));
end;
Exec(ExpandConstant('{cmd}'), '/k dir "' + UpdatePath + '"', '', SW_SHOW,
ewWaitUntilTerminated, ResultCode);
end;
The FileExists internally uses FindFirst/FindNext and GetFileAttributes. So this is to find out what causes the problem.
My wild guess is that the target machine is 64-bit and file system redirection jumps in for some reason.
Try using EnableFsRedirection to disable the redirection before you call FileExists:
EnableFsRedirection(False);

Related

Verify date online in Inno Setup for expiring installer

I have Inno Setup code to show error message when installing a setup developed using Inno Setup. The error message will shown when date of expire happens.
The code is as follows:
const MY_EXPIRY_DATE_STR = '20171112'; // Date format: yyyymmdd
function InitializeSetup(): Boolean;
begin
// If current date exceeds MY_EXPIRY_DATE_STR then return false and
// exit Installer.
Result :=
CompareStr(GetDateTimeString('yyyymmdd', #0,#0), MY_EXPIRY_DATE_STR) <= 0;
if not Result then
MsgBox('Due to some problem', mbError, MB_OK);
end;
Now my question is that I want verify the date using internet, not by local system date.
Use some online service to retrieve the time (or build your own service).
See Free Rest API to retrieve current datetime as string (timezone irrelevant).
Make sure, you use HTTPS, so that it not easy to bypass the check.
The following example uses TimeZoneDB service.
You have to set your own API key (that you get after a free registration).
const
TimezoneDbApiKey = 'XXXXXXXXXXXX';
function GetOnlineTime: string;
var
Url: string;
XMLDocument: Variant;
XMLNodeList: Variant;
WinHttpReq: Variant;
S: string;
P: Integer;
begin
try
// Retrieve XML from with current time in London
// See https://timezonedb.com/references/get-time-zone
WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
Url :=
'https://api.timezonedb.com/v2/get-time-zone?key=' + TimezoneDbApiKey +
'&format=xml&by=zone&zone=Europe/London';
WinHttpReq.Open('GET', Url, False);
WinHttpReq.Send('');
if WinHttpReq.Status <> 200 then
begin
Log('HTTP Error: ' + IntToStr(WinHttpReq.Status) + ' ' +
WinHttpReq.StatusText);
end
else
begin
Log('HTTP Response: ' + WinHttpReq.ResponseText);
// Parse the XML
XMLDocument := CreateOleObject('Msxml2.DOMDocument.6.0');
XMLDocument.async := False;
XMLDocument.loadXML(WinHttpReq.ResponseText);
if XMLDocument.parseError.errorCode <> 0 then
begin
Log('The XML file could not be parsed. ' + XMLDocument.parseError.reason);
end
else
begin
XMLDocument.setProperty('SelectionLanguage', 'XPath');
XMLNodeList := XMLDocument.selectNodes('/result/formatted');
if XMLNodeList.length > 0 then
begin
S := Trim(XMLNodeList.item[0].text);
// Remove the time portion
P := Pos(' ', S);
if P > 0 then
begin
S := Copy(S, 1, P - 1);
// Remove the dashes to get format yyyymmdd
StringChange(S, '-', '');
if Length(S) <> 8 then
begin
Log('Unexpected date format: ' + S);
end
else
begin
Result := S;
end;
end;
end;
end;
end;
except
Log('Error: ' + GetExceptionMessage);
end;
if Result = '' then
begin
// On any problem, fallback to local time
Result := GetDateTimeString('yyyymmdd', #0, #0);
end;
end;

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

My program has 4 services and the first small service controls the other three services (stops them on a file server for overwriting). The first one is very small and simple and normally it should not be closed (only when newer).
[Files]
Source: "ctrlserversvc3.exe"; DestDir: "{code:GetInstallDir|Program}"
It is in the Files section. Inno Setup asks me everytime for closing the service and restarting it at the end. But it should only ask me when my service in the setup has a newer version, not when it is the same version.
How can I tell Inno Setup to skip this file or question when nothing to change?
Inno Setup, unfortunately, does not check file version before checking if the file is locked. It should, imho. Because the file won't get installed in the end, if it has the same version.
Anyway, you can implement the check yourself, using the Check parameter;
[Files]
Source: "ctrlserversvc3.exe"; DestDir: "{code:GetInstallDir|Program}"; \
Check: IsNewer('{#GetFileVersion("ctrlserversvc3.exe")}')
[Code]
procedure CutVersionPart(var VersionString: string; var VersionPart: Word);
var
P: Integer;
begin
P := Pos('.', VersionString);
if P > 0 then
begin
VersionPart := StrToIntDef(Copy(VersionString, 1, P - 1), 0);
Delete(VersionString, 1, P);
end
else
begin
VersionPart := StrToIntDef(VersionString, 0);
VersionString := '';
end;
end;
function IsNewer(InstalledVersion: string): Boolean;
var
Filename: string;
ExistingMS, ExistingLS: Cardinal;
ExistingMajor, ExistingMinor, ExistingRev, ExistingBuild: Cardinal;
InstalledMajor, InstalledMinor, InstalledRev, InstalledBuild: Word;
begin
Filename := ExpandConstant(CurrentFilename);
Log(Format('Checking if %s should be installed', [Filename]));
if not FileExists(Filename) then
begin
Log(Format('File %s does not exist yet, allowing installation', [FileName]));
Result := True;
end
else
if not GetVersionNumbers(FileName, ExistingMS, ExistingLS) then
begin
Log(Format('Cannot retrieve version of existing file %s, allowing installation',
[FileName]));
Result := True;
end
else
begin
ExistingMajor := ExistingMS shr 16;
ExistingMinor := ExistingMS and $FFFF;
ExistingRev := ExistingLS shr 16;
ExistingBuild := ExistingLS and $FFFF;
Log(Format('Existing file %s version: %d.%d.%d.%d',
[FileName, ExistingMajor, ExistingMinor, ExistingRev, ExistingBuild]));
Log(Format('Installing version: %s', [InstalledVersion]));
CutVersionPart(InstalledVersion, InstalledMajor);
CutVersionPart(InstalledVersion, InstalledMinor);
CutVersionPart(InstalledVersion, InstalledRev);
CutVersionPart(InstalledVersion, InstalledBuild);
Log(Format('Installing version: %d.%d.%d.%d',
[InstalledMajor, InstalledMinor, InstalledRev, InstalledBuild]));
if (InstalledMajor > ExistingMajor) or
((InstalledMajor = ExistingMajor) and (InstalledMinor > ExistingMinor)) or
((InstalledMajor = ExistingMajor) and (InstalledMinor = ExistingMinor) and
(InstalledRev > ExistingRev)) then
begin
Log('Installing file that is newer than existing file, ' +
'allowing installation.');
Result := True;
end
else
begin
Log('Installing file that is same or older than existing file, ' +
'skipping installation.');
Result := False;
end;
end;
end;

Inno setup browse for directory to copy/move to new directory [duplicate]

Is there any way to browse and recursively copy/move all files and subdirectories of a directory within the code section? (PrepareToInstall)
I need to ignore a specific directory, but using xcopy it ignores all directories /default/, for example, and I need to ignore a specific only.
The Files section is executed at a later time when needed.
To recursively copy a directory programmatically use:
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;
Add any filtering you need. See how the . and .. are filtered.
Note that the function does not create the root DestPath. If you do not know if it exists, add this to be beginning of the code:
if DirExists(DestPath) or CreateDir(DestPath) then
(then the similar code before the recursive DirectoryCopy call becomes redundant)
For an example of use, see my answers to questions:
Copying hidden files in Inno Setup
How to save a folder when user confirms uninstallation? (Inno Setup).

Copy folder, subfolders and files recursively in Inno Setup Code section

Is there any way to browse and recursively copy/move all files and subdirectories of a directory within the code section? (PrepareToInstall)
I need to ignore a specific directory, but using xcopy it ignores all directories /default/, for example, and I need to ignore a specific only.
The Files section is executed at a later time when needed.
To recursively copy a directory programmatically use:
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;
Add any filtering you need. See how the . and .. are filtered.
Note that the function does not create the root DestPath. If you do not know if it exists, add this to be beginning of the code:
if DirExists(DestPath) or CreateDir(DestPath) then
(then the similar code before the recursive DirectoryCopy call becomes redundant)
For an example of use, see my answers to questions:
Copying hidden files in Inno Setup
How to save a folder when user confirms uninstallation? (Inno Setup).

Read modified time from one file and use it to set the time for files in a whole directory

In my installer I'm extracting files from archives that don't store time/date attributes, so when they're extracted the last modified date is set to the current date. I would like to set it to the last modified date of the archive file but I can't figure out how. I tried using pieces of the code from here and here but while it didn't give any errors, it didn't work for changing the time. Last modified date would need to be changed for * .* in a folder.
Also, where do I need to hook into to delete these files if the user cancels setup and it starts rolling back changes? I've got it taken care of in UninstallDelete but not if the user cancels setup.
EDIT: Disregard the second part, I actually figured it out shortly after I posted here. Added my own CleanUp() to DeinitializeSetup() with a check for the uninstaller registry key.
Here is the section of code I'm trying to add it to:
procedure VolExtract(VWorld: String);
var
ResultCode: Integer;
VPath: String;
begin
// Files are extracted to {app}\VWorld\One, {app}\VWorld\Two, etc.
VPath := ExpandConstant('{app}\' + VWorld);
WizardForm.FilenameLabel.Caption := DriveLetter + VWorld + '\one.vol';
if Exec(ExpandConstant('{tmp}\volextract.exe'), '"' + DriveLetter + VWorld + '\one.vol" "' + VPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
begin
// Yep, it executed successfully
WizardForm.FilenameLabel.Caption := DriveLetter + VWorld + '\two.vol';
if Exec(ExpandConstant('{tmp}\volextract.exe'), '"' + DriveLetter + VWorld + '\two.vol" "' + VPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
begin
// Next
WizardForm.FilenameLabel.Caption := DriveLetter + VWorld + '\three.vol';
if Exec(ExpandConstant('{tmp}\volextract.exe'), '"' + DriveLetter + VWorld + '\three.vol" "' + VPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
begin
// Next
WizardForm.FilenameLabel.Caption := DriveLetter + VWorld + '\four.vol';
Exec(ExpandConstant('{tmp}\volextract.exe'), '"' + DriveLetter + VWorld + '\four.vol" "' + VPath + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
end;
if ResultCode <> 0 then
begin
// Handle Fail
CDFound := False;
MsgBox(CustomMessage('FileErr'), mbInformation, MB_OK);
WizardForm.Close;
end;
end;
To change the last modified time (let's call it LastWriteTime for now) for all files from a specified directory by the LastWriteTime of a certain file, use the following code after you have your files extracted. You can follow the commented version of the previous version of this post, but note that I've had bugs there (mixed time parameters and unused file flag parameter), but the point remains.
Also note that this code is for ANSI version of InnoSetup. If you need to use this for Unicode version, you should define the CreateFile function import as CreateFileW instead of CreateFileA or use the trick suggested by kobik in this post.
[code]
const
OPEN_EXISTING = 3;
FILE_SHARE_WRITE = 2;
GENERIC_WRITE = $40000000;
INVALID_HANDLE_VALUE = 4294967295;
function CreateFile(lpFileName: string; dwDesiredAccess, dwShareMode,
lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes: DWORD;
hTemplateFile: THandle): THandle;
external 'CreateFileA#kernel32.dll stdcall';
function CloseHandle(hObject: THandle): BOOL;
external 'CloseHandle#kernel32.dll stdcall';
function SetFileTime(hFile: THandle; const lpCreationTime, lpLastAccessTime,
lpLastWriteTime: TFileTime): BOOL;
external 'SetFileTime#kernel32.dll stdcall';
function FileSetTime(const AFileName: string; const ACreationTime,
ALastAccessTime, ALastWriteTime: TFileTime): Boolean;
var
FileHandle: THandle;
begin
Result := False;
FileHandle := CreateFile(AFileName, GENERIC_WRITE, FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if FileHandle <> INVALID_HANDLE_VALUE then
try
Result := SetFileTime(FileHandle, ACreationTime, ALastAccessTime,
ALastWriteTime);
finally
CloseHandle(FileHandle);
end;
end;
procedure ModifyLastWriteTime(const ASourceFile, ATargetFolder: string);
var
FindRec: TFindRec;
LastWriteTime: TFileTime;
begin
if FindFirst(ASourceFile, FindRec) then
begin
LastWriteTime := FindRec.LastWriteTime;
if FindFirst(ATargetFolder + '*.*', FindRec) then
try
repeat
if (FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
FileSetTime(ATargetFolder + FindRec.Name, FindRec.CreationTime,
FindRec.LastAccessTime, LastWriteTime);
until
not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
And the usage. The first parameter of the ModifyLastWriteTime procedure is the name of the source file from which the LastWriteTime is taken. The second parameter is the directory in what the files will get modified their LastWriteTime values by the source file (don't forget to have the trailing backslash in the target folder parameter):
ModifyLastWriteTime('c:\SourceFile.xxx', 'c:\TargetFolder\')
About the second question, you can delete the files which have been extracted in a procedure called
Procedure CancelButtonClick(CurPageID: Integer; Var Cancel, Confirm: Boolean);
Begin
End;
As explained in the chm, section Pascal Scripting: Event Functions
About the first question I would suggest you to use inno setup [files] section instead of extracting from an archive. You could probably extract this archive to a local folder (so from your side, before compiling, and add this local folder to the [files]. But I may misunderstand your imperative about the file modification date.

Resources