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: