Change download location for Inno Setup TDownloadWizardPage - inno-setup

I'm using Inno Setup's download system, but I would like to change the download folder from {tmp} to another one, can someone help me?
of:
DownloadPage.Add('{#LinkJava}', ExpandConstant('{tmp}\Java.exe'), '');
for:
DownloadPage.Add('{#LinkJava}', ExpandConstant('{src}{#PastaGeralDeApps}{#PastaDownPlugins}{#NomeExeJava}'), '');
I'm creating an automatic installer and I need to store the apps in a folder, to use offline.

You cannot change the folder, where the files are downloaded to using TDownloadWizardPage (note that the BaseName argument of TDownloadWizardPage.Add accepts a filename only, not a path).
But you can copy over the files to the final folder, once they are downloaded.
[Files]
Source: "{tmp}\downloaded-file.exe"; DestDir: "{app}"; flags: external
You of course need to download the files before the actual installation. So for example from NextButtonClick(wpReady), the way the official CodeDownloadFiles.iss example does.
If you need to move the files to the final location before the installation, use RenameFile or FileCopy functions. The copying is obviously slower, but more reliable, as you cannot move/rename between disks/filesystems. You might try moving, falling back to copying, if moving fails.

//function that performs the download - OK
function DownloadFiles(Url, Destination, FileName, Hash : String): Boolean;
begin
DownloadPage.Clear;
DownloadPage.Add(Url, FileName, Hash);
DownloadPage.Show;
try
try
DownloadPage.Download;
Result := True;
except
if DownloadPage.AbortedByUser then
Log('Aborted by user.')
else
SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
FileCopyLogged(ExpandConstant('{tmp}\'+FileName), ExpandConstant(Destination+'\'+FileName), False);
end;
Result := True;
end;

Related

Get the user download folder path using Inno Setup?

How I get the user download folder path using Inno Setup?
[Code]
function InitializeSetup(): Boolean;
begin
if (FileExists(ExpandConstant('{%username%}\Downloads\file.txt'))) then
begin
MsgBox('Installation validated', mbInformation, MB_OK);
Result := True;
end
else
begin
MsgBox('Abort installation', mbCriticalError, MB_OK);
Result := False;
end;
end;
This always came to end in else part.
There is constant for "user docs" and "user desktop" but not for "download". Can anyone please help me how I get user "download" folder path.
The syntax for resolving environment variable is {%NAME}, not {%NAME%}. Moreover the USERNAME environment variable is not a path, it's just a username. You want USERPROFILE instead:
ExpandConstant('{%USERPROFILE}\Downloads\file.txt')
And hard-coding a path to Downloads folder seem like a bad idea to me. Who guarantees you that the file is there? I personally never download anything to the Downloads folder.
Aren't you actually looking for a file that is downloaded to the same folder, where your installer was downloaded to? Use {src} constant:
ExpandConstant('{src}\file.txt')
See Create small setup installer just for wizard, rest of files apart

Inno Setup: Can the installer update itself?

My installer creates a folder with my app and the installer itself. The installer is later on used as an updater for the application.
All of this works well but I would like to update the installer itself and not just my application.
I download a zip from my server and expect everything inside the zip to override everything in the app folder (including the installer itself).
Every time I run the installer I get an error that a file is already in use.
Can the installer update itself?
You cannot replace running application.
You have these options:
Start the "updater" via batch file (referring to assumed shortcut to the updater in a Start menu or any other method of invocation), that makes a copy of the installer to a temporary location and runs the updater from there. When updating, update the original copy.
To avoid the batch file (and an unpleasant console window), you can use JScript. Or even make the installer (updater) do this itself (create a copy of itself, launch the copy, exit itself).
Use restartreplace flag in Files section entry to schedule installer/updater replace for the next Windows start.
Keeping the installer in the {app} directory is probably acceptable for small applications, for larger ones consider an updater, or even another location, (in the form of a feature request) {Backup} to refer to a path on some flash or removable drive.
Run the setup from the {app} directory and after the version check, download the installer to the {tmp} folder.
Exec the installer thus before quitting, keeping mind of possible mutex conditions in the code section of your script:
if Exec(ExpandConstant('{tmp}\{OutputBaseFilename}), '', '', SW_SHOW,
ewNoWait, ResultCode) then
// success/fail code follows
To copy the installer back to {app} the Install script will have this in Files:
[Files]
Source: "{srcexe}"; DestDir: "{app}"; Flags: external
Presumably the above line will not produce an error when the installer is actually run from {app}.
Then, to clean up, the next time the installer is run from the {src} (= {app}) directory, the downloaded one can be removed from the {tmp} directory with
DeleteFile({tmp}\{OutputBaseFilename})
I've run into this same problem recently. We have a main installer that manages a bunch of other setup packages for our applications, and I wanted to add some mechanism for this main installer to update itself.
I've managed to find a solution creating a second Inno Setup package that serves just as an updater for the main installer, and that updater goes embedded in the main installer.
So, we have a XML file in our website that gives the latest version available for the main installer:
[ InstallerLastVersion.xml ]
<?xml version="1.0" encoding="utf-8"?>
<installer name="Our Central Installer" file="OurInstaller.exe" version="1.0.0.1" date="10/15/2021" />
The main code for this auto-update functionality in the main installer is that:
[ OurInstaller.iss ]
[Files]
; This file won't be installed ('dontcopy' flag), it is just embedded
; into the installer to be extracted and executed in case it's necessary
; to update the Installer.
Source: ".\OurInstallerUpdater.exe"; Flags: dontcopy
[Code]
const
UrlRoot = 'http://ourwebsite.com/';
// Downloads a XML file from a website and loads it into XmlDoc parameter.
function LoadXml(XmlFile: String; var XmlDoc: Variant): Boolean;
begin
XmlDoc := CreateOleObject('MSXML2.DOMDocument');
XmlDoc.async := False;
Result := XmlDoc.Load(UrlRoot + XmlFile);
end;
// Checks if there's a newer version of the Installer
// and fires the updater if necessary.
function InstallerWillBeUpdated(): Boolean;
var
XmlDoc: Variant;
LastVersion, Filename, Param: String;
ResultCode: Integer;
begin
if not LoadXml('InstallerLastVersion.xml', XmlDoc) then
begin
Result := False;
Exit;
end;
// Gets the latest version number, retrieved from
// the XML file download from the website.
LastVersion := XmlDoc.documentElement.getAttribute('version');
// If this installer version is the same as the one available
// at the website, there's no need to update it.
if '{#SetupSetting("AppVersion")}' = LastVersion then
begin
Result := False;
Exit;
end;
if MsgBox('There is an update for this installer.' + #13#10 +
'Do you allow this installer to be updated right now?',
mbConfirmation, MB_YESNO) = IDNO then
begin
Result := False;
Exit;
end;
// Extracts the updater, that was embedded into this installer,
// to a temporary folder ({tmp}).
ExtractTemporaryFile('OurInstallerUpdater.exe');
// Gets the full path for the extracted updater in the temp folder.
Filename := ExpandConstant('{tmp}\OurInstallerUpdater.exe');
// The current folder where the installer is stored is going to be
// passed as a parameter to the updater, so it can save the new version
// of the installer in this same folder.
Param := ExpandConstant('/Path={src}');
// Executes the updater, with a command-line like this:
// OurInstallerUpdater.exe /Path=C:\InstallerPath
Result := Exec(Filename, Param, '', SW_SHOW, ewNoWait, ResultCode);
end;
function InitializeSetup(): Boolean;
begin
// Checks if the installer needs to be updated and fires the update.
// If the update is fired the installer must be ended, so it can be
// replaced with the new version. Returning this InitializeSetup()
// function with False already makes the installer to be closed.
if InstallerWillBeUpdated() then
begin
Result := False;
Exit;
end;
Result := True;
end;
Now to the updater code (I'm using the "new" DownloadTemporaryFile() function added in Inno Setup 6.1):
[ OurInstallerUpdater.iss ]
[Code]
const
UrlRoot = 'http://ourwebsite.com/';
Installer = 'OurInstaller.exe';
function InitializeSetup(): Boolean;
var
DestinationPath: String;
ResultCode: Integer;
begin
// Retrieves the parameter passed in the execution
// of this installer, for example:
// OurInstallerUpdater.exe /Path=C:\InstallerPath
// If no parameter was passed it uses 'C:\InstallerPath' as default.
// (where {sd} is the constant that represents the System Drive)
DestinationPath := ExpandConstant('{param:Path|{sd}\InstallerPath}') + '\' + Installer;
try
// Downloads the newer version of the installer to {tmp} folder.
DownloadTemporaryFile(UrlRoot + Installer, Installer, '', nil);
// Copies the downloaded file from the temp folder to the folder where
// the current installer is stored, the one that fired this updater.
FileCopy(ExpandConstant('{tmp}\') + Installer, DestinationPath, False);
// Runs the updated installer.
Exec(DestinationPath, '', '', SW_SHOW, ewNoWait, ResultCode);
except
MsgBox('The file ''' + Installer + ''' could not be downloaded.', mbInformation, MB_OK);
end;
// Returning False from this function implies that this
// updater can now be finished, since its goal has already
// been reached (to update the main installer).
Result := False;
end;
In this setting you have to build OurInstallerUpdater.exe before OurInstaller.exe, since the first one is embedded into the second one.
Some sources:
Inno Setup: Install file from Internet
Using {AppVersion} as a parameter for a function in Inno Setup
Exit from Inno Setup installation from [Code]
Is it possible to accept custom command line parameters with Inno Setup
How to get the installer path in Inno Setup?

Inno setup search for existing file

How can I search for an existing exe file and then use that directory for my installer ?
If the exe file is not found I would like the user to browse for the path. In case the exe file is installed somewhere else.
Senario 1 (most common cases):
Default dir is c:\test\My program
This should be shown as the path on the "Select Destination Location" page
When the user press Next, there should be a check. To make sure that the default dir exist (c:\test\My program)
If it exist, the user should just continue to the Ready to Install page.
Senario 2 (very seldom cases):
Default dir is c:\test\My program
This should be shown as the path on the "Select Destination Location" page
When the user press Next, there should be a check. To make sure that the default dir exist (c:\test\My program)
If it does not exist, the user should be prompt for the path to "My program". The user should afterwards continue to the Ready to Install page.
I then just trust that the user selects the correct path
How can I do this in InnoSetup ?
I do a similar thing with my installer already. The first thing I do is need to read the registry value from the program, and if that registry value is absent then I select that program's default directory. For example:
DefaultDirName={reg:HKLM\Software\Activision\Battlezone II,STInstallDir|reg:HKLM\Software\Activision\Battlezone II,Install121Dir|{pf32}\Battlezone II}
Now, the user runs the installer, and it has to check if the program is in the right folder. It does so by checking that the program's executable file already exists. I use this piece of code to do so.
{ Below code warns end user if he tries to install into a folder that does not contain bzone.exe. Useful if user tries to install into addon or any non-BZ2 folder. }
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Log('NextButtonClick(' + IntToStr(CurPageID) + ') called');
case CurPageID of
wpSelectDir:
if not FileExists(ExpandConstant('{app}\bzone.exe')) then begin
MsgBox('Setup has detected that that this is not the main program folder of a Battlezone II install, and the created shortcuts to launch {#MyAppName} will not work.' #13#13 'You should probably go back and browse for a valid Battlezone II folder (and not any subfolders like addon).', mbError, MB_OK);
end;
wpReady:
end;
Result := True;
end;
The above code simply checks that the target executable exists and warns the user if it does not, giving him the chance to go back and change directories but also to go ahead with the install anyways.
Also, as you appear to be installing a patch or addon to an existing program, I recommend you set
DirExistsWarning=no
AppendDefaultDirName=false
And optionally to prevent an unnecessary screen if you are not creating start menu entries
DisableProgramGroupPage=yes
I would make a file input page and let user choose the Picture.exe binary location manually, when it won't be found on expected location.
You can follow the commented version of this code:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
[Files]
Source: "CurrentBinary.exe"; DestDir: "{app}"
Source: "PictureExtension.dll"; DestDir: "{code:GetDirPath}"
[Code]
var
FilePage: TInputFileWizardPage;
function GetDirPath(const Value: string): string;
begin
Result := '';
if FileExists(FilePage.Values[0]) then
Result := ExtractFilePath(FilePage.Values[0]);
end;
procedure InitializeWizard;
var
FilePath: string;
begin
FilePage := CreateInputFilePage(wpSelectDir, 'Select Picture.exe location',
'Where is Picture.exe installed ?', 'Select where Picture.exe is located, ' +
'then click Next.');
FilePage.Add('Location of Picture.exe:', 'Picture project executable|Picture.exe',
'.exe');
FilePage.Edits[0].ReadOnly := True;
FilePage.Edits[0].Color := clBtnFace;
FilePath := ExpandConstant('{pf}\Picture\Picture.exe');
if FileExists(FilePath) then
FilePage.Values[0] := FilePath;
end;

Inno Setup: Install file from Internet

I am using Inno Setup to distribute my application.
Is it possible to check in Inno Script for a particular condition and download and install some file from internet if required.
Inno Setup 6.1 and newer has a built-in support for downloads. No 3rd party solution are needed anymore.
Check the Examples\CodeDownloadFiles.iss in Inno Setup installation folder.
The important parts of the example are:
[Files]
; These files will be downloaded
Source: "{tmp}\innosetup-latest.exe"; DestDir: "{app}"; Flags: external
Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external
[Code]
var
DownloadPage: TDownloadWizardPage;
function OnDownloadProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean;
begin
if Progress = ProgressMax then
Log(Format('Successfully downloaded file to {tmp}: %s', [FileName]));
Result := True;
end;
procedure InitializeWizard;
begin
DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), #OnDownloadProgress);
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpReady then begin
DownloadPage.Clear;
DownloadPage.Add('https://jrsoftware.org/download.php/is.exe', 'innosetup-latest.exe', '');
DownloadPage.Add('https://jrsoftware.org/download.php/iscrypt.dll', 'ISCrypt.dll', '2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc');
DownloadPage.Show;
try
try
DownloadPage.Download;
Result := True;
except
SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
end else
Result := True;
end;
For alternatives, see Running a program after it is downloaded in Code section in Inno Setup
Inno Download Plugin by Mitrich Software.
It's an InnoSetup script and DLL, which allows you to download files as part of your installation.
It supports FTP, HTTP and HTTPS.
It's kind of a drop-in replacement for InnoTools Downloader. Only few changes required.
It brings a decent download display and HTTPS and Mirror(s) support.
Example:
#include <idp.iss>
[Files]
Source: "{tmp}\file.zip"; DestDir: "{app}"; Flags: external; ExternalSize: 1048576
[Code]
procedure InitializeWizard();
begin
idpAddFileSize('http://127.0.0.1/file.zip', ExpandConstant('{tmp}\file.zip'), 1048576);
idpDownloadAfter(wpReady);
end.
Yes, there is a library called InnoTools Downloader which has samples that do pretty much this. They can be conditioned on anything you want using normal Inno code.
Found on Inno 3rd Party is one very similar in scope and style to the Inno Download Plugin, DWinsHs.
Included with an easy and intuitive chm file which requires unblocking to view.

How to get Inno Setup to unzip a file it installed (all as part of the one installation process)

To save bandwidth/space as well as prevent accidental meddling, the installation files for a database product (call it Ajax), have been zipped up (call that file "AJAX_Install_Files.ZIP). I would like to have Inno-Setup "install" (i.e., copy) the AJAX_Install_Files.ZIP file to the destination, and then Unzip the files into the same folder where the .ZIP file is located. A subsequent program would be fired off by Inno Setup to actually run the install of product "Ajax".
I've looked through the documentation, FAQ, and KB at the Inno Setup website, and this does not seem possible other than writing a Pascal script (code) - would that be correct, or are there are any alternative solutions?
You can use an external command line tool for unzipping your archive, see here for example. Put it in your [Files] section:
[Files]
Source: "UNZIP.EXE"; DestDir: "{tmp}"; Flags: deleteafterinstall
Then call it in your [Run] section, like this:
[Run]
Filename: "{tmp}\UNZIP.EXE"; Parameters: "{tmp}\ZipFile.ZIP -d C:\TargetDir"
(You'll probably want to take your target directory from a script variable, so there is some more work that needs to be done)
You can use the shell Folder.CopyHere method to extract a ZIP.
const
SHCONTCH_NOPROGRESSBOX = 4;
SHCONTCH_RESPONDYESTOALL = 16;
procedure UnZip(ZipPath, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
TargetFolder: Variant;
begin
Shell := CreateOleObject('Shell.Application');
ZipFile := Shell.NameSpace(ZipPath);
if VarIsClear(ZipFile) then
RaiseException(
Format('ZIP file "%s" does not exist or cannot be opened', [ZipPath]));
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsClear(TargetFolder) then
RaiseException(Format('Target path "%s" does not exist', [TargetPath]));
TargetFolder.CopyHere(
ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
end;
Note that the flags SHCONTCH_NOPROGRESSBOX and SHCONTCH_RESPONDYESTOALL work on Windows Vista and newer.
For an example of extracting some files only, see:
How to get Inno Setup to unzip a single file?
I answered a very similar question and some of the details apply.
I would question why you need a ZIP file of the contents? I personally would place the uncompressed files into the setup. I would then have two [category] entries one for the application and one for the data. Default both the be checked.
This would allow the users to install a fresh set of the data if needed at a later date.
If you really want a ZIP file and want to keep it easy you could, ship both the zip files and the uncompressed files in the same setup.
Update:
By default files that get placed in your setup.exe are compressed.
You can also have the files extracted to a temporary location so you can run your
installation application, then have them deleted.
[Files]
Source: "Install1.SQL"; DestDir: "{tmp}"; Flags:deleteafterinstall;
Source: "Install2.SQL"; DestDir: "{tmp}"; Flags:deleteafterinstall;
You can just create silent self-extracting archive (SFX) archive, example described here how to create SFX archive for stuff you need, and write Pascal code to just run it like this (script for Inno Setup 6.0.2):
[Tasks]
Name: "intallSenselockDriver"; Description: "Install Senselock driver."; GroupDescription: "Install the necessary software:";
[Code]
function ExecTmpFile(FileName: String): Boolean;
var
ResultCode: Integer;
begin
if not Exec(ExpandConstant('{tmp}\' + FileName), '', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode)
then
begin
MsgBox('Other installer failed to run!' + #13#10 + SysErrorMessage(ResultCode), mbError, MB_OK);
Result := False;
end
else
Result := True;
end;
procedure RunOtherInstallerSFX(ArchiveName: String; ExePath: String);
begin
ExtractTemporaryFile(ArchiveName);
ExecTmpFile(ArchiveName);
ExecTmpFile(ExePath);
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
if WizardIsTaskSelected('intallSenselockDriver') then
RunOtherInstallerSFX('1_senselock_windows_3.1.0.0.exe', '1_senselock_windows_3.1.0.0\InstWiz3.exe');
Result := '';
end;
It worked perfectly for me.
Using Double quotes worked for me.
Single quotes were not working.
[Files]
Source: "unzip.exe"; DestDir: "{userappdata}\{#MyAppName}\{#InputFolderName}"; Flags: ignoreversion
[Run]
Filename: "{userappdata}\{#MyAppName}\{#InputFolderName}\unzip.exe"; Parameters: " ""{userappdata}\{#MyAppName}\{#InputFolderName}\ZIPFILENAME.zip"" -d ""{userappdata}\{#MyAppName}\{#InputFolderName}"" "; Flags: runascurrentuser

Resources