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).
Related
How can I delete a series of files once the Run has been executed?
I want to delete a lot of DLL files and some other files because they are now in separate sub folders. But some of the DLL files are registered COM etc. Once Run has finished the old DLLs will not be registered anymore. So it will be safe for me to delete them.
InstallDelete is too early:
https://jrsoftware.org/ishelp/index.php?topic=installorder
So I want ability to delete *.dll but exclude pattern*.dll ONCE install finished.
I've seen a similar question:
Delete a file AFTER installation in Inno Setup
It seems I need to use something like:
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then begin
DeleteFile(ExpandConstant('path'));
..
Ideally I want to delete:
{app}\*.dll
{app}\*.config
{app}\GoogleAuthAndSync.exe
But, I want to KEEP these files in the {app} folder:
{app}\MeetSchedAssist*.dll
{app}\VclStylesinno.dll
That is what I want to achieve. This is because the files are now being installed into distinct sub-folders and being managed there, and not all mixed up in the app folder.
The CurStepChanged(ssPostInstall) seems like the the right approach to me. So all you need on top of that is a way to delete all files, with some exceptions, right?
Start here:
Delete whole application folder except for "data" subdirectory in Inno Setup
If you need to know how to test for partial filename (MeetSchedAssist*.dll):
If you need just one-off test, an ad-hoc condition like this will do:
(CompareText(Copy(FindRec.Name, 1, 15), 'MeetSchedAssist') = 0) and
(CompareText(Copy(FindRec.Name, Length(FindRec.Name) - 3, 4), '.dll') = 0)
If you have many tests, you can use MatchesMaskEx function from Inno Setup - Integer or Set/Range wildcard?:
MatchesMaskEx('MeetSchedAssist*.dll', FindRec.Name)
I thought I would add my final code:
(* Cannot use built-in DeleteFile directly in AfterInstall as it's a function,
not a procedure. And this way we can add some error handling too. *)
procedure DoDeleteFile(FileName: string);
begin
if (FileExists(FileName)) then
begin
if DeleteFile(FileName) then
begin
Log(Format('"%s" deleted', [FileName]));
end
else begin
MsgBox(Format('Failed to delete "%s"', [FileName]), mbError, MB_OK);
end;
end;
end;
{ Deletes obsolete DLL files etc. }
procedure DelObsoleteFiles(Path: string);
var
FindRec: TFindRec;
FilePath: string;
begin
if FindFirst(Path + '\*.dll', FindRec) then
begin
try
repeat
FilePath := Path + '\' + FindRec.Name;
if CompareText(FindRec.Name, 'VclStylesinno.dll') = 0 then
begin
Log(Format('Keeping DLL %s', [FilePath]));
end else if CompareText(Copy(FindRec.Name, 1, 15), 'MeetSchedAssist') = 0 then
begin
Log(Format('Keeping DLL %s', [FilePath]));
end else begin
DoDeleteFile(FilePath);
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end else
begin
Log(Format('Failed to list %s', [Path]));
end;
DoDeleteFile(Path + '\GoogleAuthAndSync.exe')
DoDeleteFile(Path + '\GoogleAuthAndSync.exe.config')
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
ResultCode: integer;
begin
if (CurStep = ssPostInstall) then
begin
(* Delete obsolete DLL files etc. because they are now maintained in
separate sub-folders *)
DelObsoleteFiles(ExpandConstant('{app}'));
end;
end;
I am trying to write a function that returns the size of a directory. I have written the following code, but it is not returning the correct size. For example, when I run it on the {pf} directory it returns 174 bytes, which is clearly wrong as this directory is multiple Gigabytes in size. Here is the code I have:
function GetDirSize(DirName: String): Int64;
var
FindRec: TFindRec;
begin
if FindFirst(DirName + '\*', FindRec) then
begin
try
repeat
Result := Result + (Int64(FindRec.SizeHigh) shl 32 + FindRec.SizeLow);
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Result := -1;
end;
end;
I suspect that the FindFirst function does not include subdirectories, which is why I am not getting the correct result. Therefore, how can I return the correct size of a directory i.e. including all files in all subdirectories, the same as selecting Properties on a Folder in Windows Explorer? I am using FindFirst as the function needs to support directory sizes over 2GB.
The FindFirst does include subdirectories, but it won't get you their sizes.
You have to recurse into subdirectories and calculate the total size file by file, similarly as to for example Inno Setup: copy folder, subfolders and files recursively in Code section.
function GetDirSize(Path: String): Int64;
var
FindRec: TFindRec;
FilePath: string;
Size: Int64;
begin
if FindFirst(Path + '\*', FindRec) then
begin
Result := 0;
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
FilePath := Path + '\' + FindRec.Name;
if (FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
begin
Size := GetDirSize(FilePath);
end
else
begin
Size := Int64(FindRec.SizeHigh) shl 32 + FindRec.SizeLow;
end;
Result := Result + Size;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Log(Format('Failed to list %s', [Path]));
Result := -1;
end;
end;
For Int64, you need Unicode version of Inno Setup, what you should be using in any case. Only if you have a very good reason to stick with Ansi version, you can replace the Int64 with Integer, but than you are limited to 2 GB.
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).
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);
How to traverse a directory and its sub directories in Inno Setup Pascal scripting? I can not find any method and interface in Inno Setup Help Document.
Use FindFirst and FindNext support functions.
procedure RecurseDirectory(Path: string);
var
FindRec: TFindRec;
FilePath: string;
begin
if FindFirst(Path + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
FilePath := Path + '\' + FindRec.Name;
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
begin
Log(Format('File %s', [FilePath]));
end
else
begin
Log(Format('Directory %s', [FilePath]));
RecurseDirectory(FilePath);
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Log(Format('Failed to list %s', [Path]));
end;
end;
For examples of use, see:
Inno Setup: copy folder, subfolders and files recursively in Code section
Search subdirectories for Inno Setup DestDir
Inno Setup: Check if file exists anywhere in C: drive
Inno Setup get directory size including subdirectories