Contents
English (United Kingdom)French (Fr)Deutsch (DE-CH-AT)
Search
Login
Who Is Online?
We have 86 guests online
Navigation
Home Source Code Inno Setup Scan a disk path or an entire disk with Inno Setup
Most Recent
Featured Articles
Joomla 1.5 Featured Articles
Navigation
Home Source Code Inno Setup Scan a disk path or an entire disk with Inno Setup
English (United Kingdom)French (Fr)Deutsch (DE-CH-AT)
Scan a disk path or an entire disk with Inno Setup E-mail
User Rating: / 11
PoorBest 
Source Code - Inno Setup
Written by Thomas   
Monday, 01 June 2009 19:30
Article Index
Scan a disk path or an entire disk with Inno Setup
Progress page
Cancel button
Example script
All Pages

 

For an installation program I wanted to give the user an option to automatically scan for a particular file on his hard drive or maybe only in a subtree of a drive.

A search through the Inno Setup newsgroups brings up several posts to discourage people to do so. It is understandable, because network and local drives could contain several million files or a similar amount of folders. It could take hours to read the directory tree in and search for a single file. The reasonable alternative is to find the file through other clues like registry entries.

However, the location of the file my installation program is supposed to look for is stored nowhere. The software saves its settings in a Windows ini file, and this file resides just next to the executable file in the same folder.

 

Preparing the Inno Setup script to search for it in a given folder tree or an entire drive seems helpful to the user.

It didn't even look very complicated as the helpfile has a pretty good example on how to use the functions FirndFirst and FindNext. The only minor obstacle was my limited Pascal knowledge which had become a bit rusty over the years, and of course because Inno Setup's Pascal script is not comparable with a fully-featured Pascal compiler.

That's probably the reason why my first attempt didn't want to work on larger directory trees. I kept getting an 'Out of memory' message after a few seconds. I had tried to read the tree into a string array (TArrayOfString) by dynamically expanding it as files and folder names where coming in from the tree reading function. I wanted a flexible function to read the folder tree and then go through the array and process every single file and directory in a second step outside that reading function.

  1. procedure ProcessDirectory (RootDir: String; Progress: Boolean);
  2. var
  3. NewRoot:        String;
  4. FilePath:        String;
  5. FindRec:        TFindRec;
  6. begin
  7. NewRoot := AddBackSlash (RootDir);
  8. if FindFirst (NewRoot + '*', FindRec) then
  9. begin
  10. try
  11. repeat
  12. if (FindRec.Name <> '.') AND (FindRec.Name <> '..') then
  13. begin
  14. FilePath := NewRoot + FindRec.Name;
  15. if FindRec.Attributes AND FILE_ATTRIBUTE_DIRECTORY > 0 then
  16. ReadDirectory (FilePath, Progress)
  17. else
  18. begin
  19. // Start action -->
  20. // .
  21. // Add your custom code here.
  22. // FilePath contains the file name
  23. // including its full path name.
  24. // Try not to call a function for every file
  25. // as this could take a very long time.
  26. // .
  27. // <-- End action.
  28. end;
  29. end;
  30. until NOT FindNext (FindRec);
  31. finally
  32. FindClose(FindRec);
  33. end;
  34. end;
  35. end;
  36.  
  37.  

When this function is called, the installer can't do anything else. In effect, that means that not even the installation wizard's window can be moved. The user interface blocks completely without feeding anything back.

 


 

In order to show at least something in Inno Setup's wizard, a progress page can be inserted. Reading and processing a directory tree is a serial operation, meaning that it's finishing time can't be predicted in advance. This is why I set the progress bar up to change its status every 1000 files or folders, and when it has hit the right side of the bar it rolls over to the left again.

  1. var
  2. ProgressPage:    TOutputProgressWizardPage;
  3. ProgressValue:    Integer;
  4. ArrayLen:        LongInt;
  5.  
  6. procedure ProcessDirectory (RootDir: String; Progress: Boolean);
  7. var
  8. NewRoot:        String;
  9. FilePath:        String;
  10. FindRec:        TFindRec;
  11. begin
  12. NewRoot := AddBackSlash (RootDir);
  13. if FindFirst (NewRoot + '*', FindRec) then
  14. begin
  15. try
  16. repeat
  17. if (FindRec.Name <> '.') AND (FindRec.Name <> '..') then
  18. begin
  19. FilePath := NewRoot + FindRec.Name;
  20. if FindRec.Attributes AND FILE_ATTRIBUTE_DIRECTORY > 0 then
  21. ProcessDirectory (FilePath, Progress)
  22. else
  23. begin
  24. // Start action -->
  25. // .
  26. // Add your custom code here.
  27. // FilePath contains the file name
  28. // including its full path name.
  29. // Try not to call a function for every file
  30. // as this could take a very long time.
  31. // .
  32. // <-- End action.
  33. ArrayLen := ArrayLen + 1;
  34. if (Progress) then
  35. begin
  36. if (ArrayLen mod 1000) = (ArrayLen / 1000) then
  37. begin
  38. ProgressValue := ProgressValue + 1;
  39. if ProgressValue = 100 then
  40. ProgressValue := 0;
  41. ProgressPage.SetProgress (ProgressValue, 100);
  42. end;
  43. end;
  44. end;
  45. end;
  46. until NOT FindNext (FindRec);
  47. finally
  48. FindClose(FindRec);
  49. end;
  50. end;
  51. end;
  52.  
  53. procedure CurStepChanged (CurStep: TSetupStep);
  54. var
  55. lI:            LongInt;
  56. Dir:        String;
  57. begin
  58. if (CurStep = ssInstall) then
  59. begin
  60. // The folder to scan.
  61. Dir := 'C:\';
  62. // The progress page.
  63. ProgressPage := CreateOutputProgressPage (CustomMessage ('ProgressTitle'),
  64. CustomMessage ('ProgressCaption'));
  65. ProgressPage.SetText (CustomMessage ('ProgressText'), Dir);
  66. ProgressPage.SetProgress(0, 0);
  67. ProgressPage.Show;
  68. // Scan the folder.
  69. ProcessDirectory (Dir, TRUE);
  70. // Hide the progress page.
  71. try
  72. finally
  73. ProgressPage.Hide;
  74. end;
  75. end;
  76. end;
  77.  

With the progress bar moving, the installation program gained a nice touch towards the standards of what people expect from a fashionate installer. Saying that, this is however only on the surface. There is no chance for the user to interrupt the disk scan unless they use the Taskmanager and kill the setup process.

 


 

As mentioned earlier, the scan could take a very long time, maybe even several hours. It is unacceptable not to provide an option to cancelling the process.

The Cancel button is actually there, stuck on the installation wizard. It's just not visible while the progress page works.

To get the button back, it needs to be made visible:

  1. WizardForm.CancelButton.Visible := TRUE;

Once the Cancel button is visible it automatically gets its function back. Of course, it can't interrupt the directory reading function while files are read in and being processed. When Cancel is clicked, the directory scan happily carries on, and as soon as it is finished Inno Setup asks if the installation is to be aborted.

This still is not how it should be. The scan should be stopped immediately, or at least as quickly as possible.

Inno Setup's script engine provides an event function for the Cancel button: CancelButtonClick ().

With a simple message box identical to the 'Exit Setup' confirmation message I tricked the user in believing that they see Inno Setup's original confirmation:

  1. procedure CancelButtonClick (CurPageID: Integer; var Cancel, Confirm: Boolean);
  2. // No confirmation message, because we roll our own.
  3. // The base idea has been nicked from
  4. // http://www.vincenzo.net/isxkb/index.php?title=No_%27Exit_Setup%27_message .
  5. // The only difference to Inno Setup's 'Exit Setup' message box is its title,
  6. // but that's only a minor cosmetic glitch.
  7. begin
  8. Confirm := FALSE;
  9. if (MsgBox (SetupMessage (msgExitSetupMessage),
  10. mbConfirmation, MB_YESNO) = IDYES) then
  11. begin
  12. bExitSetup := TRUE;
  13. end;
  14. end;
  15.  

The difference between this 'Exit Setup' confirmation box and Inno Setup's own can be seen in the window title.

Exit Setup with correct title bar

Exit Setup - Setup

The original confirmation message box is called 'Exit Setup' (image on the top) while mine contains the title of the setup (bottom image), which is 'Setup' by default. Although this is a fairly good working solution already, I was not happy with this difference.

Since Inno Setup uses the Windows API MessageBox (), I was sure it can be used too to correct the window title. I found the declaration for Pascal script on the ISXKB under MessageBox () and nicked it from there:

[Code]
function MessageBox (hWnd: Integer; lpText, lpCaption: String; uType: Cardinal): Integer; external 'MessageBoxA@user32.dll stdcall';

Pascal seems a bit picky when it comes to types, hence I didn't want to bother with all those required conversions and looked up the parameter constants in the file WinUser.h from the Windows SDK instead. Here's the relevant excerpt from that file:

  1. /*
  2.  * MessageBox() Flags
  3.  */
  4. #define MB_OK                       0x00000000L
  5. #define MB_OKCANCEL                 0x00000001L
  6. #define MB_ABORTRETRYIGNORE         0x00000002L
  7. #define MB_YESNOCANCEL              0x00000003L
  8. #define MB_YESNO                    0x00000004L
  9. #define MB_RETRYCANCEL              0x00000005L
  10. #if(WINVER >= 0x0500)
  11. #define MB_CANCELTRYCONTINUE        0x00000006L
  12. #endif /* WINVER >= 0x0500 */
  13.  
  14.  
  15. #define MB_ICONHAND                 0x00000010L
  16. #define MB_ICONQUESTION             0x00000020L
  17. #define MB_ICONEXCLAMATION          0x00000030L
  18. #define MB_ICONASTERISK             0x00000040L
  19.  
  20. #if(WINVER >= 0x0400)
  21. #define MB_USERICON                 0x00000080L
  22. #define MB_ICONWARNING              MB_ICONEXCLAMATION
  23. #define MB_ICONERROR                MB_ICONHAND
  24. #endif /* WINVER >= 0x0400 */
  25.  
  26. #define MB_ICONINFORMATION          MB_ICONASTERISK
  27. #define MB_ICONSTOP                 MB_ICONHAND
  28.  
  29. #define MB_DEFBUTTON1               0x00000000L
  30. #define MB_DEFBUTTON2               0x00000100L
  31. #define MB_DEFBUTTON3               0x00000200L
  32. #if(WINVER >= 0x0400)
  33. #define MB_DEFBUTTON4               0x00000300L
  34. #endif /* WINVER >= 0x0400 */
  35.  
  36. #define MB_APPLMODAL                0x00000000L
  37. #define MB_SYSTEMMODAL              0x00001000L
  38. #define MB_TASKMODAL                0x00002000L
  39. #if(WINVER >= 0x0400)
  40. #define MB_HELP                     0x00004000L // Help Button
  41. #endif /* WINVER >= 0x0400 */
  42.  
  43. #define MB_NOFOCUS                  0x00008000L
  44. #define MB_SETFOREGROUND            0x00010000L
  45. #define MB_DEFAULT_DESKTOP_ONLY     0x00020000L
  46.  
  47. #if(WINVER >= 0x0400)
  48. #define MB_TOPMOST                  0x00040000L
  49. #define MB_RIGHT                    0x00080000L
  50. #define MB_RTLREADING               0x00100000L
  51.  
  52.  
  53. #endif /* WINVER >= 0x0400 */
  54.  

I used the values directly and changed the function accordingly:

  1. procedure CancelButtonClick (CurPageID: Integer; var Cancel, Confirm: Boolean);
  2. begin
  3. Confirm := FALSE;
  4. if (MessageBox (0, SetupMessage (msgExitSetupMessage),
  5. SetupMessage (msgExitSetupTitle), 4 + 32) = 6) then
  6. begin
  7. bExitSetup := TRUE;
  8. end;
  9. end;
  10.  

The outcome is an example Inno Setup script that scans the entire C: drive. If the user gets bored of watching the scan they can press the Cancel button and terminate the installer.

Scan for files with a Cancel button

Scan for files and Cancel pressed

 


 

Here's the example script:

Open ReadDirectoryTree.iss
 

ReadDirectoryTree.iss:

  1. ; Script generated by the Inno Setup Script Wizard.
  2. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
  3.  
  4. #define MyAppName "My Program"
  5. #define MyAppVerName "My Program 1.5"
  6. #define MyAppPublisher "My Company, Inc."
  7. #define MyAppURL "http://www.example.com/"
  8. #define MyAppExeName "MyProg.exe"
  9.  
  10. [Setup]
  11. ; NOTE: The value of AppId uniquely identifies this application.
  12. ; Do not use the same AppId value in installers for other applications.
  13. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
  14. AppId={{753F2C1F-EA02-488E-A204-A047F1EFA0BC}
  15. AppName={#MyAppName}
  16. AppVerName={#MyAppVerName}
  17. AppPublisher={#MyAppPublisher}
  18. AppPublisherURL={#MyAppURL}
  19. AppSupportURL={#MyAppURL}
  20. AppUpdatesURL={#MyAppURL}
  21. DefaultDirName={pf}\{#MyAppName}
  22. DefaultGroupName={#MyAppName}
  23. OutputBaseFilename=setup
  24. Compression=lzma
  25. SolidCompression=yes
  26.  
  27. [Languages]
  28. Name: english; MessagesFile: compiler:Default.isl
  29.  
  30. [CustomMessages]
  31. english.ProgressTitle=Searching
  32. english.ProgressCaption=Searching for files
  33. english.ProgressText=Searching for files...
  34.  
  35. [Code]
  36. var
  37. ProgressPage: TOutputProgressWizardPage;
  38. ProgressValue: Integer;
  39. ArrayLen: LongInt;
  40. bExitSetup: Boolean;
  41.  
  42. procedure ProcessDirectory (RootDir: String; Progress: Boolean);
  43. var
  44. NewRoot: String;
  45. FilePath: String;
  46. FindRec: TFindRec;
  47. begin
  48. if bExitSetup then
  49. Exit;
  50. NewRoot := AddBackSlash (RootDir);
  51. if FindFirst (NewRoot + '*', FindRec) then
  52. begin
  53. try
  54. repeat
  55. if (FindRec.Name <> '.') AND (FindRec.Name <> '..') then
  56. begin
  57. FilePath := NewRoot + FindRec.Name;
  58. if FindRec.Attributes AND FILE_ATTRIBUTE_DIRECTORY > 0 then
  59. ProcessDirectory (FilePath, Progress)
  60. else
  61. begin
  62. // Start action -->
  63. // .
  64. // Add your custom code here.
  65. // FilePath contains the file name
  66. // including its full path name.
  67. // Try not to call a function for every file
  68. // as this could take a very long time.
  69. // .
  70. // <-- End action.
  71. ArrayLen := ArrayLen + 1;
  72. if (Progress) then
  73. begin
  74. if (ArrayLen mod 1000) = (ArrayLen / 1000) then
  75. begin
  76. ProgressValue := ProgressValue + 1;
  77. if ProgressValue = 100 then
  78. ProgressValue := 0;
  79. ProgressPage.SetProgress (ProgressValue, 100);
  80. end;
  81. end;
  82. end;
  83. end;
  84. if (bExitSetup) then
  85. Exit;
  86. until NOT FindNext (FindRec);
  87. finally
  88. FindClose(FindRec);
  89. end;
  90. end;
  91. end;
  92.  
  93. function MessageBox (hWnd: Integer; lpText, lpCaption: String; uType: Cardinal): Integer;
  94. external 'MessageBoxA@user32.dll stdcall';
  95.  
  96. procedure CancelButtonClick (CurPageID: Integer; var Cancel, Confirm: Boolean);
  97. begin
  98. Confirm := FALSE;
  99. if (MessageBox (0, SetupMessage (msgExitSetupMessage),
  100. SetupMessage (msgExitSetupTitle), 4 + 32) = 6) then
  101. begin
  102. bExitSetup := TRUE;
  103. end;
  104. end;
  105.  
  106. procedure CurStepChanged (CurStep: TSetupStep);
  107. var
  108. lI: LongInt;
  109. Dir: String;
  110. begin
  111. if (CurStep = ssInstall) then
  112. begin
  113. // The folder to scan.
  114. Dir := 'C:\';
  115. // The progress page.
  116. ProgressPage := CreateOutputProgressPage (CustomMessage ('ProgressTitle'),
  117. CustomMessage ('ProgressCaption'));
  118. ProgressPage.SetText (CustomMessage ('ProgressText'), Dir);
  119. ProgressPage.SetProgress(0, 0);
  120. ProgressPage.Show;
  121. // Make the Cancel button visible during the operation.
  122. ;WizardForm.CancelButton.Visible := TRUE;
  123. // Scan the folder.
  124. ProcessDirectory (Dir, TRUE);
  125. // Hide the progress page.
  126. try
  127. finally
  128. ProgressPage.Hide;
  129. end;
  130. end;
  131. end;
  132.  
  133.  
Open ReadDirectoryTree.iss

 

Last Updated on Thursday, 15 October 2009 15:26
 
You need to login or register to post comments.
Discuss this item on the forums. (5 posts)
Discuss (5 posts)
Re: Scan a disk path or an entire disk with Inno Setup
Apr 02 2018 20:10:17
Not sure I understand what a "registry directory" is
#2352
Scan a disk path or an entire disk with Inno Setup
Apr 02 2018 17:13:28
hi
this above scanning drive inno setup code can be use just like installaing files to registry directory
#2351
Scan a disk path or an entire disk with Inno Setup
Apr 02 2018 17:11:28
#2350
Scan a disk path or an entire disk with Inno Setup
Apr 02 2018 17:11:28
hi
this above scanning drive inno setup code can be use just like installaing files to registry directory
#2349
Scan a disk path or an entire disk with Inno Setup
Sep 25 2012 06:22:16
This is very helpful post. Thanks for this dude. It really helps me a lot.

*** some spam removed *** (by George)
#1454