Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 18 additions & 28 deletions DotnetRuntimeBootstrapper.AppHost.Core/BootstrapperBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,38 +111,28 @@ IPrerequisite[] missingPrerequisites

private int Run(TargetAssembly targetAssembly, string[] args)
{
try
{
// Hot path: attempt to run the target first without any checks
return targetAssembly.Run(args);
}
// Possible exception causes:
// - .NET host not found (DirectoryNotFoundException)
// - .NET host failed to initialize (BootstrapperException)
catch
{
// Check for missing prerequisites and install them
var missingPrerequisites = targetAssembly.GetMissingPrerequisites();
if (missingPrerequisites.Any())
{
var isReadyToRun = PromptAndInstall(targetAssembly, missingPrerequisites);
// need to check the prerequisites first, otherwise the application may start but fail later
// e.g. VS runtime 2019 are installed, but app needs 2022
// e.g. .NET 8.0.4 is installed but 8.0.20 is desired
// both conditions does not prevent starting the app, but is not what the dev desired.
Comment on lines +114 to +117
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the use case but the design philosophy was to ensure that the bootstrapper imposes minimal startup overhead on the app. If we check for prerequisites before each launch, we'd be delaying it.

.NET 8.0.4 is installed but 8.0.20 is desired

This shouldn't be an issue if the user specifies a minimum runtime version.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Tyrrrz,
I understand your reasoning, and it works fine with setting the minimum runtime version in the project.
However, this does not work for the VS runtimes, where VS 2022 is required for some native dependencies, but only VS 2019 is installed. So starting the .NET app will succeed, but the application will still fail.
This also applies to the KB updates you have added. If an acceptable .NET runtime is present, those prerequisites don't get installed.

And as long as the IsInstalled checks do not require a download, their impact on startup time should be negligible.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be more comfortable if the forced check would be an off-by-default option. Even though the delay is negligible, it can still be impactful in some scenarios, such as CLI apps that are ran in quick succession.


// User did not accept the installation or reboot is required
if (!isReadyToRun)
return 0xB007;
// Check for missing prerequisites and install them
var missingPrerequisites = targetAssembly.GetMissingPrerequisites();
if (!missingPrerequisites.Any())
return targetAssembly.Run(args);
Comment on lines +119 to +122
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation. Line 122 uses tabs while the surrounding code uses spaces. Should maintain consistent indentation style.

Copilot uses AI. Check for mistakes.

// Reset the environment to update PATH and other variables
// that may have been changed by the installation process.
EnvironmentEx.RefreshEnvironmentVariables();
var isReadyToRun = PromptAndInstall(targetAssembly, missingPrerequisites);
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation. This line and subsequent lines use tabs while the surrounding code uses spaces. Should maintain consistent indentation style.

Copilot uses AI. Check for mistakes.

// Attempt to run the target again
return targetAssembly.Run(args);
}
// User did not accept the installation or reboot is required
if (!isReadyToRun)
return 0xB007;

// There are no missing prerequisites to install, meaning that the
// app failed to run for reasons unrelated to the bootstrapper.
throw;
}
// Reset the environment to update PATH and other variables
// that may have been changed by the installation process.
EnvironmentEx.RefreshEnvironmentVariables();

// Attempt to run the target
return targetAssembly.Run(args);
}

public int Run(string[] args)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using DotnetRuntimeBootstrapper.AppHost.Core.Platform;
using DotnetRuntimeBootstrapper.AppHost.Core.Utils;
using DotnetRuntimeBootstrapper.AppHost.Core.Utils.Extensions;
Expand All @@ -8,25 +9,35 @@ namespace DotnetRuntimeBootstrapper.AppHost.Core.Prerequisites;

internal class VisualCppPrerequisite : IPrerequisite
{
public string DisplayName => "Visual C++ Redistributable 2015-2019";
public string DisplayName => "Visual C++ Redistributable 2015-2022";

public bool IsInstalled() =>
Registry.LocalMachine.ContainsSubKey(
public bool IsInstalled()
{
var registryKey = Registry.LocalMachine.OpenSubKey(
(
OperatingSystemEx.ProcessorArchitecture.Is64Bit()
? "SOFTWARE\\Wow6432Node\\"
: "SOFTWARE\\"
)
+ "Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\"
+ OperatingSystemEx.ProcessorArchitecture.GetMoniker()
+ OperatingSystemEx.ProcessorArchitecture.GetMoniker(),
false
);

if (registryKey == null)
return false;

// than check if the minor version is ok for 2022
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar error in comment. Should be 'then' instead of 'than'.

Suggested change
// than check if the minor version is ok for 2022
// then check if the minor version is ok for 2022

Copilot uses AI. Check for mistakes.
var minorVersion = Convert.ToInt32(registryKey.GetValue("Minor", 0));
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetValue can return null, which will cause Convert.ToInt32 to throw an exception. The registry value should be validated before conversion.

Suggested change
var minorVersion = Convert.ToInt32(registryKey.GetValue("Minor", 0));
var minorValue = registryKey.GetValue("Minor", 0);
int minorVersion = 0;
if (minorValue is int i)
minorVersion = i;
else if (minorValue is string s && int.TryParse(s, out var parsed))
minorVersion = parsed;
// else minorVersion remains 0

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number 44 lacks explanation. Add a comment explaining why this specific minor version is required for VS 2022 compatibility.

Suggested change
var minorVersion = Convert.ToInt32(registryKey.GetValue("Minor", 0));
var minorVersion = Convert.ToInt32(registryKey.GetValue("Minor", 0));
// Visual Studio 2022 requires at least minor version 44 of the VC++ runtime.
// See: https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist

Copilot uses AI. Check for mistakes.
return minorVersion >= 44;
}

public IPrerequisiteInstaller DownloadInstaller(Action<double>? handleProgress)
{
var fileName = $"VC_redist.{OperatingSystemEx.ProcessorArchitecture.GetMoniker()}.exe";
var filePath = FileEx.GenerateTempFilePath(fileName);

Http.DownloadFile($"https://aka.ms/vs/16/release/{fileName}", filePath, handleProgress);
Http.DownloadFile($"https://aka.ms/vs/17/release/{fileName}", filePath, handleProgress);

return new ExecutablePrerequisiteInstaller(this, filePath);
}
Expand Down