Ok, I’m pretty pleased with myself, I’ve got it to work how I want and the user experience is quite streamlined, I feel
Plus I’ve learnt a bit more about how InnoSetup and shell work.
For Windows only, an InnoSetup installer is registered in the Kaju Admin app instead of a zip file and uploaded to your website. I’ve followed the usual Kaju workflow, up till the point where the file has been downloaded. So the benefits of Kaju checking the integrity of the downloaded data, using Crypto.RSAVerifySignature, and the downloaded file, by checking against the hash, are retained.
When the user presses Quit & Install, the following sequence occurs:
- The setup.exe (which is downloaded into a temp folder) is launched
- App quits
- UAC dialog box appears asking if the user wants to allow Setup to make changes (get admin privileges)
- The wizard shows only the ready to install page and user click Install
- The wizard shows the installation complete page and the user can launch the updated app or close the wizard
- A small .bat file is created that deletes the setup.exe file then deletes itself
It’s worked out better than I had hoped, I’m starting to see how powerful InnoSetup is. You can use the same installer as for a new installation, as long as AppId is kept the same, InnoSetup will automatically install into the same directory and update the version info in the registry.
Here is a dropbox link to a Kaju Update Test Altered project which demonstrates the changes that I made. All the changes are labelled with “//MyKajuChanges”. Below is the InnoSetup script that I use to make the installer, which is based on the Xojo Documentation 64-bit example with bits of code that I found on various forums. I hope this can help other people who may have experienced similar problems to me.
Kem, is it ok for me to share this? I’ve kind of bastardised your code and possibly introduced some errors in logic.
InnoSetup Script
; Sample script for creating an installer for a 64-bit Xojo desktop app
#define XojoAppName “MyApp”
#define MyAppURL “https://mywebsite.com/”
#define AppGUID “{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}”
#define MyAppVersion GetFileVersion(AddBackslash(SourcePath) + “MyApp.exe”)
#define MyAppPublisher “Frank Yung-Tai Sun”
#define MyAppExeName “{#XojoAppName}.exe”
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
VersionInfoVersion=1.0.0.0
SetupLogging=yes
SignTool=signtool
SignedUninstaller=yes
AppId={{#AppGUID}
AppName={#XojoAppName}
AppVersion={#MyAppVersion}
AppVerName={#XojoAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={commonpf}\{#XojoAppName}
; Since no icons will be created in “{group}”, we don’t need the wizard
; to ask for a Start Menu folder name:
;DefaultGroupName={#XojoAppName}
DisableProgramGroupPage=yes
AllowNoIcons=yes
LicenseFile=C:\path\to\{#XojoAppName} End User License Agreement.rtf
OutputDir=.
OutputBaseFilename=Setup{#XojoAppName}v{#MyAppVersion}_Win64
Compression=lzma
SolidCompression=yes
ChangesAssociations=yes
ArchitecturesInstallIn64BitMode=x64
; Require Windows 7 SP1 or later
MinVersion=6.1.7601
[Languages]
Name: “english”; MessagesFile: “compiler:Default.isl”
[Tasks]
Name: “desktopicon”; Description: “{cm:CreateDesktopIcon}”; GroupDescription: “{cm:AdditionalIcons}”; Flags: unchecked
; These directories will be created by the installer inside the DefaultDirName
; (defined above).
[Dirs]
Name: “{app}\{#XojoAppName} Libs”
Name: “{app}\{#XojoAppName} Resources”
Name: “{app}\locales”
Name: “{app}\swiftshader”
; These are the files to include. By default you want to include
; the EXE plus the Libs and Resources folders
; but you can include any other files you like as well.
[Files]
Source: “.\{#XojoAppName}\{#XojoAppName}.exe”; DestDir: “{app}”; Flags: ignoreversion signonce
Source: “.\{#XojoAppName}\"; DestDir: “{app}”; Flags: ignoreversion recursesubdirs createallsubdirs
Source: ".\{#XojoAppName}\{#XojoAppName} Libs\”; DestDir: “{app}\{#XojoAppName} Libs”; Flags: ignoreversion recursesubdirs createallsubdirs
Source: “.\{#XojoAppName}\{#XojoAppName} Resources\"; DestDir: “{app}\{#XojoAppName} Resources”; Flags: ignoreversion recursesubdirs createallsubdirs
Source: ".\{#XojoAppName}\locales\”; DestDir: “{app}\locales”; Flags: ignoreversion recursesubdirs createallsubdirs
Source: “.\{#XojoAppName}\swiftshader\*”; DestDir: “{app}\swiftshader”; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don’t use “Flags: ignoreversion” on any shared system files
; Creates icons/links in the Start Menu and/or the desktop if the user chooses during installation.
[Icons]
;Name: “{group}\{#XojoAppName}”; Filename: “{app}\{#XojoAppName}.exe”;
Name: “{commonprograms}\{#XojoAppName}”; Filename: “{app}\{#XojoAppName}.exe”;
Name: “{commondesktop}\{#XojoAppName}”; Filename: “{app}\{#XojoAppName}.exe”; Tasks: desktopicon;
; Give the user the option to run the app after the installation is finished.
[Run]
Filename: “{app}\{#XojoAppName}.exe”; Description: “{cm:LaunchProgram,{#XojoAppName}}”; Flags: nowait postinstall skipifsilent
; This specifies the Visual C++ Windows Runtime Redistributable to also install because
; it is required by Xojo apps made with 2016r1 or later.
[Files]
Source: “.\Windows Universal Runtime\Installers\VC_redist.x64.exe”; DestDir: {tmp}
[Run]
Filename: {tmp}\VC_redist.x64.exe; Parameters: “/install /quiet /norestart”; StatusMsg: “Installing 64-bit Windows Universal runtime…”; Flags: waituntilterminated; Check: InstallVCRuntime();
[Code]
// Most of this code is pinched from https://stackoverflow.com/questions/2000296/inno-setup-how-to-automatically-uninstall-previous-installed-version
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant(‘Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting(“AppId”)}_is1’);
sUnInstallString := ‘’;
if not RegQueryStringValue(HKLM, sUnInstPath, ‘UninstallString’, sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, ‘UninstallString’, sUnInstallString);
Result := sUnInstallString;
end;
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> ‘’);
end;
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
// Return Values:
// 1 - uninstall string is empty
// 2 - error executing the UnInstallString
// 3 - successfully executed the UnInstallString
// default return value
Result := 0;
// get the uninstall string of the old app
sUnInstallString := GetUninstallString();
if sUnInstallString <> ‘’ then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, ‘/SILENT /NORESTART /SUPPRESSMSGBOXES’,’’, SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
// Uninstalling old version doesn’t seem to be necessary unless app file structure has changed
// procedure CurStepChanged(CurStep: TSetupStep);
// begin
// if (CurStep=ssInstall) then
// begin
// if (IsUpgrade()) then
// begin
// UnInstallOldVersion();
// end;
// end;
// end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False;
if ((PageID = wpLicense) or (PageID = wpSelectTasks)) and (IsUpgrade()) then
begin
Result := True;
end;
end;
// Adapted from https://stackoverflow.com/questions/3618257/is-it-possible-to-accept-custom-command-line-parameters-with-inno-setup
{ Check if there is a command-line parameter “kajuupdate=true” }
function IsKajuUpdate(): Boolean;
begin
Result := False;
if ExpandConstant(’{param:kajuupdate|false}’) = ‘true’ then
Result := True;
end;
{ If installer is being run by a kaju update then user already has VCRuntime }
function InstallVCRuntime(): Boolean;
begin
Result := not IsKajuUpdate();
end;
// Adapted from https://stackoverflow.com/questions/28763220/inno-setup-delete-the-installer-after-the-install-process
procedure CurStepChanged(CurStep: TSetupStep);
var
strContent: String;
intErrorCode: Integer;
strSelf_Delete_BAT: String;
begin
if (CurStep=ssDone) and (IsKajuUpdate()) then
begin
// strContent := ‘:try_delete’ + #13 + #10 +
// ‘del "’ + ExpandConstant(’{srcexe}’) + ‘"’ + #13 + #10 +
// ‘if exist "’ + ExpandConstant(’{srcexe}’) + ‘" goto try_delete’ + #13 + #10 +
// ‘del %0’;
strContent := 'for /L %%a in (1,1,100) do (' + #13 + #10 +
'del "' + ExpandConstant('{srcexe}') + '"' + #13 + #10 +
'if not exist "' + ExpandConstant('{srcexe}') + '" goto end_loop' + #13 + #10 +
')' + #13 + #10 +
':end_loop' + #13 + #10 +
'del %0';
strSelf_Delete_BAT := ExtractFilePath(ExpandConstant('{tmp}')) + 'SelfDelete.bat';
SaveStringToFile(strSelf_Delete_BAT, strContent, False);
Exec(strSelf_Delete_BAT, '', '', SW_HIDE, ewNoWait, intErrorCode);
end;
end;