diff --git a/Directory.Packages.props b/Directory.Packages.props index 0fff8254..13432064 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,8 +5,8 @@ $(NoWarn);NU1507 - - + + diff --git a/mRemoteNG/App/ProgramRoot.cs b/mRemoteNG/App/ProgramRoot.cs index ee9af1c1..848b4fe7 100644 --- a/mRemoteNG/App/ProgramRoot.cs +++ b/mRemoteNG/App/ProgramRoot.cs @@ -1,14 +1,20 @@ -using System; +using mRemoteNG.Config.Settings; +using mRemoteNG.DotNet.Update; +using mRemoteNG.UI.Forms; + +using System; using System.Diagnostics; using System.Drawing; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using System.Windows.Forms; -using mRemoteNG.Config.Settings; -using mRemoteNG.UI.Forms; -using System.Runtime.InteropServices; +using System.Threading.Tasks; +using mRemoteNG.DotNet.Update; + + namespace mRemoteNG.App { @@ -25,13 +31,48 @@ namespace mRemoteNG.App [STAThread] public static void Main(string[] args) { - Trace.WriteLine("!!!!!!=============== TEST ==================!!!!!!!!!!!!!"); - // Forcing to load System.Configuration.ConfigurationManager before any other assembly to be able to check settings try { - string assemblyFile = "System.Configuration.ConfigurationManager" + ".dll"; - string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile); + // FIX: Awaited Task synchronously to obtain bool result + bool isInstalled = DotNetRuntimeCheck + .IsDotnetRuntimeInstalled(DotNetRuntimeCheck.RequiredDotnetVersion) + .GetAwaiter() + .GetResult(); + if (!isInstalled) + { + 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", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } catch { + // Ignore UI issues + } + + Environment.Exit(1); + return; + } + + 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 ==================!!!!!!!!!!!!!"); + try + { + string assemblyFile = "System.Configuration.ConfigurationManager.dll"; + string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile); if (File.Exists(assemblyPath)) { @@ -40,13 +81,11 @@ namespace mRemoteNG.App } catch (FileNotFoundException ex) { - Trace.WriteLine("Error occured: " + ex.Message); + Trace.WriteLine("Error occured: " + ex.Message); } - //Subscribe to AssemblyResolve event AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; - //Check if needed runtime is installed if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { string runtimeVersion = RuntimeInformation.FrameworkDescription; @@ -66,12 +105,9 @@ namespace mRemoteNG.App Console.WriteLine("This application requires the .NET Desktop Runtime 9.0.2 on Windows."); } - - - //Check if local settings DB exist or accessible CheckLockalDB(); - Lazy singleInstanceOption = new Lazy(() => Properties.OptionsStartupExitPage.Default.SingleInstance); + Lazy singleInstanceOption = new(() => Properties.OptionsStartupExitPage.Default.SingleInstance); if (singleInstanceOption.Value) { @@ -87,20 +123,20 @@ 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(); @@ -114,7 +150,6 @@ namespace mRemoteNG.App Rectangle viewport = targetScreen.WorkingArea; _frmSplashScreen.Top = viewport.Top; _frmSplashScreen.Left = viewport.Left; - // normally it should be screens[1] however due DPI apply 1 size "same" as default with 100% _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; @@ -168,14 +203,13 @@ namespace mRemoteNG.App private static void CatchAllUnhandledExceptions() { - System.Windows.Forms.Application.ThreadException += ApplicationOnThreadException; - System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + Application.ThreadException += ApplicationOnThreadException; + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException; } private static void ApplicationOnThreadException(object sender, ThreadExceptionEventArgs e) { - // if (PresentationSource.FromVisual(FrmSplashScreenNew)) FrmSplashScreenNew.GetInstance().Close(); if (FrmMain.Default.IsDisposed) return; @@ -186,13 +220,8 @@ namespace mRemoteNG.App private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e) { - //TODO: Check if splash closed properly - //if (!FrmSplashScreenNew.GetInstance().IsDisposed) - // FrmSplashScreenNew.GetInstance().Close(); - FrmUnhandledException window = new(e.ExceptionObject as Exception, e.IsTerminating); window.ShowDialog(FrmMain.Default); } - } } \ No newline at end of file diff --git a/mRemoteNG/App/Update/DotNetRuntimeCheck.cs b/mRemoteNG/App/Update/DotNetRuntimeCheck.cs new file mode 100644 index 00000000..019343a8 --- /dev/null +++ b/mRemoteNG/App/Update/DotNetRuntimeCheck.cs @@ -0,0 +1,66 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace mRemoteNG.DotNet.Update +{ + 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 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."); + } + } + /// + /// Checks if a specific version of the .NET runtime is installed by running `dotnet --list-runtimes`. + /// + public static async Task IsDotnetRuntimeInstalled(string version) + { + try + { + // Set up a process to run the 'dotnet' command. + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "--list-runtimes", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + + 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; + } + } + } //Check +}