using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32; namespace DeskCoreSetup; /// Prüft und installiert die von DeskCore benötigte "Microsoft .NET 9 Desktop Runtime". public static class Runtime { public const string DisplayName = "Microsoft .NET 9 Desktop Runtime"; public const int Major = 9; public const string DownloadPage = "https://dotnet.microsoft.com/de-de/download/dotnet/9.0"; // offizieller Microsoft-Kurzlink auf die jeweils aktuelle 9.0-Version private const string InstallerUrl = "https://aka.ms/dotnet/9.0/windowsdesktop-runtime-win-x64.exe"; public static bool Is64BitWindows => Environment.Is64BitOperatingSystem; public static bool IsInstalled { get { try { var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "shared", "Microsoft.WindowsDesktop.App"); if (Directory.Exists(dir) && Directory.GetDirectories(dir).Any(d => Path.GetFileName(d).StartsWith($"{Major}."))) return true; } catch { } try { using var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); using var k = hklm.OpenSubKey(@"SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App"); if (k?.GetValueNames().Any(n => n.StartsWith($"{Major}.")) == true) return true; } catch { } return false; } } /// Lädt die Laufzeit von Microsoft und installiert sie (Windows fragt einmal nach Administratorrechten). public static void Install(Action progress) { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; var file = Path.Combine(Path.GetTempPath(), $"windowsdesktop-runtime-{Major}-win-x64.exe"); using (var http = new HttpClient { Timeout = TimeSpan.FromMinutes(20) }) { http.DefaultRequestHeaders.UserAgent.ParseAdd("DeskCore-Setup"); using var resp = http.GetAsync(InstallerUrl, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); resp.EnsureSuccessStatusCode(); long total = resp.Content.Headers.ContentLength ?? 0; using var input = resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); using var output = File.Create(file); var buffer = new byte[81920]; long done = 0; int read; while ((read = input.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, read); done += read; progress(total > 0 ? (double)done / total : 0, total > 0 ? $"{DisplayName} wird heruntergeladen … {done / 1048576} von {total / 1048576} MB" : $"{DisplayName} wird heruntergeladen … {done / 1048576} MB"); } } // Sicherheit: nur ausführen, wenn die Datei gültig von Microsoft signiert ist if (!IsSignedByMicrosoft(file)) { try { File.Delete(file); } catch { } throw new InvalidOperationException("Die heruntergeladene Datei ist nicht gültig von Microsoft signiert und wurde nicht ausgeführt."); } progress(1, $"{DisplayName} wird installiert – bitte die Windows-Abfrage bestätigen …"); Process p; try { p = Process.Start(new ProcessStartInfo(file, "/install /quiet /norestart") { UseShellExecute = true, Verb = "runas" })!; } catch (Win32Exception ex) when (ex.NativeErrorCode == 1223) { throw new InvalidOperationException($"Die Installation von {DisplayName} wurde abgebrochen."); } p.WaitForExit(); try { File.Delete(file); } catch { } // 0 = ok, 3010/1641 = ok, Neustart empfohlen if (p.ExitCode is not (0 or 3010 or 1641) || !IsInstalled) throw new InvalidOperationException($"{DisplayName} konnte nicht installiert werden (Code {p.ExitCode})."); } // ---------- Signaturprüfung (WinVerifyTrust) ---------- private static bool IsSignedByMicrosoft(string file) { try { var subject = X509Certificate.CreateFromSignedFile(file).Subject; if (subject.IndexOf("O=Microsoft Corporation", StringComparison.OrdinalIgnoreCase) < 0) return false; } catch { return false; } return VerifyTrust(file); } private static bool VerifyTrust(string file) { var fileInfo = new WINTRUST_FILE_INFO { cbStruct = (uint)Marshal.SizeOf(), pcwszFilePath = file, }; var pFile = Marshal.AllocHGlobal(Marshal.SizeOf()); try { Marshal.StructureToPtr(fileInfo, pFile, false); var data = new WINTRUST_DATA { cbStruct = (uint)Marshal.SizeOf(), dwUIChoice = 2, // WTD_UI_NONE fdwRevocationChecks = 0, // WTD_REVOKE_NONE dwUnionChoice = 1, // WTD_CHOICE_FILE pFile = pFile, dwStateAction = 0, dwProvFlags = 0x40, // WTD_SAFER_FLAG }; var action = new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE"); // WINTRUST_ACTION_GENERIC_VERIFY_V2 return WinVerifyTrust(IntPtr.Zero, ref action, ref data) == 0; } finally { Marshal.FreeHGlobal(pFile); } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WINTRUST_FILE_INFO { public uint cbStruct; public string pcwszFilePath; public IntPtr hFile; public IntPtr pgKnownSubject; } [StructLayout(LayoutKind.Sequential)] private struct WINTRUST_DATA { public uint cbStruct; public IntPtr pPolicyCallbackData; public IntPtr pSIPClientData; public uint dwUIChoice; public uint fdwRevocationChecks; public uint dwUnionChoice; public IntPtr pFile; public uint dwStateAction; public IntPtr hWVTStateData; public IntPtr pwszURLReference; public uint dwProvFlags; public uint dwUIContext; public IntPtr pSignatureSettings; } [DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)] private static extern int WinVerifyTrust(IntPtr hwnd, ref Guid pgActionID, ref WINTRUST_DATA pWVTData); }