App Deployment#

Static vs Shared#

When deploying your app, you must choose whether you want to use Static or Shared libraries.
The static deployment means you will have to pack all required libraries along with your program, creating a standalone executable. The cons will be its increased size, and the need to rebuild the app when an update to one of the library is made.
The shared libraries will create a smaller executable, but will require the user to have the libraries installed on his machine. Plus, incompatibilities may found their ways between the libraries revisions.

Qt documentation will guide you through the build process following the platform you are working on.

Static deployment (Windows)#

Qt offers a tool to bundle the Qt-related dependencies into the application folder. Extra steps are required to transform them into an installation package.

It is named windeployqt and found under QtFolder/bin/windeployqt
  1. Build your project under Release mode

  2. Open a command prompt (requires privileges if Qt files are inside a protected path)
    • cd qtfolder/5.x.x/msvc*/bin (or add it to path to use with a command prompt anywhere)

  3. Windeployqt takes as argument the directory where your .exe lies, or the .exe directly. It will scan for the dependencies.
    • windeployqt "path\to\exe"

    • Qt may fail finding some of your dependencies. Try to run the program and, if needed, add them manually next to your .exe.

    • You can use CFF Explorer to select your .exe and have a view on required .dll files and their path. Sadly, no automatic system exists to copy the required files automatically. Also, the list of dependencies may not include everything (some .dll will require other .dll …).

    Main Window Structure
  4. It will gather the required Qt dependencies into given folder. You can select an app, a library and a plugin directory for a cleaner work tree :
    • windeployqt "path\to\exe" -dir "path\to\distrib" -libdir "path\to\distrib\lib" -plugindir "path\to\distrib\plugin"

    • If you modify the plugin dir, you may need to add it to PATH, since your app won’t know where the files are now.

You need to test your folder with a new, clean Windows image. It may run on your machine, but not on another as some .dll are not linked in the PATH.
Under Windows, the quickest and simplest way is to run Windows Sandbox.

Turn it into an installer#

To get a truly portable application, we need to create an installer that will copy the application and its dependencies on the user’s PC.
We will use InnoSetup QSP.
  1. Install it

  2. Launch Inno Setup Compiler and select Create a new empty script file using the Script Wizard

  3. Follow the Wizard to create the script
    • When getting on the App Files window, add the folders created with windeployqt, when asked say Yes for recursive folders and files inclusion.

    App Files - add files
    • After adding them, click on them -> Edit -> and set the Destination Subfolder to correspond to subfolder name

    App Files - relocate files
  4. Finish the Wizard, then you will have your script that can be edited

    Deployment script
  5. If you compile then run, your setup.exe file is created and you will be able to install your program

    Installer

License .rtf#

If you are working with Qt, the following license file is the (L)GPL for open-source projects.
In InnoSetup, link it with the command LicenseFile=path\to\license\license.rtf under [SETUP] tag.
You also must ensure to comply with the licenses from the libraries you are using.

Add a dependency installer#

Sometimes, you may need to embed dependencies as .exe files.
The following script propose the installation of your runnable and, if cancelled, will prevent the installation of your app.
You can modify it for your own files (see DetectAndInstallPrerequisites function, along with the [Files] section to include the source file) :
  1#define MyAppName "Slicer"
  2#define MyAppVersion "1.0"
  3#define MyAppPublisher "SPL Group"
  4#define MyAppURL "https://www.hevs.ch/"
  5#define MyAppExeName "Slicer.exe"
  6#define MyAppAssocName "Point Cloud Samples"
  7#define MyAppAssocExt ".pcs"
  8#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
  9
 10[Setup]
 11; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
 12; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
 13AppId={{547814E9-1BD4-4A91-9511-9FEF1892A9EF}
 14AppName={#MyAppName}
 15AppVersion={#MyAppVersion}
 16;AppVerName={#MyAppName} {#MyAppVersion}
 17AppPublisher={#MyAppPublisher}
 18AppPublisherURL={#MyAppURL}
 19AppSupportURL={#MyAppURL}
 20AppUpdatesURL={#MyAppURL}
 21DefaultDirName={autopf}\{#MyAppName}
 22ChangesAssociations=yes
 23DefaultGroupName={#MyAppName}
 24AllowNoIcons=yes
 25; Uncomment the following line to run in non administrative install mode (install for current user only.)
 26;PrivilegesRequired=lowest
 27OutputDir=C:\Users\Axel\Desktop\slicer_qt\install
 28OutputBaseFilename=slicer
 29Compression=lzma
 30SolidCompression=yes
 31WizardStyle=modern
 32
 33[Languages]
 34Name: "english"; MessagesFile: "compiler:Default.isl"
 35Name: "french"; MessagesFile: "compiler:Languages\French.isl"
 36Name: "german"; MessagesFile: "compiler:Languages\German.isl"
 37
 38[Tasks]
 39Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
 40
 41[Files]
 42Source: "C:\Users\Axel\Desktop\slicer_qt\bin\deploy\*"; DestDir: "{tmp}"; Flags: dontcopy
 43Source: "C:\Users\Axel\Desktop\slicer_qt\bin\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
 44Source: "C:\Users\Axel\Desktop\slicer_qt\bin\deploy\qt\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 45Source: "C:\Users\Axel\Desktop\slicer_qt\bin\dlls\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 46; NOTE: Don't use "Flags: ignoreversion" on any shared system files
 47
 48[Registry]
 49Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
 50Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
 51Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
 52Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
 53Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: ""
 54
 55[Icons]
 56Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
 57Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
 58
 59[Run]
 60Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
 61
 62[Code]
 63const
 64QuitMessageReboot = 'The installation needs to be restarted';
 65QuitMessageError = 'Prerequistes installation aborted';
 66
 67function GetString(const x86, x64: String): String;
 68begin
 69if IsX64 then begin
 70    Result := x64;
 71end else begin
 72    Result := x86;
 73end;
 74end;
 75
 76function CompareVersion(const Version1, Version2: String): Integer;
 77var
 78Position, Number1, Number2: Integer;
 79begin
 80Result := 0;
 81while (Version1 <> '') or (Version2 <> '') do begin
 82    Position := Pos('.', Version1);
 83    if Position > 0 then begin
 84    Number1 := StrToIntDef(Copy(Version1, 1, Position - 1), 0);
 85    Delete(Version1, 1, Position);
 86    end else if Version1 <> '' then begin
 87    Number1 := StrToIntDef(Version1, 0);
 88    Version1 := '';
 89    end else begin
 90    Number1 := 0;
 91    end;
 92    Position := Pos('.', Version2);
 93    if Position > 0 then begin
 94    Number2 := StrToIntDef(Copy(Version2, 1, Position - 1), 0);
 95    Delete(Version2, 1, Position);
 96    end else if Version2 <> '' then begin
 97    Number2 := StrToIntDef(Version2, 0);
 98    Version2 := '';
 99    end else begin
100    Number2 := 0;
101    end;
102    if Number1 < Number2 then begin
103    Result := -1;
104    break;
105    end else if Number1 > Number2 then begin
106    Result := 1;
107    break;
108    end;
109end;
110end;
111
112function MsiEnumRelatedProducts(UpgradeCode: String; Reserved, Index: DWORD; ProductCode: String): Integer;
113external 'MsiEnumRelatedProductsW@msi.dll stdcall';
114function MsiGetProductInfo(ProductCode, PropertyName, Value: String; var ValueSize: DWORD): Integer;
115external 'MsiGetProductInfoW@msi.dll stdcall';
116function IsMsiProductInstalled(const UpgradeCode, MinVersion: String): Boolean;
117var
118ProductCode, Version: String;
119ValueSize: DWORD;
120begin
121SetLength(ProductCode, 39);
122Result := False;
123if MsiEnumRelatedProducts(UpgradeCode, 0, 0, ProductCode) = 0 then begin
124    SetLength(Version, 39);
125    ValueSize := Length(Version);
126    if MsiGetProductInfo(ProductCode, 'VersionString', Version, ValueSize) = 0 then begin
127    Result := CompareVersion(Version, MinVersion) >= 0;
128    end;
129end;
130end;
131
132
133(*** Install the prerequistes ***)
134function DetectAndInstallPrerequisites: Boolean;
135var
136ResultCode: Integer;
137InstallOk: Boolean;
138begin
139Result := True;
140
141(*** Check VC_REDIST ***)
142    (*** Check if already exists, else install ***)
143// https://support.microsoft.com/en-US/help/2977003/the-latest-supported-visual-c-downloads
144if not IsMsiProductInstalled(GetString('{65E5BD06-6392-3027-8C26-853107D3CF1A}', '{36F68A90-239C-34DF-B58C-64B30153CE35}'), '14.28.29325') then
145begin
146    (*** Does not exists - install ***)
147    ExtractTemporaryFile('vcredist2015_2017_2019_x64.exe');
148    InstallOk := Exec(ExpandConstant('{tmp}\vcredist2015_2017_2019_x64.exe'), '-install "' + ExpandConstant('{tmp}') + '"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
149    (*** Result code gives indication if install succeeded - try with message boxes to determine ok codes ***)
150    if not ResultCode = 0 then
151    begin
152    Result := False
153    exit;
154    end;
155end;
156if not IsMsiProductInstalled(GetString('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), '10.0.40219') then
157begin
158    (*** Does not exists - install ***)
159    ExtractTemporaryFile('vcredist2010_x64.exe');
160    InstallOk := Exec(ExpandConstant('{tmp}\vcredist2010_x64.exe'), '-install "' + ExpandConstant('{tmp}') + '"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
161    (*** Result code gives indication if install succeeded - try with message boxes to determine ok codes ***)
162    if not ResultCode = 0 then
163    begin
164    Result := False
165    exit;
166    end;
167end;
168
169    (*** To display a message to the user : MsgBox('Code value : ' + IntToStr(ResultCode) + ' / install NOT Ok', mbError, MB_OK); ***)
170end;
171
172(*** Callback when install will be launched ***)
173function PrepareToInstall(var NeedsRestart: Boolean): String;
174var
175ChecksumBefore, ChecksumAfter: String;
176begin
177ChecksumBefore := MakePendingFileRenameOperationsChecksum;
178(*** Check prerequistes ***)
179if DetectAndInstallPrerequisites then begin
180    ChecksumAfter := MakePendingFileRenameOperationsChecksum;
181    if ChecksumBefore <> ChecksumAfter then begin
182    NeedsRestart := True;
183    Result := QuitMessageReboot;
184    end;
185end else
186    Result := QuitMessageError;
187end;

Add downloadable dependencies#

Sometimes, you may need to fetch dependencies from internet (e.g. the VC Redistributables files to reduce your installer size).
The following script propose an automatic internet downloading and installation of dependencies such as VC Redistributables, that you can enable by uncommenting the corresponding #define.
You can modify it for your own files (see InitializeSetup function) :
  1// comment out dependency defines to disable installing them
  2//#define UseMsi45
  3
  4//#define UseDotNet11
  5//#define UseDotNet20
  6//#define UseDotNet35
  7//#define UseDotNet40Client
  8//#define UseDotNet40Full
  9//#define UseDotNet45
 10//#define UseDotNet46
 11//#define UseDotNet47
 12//#define UseDotNet48
 13
 14// requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
 15//#define UseNetCoreCheck
 16#ifdef UseNetCoreCheck
 17#define UseNetCore31
 18#define UseNetCore31Asp
 19#define UseNetCore31Desktop
 20#define UseDotNet50
 21#define UseDotNet50Asp
 22#define UseDotNet50Desktop
 23#endif
 24
 25#define UseMsiProductCheck
 26#ifdef UseMsiProductCheck
 27//#define UseVC2005
 28//#define UseVC2008
 29#define UseVC2010
 30//#define UseVC2012
 31//#define UseVC2013
 32#define UseVC2015To2019
 33#endif
 34
 35// requires dxwebsetup.exe (see download link below)
 36//#define UseDirectX
 37
 38//#define UseSql2008Express
 39//#define UseSql2012Express
 40//#define UseSql2014Express
 41//#define UseSql2016Express
 42//#define UseSql2017Express
 43//#define UseSql2019Express
 44
 45#define MyAppName "Slicer"
 46#define MyAppVersion "1.0"
 47#define MyAppPublisher "SPL Group"
 48#define MyAppURL "https://www.hevs.ch/"
 49#define MyAppExeName "Slicer.exe"
 50#define MyAppAssocName "Point Cloud Samples"
 51#define MyAppAssocExt ".pcs"
 52#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
 53
 54[Setup]
 55; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
 56; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
 57AppId={{547814E9-1BD4-4A91-9511-9FEF1892A9EF}
 58AppName={#MyAppName}
 59AppVersion={#MyAppVersion}
 60;AppVerName={#MyAppName} {#MyAppVersion}
 61AppPublisher={#MyAppPublisher}
 62AppPublisherURL={#MyAppURL}
 63AppSupportURL={#MyAppURL}
 64AppUpdatesURL={#MyAppURL}
 65DefaultDirName={autopf}\{#MyAppName}
 66ChangesAssociations=yes
 67DefaultGroupName={#MyAppName}
 68AllowNoIcons=yes
 69; Uncomment the following line to run in non administrative install mode (install for current user only.)
 70;PrivilegesRequired=lowest
 71OutputDir=C:\Users\Axel\Desktop\slicer_qt\install
 72OutputBaseFilename=slicer
 73Compression=lzma
 74SolidCompression=yes
 75WizardStyle=modern
 76
 77// remove next line if you only deploy 32-bit binaries and dependencies
 78ArchitecturesInstallIn64BitMode=x64
 79
 80[Languages]
 81Name: "english"; MessagesFile: "compiler:Default.isl"
 82Name: "french"; MessagesFile: "compiler:Languages\French.isl"
 83Name: "german"; MessagesFile: "compiler:Languages\German.isl"
 84
 85[Tasks]
 86Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
 87
 88[Files]
 89//Source: "C:\Users\Axel\Desktop\slicer_qt\bin\deploy\*"; DestDir: "{tmp}"; Flags: dontcopy
 90Source: "C:\Users\Axel\Desktop\slicer_qt\bin\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
 91Source: "C:\Users\Axel\Desktop\slicer_qt\bin\deploy\qt\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 92Source: "C:\Users\Axel\Desktop\slicer_qt\bin\dlls\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 93; NOTE: Don't use "Flags: ignoreversion" on any shared system files
 94
 95[Registry]
 96Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
 97Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
 98Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
 99Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
100Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: ""
101
102[Icons]
103Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
104Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
105
106[Run]
107Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
108
109// shared code for installing the dependencies
110[Code]
111// types and variables
112type
113TDependency = record
114    Filename: String;
115    Parameters: String;
116    Title: String;
117    URL: String;
118    Checksum: String;
119    ForceSuccess: Boolean;
120    InstallClean: Boolean;
121    RebootAfter: Boolean;
122end;
123InstallResult = (InstallSuccessful, InstallRebootRequired, InstallError);
124var
125MemoInstallInfo: String;
126Dependencies: array of TDependency;
127DelayedReboot, ForceX86: Boolean;
128DownloadPage: TDownloadWizardPage;
129procedure AddDependency(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, InstallClean, RebootAfter: Boolean);
130var
131Dependency: TDependency;
132I: Integer;
133begin
134MemoInstallInfo := MemoInstallInfo + #13#10 + '%1' + Title;
135Dependency.Filename := Filename;
136Dependency.Parameters := Parameters;
137Dependency.Title := Title;
138if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin
139    Dependency.URL := '';
140end else begin
141    Dependency.URL := URL;
142end;
143Dependency.Checksum := Checksum;
144Dependency.ForceSuccess := ForceSuccess;
145Dependency.InstallClean := InstallClean;
146Dependency.RebootAfter := RebootAfter;
147I := GetArrayLength(Dependencies);
148SetArrayLength(Dependencies, I + 1);
149Dependencies[I] := Dependency;
150end;
151function IsPendingReboot: Boolean;
152var
153Value: String;
154begin
155Result := RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'PendingFileRenameOperations', Value) or
156    (RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'SetupExecute', Value) and (Value <> ''));
157end;
158function InstallProducts: InstallResult;
159var
160ResultCode, I, ProductCount: Integer;
161begin
162Result := InstallSuccessful;
163ProductCount := GetArrayLength(Dependencies);
164MemoInstallInfo := SetupMessage(msgReadyMemoTasks);
165if ProductCount > 0 then begin
166    DownloadPage.Show;
167    for I := 0 to ProductCount - 1 do begin
168    if Dependencies[I].InstallClean and (DelayedReboot or IsPendingReboot) then begin
169        Result := InstallRebootRequired;
170        break;
171    end;
172    DownloadPage.SetText(Dependencies[I].Title, '');
173    DownloadPage.SetProgress(I + 1, ProductCount);
174    while True do begin
175        ResultCode := 0;
176        if ShellExec('', ExpandConstant('{tmp}{\}') + Dependencies[I].Filename, Dependencies[I].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin
177        if Dependencies[I].RebootAfter then begin
178            // delay reboot after install if we installed the last dependency anyways
179            if I = ProductCount - 1 then begin
180            DelayedReboot := True;
181            end else begin
182            Result := InstallRebootRequired;
183            MemoInstallInfo := Dependencies[I].Title;
184            end;
185            break;
186        end else if (ResultCode = 0) or Dependencies[I].ForceSuccess then begin
187            break;
188        end else if ResultCode = 3010 then begin
189            // Windows Installer ResultCode 3010: ERROR_SUCCESS_REBOOT_REQUIRED
190            DelayedReboot := True;
191            break;
192        end;
193        end;
194        case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependencies[I].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
195        IDABORT: begin
196            Result := InstallError;
197            MemoInstallInfo := MemoInstallInfo + #13#10 + '      ' + Dependencies[I].Title;
198            break;
199        end;
200        IDIGNORE: begin
201            MemoInstallInfo := MemoInstallInfo + #13#10 + '      ' + Dependencies[I].Title;
202            break;
203        end;
204        end;
205    end;
206    if Result <> InstallSuccessful then begin
207        break;
208    end;
209    end;
210    DownloadPage.Hide;
211end;
212end;
213// Inno Setup event functions
214procedure InitializeWizard;
215begin
216DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
217end;
218function PrepareToInstall(var NeedsRestart: Boolean): String;
219begin
220DelayedReboot := False;
221case InstallProducts of
222    InstallError: begin
223    Result := MemoInstallInfo;
224    end;
225    InstallRebootRequired: begin
226    Result := MemoInstallInfo;
227    NeedsRestart := True;
228    // write into the registry that the installer needs to be executed again after restart
229    RegWriteStringValue(HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InstallBootstrap', ExpandConstant('{srcexe}'));
230    end;
231end;
232end;
233function NeedRestart: Boolean;
234begin
235Result := DelayedReboot;
236end;
237function UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
238begin
239Result := '';
240if MemoUserInfoInfo <> '' then begin
241    Result := Result + MemoUserInfoInfo + Newline + NewLine;
242end;
243if MemoDirInfo <> '' then begin
244    Result := Result + MemoDirInfo + Newline + NewLine;
245end;
246if MemoTypeInfo <> '' then begin
247    Result := Result + MemoTypeInfo + Newline + NewLine;
248end;
249if MemoComponentsInfo <> '' then begin
250    Result := Result + MemoComponentsInfo + Newline + NewLine;
251end;
252if MemoGroupInfo <> '' then begin
253    Result := Result + MemoGroupInfo + Newline + NewLine;
254end;
255if MemoTasksInfo <> '' then begin
256    Result := Result + MemoTasksInfo;
257end;
258if MemoInstallInfo <> '' then begin
259    if MemoTasksInfo = '' then begin
260    Result := Result + SetupMessage(msgReadyMemoTasks);
261    end;
262    Result := Result + FmtMessage(MemoInstallInfo, [Space]);
263end;
264end;
265function NextButtonClick(const CurPageID: Integer): Boolean;
266var
267I, ProductCount: Integer;
268Retry: Boolean;
269begin
270Result := True;
271if (CurPageID = wpReady) and (MemoInstallInfo <> '') then begin
272    DownloadPage.Show;
273    ProductCount := GetArrayLength(Dependencies);
274    for I := 0 to ProductCount - 1 do begin
275    if Dependencies[I].URL <> '' then begin
276        DownloadPage.Clear;
277        DownloadPage.Add(Dependencies[I].URL, Dependencies[I].Filename, Dependencies[I].Checksum);
278        Retry := True;
279        while Retry do begin
280        Retry := False;
281        try
282            DownloadPage.Download;
283        except
284            if GetExceptionMessage = SetupMessage(msgErrorDownloadAborted) then begin
285            Result := False;
286            I := ProductCount;
287            end else begin
288            case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
289                IDABORT: begin
290                Result := False;
291                I := ProductCount;
292                end;
293                IDRETRY: begin
294                Retry := True;
295                end;
296            end;
297            end;
298        end;
299        end;
300    end;
301    end;
302    DownloadPage.Hide;
303end;
304end;
305// architecture helper functions
306function IsX64: Boolean;
307begin
308Result := not ForceX86 and Is64BitInstallMode;
309end;
310function GetString(const x86, x64: String): String;
311begin
312if IsX64 then begin
313    Result := x64;
314end else begin
315    Result := x86;
316end;
317end;
318function GetArchitectureSuffix: String;
319begin
320Result := GetString('', '_x64');
321end;
322function GetArchitectureTitle: String;
323begin
324Result := GetString(' (x86)', ' (x64)');
325end;
326function CompareVersion(const Version1, Version2: String): Integer;
327var
328Position, Number1, Number2: Integer;
329begin
330Result := 0;
331while (Version1 <> '') or (Version2 <> '') do begin
332    Position := Pos('.', Version1);
333    if Position > 0 then begin
334    Number1 := StrToIntDef(Copy(Version1, 1, Position - 1), 0);
335    Delete(Version1, 1, Position);
336    end else if Version1 <> '' then begin
337    Number1 := StrToIntDef(Version1, 0);
338    Version1 := '';
339    end else begin
340    Number1 := 0;
341    end;
342    Position := Pos('.', Version2);
343    if Position > 0 then begin
344    Number2 := StrToIntDef(Copy(Version2, 1, Position - 1), 0);
345    Delete(Version2, 1, Position);
346    end else if Version2 <> '' then begin
347    Number2 := StrToIntDef(Version2, 0);
348    Version2 := '';
349    end else begin
350    Number2 := 0;
351    end;
352    if Number1 < Number2 then begin
353    Result := -1;
354    break;
355    end else if Number1 > Number2 then begin
356    Result := 1;
357    break;
358    end;
359end;
360end;
361#ifdef UseNetCoreCheck
362// source code: https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
363function IsNetCoreInstalled(const Version: String): Boolean;
364var
365ResultCode: Integer;
366begin
367if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureSuffix + '.exe') then begin
368    ExtractTemporaryFile('netcorecheck' + GetArchitectureSuffix + '.exe');
369end;
370Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureSuffix + '.exe', Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
371end;
372#endif
373#ifdef UseMsiProductCheck
374function MsiEnumRelatedProducts(UpgradeCode: String; Reserved, Index: DWORD; ProductCode: String): Integer;
375external 'MsiEnumRelatedProductsW@msi.dll stdcall';
376function MsiGetProductInfo(ProductCode, PropertyName, Value: String; var ValueSize: DWORD): Integer;
377external 'MsiGetProductInfoW@msi.dll stdcall';
378function IsMsiProductInstalled(const UpgradeCode, MinVersion: String): Boolean;
379var
380ProductCode, Version: String;
381ValueSize: DWORD;
382begin
383SetLength(ProductCode, 39);
384Result := False;
385if MsiEnumRelatedProducts(UpgradeCode, 0, 0, ProductCode) = 0 then begin
386    SetLength(Version, 39);
387    ValueSize := Length(Version);
388    if MsiGetProductInfo(ProductCode, 'VersionString', Version, ValueSize) = 0 then begin
389    Result := CompareVersion(Version, MinVersion) >= 0;
390    end;
391end;
392end;
393#endif
394
395function InitializeSetup: Boolean;
396var
397Version: String;
398begin
399#ifdef UseMsi45
400// https://www.microsoft.com/en-US/download/details.aspx?id=8483
401if not GetVersionNumbersString(ExpandConstant('{sys}{\}msi.dll'), Version) or (CompareVersion(Version, '4.5') < 0) then begin
402    AddDependency('msi45' + GetArchitectureSuffix + '.msu',
403    '/quiet /norestart',
404    'Windows Installer 4.5',
405    GetString('https://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/Windows6.0-KB942288-v2-x86.msu', 'https://download.microsoft.com/download/2/6/1/261fca42-22c0-4f91-9451-0e0f2e08356d/Windows6.0-KB942288-v2-x64.msu'),
406    '', False, False, False);
407end;
408#endif
409#ifdef UseDotNet11
410// https://www.microsoft.com/en-US/download/details.aspx?id=26
411if not IsDotNetInstalled(net11, 0) then begin
412    AddDependency('dotnetfx11.exe',
413    '/q',
414    '.NET Framework 1.1',
415    'https://download.microsoft.com/download/a/a/c/aac39226-8825-44ce-90e3-bf8203e74006/dotnetfx.exe',
416    '', False, False, False);
417end;
418// https://www.microsoft.com/en-US/download/details.aspx?id=33
419if not IsDotNetInstalled(net11, 1) then begin
420    AddDependency('dotnetfx11sp1.exe',
421    '/q',
422    '.NET Framework 1.1 Service Pack 1',
423    'https://download.microsoft.com/download/8/b/4/8b4addd8-e957-4dea-bdb8-c4e00af5b94b/NDP1.1sp1-KB867460-X86.exe',
424    '', False, False, False);
425end;
426#endif
427#ifdef UseDotNet20
428// https://www.microsoft.com/en-US/download/details.aspx?id=1639
429if not IsDotNetInstalled(net20, 2) then begin
430    AddDependency('dotnetfx20' + GetArchitectureSuffix + '.exe',
431    '/lang:enu /passive /norestart',
432    '.NET Framework 2.0 Service Pack 2',
433    GetString('https://download.microsoft.com/download/c/6/e/c6e88215-0178-4c6c-b5f3-158ff77b1f38/NetFx20SP2_x86.exe', 'https://download.microsoft.com/download/c/6/e/c6e88215-0178-4c6c-b5f3-158ff77b1f38/NetFx20SP2_x64.exe'),
434    '', False, False, False);
435end;
436#endif
437#ifdef UseDotNet35
438// https://www.microsoft.com/en-US/download/details.aspx?id=22
439if not IsDotNetInstalled(net35, 1) then begin
440    AddDependency('dotnetfx35.exe',
441    '/lang:enu /passive /norestart',
442    '.NET Framework 3.5 Service Pack 1',
443    'https://download.microsoft.com/download/0/6/1/061f001c-8752-4600-a198-53214c69b51f/dotnetfx35setup.exe',
444    '', False, False, False);
445end;
446#endif
447#ifdef UseDotNet40Client
448// https://www.microsoft.com/en-US/download/details.aspx?id=24872
449if not IsDotNetInstalled(net4client, 0) and not IsDotNetInstalled(net4full, 0) then begin
450    AddDependency('dotNetFx40_Client_setup.exe',
451    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
452    '.NET Framework 4.0 Client',
453    'https://download.microsoft.com/download/7/B/6/7B629E05-399A-4A92-B5BC-484C74B5124B/dotNetFx40_Client_setup.exe',
454    '', False, False, False);
455end;
456#endif
457#ifdef UseDotNet40Full
458// https://www.microsoft.com/en-US/download/details.aspx?id=17718
459if not IsDotNetInstalled(net4full, 0) then begin
460    AddDependency('dotNetFx40_Full_setup.exe',
461    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
462    '.NET Framework 4.0',
463    'https://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe',
464    '', False, False, False);
465end;
466#endif
467#ifdef UseDotNet45
468// https://www.microsoft.com/en-US/download/details.aspx?id=42643
469if not IsDotNetInstalled(net452, 0) then begin
470    AddDependency('dotnetfx45.exe',
471    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
472    '.NET Framework 4.5.2',
473    'https://download.microsoft.com/download/B/4/1/B4119C11-0423-477B-80EE-7A474314B347/NDP452-KB2901954-Web.exe',
474    '', False, False, False);
475end;
476#endif
477#ifdef UseDotNet46
478// https://www.microsoft.com/en-US/download/details.aspx?id=53345
479if not IsDotNetInstalled(net462, 0) then begin
480    AddDependency('dotnetfx46.exe',
481    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
482    '.NET Framework 4.6.2',
483    'https://download.microsoft.com/download/D/5/C/D5C98AB0-35CC-45D9-9BA5-B18256BA2AE6/NDP462-KB3151802-Web.exe',
484    '', False, False, False);
485end;
486#endif
487#ifdef UseDotNet47
488// https://support.microsoft.com/en-US/help/4054531
489if not IsDotNetInstalled(net472, 0) then begin
490    AddDependency('dotnetfx47.exe',
491    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
492    '.NET Framework 4.7.2',
493    'https://download.microsoft.com/download/0/5/C/05C1EC0E-D5EE-463B-BFE3-9311376A6809/NDP472-KB4054531-Web.exe',
494    '', False, False, False);
495end;
496#endif
497#ifdef UseDotNet48
498// https://dotnet.microsoft.com/download/dotnet-framework/net48
499if not IsDotNetInstalled(net48, 0) then begin
500    AddDependency('dotnetfx48.exe',
501    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
502    '.NET Framework 4.8',
503    'https://download.visualstudio.microsoft.com/download/pr/7afca223-55d2-470a-8edc-6a1739ae3252/c9b8749dd99fc0d4453b2a3e4c37ba16/ndp48-web.exe',
504    '', False, False, False);
505end;
506#endif
507#ifdef UseNetCore31
508// https://dotnet.microsoft.com/download/dotnet-core/3.1
509if not IsNetCoreInstalled('Microsoft.NETCore.App 3.1.12') then begin
510    AddDependency('netcore31' + GetArchitectureSuffix + '.exe',
511    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
512    '.NET Core Runtime 3.1.12' + GetArchitectureTitle,
513    GetString('https://go.microsoft.com/fwlink/?linkid=2155262', 'https://go.microsoft.com/fwlink/?linkid=2155351'),
514    '', False, False, False);
515end;
516#endif
517#ifdef UseNetCore31Asp
518// https://dotnet.microsoft.com/download/dotnet-core/3.1
519if not IsNetCoreInstalled('Microsoft.AspNetCore.App 3.1.12') then begin
520    AddDependency('netcore31asp' + GetArchitectureSuffix + '.exe',
521    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
522    'ASP.NET Core Runtime 3.1.12' + GetArchitectureTitle,
523    GetString('https://go.microsoft.com/fwlink/?linkid=2155260', 'https://go.microsoft.com/fwlink/?linkid=2155349'),
524    '', False, False, False);
525end;
526#endif
527#ifdef UseNetCore31Desktop
528// https://dotnet.microsoft.com/download/dotnet-core/3.1
529if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 3.1.12') then begin
530    AddDependency('netcore31desktop' + GetArchitectureSuffix + '.exe',
531    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
532    '.NET Desktop Runtime 3.1.12' + GetArchitectureTitle,
533    GetString('https://go.microsoft.com/fwlink/?linkid=2155261', 'https://go.microsoft.com/fwlink/?linkid=2155350'),
534    '', False, False, False);
535end;
536#endif
537#ifdef UseDotNet50
538// https://dotnet.microsoft.com/download/dotnet/5.0
539if not IsNetCoreInstalled('Microsoft.NETCore.App 5.0.3') then begin
540    AddDependency('dotnet50' + GetArchitectureSuffix + '.exe',
541    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
542    '.NET Runtime 5.0.3' + GetArchitectureTitle,
543    GetString('https://go.microsoft.com/fwlink/?linkid=2155348', 'https://go.microsoft.com/fwlink/?linkid=2155259'),
544    '', False, False, False);
545end;
546#endif
547#ifdef UseDotNet50Asp
548// https://dotnet.microsoft.com/download/dotnet/5.0
549if not IsNetCoreInstalled('Microsoft.AspNetCore.App 5.0.3') then begin
550    AddDependency('dotnet50asp' + GetArchitectureSuffix + '.exe',
551    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
552    'ASP.NET Core Runtime 5.0.3' + GetArchitectureTitle,
553    GetString('https://go.microsoft.com/fwlink/?linkid=2155346', 'https://go.microsoft.com/fwlink/?linkid=2155257'),
554    '', False, False, False);
555end;
556#endif
557#ifdef UseDotNet50Desktop
558// https://dotnet.microsoft.com/download/dotnet/5.0
559if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.3') then begin
560    AddDependency('dotnet50desktop' + GetArchitectureSuffix + '.exe',
561    '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
562    '.NET Desktop Runtime 5.0.3' + GetArchitectureTitle,
563    GetString('https://go.microsoft.com/fwlink/?linkid=2155347', 'https://go.microsoft.com/fwlink/?linkid=2155258'),
564    '', False, False, False);
565end;
566#endif
567#ifdef UseVC2005
568// https://www.microsoft.com/en-US/download/details.aspx?id=26347
569if not IsMsiProductInstalled(GetString('{86C9D5AA-F00C-4921-B3F2-C60AF92E2844}', '{A8D19029-8E5C-4E22-8011-48070F9E796E}'), '8.0.61000') then begin
570    AddDependency('vcredist2005' + GetArchitectureSuffix + '.exe',
571    '/q',
572    'Visual C++ 2005 Service Pack 1 Redistributable' + GetArchitectureTitle,
573    GetString('https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE', 'https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x64.EXE'),
574    '', False, False, False);
575end;
576#endif
577#ifdef UseVC2008
578// https://www.microsoft.com/en-US/download/details.aspx?id=26368
579if not IsMsiProductInstalled(GetString('{DE2C306F-A067-38EF-B86C-03DE4B0312F9}', '{FDA45DDF-8E17-336F-A3ED-356B7B7C688A}'), '9.0.30729.6161') then begin
580    AddDependency('vcredist2008' + GetArchitectureSuffix + '.exe',
581    '/q',
582    'Visual C++ 2008 Service Pack 1 Redistributable' + GetArchitectureTitle,
583    GetString('https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe', 'https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x64.exe'),
584    '', False, False, False);
585end;
586#endif
587#ifdef UseVC2010
588// https://www.microsoft.com/en-US/download/details.aspx?id=26999
589if not IsMsiProductInstalled(GetString('{1F4F1D2A-D9DA-32CF-9909-48485DA06DD5}', '{5B75F761-BAC8-33BC-A381-464DDDD813A3}'), '10.0.40219') then begin
590    AddDependency('vcredist2010' + GetArchitectureSuffix + '.exe',
591    '/passive /norestart',
592    'Visual C++ 2010 Service Pack 1 Redistributable' + GetArchitectureTitle,
593    GetString('https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x64.exe'),
594    '', False, False, False);
595end;
596#endif
597#ifdef UseVC2012
598// https://www.microsoft.com/en-US/download/details.aspx?id=30679
599if not IsMsiProductInstalled(GetString('{4121ED58-4BD9-3E7B-A8B5-9F8BAAE045B7}', '{EFA6AFA1-738E-3E00-8101-FD03B86B29D1}'), '11.0.61030') then begin
600    AddDependency('vcredist2012' + GetArchitectureSuffix + '.exe',
601    '/passive /norestart',
602    'Visual C++ 2012 Update 4 Redistributable' + GetArchitectureTitle,
603    GetString('https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe', 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe'),
604    '', False, False, False);
605end;
606#endif
607#ifdef UseVC2013
608//ForceX86 := True; // force 32-bit install of next dependencies
609// https://support.microsoft.com/en-US/help/4032938
610if not IsMsiProductInstalled(GetString('{B59F5BF1-67C8-3802-8E59-2CE551A39FC5}', '{20400CF0-DE7C-327E-9AE4-F0F38D9085F8}'), '12.0.40664') then begin
611    AddDependency('vcredist2013' + GetArchitectureSuffix + '.exe',
612    '/passive /norestart',
613    'Visual C++ 2013 Update 5 Redistributable' + GetArchitectureTitle,
614    GetString('https://download.visualstudio.microsoft.com/download/pr/10912113/5da66ddebb0ad32ebd4b922fd82e8e25/vcredist_x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/10912041/cee5d6bca2ddbcd039da727bf4acb48a/vcredist_x64.exe'),
615    '', False, False, False);
616end;
617//ForceX86 := False; // disable forced 32-bit install again
618#endif
619#ifdef UseVC2015To2019
620// https://support.microsoft.com/en-US/help/2977003/the-latest-supported-visual-c-downloads
621if not IsMsiProductInstalled(GetString('{65E5BD06-6392-3027-8C26-853107D3CF1A}', '{36F68A90-239C-34DF-B58C-64B30153CE35}'), '14.28.29325') then begin
622    AddDependency('vcredist2019' + GetArchitectureSuffix + '.exe',
623    '/passive /norestart',
624    'Visual C++ 2015-2019 Redistributable' + GetArchitectureTitle,
625    GetString('https://aka.ms/vs/16/release/vc_redist.x86.exe', 'https://aka.ms/vs/16/release/vc_redist.x64.exe'),
626    '', False, False, False);
627end;
628#endif
629#ifdef UseDirectX
630// https://www.microsoft.com/en-US/download/details.aspx?id=35
631ExtractTemporaryFile('dxwebsetup.exe');
632AddDependency('dxwebsetup.exe',
633    '/q',
634    'DirectX Runtime',
635    'https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe',
636    '', True, False, False);
637#endif
638#ifdef UseSql2008Express
639// https://www.microsoft.com/en-US/download/details.aspx?id=30438
640if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or (CompareVersion(Version, '10.50.4000') < 0) then begin
641    AddDependency('sql2008express' + GetArchitectureSuffix + '.exe',
642    '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
643    'SQL Server 2008 R2 Service Pack 2 Express',
644    GetString('https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/0/4/B/04BE03CD-EAF3-4797-9D8D-2E08E316C998/SQLEXPR_x64_ENU.exe'),
645    '', False, False, False);
646end;
647#endif
648#ifdef UseSql2012Express
649// https://www.microsoft.com/en-US/download/details.aspx?id=56042
650if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or (CompareVersion(Version, '11.0.7001') < 0) then begin
651    AddDependency('sql2012express' + GetArchitectureSuffix + '.exe',
652    '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
653    'SQL Server 2012 Service Pack 4 Express',
654    GetString('https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/B/D/E/BDE8FAD6-33E5-44F6-B714-348F73E602B6/SQLEXPR_x64_ENU.exe'),
655    '', False, False, False);
656end;
657#endif
658#ifdef UseSql2014Express
659// https://www.microsoft.com/en-US/download/details.aspx?id=57473
660if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or (CompareVersion(Version, '12.0.6024') < 0) then begin
661    AddDependency('sql2014express' + GetArchitectureSuffix + '.exe',
662    '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
663    'SQL Server 2014 Service Pack 3 Express',
664    GetString('https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR32_x86_ENU.exe', 'https://download.microsoft.com/download/3/9/F/39F968FA-DEBB-4960-8F9E-0E7BB3035959/SQLEXPR_x64_ENU.exe'),
665    '', False, False, False);
666end;
667#endif
668#ifdef UseSql2016Express
669// https://www.microsoft.com/en-US/download/details.aspx?id=56840
670if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or (CompareVersion(Version, '13.0.5026') < 0) then begin
671    AddDependency('sql2016express' + GetArchitectureSuffix + '.exe',
672    '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
673    'SQL Server 2016 Service Pack 2 Express',
674    'https://download.microsoft.com/download/3/7/6/3767D272-76A1-4F31-8849-260BD37924E4/SQLServer2016-SSEI-Expr.exe',
675    '', False, False, False);
676end;
677#endif
678#ifdef UseSql2017Express
679// https://www.microsoft.com/en-US/download/details.aspx?id=55994
680if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or (CompareVersion(Version, '14') < 0) then begin
681    AddDependency('sql2017express' + GetArchitectureSuffix + '.exe',
682    '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
683    'SQL Server 2017 Express',
684    'https://download.microsoft.com/download/5/E/9/5E9B18CC-8FD5-467E-B5BF-BADE39C51F73/SQLServer2017-SSEI-Expr.exe',
685    '', False, False, False);
686end;
687#endif
688#ifdef UseSql2019Express
689// https://www.microsoft.com/en-US/download/details.aspx?id=101064
690if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQLServer\CurrentVersion', 'CurrentVersion', Version) or (CompareVersion(Version, '15') < 0) then begin
691    AddDependency('sql2019express' + GetArchitectureSuffix + '.exe',
692    '/QS /IACCEPTSQLSERVERLICENSETERMS /ACTION=INSTALL /FEATURES=SQL /INSTANCENAME=MSSQLSERVER',
693    'SQL Server 2019 Express',
694    'https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe',
695    '', False, False, False);
696end;
697#endif
698Result := True;
699end;

QT Pascal