Files
DeskCore/Setup/Runtime.cs
T
M_ViperandClaude Opus 5.5 1ae6e1abc2 Fortschritt, .NET-Prüfung, Updates, Fenster merken, Box-Funktionen
- Große Kopien laufen im Hintergrund mit Fortschritt und Abbrechen;
  abgebrochene Kopien werden aufgeräumt, das Original bleibt unverändert
- Sichern/Wiederherstellen mit Fortschritt, keine halben ZIP-Dateien
- Installer auf .NET Framework 4.8: erkennt fehlende .NET 9 Desktop Runtime
  und installiert sie auf Wunsch (Microsoft-Signatur wird geprüft)
- Update-Hinweis über Gitea-Releases, Update mit automatischem Neustart
- Fenstergröße und -position werden gemerkt
- Desktop-Boxen: umbenennen, löschen, verschieben, herausziehen
- Fenster und Boxen kehren auf sichtbare Bildschirme zurück
- README ergänzt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 20:46:48 +02:00

170 lines
6.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>Prüft und installiert die von DeskCore benötigte "Microsoft .NET 9 Desktop Runtime".</summary>
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;
}
}
/// <summary>Lädt die Laufzeit von Microsoft und installiert sie (Windows fragt einmal nach Administratorrechten).</summary>
public static void Install(Action<double, string> 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<WINTRUST_FILE_INFO>(),
pcwszFilePath = file,
};
var pFile = Marshal.AllocHGlobal(Marshal.SizeOf<WINTRUST_FILE_INFO>());
try
{
Marshal.StructureToPtr(fileInfo, pFile, false);
var data = new WINTRUST_DATA
{
cbStruct = (uint)Marshal.SizeOf<WINTRUST_DATA>(),
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);
}