In Inno Setup create page where the user may select files from a folder that is packaged in the setup file - inno-setup

In Inno Setup I want to create a page that has a list of checkboxes that correspond to the files in a folder I have listed in the [Files] section.
How can I read the files in this folder and then copy only those files, that the user selected? Right now, I don't even get the list, because the folder {tmp}\list doesn't exist.
[Files]
Source: "list\*"; DestDir:"{tmp}"; Flags: recursesubdirs
[Code]
var
karten : TNewCheckListBox;
FileListBox : TNewListBox;
KartenFormular: TWizardPage;
procedure KartenEinlesen(const Directory: string; Files: TStrings);
var
FindRec: TFindRec;
begin
Files.Clear;
if FindFirst(ExpandConstant(Directory + '*'), FindRec) then
try
repeat
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 1 then
Files.Add(FindRec.Name);
until
not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
procedure InitializeWizard;
var
AfterID: Integer;
begin
AfterID := wpSelectTasks;
KartenFormular :=
CreateCustomPage(
AfterID, 'Kartendaten', 'Ort für den Ordner Kartendaten auswählen');
FileListBox := TNewListBox.Create(WizardForm);
FileListBox.Parent := KartenFormular.Surface;
FileListBox.Align := alClient;
AfterID := KartenFormular.ID;
KartenEinlesen('{tmp}\maptiles\*',FileListBox.Items)
end;

The InitializeWizard happens at the very beginning, long before the files are extracted. Moreover it would be inefficient to extract (all) files to {tmp} and then copy them somewhere else after users selects the files to install. And you would have to code the actual installation process.
The right solution is to generate the list box contents on compile (not install) time, using Inno Setup preprocessor.
The following question shows something similar. It creates a Inno Setup component for each file. It might even be better than a custom page.
Inno Setup: Dynamically add a component for all files in a folder and its subfolders
But if you want the custom page, the solution won't be too different. You just would generate FileListBox.Items.Add call for each file, instead of [Components] section entry. The code is somewhat complicated as it works recursively (what you do not seem to need). For flat solution, it can be simplified.
Also note that there's CreateInputOptionPage, what makes it easier to create a custom page with check list box.

Related

InnoSetup - What is the textbox name in the folder selection dialog?

Can you modify the default folder selection dialog in InnoSetup? I mainly need to change the folder path textbox in this dialog. When the user clicks the Next button it needs to check if the folder path given is "C:\Data1". If this path is given then it needs to be changed to "C:\Data".
This solution worked,
[code]
function NextButtonClick(CurPageID: Integer): Boolean;
begin
case CurPageID of
wpSelectDir:
begin
if WizardForm.DirEdit.Text = 'C:\Data1' then
WizardForm.DirEdit.Text := 'C:\Data';
end;
end;
end;
Thanks to these links,
Get the path in the wpSelectDir before {app} is set Inno Setup
How to allow installation only to a specific folder?
Inno Setup with three destination folders

Is there a way to extract .zip files in Inno Setup after a certain page is done?

So I basically have a .zip file in my {tmp} directory, and want to extract it's contents in {tmp} but only when my third form is done with it's work, not earlier. The reason is: Because in the third form I download this .zip from the internet and it is saved into {tmp}. Now after this I want to extract these files into {tmp} from which I am to get the files from the extracted folders, like the release notes, license agreement files to use in the rest of the forms in the installer. Meaning, already in the form after the third one, I am using the files extracted.
I can't find anywhere how to do this after a certain form. I only found in the run section how extracting is done.
EDIT: The old way I described proved not to work well on some Windows versions. It may pop up a dialog window instead of overwriting files silently. This is easy to google: CopyHere ignores options.
The new way:
Mitchich Inno Download plugin
7zip
The new way uses 7zip standalone console version. It is a single 7za.exe, you don't need the DLLs.
#include <idp.iss>
; Languages section
; Includes for Mitrich plugin's additional languages
; #include <idplang\Russian.iss>
[Files]
Source: "7za.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall;
[Run]
Filename: {tmp}\7za.exe; Parameters: "x ""{tmp}\example.zip"" -o""{app}\"" * -r -aoa"; Flags: runhidden runascurrentuser;
[Code]
procedure InitializeWizard;
begin
idpAddFile('https://example.comt/example.zip', ExpandConstant('{tmp}\example.zip'));
{ Download after "Ready" wizard page }
idpDownloadAfter(wpReady);
end;
If you want to download, unzip and use files (for example, as license agreement) before the installation starts, I can only give the general guideline:
Enable welcome page in [Setup]: DisableWelcomePage=no.
Use idpDownloadAfter(wpWelcome);. Now it downloads right after "Welcome" page.
You need an empty license file in [Setup]: LicenseFile=license.txt for license page to show up. Or probably not empty, but with "Loading license agreement..." text.
You implement procedure CurPageChanged(): if current page is wpLicense then you call Exec() function to launch 7zip and wait for it to terminate. No 7zip in [Run] section now. Then you probably use LoadStringFromFile() function to get license agreement from extracted file. Then put it into UI. Probably WizardForm.LicenseMemo.RTFText = ... should work. Anyway, UI is accessible, if you have trouble setting the text, ask a separate question on this.
The old buggy way:
Mitchich Inno Download plugin
Inno Unzip Plugin
An equivalent, cleaner way without unzipper.dll is described here. One way or another, it uses buggy CopyHere Windows feature.
#include <idp.iss>
; Languages section
; Includes for Mitrich plugin's additional languages
; #include <idplang\Russian.iss>
[Files]
Source: "unzipper.dll"; Flags: dontcopy
[Code]
procedure InitializeWizard;
begin
idpAddFile('https://example.comt/example.zip', ExpandConstant('{tmp}\example.zip'));
{ Download after "Ready" wizard page }
idpDownloadAfter(wpReady);
end;
procedure unzip(src, target: AnsiString);
external 'unzip#files:unzipper.dll stdcall delayload';
procedure ExtractMe(src, target : AnsiString);
begin
unzip(ExpandConstant(src), ExpandConstant(target));
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
{ Extract when "Finishing installation" setup step is being performed. }
{ Extraction crashes if the output dir does not exist. }
{ If so, create it first: }
{ CreateDir(ExpandConstant(...)); }
ExtractMe('{tmp}\example.zip', '{app}\');
end;
end;
You can probably try other things instead of wpReady and ssPostInstall. For my small zip this works well.

Inno Setup: How to let Folder.CopyHere finish copying before moving to the next folder?

This is a continuation from an earlier question: here. TLama provided a solution where a function requires:
- an Archive parameter to be the name of an existing archive file
- a Folder parameter can be the name of a file or folder
Call is like this:
CopyToArchive('C:\ExistingArchive.zip', 'C:\FolderToCopy\');
Procedure Runs...
procedure CopyToArchive(const Archive, Content: string);
var
Shell: Variant;
Folder: Variant;
begin
Shell := CreateOleObject('Shell.Application');
Folder := Shell.NameSpace(Archive);
Folder.CopyHere(Content);
end;
It is a brilliant solution but behaves slightly differently than the VBScript in the previous question. Specifically, this solution is unable to copy my files in 1 go. I get an invalid file name error which does not occur with the VBScript - I've no idea why. I found that I can work around the issue by modifying the code like this:
// ----------------------------------------------------------------------------
procedure CopyToArchive(); //(const Archive, Content: string);
var
Shell: Variant;
Folder: Variant;
Archive, Content: string;
begin
Shell := CreateOleObject('Shell.Application');
Archive := ExpandConstant('{code:GetDataDir_S|0}') + '\myZipFile.zip';
Folder := Shell.NameSpace(Archive);
Content := ExpandConstant('{code:GetDataDir_S|0}\Temp\Folder1\');
Folder.CopyHere(Content, $0100);
sleep(1100)
Content := ExpandConstant('{code:GetDataDir_S|0}\Temp\Folder2\');
Folder.CopyHere(Content, $0100);
sleep(1100)
Content := ExpandConstant('{code:GetDataDir_S|0}\Temp\Folder3\');
Folder.CopyHere(Content, $0100);
sleep(1100)
Content := ExpandConstant('{code:GetDataDir_S|0}\Temp\File1.abc');
Folder.CopyHere(Content, $0100);
sleep(1100)
Content := ExpandConstant('{code:GetDataDir_S|0}\Temp\File2.abc');
Folder.CopyHere(Content, $0100);
sleep(1100)
Content := ExpandConstant('{code:GetDataDir_S|0}\Temp\File3.abc');
Folder.CopyHere(Content, $0100);
sleep(1100)
end;
But, as you can see that is a LOT of waiting. I get file permission errors if I reduce the Sleep time so I am pretty confident that I need to provide time for the files to finish transferring before starting the next operation.
In VBScript the solution was to check the file size and only move on when it stopped growing. The code that I pulled together for Inno Setup is:
Var
max0, max1: integer;
Begin
// Initialise FileSizeCounters
max0 := 1
max1 := 0
// Replace each Sleep(1100) line with the following:
while max0 > max1 do begin
FileSize( Archive, max0 );
sleep(500)
FileSize( Archive, max1 );
log(max1)
end;
But it does not seem to work - still crashes. To stop crashing I need to increase the sleep time to at least 1100 ms and event then it still crashes on some test runs. Is there are better way to write this?
Solution was to leave as many files in the zip container as possible. Then selectively copy remaining files using the solution provided earlier by TLama. This makes the overall process significantly faster and significantly more robust than my initial attempt which was to fill an empty zip container.
I did find that operating with the zip container is a bit temperamental and it seems to work best when I perform the following steps on my original zip file:
extract to a folder
open the zip file
select all & delete
drag & drop files that dont change
Install via Inno then use the no notification, annoying, asynch, script with a dose of sleep to copy remaining files that have been modified during install.
Hope this helps someone :-)
Big thanks to TLama

How can I get system display name with Inno Setup?

How can I get system display name of a file, directory, or folder as it would be displayed in a system file browser? For example original CD is named somehow and I want to do little copy protection
Inno Setup provides no such functions. However, you can easily write your own DLL that investigates the label of the CD drive of the setup program. In Pascal, just do
function GetCDLabel: string;
var
VolumeName: PChar;
dummy: cardinal;
begin
GetMem(VolumeName, MAX_PATH * sizeof(char));
try
if GetVolumeInformation(PChar('D:\'), VolumeName, MAX_PATH + 1, nil, dummy, dummy, nil, 0) then
result := VolumeName
else
RaiseLastOSError; // or result := 'Invalid';
finally
FreeMem(VolumeName);
end;
end;
Inno Setup lets you include DLLs with your setup, and call functions in these DLLs during setup. Of course, your setup must tell the DLL function its filename, so that the DLL function can use the right drive. You cannot just assume it is D:\.

Inno setup and DefaultDirName

Is there some way to set the DefaultDirName by code depending on some decission a user did on installtion?
Let me comment:
I have some code which is build for two different systems (using different interops/ocx's and such stuff). My input files are stored in two directories input\A and input\B.
I want to have only one setup-file for both systems.
In the setup file i use CreateInputOptionPage with 2 options to determin which files to install (using Check on each file). This works okay.
But i do also have some ShellExec on finish of setup, which at the moment uses {app} to e.g. register some .Net classes and ShellExec to unregister the .Net classes on InitializeUninstall (also uses {app})
The setup must install the software on two different locations (depending on the selection of the user (eg. c:\software_a or c:\software_b). Can't change this.
So is there some way to specify the DefaultDirName before the files get copied to the system, so i can use the same ShellExec on install and uninstall? I could of course add the same ShellExec for both systems on installtation and use an if to check which files to register (depending on the user selection) but on uninstall i would not have this information (user selection), so i can not unregister the .Net classes.
thanks
In your CreateInputOptionPage code section, you might be able to set a value then use that value in the code snippet below. I haven't tested it but it might work.
[Setup]
DefaultDirName={code:getpath}
[Code]
function GetPath( Default: string ): string;
begin
if (CreateInputOptionPageValue1) then
Result := ExpandConstant({sd}) + '\path1';
else
Result := ExpandConstant({sd}) + '\path2';
end;
If you need to change the install folder after the DefaultDirName has been initialized, this was working for me quite well:
procedure CurPageChanged(CurPageID: Integer);
begin
{ updates the install path depending on the install type or the entered suffix }
if CurPageID = wpSelectDir then begin
WizardForm.DirEdit.Text := ExpandConstant('{pf}') + '\MyAppName' + GetAppSuffix('');
end;
end;
Cheers
Chris

Resources