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
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 have created a shortcut named Myapp in the desktop. My installed app changes that shortcut, if I choose to others languages, for example: Spanish or French. Then the shorcut name changes to: Myapp Spanish or Myapp French.
That s why Inno Setup can not detect it on uninstall. And this doesn't work wither :
[UninstallDelete]
Type: files; Name: "{commondesktop}\Myapp*.ink";`
To delete files matching a mask on uninstall, you can use:
[Code]
function DeleteWithMask(Path, Mask: string): Boolean;
var
FindRec: TFindRec;
FilePath: string;
begin
Result := FindFirst(Path + '\' + Mask, FindRec);
if not Result then
begin
Log(Format('"%s" not found', [Path + '\' + Mask]));
end
else
begin
try
repeat
FilePath := Path + '\' + FindRec.Name;
if not DeleteFile(FilePath) then
begin
Log(Format('Error deleting "%s"', [FilePath]));
end
else
begin
Log(Format('Deleted "%s"', [FilePath]));
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
Log('Deleting shortcuts')
DeleteWithMask(ExpandConstant('{commondesktop}'), 'Myapp*.ink');
end;
end;
(I'm not sure, what .ink is about, though)
Safer would be to iterate all shortcut files in the folder (desktop), deleting only those that point to your application.
See my answer to Check for existence of a shortcut pointing to a specific target in Inno Setup.
If I understand your question correctly, your application can already identify the correct shortcut file (as it seems to rename or delete the old shortcut, when the language changes). In that case, consider adding the "uninstall shortcut" functions to the application itself. Make the application process (undocumented) command-line switch to delete the shortcut (e.g. /DeleteShortcut). And use that from [UninstallRun] section:
[UninstallRun]
Filename: "{app}\MyApp.exe"; Parameters: "/DeleteShortcut"; RunOnceId: "DeleteShortcut"
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).
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).