In this guide I will walk through how to get the .NET 5 runtime to download and install on-the-fly in an Inno Setup installer.
It works in 3 steps:
- Detect if the desired .NET runtime is installed
- Download the .NET Runtime bootstrap installer with Inno Download Plugin
- Run the bootstrap installer in quiet mode, which will download and install the .NET Framework. This is better than downloading the full installer since it only downloads the files it needs for your platform.
You can download the regular .NET Runtime or the .NET Desktop Runtime, which supports Windows desktop applications. If you install the Desktop Runtime you do not need to also install the normal .NET Runtime.
Here’s the full code I’m using, with the x64 .NET 5 Desktop Runtime:
#include <idp.iss>
// Other parts of installer file go here
[CustomMessages]
IDP_DownloadFailed=Download of .NET 5 failed. .NET 5 Desktop runtime is required to run VidCoder.
IDP_RetryCancel=Click 'Retry' to try downloading the files again, or click 'Cancel' to terminate setup.
InstallingDotNetRuntime=Installing .NET 5 Desktop Runtime. This might take a few minutes...
DotNetRuntimeFailedToLaunch=Failed to launch .NET Runtime Installer with error "%1". Please fix the error then run this installer again.
DotNetRuntimeFailed1602=.NET Runtime installation was cancelled. This installation can continue, but be aware that this application may not run unless the .NET Runtime installation is completed successfully.
DotNetRuntimeFailed1603=A fatal error occurred while installing the .NET Runtime. Please fix the error, then run the installer again.
DotNetRuntimeFailed5100=Your computer does not meet the requirements of the .NET Runtime. Please consult the documentation.
DotNetRuntimeFailedOther=The .NET Runtime installer exited with an unexpected status code "%1". Please review any other messages shown by the installer to determine whether the installation completed successfully, and abort this installation and fix the problem if it did not.
[Code]
var
requiresRestart: boolean;
function CompareVersion(V1, V2: string): Integer;
var
P, N1, N2: Integer;
begin
Result := 0;
while (Result = 0) and ((V1 <> '') or (V2 <> '')) do
begin
P := Pos('.', V1);
if P > 0 then
begin
N1 := StrToInt(Copy(V1, 1, P - 1));
Delete(V1, 1, P);
end
else
if V1 <> '' then
begin
N1 := StrToInt(V1);
V1 := '';
end
else
begin
N1 := 0;
end;
P := Pos('.', V2);
if P > 0 then
begin
N2 := StrToInt(Copy(V2, 1, P - 1));
Delete(V2, 1, P);
end
else
if V2 <> '' then
begin
N2 := StrToInt(V2);
V2 := '';
end
else
begin
N2 := 0;
end;
if N1 < N2 then Result := -1
else
if N1 > N2 then Result := 1;
end;
end;
function NetRuntimeIsMissing(): Boolean;
var
runtimes: TArrayOfString;
registryKey: string;
I: Integer;
meetsMinimumVersion: Boolean;
meetsMaximumVersion: Boolean;
minimumVersion: string;
maximumExclusiveVersion: string;
begin
Result := True;
minimumVersion := '5.0.0';
maximumExclusiveVersion := '5.1.0';
registryKey := 'SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App';
if RegGetValueNames(HKLM, registryKey, runtimes) then
begin
for I := 0 to GetArrayLength(runtimes)-1 do
begin
meetsMinimumVersion := not (CompareVersion(runtimes[I], minimumVersion) = -1);
meetsMaximumVersion := CompareVersion(runtimes[I], maximumExclusiveVersion) = -1;
if meetsMinimumVersion and meetsMaximumVersion then
begin
Log(Format('[.NET] Selecting %s', [runtimes[I]]));
Result := False;
Exit;
end;
end;
end;
end;
procedure InitializeWizard;
begin
if NetRuntimeIsMissing() then
begin
idpAddFile('http://go.microsoft.com/fwlink/?linkid=2155258', ExpandConstant('{tmp}\NetRuntimeInstaller.exe'));
idpDownloadAfter(wpReady);
end;
end;
function InstallDotNetRuntime(): String;
var
StatusText: string;
ResultCode: Integer;
begin
StatusText := WizardForm.StatusLabel.Caption;
WizardForm.StatusLabel.Caption := CustomMessage('InstallingDotNetRuntime');
WizardForm.ProgressGauge.Style := npbstMarquee;
try
if not Exec(ExpandConstant('{tmp}\NetRuntimeInstaller.exe'), '/passive /norestart /showrmui /showfinalerror', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
begin
Result := FmtMessage(CustomMessage('DotNetRuntimeFailedToLaunch'), [SysErrorMessage(resultCode)]);
end
else
begin
// See https://msdn.microsoft.com/en-us/library/ee942965(v=vs.110).aspx#return_codes
case resultCode of
0: begin
// Successful
end;
1602 : begin
MsgBox(CustomMessage('DotNetRuntimeFailed1602'), mbInformation, MB_OK);
end;
1603: begin
Result := CustomMessage('DotNetRuntimeFailed1603');
end;
1641: begin
requiresRestart := True;
end;
3010: begin
requiresRestart := True;
end;
5100: begin
Result := CustomMessage('DotNetRuntimeFailed5100');
end;
else begin
MsgBox(FmtMessage(CustomMessage('DotNetRuntimeFailedOther'), [IntToStr(resultCode)]), mbError, MB_OK);
end;
end;
end;
finally
WizardForm.StatusLabel.Caption := StatusText;
WizardForm.ProgressGauge.Style := npbstNormal;
DeleteFile(ExpandConstant('{tmp}\NetRuntimeInstaller.exe'));
end;
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
// 'NeedsRestart' only has an effect if we return a non-empty string, thus aborting the installation.
// If the installers indicate that they want a restart, this should be done at the end of installation.
// Therefore we set the global 'restartRequired' if a restart is needed, and return this from NeedRestart()
if NetRuntimeIsMissing() then
begin
Result := InstallDotNetRuntime();
end;
end;
function NeedRestart(): Boolean;
begin
Result := requiresRestart;
end;
Detecting if the desired .NET Runtime is installed
Antoine Aflalo has found the registry key location to check to see what versions of the .NET runtime are installed.
Check SOFTWARE\dotnet\Setup\InstalledVersions\x86\sharedfx
on x86 or SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx
on x64. Use Microsoft.WindowsDesktop.App
for the Desktop Runtime and Microsoft.NETCore.App
for the regular runtime.
I am checking SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App
.
We are checking for a version installed >= 5.0.0 and < 5.1.0. Having .NET 6 installed would not run apps built for the .NET 5 Runtime.
Downloading the bootstrapper
First you need to locate the correct download link for the installer. This will depend on if you want the x86 or x64 version, what version you’re installing, and whether you want the normal or Desktop runtime. The Inno Dependency Installer source code has a good listing of stable links; I haven’t been able to find an official list yet.
You could also use the Inno Dependency Installer instead, though I like having slimmer installer code for just the framework I want to install, and I think checking the registry is more efficient than including another executable to run and check the version.
Now install Inno Download Plugin. This will make the idpAddFile and idpDownloadAfter calls inside InitializeWizard work. Remember that you need #include <idp.iss>
at the top of your file to bring this in.
You will also need to add a line to the end of C:\Program Files (x86)\Inno Setup 6\ISPPBuiltins.iss
. The Inno Download Plugin installer tries to do it but hasn’t been updated in a while and is still trying to update Inno Setup 5.
#pragma include __INCLUDE__ + ";" + "C:\Program Files (x86)\Inno Download Plugin"
This “InitializeWizard” method is a special one that InnoSetup calls when first setting up its wizard pages. We call our helper function to detect if the framework is installed, then schedule a download step after the “Ready to install” page. We include our direct download link determined earlier, and save it in a temp folder, with a file name of “NetRuntimeInstaller.exe”. This name I picked arbitrarily; we just need to refer to it later when we’re installing and cleaning up.
Installing the bootstrapper
Our code is activated on PrepareToInstall. When this function returns a string, that string is shown as an error message that stops the install from happening. We call into InstallDotNetRuntime and return its result.
Inside InstallDotNetRuntime we’re running the bootstrapper we downloaded earlier, with a flag to make the install passive (non interactive). The /showrmui option prompts the user to close applications to avoid a system restart. /showfinalerror tells the installer to show an error message if the install fails.
The .NET installer UI will show on top of the first window:
After running the installer, the bootstrapper is deleted (whether or not the install succeeded).
And now your installer is done!
Testing the installer
I would recommend setting up a Windows 10 VM in Hyper-V to test it out. You can go to “Apps and Features” and sort by install date to uninstall your app and the .NET Runtime to repeat the testing.
Thanks to Antony Male for suggesting updates to the error handling code.
Hi, great work! I’d like to modify this to work with .Net 6 – is there anything I need to tweak other than message text, the min & max version strings, and download link? (The reg key is still correct, and I can see my 6.x listed there).
Also, there are numerous places in the script that refer to the filename “NetRuntimeInstaller.exe”. Should this match the name of the downloaded file as I’m a little confused because the .Net 5 download link in your script gets a file called “windowsdesktop-runtime-5.0.3-win-x64”. Is it because MS have changed the filename at their end?
NetRuntimeInstaller.exe is the local file name it uses when it is downloading the bootstrapper from the URL. It doesn’t need to change, and it doesn’t need to match the suggested file name that the browser gives you when you download. It will just run that same file later. I think the other tweaks sound fine.
I won’t be updating this guide though since I’ve switched to Clowd.Squirrel.