mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-17 22:11:48 +08:00
.Net version detection fix
optimization
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using mRemoteNG.Config.Settings;
|
||||
using mRemoteNG.App.Update;
|
||||
using mRemoteNG.Config.Settings;
|
||||
using mRemoteNG.DotNet.Update;
|
||||
using mRemoteNG.DotNet.Update;
|
||||
using mRemoteNG.UI.Forms;
|
||||
|
||||
@@ -10,9 +12,8 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using System.Threading.Tasks;
|
||||
using mRemoteNG.DotNet.Update;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
||||
|
||||
@@ -21,102 +22,85 @@ namespace mRemoteNG.App
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static class ProgramRoot
|
||||
{
|
||||
private static Mutex _mutex;
|
||||
private static Mutex? _mutex;
|
||||
private static FrmSplashScreenNew _frmSplashScreen = null;
|
||||
private static string customResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
private static System.Threading.Thread? _wpfSplashThread;
|
||||
private static FrmSplashScreenNew? _wpfSplash;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
// Ensure the real entry point is definitely STA
|
||||
MainAsync(args).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static Task MainAsync(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||||
|
||||
string? installedVersion = DotNetRuntimeCheck.GetLatestDotNetRuntimeVersion();
|
||||
|
||||
if (InternetConnection.IsPosible())
|
||||
{
|
||||
// FIX: Awaited Task<bool> synchronously to obtain bool result
|
||||
bool isInstalled = DotNetRuntimeCheck
|
||||
.IsDotnetRuntimeInstalled(DotNetRuntimeCheck.RequiredDotnetVersion)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
var (latestRuntimeVersion, downloadUrl) = DotNetRuntimeCheck.GetLatestAvailableDotNetVersionAsync().GetAwaiter().GetResult();
|
||||
|
||||
if (!isInstalled)
|
||||
if (string.IsNullOrEmpty(installedVersion))
|
||||
{
|
||||
Trace.WriteLine($".NET Desktop Runtime {DotNetRuntimeCheck.RequiredDotnetVersion} is NOT installed.");
|
||||
Trace.WriteLine("Please download and install it from:");
|
||||
Trace.WriteLine("https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.8-windows-x64-installer");
|
||||
|
||||
try
|
||||
{
|
||||
MessageBox.Show(
|
||||
$".NET Desktop Runtime {DotNetRuntimeCheck.RequiredDotnetVersion} is required.\n" +
|
||||
"The application will now exit.\n\nDownload:\nhttps://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.8-windows-x64-installer",
|
||||
"Missing .NET Runtime",
|
||||
_ = MessageBox.Show(
|
||||
$".NET Desktop Runtime at least {DotNetRuntimeCheck.RequiredDotnetVersion}.0 is required.\n" +
|
||||
"The application will now exit.\n\nPlease download and install latest desktop runtime:\n" + downloadUrl,
|
||||
"Missing .NET " + DotNetRuntimeCheck.RequiredDotnetVersion + " Runtime",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
} catch {
|
||||
// Ignore UI issues
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(fileName: downloadUrl) { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Unable to open download link: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
Environment.Exit(1);
|
||||
return;
|
||||
catch { }
|
||||
}
|
||||
|
||||
Trace.WriteLine($".NET Desktop Runtime {DotNetRuntimeCheck.RequiredDotnetVersion} is installed.");
|
||||
} catch (Exception ex) {
|
||||
Trace.WriteLine("Runtime check failed: " + ex);
|
||||
Environment.Exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
Trace.WriteLine("!!!!!!=============== TEST ==================!!!!!!!!!!!!!");
|
||||
Lazy<bool> singleInstanceOption = new(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
|
||||
if (singleInstanceOption.Value)
|
||||
StartApplicationAsSingleInstance();
|
||||
else
|
||||
StartApplication();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Assembly resolve handler
|
||||
private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string assemblyFile = "System.Configuration.ConfigurationManager.dll";
|
||||
string assemblyName = new AssemblyName(args.Name).Name ?? string.Empty;
|
||||
if (assemblyName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
|
||||
return null;
|
||||
|
||||
string assemblyFile = assemblyName + ".dll";
|
||||
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
return Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
catch
|
||||
{
|
||||
Trace.WriteLine("Error occured: " + ex.Message);
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
string runtimeVersion = RuntimeInformation.FrameworkDescription;
|
||||
if (runtimeVersion.Contains(".NET 9.0.2"))
|
||||
{
|
||||
Console.WriteLine(".NET Desktop Runtime 9.0.2 is already installed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(".NET Desktop Runtime 9.0.2 is not installed. Please download and install it from the following link:");
|
||||
Console.WriteLine("https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.2-windows-x64-installer");
|
||||
Console.WriteLine("After installation, please restart the application.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("This application requires the .NET Desktop Runtime 9.0.2 on Windows.");
|
||||
}
|
||||
|
||||
CheckLockalDB();
|
||||
|
||||
Lazy<bool> singleInstanceOption = new(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
|
||||
|
||||
if (singleInstanceOption.Value)
|
||||
{
|
||||
StartApplicationAsSingleInstance();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartApplication();
|
||||
// Suppress resolution exceptions; return null to continue standard probing
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CheckLockalDB()
|
||||
@@ -124,36 +108,13 @@ namespace mRemoteNG.App
|
||||
LocalDBManager settingsManager = new LocalDBManager(dbPath: "mRemoteNG.appSettings", useEncryption: false, schemaFilePath: "");
|
||||
}
|
||||
|
||||
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs resolveArgs)
|
||||
{
|
||||
string assemblyName = new AssemblyName(resolveArgs.Name).Name.Replace(".resources", string.Empty);
|
||||
string assemblyFile = assemblyName + ".dll";
|
||||
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
return Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void StartApplication()
|
||||
{
|
||||
CatchAllUnhandledExceptions();
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
_frmSplashScreen = FrmSplashScreenNew.GetInstance();
|
||||
|
||||
Screen targetScreen = Screen.PrimaryScreen;
|
||||
|
||||
Rectangle viewport = targetScreen.WorkingArea;
|
||||
_frmSplashScreen.Top = viewport.Top;
|
||||
_frmSplashScreen.Left = viewport.Left;
|
||||
_frmSplashScreen.Left = viewport.Left + (targetScreen.Bounds.Size.Width - _frmSplashScreen.Width) / 2;
|
||||
_frmSplashScreen.Top = viewport.Top + (targetScreen.Bounds.Size.Height - _frmSplashScreen.Height) / 2;
|
||||
_frmSplashScreen.ShowInTaskbar = false;
|
||||
_frmSplashScreen.Show();
|
||||
ShowSplashOnStaThread();
|
||||
|
||||
Application.Run(FrmMain.Default);
|
||||
}
|
||||
@@ -210,10 +171,8 @@ namespace mRemoteNG.App
|
||||
|
||||
private static void ApplicationOnThreadException(object sender, ThreadExceptionEventArgs e)
|
||||
{
|
||||
FrmSplashScreenNew.GetInstance().Close();
|
||||
|
||||
CloseSplash();
|
||||
if (FrmMain.Default.IsDisposed) return;
|
||||
|
||||
FrmUnhandledException window = new(e.Exception, false);
|
||||
window.ShowDialog(FrmMain.Default);
|
||||
}
|
||||
@@ -223,5 +182,38 @@ namespace mRemoteNG.App
|
||||
FrmUnhandledException window = new(e.ExceptionObject as Exception, e.IsTerminating);
|
||||
window.ShowDialog(FrmMain.Default);
|
||||
}
|
||||
|
||||
private static void ShowSplashOnStaThread()
|
||||
{
|
||||
_wpfSplashThread = new System.Threading.Thread(() =>
|
||||
{
|
||||
_wpfSplash = FrmSplashScreenNew.GetInstance();
|
||||
|
||||
// Center the splash screen on the primary screen before showing it
|
||||
_wpfSplash.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
|
||||
|
||||
_wpfSplash.ShowInTaskbar = false;
|
||||
_wpfSplash.Show();
|
||||
System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(_wpfSplash);
|
||||
System.Windows.Threading.Dispatcher.Run(); // WPF message loop
|
||||
})
|
||||
{ IsBackground = true };
|
||||
_wpfSplashThread.SetApartmentState(System.Threading.ApartmentState.STA);
|
||||
_wpfSplashThread.Start();
|
||||
}
|
||||
|
||||
private static void CloseSplash()
|
||||
{
|
||||
if (_wpfSplash != null)
|
||||
{
|
||||
_wpfSplash.Dispatcher.Invoke(() => _wpfSplash.Close());
|
||||
_wpfSplash = null;
|
||||
}
|
||||
if (_wpfSplashThread != null)
|
||||
{
|
||||
_wpfSplashThread.Join();
|
||||
_wpfSplashThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,107 @@
|
||||
using System;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace mRemoteNG.DotNet.Update
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class DotNetRuntimeCheck
|
||||
{
|
||||
public const string RequiredDotnetVersion = "9.0.8";
|
||||
public const string DotnetInstallerUrl = "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.8-windows-x64-installer";
|
||||
public const string DotnetInstallerFileName = "windowsdesktop-runtime-9.0.8-win-x64.exe";
|
||||
public const string RequiredDotnetVersion = "9.0";
|
||||
private const string ReleaseFeedUrl = "https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json";
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
if (await IsDotnetRuntimeInstalled(RequiredDotnetVersion))
|
||||
{
|
||||
Console.WriteLine($".NET Desktop Runtime {RequiredDotnetVersion} is installed. Launching application...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($".NET Desktop Runtime {RequiredDotnetVersion} is not installed.");
|
||||
}
|
||||
}
|
||||
#region Installed Version Check
|
||||
/// <summary>
|
||||
/// Checks if a specific version of the .NET runtime is installed by running `dotnet --list-runtimes`.
|
||||
/// Gets the installed .NET 9 runtime version if present
|
||||
/// </summary>
|
||||
public static async Task<bool> IsDotnetRuntimeInstalled(string version)
|
||||
/// <returns>The version string (e.g., "v9.0.0") or null if not found</returns>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string? GetLatestDotNetRuntimeVersion()
|
||||
{
|
||||
string[] registryPaths = new[]
|
||||
{
|
||||
@"SOFTWARE\dotnet\Setup\InstalledVersions\x86",
|
||||
@"SOFTWARE\dotnet\Setup\InstalledVersions\x64"
|
||||
};
|
||||
|
||||
foreach (string path in registryPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set up a process to run the 'dotnet' command.
|
||||
var process = new Process
|
||||
using RegistryKey? key = Registry.LocalMachine.OpenSubKey(path);
|
||||
if (key == null)
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the "sharedhost" subkey
|
||||
using (RegistryKey? sharedHostKey = key.OpenSubKey("sharedhost"))
|
||||
{
|
||||
if (sharedHostKey == null) {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Look for the "Version" value in sharedhost
|
||||
object? versionValue = sharedHostKey.GetValue("Version");
|
||||
if (versionValue != null)
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = "--list-runtimes",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
string? version = versionValue.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
// Read the output from the command.
|
||||
var output = await process.StandardOutput.ReadToEndAsync();
|
||||
process.WaitForExit();
|
||||
|
||||
// Check if the output contains the required runtime and version.
|
||||
// The format is typically: Microsoft.NETCore.App 9.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
|
||||
return output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Any(line => line.Trim().StartsWith($"Microsoft.NETCore.App {version}") ||
|
||||
line.Trim().StartsWith($"Microsoft.WindowsDesktop.App {version}"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Could not check .NET runtimes. Please ensure 'dotnet' is in your PATH. Error: {ex.Message}");
|
||||
return false;
|
||||
Console.WriteLine($"Error checking registry fallback: {ex.Message}");
|
||||
}
|
||||
}
|
||||
} //Check
|
||||
|
||||
return null;
|
||||
}
|
||||
#endregion Installed Version Check
|
||||
#region Latest Online Version Check
|
||||
public static async Task<(string latestRuntimeVersion, string downloadUrl)> GetLatestAvailableDotNetVersionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Add("User-Agent", "DotNetRuntimeChecker");
|
||||
|
||||
string jsonContent = await httpClient.GetStringAsync(ReleaseFeedUrl);
|
||||
JObject releasesIndex = JObject.Parse(jsonContent);
|
||||
|
||||
// Find the entry for .NET matching RequiredDotnetVersion
|
||||
JToken? dotnetEntry = releasesIndex["releases-index"]?.FirstOrDefault(entry => entry["channel-version"]?.ToString() == RequiredDotnetVersion);
|
||||
|
||||
if (dotnetEntry != null && dotnetEntry["latest-runtime"] != null)
|
||||
{
|
||||
string? latestRuntimeVersion = dotnetEntry["latest-runtime"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(latestRuntimeVersion))
|
||||
{
|
||||
// Construct the download URL using the latest version
|
||||
string downloadUrl = $"https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-{latestRuntimeVersion}-windows-x64-installer";
|
||||
return (latestRuntimeVersion, downloadUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return ("Unknown", "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error fetching latest version: {ex.Message}");
|
||||
return ("Unknown", "");
|
||||
}
|
||||
}
|
||||
#endregion Latest Online Version Check
|
||||
}
|
||||
}
|
||||
|
||||
26
mRemoteNG/App/Update/InternetConnection.cs
Normal file
26
mRemoteNG/App/Update/InternetConnection.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mRemoteNG.App.Update
|
||||
{
|
||||
public class InternetConnection
|
||||
{
|
||||
public static bool IsPosible()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(5);
|
||||
return client.GetAsync("https://www.microsoft.com").Result.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ using mRemoteNG.UI.Controls;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.Config.Settings.Registry;
|
||||
using System.Threading; // ADDED
|
||||
#endregion
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
@@ -43,7 +44,32 @@ namespace mRemoteNG.UI.Forms
|
||||
[SupportedOSPlatform("windows")]
|
||||
public partial class FrmMain
|
||||
{
|
||||
public static FrmMain Default { get; } = new FrmMain();
|
||||
// CHANGED: lazy, thread-safe, STA-enforced initialization
|
||||
private static readonly Lazy<FrmMain> s_default =
|
||||
new(InitializeOnSta, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
public static FrmMain Default => s_default.Value;
|
||||
|
||||
public static bool IsCreated => s_default.IsValueCreated;
|
||||
|
||||
private static FrmMain InitializeOnSta()
|
||||
{
|
||||
// Enforce STA to avoid OLE/WinForms threading violations
|
||||
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
|
||||
{
|
||||
// If we're already on a WinForms UI thread with a sync context, marshal to it
|
||||
if (SynchronizationContext.Current is WindowsFormsSynchronizationContext ctx)
|
||||
{
|
||||
FrmMain created = null;
|
||||
ctx.Send(_ => created = new FrmMain(), null);
|
||||
return created!;
|
||||
}
|
||||
|
||||
throw new ThreadStateException("FrmMain must be created on an STA thread.");
|
||||
}
|
||||
|
||||
return new FrmMain();
|
||||
}
|
||||
|
||||
private static ClipboardchangeEventHandler _clipboardChangedEvent;
|
||||
private bool _inSizeMove;
|
||||
@@ -208,7 +234,11 @@ namespace mRemoteNG.UI.Forms
|
||||
|
||||
pnlDock.ShowDocumentIcon = true;
|
||||
|
||||
FrmSplashScreenNew.GetInstance().Close();
|
||||
FrmSplashScreenNew splash = FrmSplashScreenNew.GetInstance();
|
||||
if (splash.Dispatcher.CheckAccess())
|
||||
splash.Close();
|
||||
else
|
||||
splash.Dispatcher.Invoke(() => splash.Close());
|
||||
|
||||
if (Properties.OptionsStartupExitPage.Default.StartMinimized)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user