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:\.
Related
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.
I would like to know how to fix this kind of Error:
You must enter a full path with drive letter; for example: C:\APP or a UNC path in the form: \server\share
This appears whenever I try to force the Inno Setup Compiler (5.5.5 u) to put my stuff into, let say H:\ instead of H:\New Folder.
I need the compiler to customize my destination location to H:\.
Here is my sample program;
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={drive:F:}
AppendDefaultDirName=no
[Files]
Source: "File1.txt"; DestDir: "{code:GetExeLocation|{app}\My_Portable_App}"; \
Flags: ignoreversion
[Code]
var
UsagePage: TInputOptionWizardPage;
procedure InitializeWizard;
begin
{ Create the pages }
UsagePage := CreateInputOptionPage(wpWelcome,
'Installation Type', 'Select Installation Option',
'Where would you like to install this program',
True, False);
UsagePage.Add('Normal – PC Hard Disk Installation ');
UsagePage.Add('Portable – USB Drive Installation');
{Set Default – Normal Install}
UsagePage.SelectedValueIndex := 0;
end;
var
bIsPortable : Boolean;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
// If we have clicked the next button on our custom page.
if (CurPageID = UsagePage.ID) then
begin
bIsPortable := not (UsagePage.SelectedValueIndex = 0);
end;
result := true; // need to return a value so next key has an impact
end;
function GetExeLocation (Param: String) : String;
begin
if bIsPortable then
result := Param
else
result := Param;
end;
function InstallType(sRequiredType:String):Boolean;
begin
if sRequiredType = 'Portable' then
result := bIsPortable
else
result := not bIsPortable;
end;
Explanation:
When I select "Normal – PC Hard Disk Installation", as my choice, all my installation files or folders should go to normal Path that is to C:\My Program, but when I select "Portable – USB Drive Installation" as my Entry, I would like to put all my installation files or folders directly into the USB Pen drive Root, that is in here H:\, where "H" is my USB Pen Drive letter which I have selected to put my stuff in. But my Program does not allow me to do so, instead it adds a New Folder by default to put my installation files or folders over there, that is to H:\New Folder which I do not need that at all!. And when I force to do what I want, it ends giving me an Error!
Please I need Your Help to fix this, and if this inno-setup can not do what I want, please point me another one, and I will be Thankful for that!
EDIT:
Let focus on the second choice that is "('portable – usb drive installation')" because that is my real target.
From the Source: I made some changes so that to make it more clear.
I added my destination Directory, that is {code:GetExeLocation|{app}\My_Portable_App}. So what I want here is that, all my installation files or folders to be installed inside this directory, I mean My_Portable_App. And the path to my USB pen drive should be H:\My_Portable_App. So when this goes fine, I want to see only This Folder My_Portable_App in my USB pen drive that will contain all my stuffs in there!!!
Thanks in advance!
If you want to install directly into h:\ then you should explicitly enter that into the location box. If you also want to stop the My Program being appended after using the browse dialog then you need to ensure that AppendDefaultDirName is set to no.
Also note that for file2, the DestDir will end up being set to {app}/{app} if bIsPortable is true which will most likely expand to an invalid path.
Your best option is to use a {code:...} function to create a single "default" path based on bIsPortable and then everything can just install into {app} from there.
I struggled with the validation too, where root was not valid in the TInputDirWizardPage.
As it turns out there is a simple Inno Setup option that changes this behavior:
AllowRootDirectory=yes
will allow the user to specify a drive root without error.
also see,
AllowUNCPath=yes/no
and
AllowNetworkDrive=yes/no
in the Inno help file for other validation modifiers that apply to the Select Destination Location Page.
This setup file must be located on disc c:.
That's all.
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
On the recommendation of several posts here on SO, I've been working with the InnoTools Downloader to try to install a 3rd party dependency for our app during the Install script in Inno setup.
Unfortunately the InnoTools Downloader hasn't been updated in a few years, and is starting to look like it's incompatible with the current Inno Tools setup (5.5.2 (u) on my machine at present). The PChar parameters in ITD have been replaced by PAnsiChar parameters, and when I try to run the various ITD_xxx procedures it gives me varying degrees of fail:
ITD_DownloadFiles gives a type mismatch error and won't compile in Inno Setup
ITD_DownloadFile compiles, but the file that shows up is 6KB in length and not runnable.
Has anyone gotten ITP to run with newer Inno (post-5.3.0) unicode versions? Or should I look around for another solution?
EDIT
Just to clarify things a bit, I have tried going into the it_download.iss file and replacing all instances of PChar with PAnsiChar. This got me past the compile errors when I first tried to integrate ITD with my setup script.
Here is a sample section of the Inno script:
[Code]
procedure InitializeWizard();
begin
ITD_Init; // initialize the InnoTools Downloader
// install 3rd party tool (ex. Git) from the internet.
if ITD_DownloadFile('http://git-scm.com/download/win',expandconstant('{tmp}\GitInstaller.exe'))=ITDERR_SUCCESS then begin
MsgBox(expandconstant('{tmp}\GitInstaller.exe'), mbInformation, MB_OK);
Exec(ExpandConstant('{tmp}\GitInstaller.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, tmpResult);
end
end;
When this is run, it will bring up a dialog stating where it "downloaded" and stored the file -- on my machine it's in a subdirectory of c:\Users\\AppData\Local\Temp. This file is 6KB, as opposed to the file downloaded from http://git-scm.com/download/win, which is currently 15,221KB.
The ITP_DownloadAfter method gives a similar result.
Except replacing all PChar type occurrences with PAnsiChar you will need to replace all occurrences of string type with AnsiString in the it_download.iss file. The next problem is the URL you're trying to get. The size of a file differs from expectation, because you are downloading a HTML document instead of the binary file on which that site redirects. So, if you have your ITD ready for Unicode, change the URL in your script to a direct binary URL. Note, that I didn't used HTTPS because ITD doesn't currently support SSL. A code proof may look like this:
[Code]
const
GitSetupURL = 'http://msysgit.googlecode.com/files/Git-1.8.4-preview20130916.exe';
procedure InitializeWizard;
var
Name: string;
Size: Integer;
begin
Name := ExpandConstant('{tmp}\GitInstaller.exe');
ITD_Init;
if ITD_DownloadFile(GitSetupURL, Name) = ITDERR_SUCCESS then
begin
if FileSize(Name, Size) then
MsgBox(Name + #13#10 + 'Size: ' + IntToStr(Size) + ' B',
mbInformation, MB_OK)
else
MsgBox('FileSize function failed!', mbError, MB_OK);
end;
end;
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