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;
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.
We use Inno Setup for our installer. Recently a user reported the following error during installation:
An error occurred while trying to copy a file: the source file is corrupted
This was due to a setup file that was indeed corrupted somehow.
Ideally the setup EXE would have performed some kind of check upon initialization to see if the entire EXE was valid or not. But apparently it only did this on a file-by-file basis. Is it possible to get InnoSetup to do that?
I looked in Inno Setup documentation for keywords like 'check', 'hash', etc. but didn't see anything - perhaps I missed it.
Quite similar question (from about 10 years ago - though specifically asking about MD5): How to implement MD5 check into Inno Setup installer to get 'like NSIS integrity check'? . That question seemed to state that such a check should already be happening. So perhaps the issue is not whether or not the setup EXE is validated but when this information is used / shown to the user. Also the accepted answer seemed quite manual, ideally I'd like Inno to do this itself.
Similar question with a different error message: "Source file corrupted: SHA-1 hash mismatch" error from Inno Setup
Add a checksum to the installer and verify it when the installer is starting. A standard (and recommended) way to do that is to sign the installer using code signing certificate. It's a must anyway these days.
Easy way to verify the signature is using PowerShell Get-AuthenticodeSignature. You need PowerShell 5.1 for that. It is bundled with Windows 10 Build 14393 (August 2016) and newer. The following code uses that (and skips the check on older versions of Windows).
function InitializeSetup(): Boolean;
var
WindowsVersion: TWindowsVersion;
S: string;
ResultCode: Integer;
begin
Result := True;
GetWindowsVersionEx(WindowsVersion);
Log(Format('Windows build %d', [WindowsVersion.Build]));
// TODO: Better would be to check PowerShell version
if WindowsVersion.Build < 14393 then
begin
Log('Old version of Windows, skipping certificate check');
end
else
begin
S := ExpandConstant('{srcexe}');
if (Pos('''', S) > 0) or (Pos('"', S) > 0) then
RaiseException('Possible code injection');
S := 'if ((Get-AuthenticodeSignature ''' + S + ''').Status -ne ''Valid'') ' +
'{ exit 1 }';
if ExecAsOriginalUser(
'powershell', '-ExecutionPolicy Bypass -command "' + S + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
(ResultCode = 0) then
begin
Log('Installer signature is valid');
end
else
begin
S := 'Installer signature is not valid. Are you sure you want to continue?';
Result := (MsgBox(S, mbError, MB_YESNO) = IDYES);
end;
end;
end;
If you need to support older versions of Windows, you will have to use more complicated methods, like:
Bundling signtool with the installer (not sure about license here);
Using X509Certificate class.
See How to check if a file has a digital signature.
This is the code used to download any prerequisites during the installation,
#include <idp.iss>
[Code]
function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
begin
idpAddFileSize('<url>', ExpandConstant('{commonappdata}\<my file name>'), <my file size>);
idpDownloadAfter(wpReady);
end;
However, if there is no internet connection it disables the Next button from continuing the installation. Here is the dialog,
The installation needs to continue even if any download fails. How to fix this?
IDP has AllowContinue option that allows continuing even on errors:
procedure InitializeWizard();
begin
idpSetOption('AllowContinue', '1');
end;
Note that Inno Setup 6.1 has a native support for downloads. You may want to consider using it instead of IDP. See Inno Setup: Install file from Internet.
Also, I believe that UpdateReadyMemo can be called multiple times (when the user returns from the "Ready" page). So your idpAddFileSize can also add the file to download queue multiple times.
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:\.
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