From b1ef0c4190ced89a014eeabc0335342e5d2d28b2 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sat, 3 Oct 2026 19:51:59 +0000 Subject: [PATCH] Upload via GUI (180 Dateien) --- Setup/App.xaml.cs | 154 +- Setup/Installer.cs | 776 ++++---- Setup/MainWindow.xaml | 366 ++-- Setup/MainWindow.xaml.cs | 558 +++--- .../win-x64/DeskCore-Setup.deps.json | 24 + .../net9.0-windows/win-x64/DeskCore-Setup.dll | Bin 0 -> 820736 bytes .../net9.0-windows/win-x64/DeskCore-Setup.exe | Bin 0 -> 233984 bytes .../win-x64/DeskCore-Setup.runtimeconfig.json | 20 + .../DeskCoreSetup.csproj.nuget.dgspec.json | 59 + Setup/obj/DeskCoreSetup.csproj.nuget.g.props | 15 + .../obj/DeskCoreSetup.csproj.nuget.g.targets | 6 + ...amework,Version=v4.8.AssemblyAttributes.cs | 4 + Setup/obj/Release/net48/App.baml | Bin 0 -> 1168 bytes Setup/obj/Release/net48/App.g.cs | 77 + Setup/obj/Release/net48/Colors.Dark.baml | Bin 0 -> 1917 bytes Setup/obj/Release/net48/Colors.Light.baml | Bin 0 -> 1917 bytes Setup/obj/Release/net48/Controls.baml | Bin 0 -> 13030 bytes Setup/obj/Release/net48/DeskCore-Setup.exe | Bin 0 -> 978944 bytes ...Core-Setup.exe.withSupportedRuntime.config | 6 + .../Release/net48/DeskCore-Setup.g.resources | Bin 0 -> 144089 bytes .../net48/DeskCore-Setup_MarkupCompile.cache | 20 + .../net48/DeskCoreSetup.AssemblyInfo.cs | 25 + .../DeskCoreSetup.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 14 + .../net48/DeskCoreSetup.GlobalUsings.g.cs | 6 + .../Release/net48/DeskCoreSetup.assets.cache | Bin 0 -> 532 bytes ...skCoreSetup.csproj.AssemblyReference.cache | Bin 0 -> 3947 bytes ...skCoreSetup.csproj.CoreCompileInputs.cache | 1 + .../DeskCoreSetup.csproj.FileListAbsolute.txt | 17 + Setup/obj/Release/net48/MainWindow.baml | Bin 0 -> 7224 bytes Setup/obj/Release/net48/MainWindow.g.cs | 556 ++++++ ...CoreApp,Version=v9.0.AssemblyAttributes.cs | 4 + .../Release/net9.0-windows/win-x64/App.baml | Bin 0 -> 1071 bytes Setup/obj/project.assets.json | 471 +++++ Setup/obj/project.nuget.cache | 11 + .../win-x64/DeskCore.GlobalUsings.g.cs | 6 + .../win-x64/DeskCore.genruntimeconfig.cache | 1 + .../win-x64/DeskCore_MarkupCompile.cache | 20 + .../net9.0-windows/win-x64/FenceWindow.baml | Bin 0 -> 3674 bytes .../net9.0-windows/win-x64/FenceWindow.g.cs | 305 +++ .../net9.0-windows/win-x64/MainWindow.baml | Bin 0 -> 26512 bytes .../net9.0-windows/win-x64/MainWindow.g.cs | 1697 +++++++++++++++++ .../win-x64/PublishOutputs.706123076a.txt | 2 + .../win-x64/Themes/Colors.Dark.baml | Bin 0 -> 1917 bytes .../win-x64/Themes/Colors.Light.baml | Bin 0 -> 1917 bytes .../win-x64/Themes/Controls.baml | Bin 0 -> 13030 bytes .../net9.0-windows/win-x64/ref/DeskCore.dll | Bin 0 -> 139776 bytes .../win-x64/refint/DeskCore.dll | Bin 0 -> 139776 bytes 48 files changed, 4295 insertions(+), 927 deletions(-) create mode 100644 Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.deps.json create mode 100644 Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.dll create mode 100644 Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.exe create mode 100644 Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.runtimeconfig.json create mode 100644 Setup/obj/DeskCoreSetup.csproj.nuget.dgspec.json create mode 100644 Setup/obj/DeskCoreSetup.csproj.nuget.g.props create mode 100644 Setup/obj/DeskCoreSetup.csproj.nuget.g.targets create mode 100644 Setup/obj/Release/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs create mode 100644 Setup/obj/Release/net48/App.baml create mode 100644 Setup/obj/Release/net48/App.g.cs create mode 100644 Setup/obj/Release/net48/Colors.Dark.baml create mode 100644 Setup/obj/Release/net48/Colors.Light.baml create mode 100644 Setup/obj/Release/net48/Controls.baml create mode 100644 Setup/obj/Release/net48/DeskCore-Setup.exe create mode 100644 Setup/obj/Release/net48/DeskCore-Setup.exe.withSupportedRuntime.config create mode 100644 Setup/obj/Release/net48/DeskCore-Setup.g.resources create mode 100644 Setup/obj/Release/net48/DeskCore-Setup_MarkupCompile.cache create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.AssemblyInfo.cs create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.AssemblyInfoInputs.cache create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.GlobalUsings.g.cs create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.assets.cache create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.csproj.AssemblyReference.cache create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.csproj.CoreCompileInputs.cache create mode 100644 Setup/obj/Release/net48/DeskCoreSetup.csproj.FileListAbsolute.txt create mode 100644 Setup/obj/Release/net48/MainWindow.baml create mode 100644 Setup/obj/Release/net48/MainWindow.g.cs create mode 100644 Setup/obj/Release/net9.0-windows/win-x64/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs create mode 100644 Setup/obj/Release/net9.0-windows/win-x64/App.baml create mode 100644 Setup/obj/project.assets.json create mode 100644 Setup/obj/project.nuget.cache create mode 100644 obj/Release/net9.0-windows/win-x64/DeskCore.GlobalUsings.g.cs create mode 100644 obj/Release/net9.0-windows/win-x64/DeskCore.genruntimeconfig.cache create mode 100644 obj/Release/net9.0-windows/win-x64/DeskCore_MarkupCompile.cache create mode 100644 obj/Release/net9.0-windows/win-x64/FenceWindow.baml create mode 100644 obj/Release/net9.0-windows/win-x64/FenceWindow.g.cs create mode 100644 obj/Release/net9.0-windows/win-x64/MainWindow.baml create mode 100644 obj/Release/net9.0-windows/win-x64/MainWindow.g.cs create mode 100644 obj/Release/net9.0-windows/win-x64/PublishOutputs.706123076a.txt create mode 100644 obj/Release/net9.0-windows/win-x64/Themes/Colors.Dark.baml create mode 100644 obj/Release/net9.0-windows/win-x64/Themes/Colors.Light.baml create mode 100644 obj/Release/net9.0-windows/win-x64/Themes/Controls.baml create mode 100644 obj/Release/net9.0-windows/win-x64/ref/DeskCore.dll create mode 100644 obj/Release/net9.0-windows/win-x64/refint/DeskCore.dll diff --git a/Setup/App.xaml.cs b/Setup/App.xaml.cs index 4e2fa34..9b81dea 100644 --- a/Setup/App.xaml.cs +++ b/Setup/App.xaml.cs @@ -1,77 +1,77 @@ -using System.Windows; -using System.Windows.Media; -using Microsoft.Win32; - -namespace DeskCoreSetup; - -public partial class App : Application -{ - protected override void OnStartup(StartupEventArgs e) - { - base.OnStartup(e); - bool uninstall = e.Args.Any(a => a.Equals("--uninstall", StringComparison.OrdinalIgnoreCase)); - bool silent = e.Args.Any(a => a.Equals("/S", StringComparison.OrdinalIgnoreCase)); - bool launch = e.Args.Any(a => a.Equals("/launch", StringComparison.OrdinalIgnoreCase)); - var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?.Substring(3); - - // Stille Installation/Deinstallation für Skripte: DeskCore-Setup.exe /S [/D=Ordner] bzw. --uninstall /S - if (silent) - { - var progress = new Progress<(double, string)>(_ => { }); - try - { - if (uninstall) Installer.Uninstall(removeData: false, progress); - else - { - var dir = dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir; - Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists, - autostart: Installer.AutostartEnabled, progress, installRuntime: true); - // nach einem Update aus DeskCore heraus: gleich wieder starten - if (launch) System.Diagnostics.Process.Start(System.IO.Path.Combine(dir, Installer.ExeName)); - } - Shutdown(0); - } - catch (Exception ex) - { - // ohne Oberfläche: Fehler für Skripte nachvollziehbar ablegen - try { System.IO.File.WriteAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "DeskCore-Setup.log"), $"[{DateTime.Now}] {ex}"); } catch { } - Shutdown(1); - } - return; - } - - ApplyTheme(); - new MainWindow(uninstall).Show(); - } - - /// Hell/Dunkel und Akzentfarbe wie in Windows eingestellt. - private void ApplyTheme() - { - bool light = ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0; - Resources.MergedDictionaries[0] = new ResourceDictionary - { - Source = new Uri($"pack://application:,,,/Themes/Colors.{(light ? "Light" : "Dark")}.xaml"), - }; - int abgr = ReadDword(@"Software\Microsoft\Windows\DWM", "AccentColor") is var v and not -1 ? v : unchecked((int)0xFFD77800); - var accent = Color.FromRgb((byte)abgr, (byte)(abgr >> 8), (byte)(abgr >> 16)); - var a = light ? Mix(accent, Colors.Black, 0.12) : Mix(accent, Colors.White, 0.45); - Resources["AccentBrush"] = new SolidColorBrush(a); - Resources["AccentHoverBrush"] = new SolidColorBrush(light ? Mix(a, Colors.White, 0.1) : Mix(a, Colors.Black, 0.1)); - Resources["OnAccentBrush"] = new SolidColorBrush(light ? Colors.White : Colors.Black); - Resources["AccentSubtleBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x22 : (byte)0x30, a.R, a.G, a.B)); - Resources["AccentSubtleStrokeBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x55 : (byte)0x60, a.R, a.G, a.B)); - } - - private static int ReadDword(string key, string name) - { - try - { - using var k = Registry.CurrentUser.OpenSubKey(key); - return k?.GetValue(name) is int i ? i : -1; - } - catch { return -1; } - } - - private static Color Mix(Color c, Color with, double t) => Color.FromRgb( - (byte)(c.R + (with.R - c.R) * t), (byte)(c.G + (with.G - c.G) * t), (byte)(c.B + (with.B - c.B) * t)); -} +using System.Windows; +using System.Windows.Media; +using Microsoft.Win32; + +namespace DeskCoreSetup; + +public partial class App : Application +{ + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + bool uninstall = e.Args.Any(a => a.Equals("--uninstall", StringComparison.OrdinalIgnoreCase)); + bool silent = e.Args.Any(a => a.Equals("/S", StringComparison.OrdinalIgnoreCase)); + bool launch = e.Args.Any(a => a.Equals("/launch", StringComparison.OrdinalIgnoreCase)); + var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?.Substring(3); + + // Stille Installation/Deinstallation für Skripte: DeskCore-Setup.exe /S [/D=Ordner] bzw. --uninstall /S + if (silent) + { + var progress = new Progress<(double, string)>(_ => { }); + try + { + if (uninstall) Installer.Uninstall(removeData: false, progress); + else + { + var dir = dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir; + Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists, + autostart: Installer.AutostartEnabled, progress, installRuntime: true); + // nach einem Update aus DeskCore heraus: gleich wieder starten + if (launch) System.Diagnostics.Process.Start(System.IO.Path.Combine(dir, Installer.ExeName)); + } + Shutdown(0); + } + catch (Exception ex) + { + // ohne Oberfläche: Fehler für Skripte nachvollziehbar ablegen + try { System.IO.File.WriteAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "DeskCore-Setup.log"), $"[{DateTime.Now}] {ex}"); } catch { } + Shutdown(1); + } + return; + } + + ApplyTheme(); + new MainWindow(uninstall).Show(); + } + + /// Hell/Dunkel und Akzentfarbe wie in Windows eingestellt. + private void ApplyTheme() + { + bool light = ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0; + Resources.MergedDictionaries[0] = new ResourceDictionary + { + Source = new Uri($"pack://application:,,,/Themes/Colors.{(light ? "Light" : "Dark")}.xaml"), + }; + int abgr = ReadDword(@"Software\Microsoft\Windows\DWM", "AccentColor") is var v and not -1 ? v : unchecked((int)0xFFD77800); + var accent = Color.FromRgb((byte)abgr, (byte)(abgr >> 8), (byte)(abgr >> 16)); + var a = light ? Mix(accent, Colors.Black, 0.12) : Mix(accent, Colors.White, 0.45); + Resources["AccentBrush"] = new SolidColorBrush(a); + Resources["AccentHoverBrush"] = new SolidColorBrush(light ? Mix(a, Colors.White, 0.1) : Mix(a, Colors.Black, 0.1)); + Resources["OnAccentBrush"] = new SolidColorBrush(light ? Colors.White : Colors.Black); + Resources["AccentSubtleBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x22 : (byte)0x30, a.R, a.G, a.B)); + Resources["AccentSubtleStrokeBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x55 : (byte)0x60, a.R, a.G, a.B)); + } + + private static int ReadDword(string key, string name) + { + try + { + using var k = Registry.CurrentUser.OpenSubKey(key); + return k?.GetValue(name) is int i ? i : -1; + } + catch { return -1; } + } + + private static Color Mix(Color c, Color with, double t) => Color.FromRgb( + (byte)(c.R + (with.R - c.R) * t), (byte)(c.G + (with.G - c.G) * t), (byte)(c.B + (with.B - c.B) * t)); +} diff --git a/Setup/Installer.cs b/Setup/Installer.cs index 5a5d210..4383a8b 100644 --- a/Setup/Installer.cs +++ b/Setup/Installer.cs @@ -1,388 +1,388 @@ -using System.Diagnostics; -using System.IO; -using Microsoft.Win32; - -namespace DeskCoreSetup; - -/// Installation/Deinstallation – nur für den aktuellen Benutzer, ohne Administratorrechte. -public static class Installer -{ - public const string AppName = "DeskCore"; - public const string Publisher = "M_Viper"; - public const string ExeName = "DeskCore.exe"; - public const string UninstallerName = "DeskCore-Uninstall.exe"; - private const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskCore"; - private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run"; - - public static string Version - { - get - { - var v = typeof(Installer).Assembly.GetName().Version!; - return $"{v.Major}.{v.Minor}.{v.Build}"; - } - } - - /// Pfad dieses Setups (auch als Deinstallationsprogramm). - public static string SelfPath => System.Reflection.Assembly.GetExecutingAssembly().Location; - - public static string DefaultDir => - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", AppName); - - private static string StartMenuLink => - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), AppName + ".lnk"); - - private static string DesktopLink => - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), AppName + ".lnk"); - - /// Ordner einer vorhandenen Installation (oder null). - public static string? InstalledDir - { - get - { - using var k = Registry.CurrentUser.OpenSubKey(UninstallKey); - return k?.GetValue("InstallLocation") as string is { } d && File.Exists(Path.Combine(d, ExeName)) ? d : null; - } - } - - public static string? InstalledVersion - { - get - { - using var k = Registry.CurrentUser.OpenSubKey(UninstallKey); - return k?.GetValue("DisplayVersion") as string; - } - } - - public static bool AutostartEnabled - { - get - { - using var k = Registry.CurrentUser.OpenSubKey(RunKey); - return k?.GetValue(AppName) is string; - } - } - - public static bool DesktopLinkExists => File.Exists(DesktopLink); - - public static string LicenseText => ReadResourceText("payload.LICENSE.txt"); - - // ---------- Installieren ---------- - - public static void Install(string dir, bool desktopLink, bool autostart, IProgress<(double, string)> progress, - bool installRuntime = false) - { - if (!Runtime.Is64BitWindows) - throw new InvalidOperationException("DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11)."); - if (installRuntime && !Runtime.IsInstalled) - { - // 0–60 % für die Laufzeit, Rest für DeskCore - Runtime.Install((v, text) => progress.Report((v * 0.6, text))); - var inner = progress; - progress = new Scaled(inner, 0.6, 0.4); - } - - progress.Report((0.05, "DeskCore wird beendet …")); - CloseRunningApp(); - RemoveLegacyInstall(); - - progress.Report((0.2, "Dateien werden kopiert …")); - Directory.CreateDirectory(dir); - var exe = Path.Combine(dir, ExeName); - WriteResource("payload.DeskCore.exe", exe); - WriteResource("payload.LICENSE.txt", Path.Combine(dir, "LICENSE.txt")); - - // Setup selbst als Deinstallationsprogramm ablegen - var self = SelfPath; - var uninstaller = Path.Combine(dir, UninstallerName); - if (!string.Equals(Path.GetFullPath(self), Path.GetFullPath(uninstaller), StringComparison.OrdinalIgnoreCase)) - { - MoveAside(uninstaller); - File.Copy(self, uninstaller, overwrite: true); - } - - progress.Report((0.55, "Verknüpfungen werden angelegt …")); - CreateShortcut(StartMenuLink, exe); - if (desktopLink) CreateShortcut(DesktopLink, exe); - - progress.Report((0.75, "Windows wird eingerichtet …")); - using (var run = Registry.CurrentUser.CreateSubKey(RunKey)) - { - if (autostart) run.SetValue(AppName, $"\"{exe}\" --minimized"); - else run.DeleteValue(AppName, throwOnMissingValue: false); - } - - using (var k = Registry.CurrentUser.CreateSubKey(UninstallKey)) - { - long size = new[] { exe, uninstaller, Path.Combine(dir, "LICENSE.txt") } - .Where(File.Exists).Sum(f => new FileInfo(f).Length) / 1024; - k.SetValue("DisplayName", AppName); - k.SetValue("DisplayVersion", Version); - k.SetValue("Publisher", Publisher); - k.SetValue("DisplayIcon", $"{exe},0"); - k.SetValue("InstallLocation", dir); - k.SetValue("UninstallString", $"\"{uninstaller}\" --uninstall"); - k.SetValue("QuietUninstallString", $"\"{uninstaller}\" --uninstall /S"); - k.SetValue("URLInfoAbout", "https://m-viper.de"); - k.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd")); - k.SetValue("EstimatedSize", (int)size, RegistryValueKind.DWord); - k.SetValue("NoModify", 1, RegistryValueKind.DWord); - k.SetValue("NoRepair", 1, RegistryValueKind.DWord); - } - - progress.Report((1.0, "Fertig")); - } - - // ---------- Frühere Version "DeskHub" entfernen ---------- - - private const string LegacyName = "DeskHub"; - private const string LegacyUninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskHub"; - - /// Ordner einer alten Installation unter dem früheren Namen "DeskHub" – nur unsere eigene. - public static string? LegacyInstallDir - { - get - { - using var k = Registry.CurrentUser.OpenSubKey(LegacyUninstallKey); - // "DeskHub" heißt auch ein anderes Programm → nur mit unserem Herausgeber anfassen - if (k?.GetValue("Publisher") as string != Publisher) return null; - return k.GetValue("InstallLocation") as string; - } - } - - private static void RemoveLegacyInstall() - { - var dir = LegacyInstallDir; - if (dir is null) return; - var exe = Path.Combine(dir, LegacyName + ".exe"); - - // nur Prozesse beenden, die wirklich aus dem alten Ordner laufen - foreach (var p in Process.GetProcessesByName(LegacyName)) - { - try - { - if (p.MainModule?.FileName is { } f && Storage_IsUnder(f, dir)) - { - p.Kill(); - p.WaitForExit(10000); - } - } - catch { } - } - - DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), LegacyName + ".lnk"), exe); - DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), LegacyName + ".lnk"), exe); - using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true)) - { - if (run?.GetValue(LegacyName) is string cmd && cmd.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0) - run.DeleteValue(LegacyName, throwOnMissingValue: false); - } - - foreach (var f in new[] { exe, Path.Combine(dir, "LICENSE.txt"), Path.Combine(dir, LegacyName + "-Uninstall.exe") }) - MoveAside(f); - CleanupOldFiles(dir); - try { Directory.Delete(dir); } catch { } // nur wenn leer - Registry.CurrentUser.DeleteSubKeyTree(LegacyUninstallKey, throwOnMissingSubKey: false); - } - - // ---------- Deinstallieren ---------- - - public static readonly string[] DataDirs = - [ - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), AppName), // Kategorien - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppName), // Einstellungen - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppName), // Rückgängig-Zwischenablage - ]; - - public static void Uninstall(bool removeData, IProgress<(double, string)> progress) - { - // Nur im eingetragenen Installationsordner löschen – nie dort, wo das Setup zufällig liegt (z. B. Downloads) - var dir = InstalledDir; - var exe = dir is null ? "" : Path.Combine(dir, ExeName); - - progress.Report((0.1, "DeskCore wird beendet …")); - CloseRunningApp(); - - progress.Report((0.3, "Verknüpfungen werden entfernt …")); - DeleteIfPointsTo(StartMenuLink, exe); - DeleteIfPointsTo(DesktopLink, exe); - - progress.Report((0.45, "Windows-Einträge werden entfernt …")); - using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true)) - run?.DeleteValue(AppName, throwOnMissingValue: false); - Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false); - - if (removeData) - { - progress.Report((0.6, "Kategorien und Einstellungen werden in den Papierkorb gelegt …")); - foreach (var d in DataDirs.Where(Directory.Exists)) - { - try - { - Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(d, - Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, - Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin); - } - catch { } - } - } - - progress.Report((0.85, "Dateien werden entfernt …")); - if (dir is null) { progress.Report((1.0, "Fertig")); return; } - TryDelete(exe); - TryDelete(Path.Combine(dir, "LICENSE.txt")); - CleanupOldFiles(dir); - - // Das laufende Deinstallationsprogramm kann sich nicht selbst löschen → kurz nach dem Beenden per cmd. - // rmdir ohne /s entfernt den Ordner nur, wenn er danach leer ist. - var self = SelfPath; - if (Storage_IsUnder(self, dir)) - { - Process.Start(new ProcessStartInfo("cmd.exe", - $"/c ping 127.0.0.1 -n 3 > nul & del /f /q \"{self}\" & del /f /q \"{Path.Combine(dir, "*.old")}\" & rmdir \"{dir}\"") - { - CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, - }); - } - else - { - TryDelete(Path.Combine(dir, UninstallerName)); - try { Directory.Delete(dir); } catch { } // nur wenn leer - } - progress.Report((1.0, "Fertig")); - } - - // ---------- Helfer ---------- - - /// Rechnet Fortschritt 0–1 in einen Teilbereich um. - private sealed class Scaled(IProgress<(double, string)> inner, double offset, double factor) : IProgress<(double, string)> - { - public void Report((double, string) value) => inner.Report((offset + value.Item1 * factor, value.Item2)); - } - - /// Laufendes DeskCore beenden (das X versteckt es nur im Infobereich). - private static void CloseRunningApp() - { - foreach (var p in Process.GetProcessesByName("DeskCore")) - { - try - { - p.Kill(); - p.WaitForExit(10000); - } - catch { } - } - } - - private static void WriteResource(string name, string path) - { - CleanupOldFiles(Path.GetDirectoryName(path)!); - MoveAside(path); - for (int attempt = 1; ; attempt++) - { - try - { - using var s = typeof(Installer).Assembly.GetManifestResourceStream(name) - ?? throw new InvalidOperationException($"Installationsdatei {name} fehlt."); - using var f = File.Create(path); - s.CopyTo(f); - return; - } - catch (IOException) when (attempt < 10) - { - Thread.Sleep(500); - } - catch (IOException) when (attempt >= 10) - { - throw new IOException($"„{Path.GetFileName(path)}“ wird noch verwendet. Bitte DeskCore beenden (Rechtsklick auf das Symbol im Infobereich → Beenden) und erneut versuchen."); - } - } - } - - /// - /// Vorhandene Datei beiseite umbenennen. Das klappt unter Windows auch dann, wenn sie noch geöffnet ist - /// (z. B. vom Explorer für das Icon oder vom Virenscanner) – überschreiben würde dagegen fehlschlagen. - /// - private static void MoveAside(string path) - { - if (!File.Exists(path)) return; - try { File.Delete(path); return; } catch { } - try { File.Move(path, $"{path}.{Guid.NewGuid():N}.old"); } catch { } - } - - /// Beiseitegelegte Dateien früherer Installationen wegräumen (sofern nicht mehr in Benutzung). - private static void CleanupOldFiles(string dir) - { - if (!Directory.Exists(dir)) return; - foreach (var f in Directory.GetFiles(dir, "*.old")) - try { File.Delete(f); } catch { } - } - - private static string ReadResourceText(string name) - { - using var s = typeof(Installer).Assembly.GetManifestResourceStream(name); - if (s is null) return ""; - using var r = new StreamReader(s); - return r.ReadToEnd().Trim(); - } - - private static void CreateShortcut(string link, string target) - { - Directory.CreateDirectory(Path.GetDirectoryName(link)!); - dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!; - dynamic lnk = shell.CreateShortcut(link); - lnk.TargetPath = target; - lnk.WorkingDirectory = Path.GetDirectoryName(target); - lnk.IconLocation = $"{target},0"; - lnk.Description = "Desktop-Icons in Kategorien sortieren"; - lnk.Save(); - } - - private static void DeleteIfPointsTo(string link, string target) - { - if (!File.Exists(link)) return; - try - { - dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!; - string t = shell.CreateShortcut(link).TargetPath; - if (!string.Equals(t, target, StringComparison.OrdinalIgnoreCase)) return; - } - catch { return; } - // Explorer/Indexdienst halten Verknüpfungen manchmal kurz fest → mehrmals versuchen - for (int i = 0; i < 10 && File.Exists(link); i++) - { - try { File.Delete(link); } - catch { Thread.Sleep(300); } - } - } - - private static void TryDelete(string path) - { - try { if (File.Exists(path)) File.Delete(path); } catch { } - } - - private static bool Storage_IsUnder(string path, string dir) => - (Path.GetFullPath(path)).StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase); - - public static long DataSize() - { - long sum = 0; - foreach (var d in DataDirs.Where(Directory.Exists)) - { - try - { - foreach (var f in Directory.EnumerateFiles(d, "*", SearchOption.AllDirectories)) - try { sum += new FileInfo(f).Length; } catch { } - } - catch { } - } - return sum; - } - - public static string FormatSize(long bytes) => bytes switch - { - < 1024 * 1024 => $"{Math.Max(1, bytes / 1024)} KB", - < 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:0.#} MB", - _ => $"{bytes / 1024.0 / 1024 / 1024:0.##} GB", - }; -} +using System.Diagnostics; +using System.IO; +using Microsoft.Win32; + +namespace DeskCoreSetup; + +/// Installation/Deinstallation – nur für den aktuellen Benutzer, ohne Administratorrechte. +public static class Installer +{ + public const string AppName = "DeskCore"; + public const string Publisher = "M_Viper"; + public const string ExeName = "DeskCore.exe"; + public const string UninstallerName = "DeskCore-Uninstall.exe"; + private const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskCore"; + private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run"; + + public static string Version + { + get + { + var v = typeof(Installer).Assembly.GetName().Version!; + return $"{v.Major}.{v.Minor}.{v.Build}"; + } + } + + /// Pfad dieses Setups (auch als Deinstallationsprogramm). + public static string SelfPath => System.Reflection.Assembly.GetExecutingAssembly().Location; + + public static string DefaultDir => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", AppName); + + private static string StartMenuLink => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), AppName + ".lnk"); + + private static string DesktopLink => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), AppName + ".lnk"); + + /// Ordner einer vorhandenen Installation (oder null). + public static string? InstalledDir + { + get + { + using var k = Registry.CurrentUser.OpenSubKey(UninstallKey); + return k?.GetValue("InstallLocation") as string is { } d && File.Exists(Path.Combine(d, ExeName)) ? d : null; + } + } + + public static string? InstalledVersion + { + get + { + using var k = Registry.CurrentUser.OpenSubKey(UninstallKey); + return k?.GetValue("DisplayVersion") as string; + } + } + + public static bool AutostartEnabled + { + get + { + using var k = Registry.CurrentUser.OpenSubKey(RunKey); + return k?.GetValue(AppName) is string; + } + } + + public static bool DesktopLinkExists => File.Exists(DesktopLink); + + public static string LicenseText => ReadResourceText("payload.LICENSE.txt"); + + // ---------- Installieren ---------- + + public static void Install(string dir, bool desktopLink, bool autostart, IProgress<(double, string)> progress, + bool installRuntime = false) + { + if (!Runtime.Is64BitWindows) + throw new InvalidOperationException("DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11)."); + if (installRuntime && !Runtime.IsInstalled) + { + // 0–60 % für die Laufzeit, Rest für DeskCore + Runtime.Install((v, text) => progress.Report((v * 0.6, text))); + var inner = progress; + progress = new Scaled(inner, 0.6, 0.4); + } + + progress.Report((0.05, "DeskCore wird beendet …")); + CloseRunningApp(); + RemoveLegacyInstall(); + + progress.Report((0.2, "Dateien werden kopiert …")); + Directory.CreateDirectory(dir); + var exe = Path.Combine(dir, ExeName); + WriteResource("payload.DeskCore.exe", exe); + WriteResource("payload.LICENSE.txt", Path.Combine(dir, "LICENSE.txt")); + + // Setup selbst als Deinstallationsprogramm ablegen + var self = SelfPath; + var uninstaller = Path.Combine(dir, UninstallerName); + if (!string.Equals(Path.GetFullPath(self), Path.GetFullPath(uninstaller), StringComparison.OrdinalIgnoreCase)) + { + MoveAside(uninstaller); + File.Copy(self, uninstaller, overwrite: true); + } + + progress.Report((0.55, "Verknüpfungen werden angelegt …")); + CreateShortcut(StartMenuLink, exe); + if (desktopLink) CreateShortcut(DesktopLink, exe); + + progress.Report((0.75, "Windows wird eingerichtet …")); + using (var run = Registry.CurrentUser.CreateSubKey(RunKey)) + { + if (autostart) run.SetValue(AppName, $"\"{exe}\" --minimized"); + else run.DeleteValue(AppName, throwOnMissingValue: false); + } + + using (var k = Registry.CurrentUser.CreateSubKey(UninstallKey)) + { + long size = new[] { exe, uninstaller, Path.Combine(dir, "LICENSE.txt") } + .Where(File.Exists).Sum(f => new FileInfo(f).Length) / 1024; + k.SetValue("DisplayName", AppName); + k.SetValue("DisplayVersion", Version); + k.SetValue("Publisher", Publisher); + k.SetValue("DisplayIcon", $"{exe},0"); + k.SetValue("InstallLocation", dir); + k.SetValue("UninstallString", $"\"{uninstaller}\" --uninstall"); + k.SetValue("QuietUninstallString", $"\"{uninstaller}\" --uninstall /S"); + k.SetValue("URLInfoAbout", "https://m-viper.de"); + k.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd")); + k.SetValue("EstimatedSize", (int)size, RegistryValueKind.DWord); + k.SetValue("NoModify", 1, RegistryValueKind.DWord); + k.SetValue("NoRepair", 1, RegistryValueKind.DWord); + } + + progress.Report((1.0, "Fertig")); + } + + // ---------- Frühere Version "DeskHub" entfernen ---------- + + private const string LegacyName = "DeskHub"; + private const string LegacyUninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskHub"; + + /// Ordner einer alten Installation unter dem früheren Namen "DeskHub" – nur unsere eigene. + public static string? LegacyInstallDir + { + get + { + using var k = Registry.CurrentUser.OpenSubKey(LegacyUninstallKey); + // "DeskHub" heißt auch ein anderes Programm → nur mit unserem Herausgeber anfassen + if (k?.GetValue("Publisher") as string != Publisher) return null; + return k.GetValue("InstallLocation") as string; + } + } + + private static void RemoveLegacyInstall() + { + var dir = LegacyInstallDir; + if (dir is null) return; + var exe = Path.Combine(dir, LegacyName + ".exe"); + + // nur Prozesse beenden, die wirklich aus dem alten Ordner laufen + foreach (var p in Process.GetProcessesByName(LegacyName)) + { + try + { + if (p.MainModule?.FileName is { } f && Storage_IsUnder(f, dir)) + { + p.Kill(); + p.WaitForExit(10000); + } + } + catch { } + } + + DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), LegacyName + ".lnk"), exe); + DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), LegacyName + ".lnk"), exe); + using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true)) + { + if (run?.GetValue(LegacyName) is string cmd && cmd.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0) + run.DeleteValue(LegacyName, throwOnMissingValue: false); + } + + foreach (var f in new[] { exe, Path.Combine(dir, "LICENSE.txt"), Path.Combine(dir, LegacyName + "-Uninstall.exe") }) + MoveAside(f); + CleanupOldFiles(dir); + try { Directory.Delete(dir); } catch { } // nur wenn leer + Registry.CurrentUser.DeleteSubKeyTree(LegacyUninstallKey, throwOnMissingSubKey: false); + } + + // ---------- Deinstallieren ---------- + + public static readonly string[] DataDirs = + [ + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), AppName), // Kategorien + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppName), // Einstellungen + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppName), // Rückgängig-Zwischenablage + ]; + + public static void Uninstall(bool removeData, IProgress<(double, string)> progress) + { + // Nur im eingetragenen Installationsordner löschen – nie dort, wo das Setup zufällig liegt (z. B. Downloads) + var dir = InstalledDir; + var exe = dir is null ? "" : Path.Combine(dir, ExeName); + + progress.Report((0.1, "DeskCore wird beendet …")); + CloseRunningApp(); + + progress.Report((0.3, "Verknüpfungen werden entfernt …")); + DeleteIfPointsTo(StartMenuLink, exe); + DeleteIfPointsTo(DesktopLink, exe); + + progress.Report((0.45, "Windows-Einträge werden entfernt …")); + using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true)) + run?.DeleteValue(AppName, throwOnMissingValue: false); + Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false); + + if (removeData) + { + progress.Report((0.6, "Kategorien und Einstellungen werden in den Papierkorb gelegt …")); + foreach (var d in DataDirs.Where(Directory.Exists)) + { + try + { + Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(d, + Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, + Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin); + } + catch { } + } + } + + progress.Report((0.85, "Dateien werden entfernt …")); + if (dir is null) { progress.Report((1.0, "Fertig")); return; } + TryDelete(exe); + TryDelete(Path.Combine(dir, "LICENSE.txt")); + CleanupOldFiles(dir); + + // Das laufende Deinstallationsprogramm kann sich nicht selbst löschen → kurz nach dem Beenden per cmd. + // rmdir ohne /s entfernt den Ordner nur, wenn er danach leer ist. + var self = SelfPath; + if (Storage_IsUnder(self, dir)) + { + Process.Start(new ProcessStartInfo("cmd.exe", + $"/c ping 127.0.0.1 -n 3 > nul & del /f /q \"{self}\" & del /f /q \"{Path.Combine(dir, "*.old")}\" & rmdir \"{dir}\"") + { + CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, + }); + } + else + { + TryDelete(Path.Combine(dir, UninstallerName)); + try { Directory.Delete(dir); } catch { } // nur wenn leer + } + progress.Report((1.0, "Fertig")); + } + + // ---------- Helfer ---------- + + /// Rechnet Fortschritt 0–1 in einen Teilbereich um. + private sealed class Scaled(IProgress<(double, string)> inner, double offset, double factor) : IProgress<(double, string)> + { + public void Report((double, string) value) => inner.Report((offset + value.Item1 * factor, value.Item2)); + } + + /// Laufendes DeskCore beenden (das X versteckt es nur im Infobereich). + private static void CloseRunningApp() + { + foreach (var p in Process.GetProcessesByName("DeskCore")) + { + try + { + p.Kill(); + p.WaitForExit(10000); + } + catch { } + } + } + + private static void WriteResource(string name, string path) + { + CleanupOldFiles(Path.GetDirectoryName(path)!); + MoveAside(path); + for (int attempt = 1; ; attempt++) + { + try + { + using var s = typeof(Installer).Assembly.GetManifestResourceStream(name) + ?? throw new InvalidOperationException($"Installationsdatei {name} fehlt."); + using var f = File.Create(path); + s.CopyTo(f); + return; + } + catch (IOException) when (attempt < 10) + { + Thread.Sleep(500); + } + catch (IOException) when (attempt >= 10) + { + throw new IOException($"„{Path.GetFileName(path)}“ wird noch verwendet. Bitte DeskCore beenden (Rechtsklick auf das Symbol im Infobereich → Beenden) und erneut versuchen."); + } + } + } + + /// + /// Vorhandene Datei beiseite umbenennen. Das klappt unter Windows auch dann, wenn sie noch geöffnet ist + /// (z. B. vom Explorer für das Icon oder vom Virenscanner) – überschreiben würde dagegen fehlschlagen. + /// + private static void MoveAside(string path) + { + if (!File.Exists(path)) return; + try { File.Delete(path); return; } catch { } + try { File.Move(path, $"{path}.{Guid.NewGuid():N}.old"); } catch { } + } + + /// Beiseitegelegte Dateien früherer Installationen wegräumen (sofern nicht mehr in Benutzung). + private static void CleanupOldFiles(string dir) + { + if (!Directory.Exists(dir)) return; + foreach (var f in Directory.GetFiles(dir, "*.old")) + try { File.Delete(f); } catch { } + } + + private static string ReadResourceText(string name) + { + using var s = typeof(Installer).Assembly.GetManifestResourceStream(name); + if (s is null) return ""; + using var r = new StreamReader(s); + return r.ReadToEnd().Trim(); + } + + private static void CreateShortcut(string link, string target) + { + Directory.CreateDirectory(Path.GetDirectoryName(link)!); + dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!; + dynamic lnk = shell.CreateShortcut(link); + lnk.TargetPath = target; + lnk.WorkingDirectory = Path.GetDirectoryName(target); + lnk.IconLocation = $"{target},0"; + lnk.Description = "Desktop-Icons in Kategorien sortieren"; + lnk.Save(); + } + + private static void DeleteIfPointsTo(string link, string target) + { + if (!File.Exists(link)) return; + try + { + dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!; + string t = shell.CreateShortcut(link).TargetPath; + if (!string.Equals(t, target, StringComparison.OrdinalIgnoreCase)) return; + } + catch { return; } + // Explorer/Indexdienst halten Verknüpfungen manchmal kurz fest → mehrmals versuchen + for (int i = 0; i < 10 && File.Exists(link); i++) + { + try { File.Delete(link); } + catch { Thread.Sleep(300); } + } + } + + private static void TryDelete(string path) + { + try { if (File.Exists(path)) File.Delete(path); } catch { } + } + + private static bool Storage_IsUnder(string path, string dir) => + (Path.GetFullPath(path)).StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase); + + public static long DataSize() + { + long sum = 0; + foreach (var d in DataDirs.Where(Directory.Exists)) + { + try + { + foreach (var f in Directory.EnumerateFiles(d, "*", SearchOption.AllDirectories)) + try { sum += new FileInfo(f).Length; } catch { } + } + catch { } + } + return sum; + } + + public static string FormatSize(long bytes) => bytes switch + { + < 1024 * 1024 => $"{Math.Max(1, bytes / 1024)} KB", + < 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:0.#} MB", + _ => $"{bytes / 1024.0 / 1024 / 1024:0.##} GB", + }; +} diff --git a/Setup/MainWindow.xaml b/Setup/MainWindow.xaml index 007e5d4..6ddee11 100644 --- a/Setup/MainWindow.xaml +++ b/Setup/MainWindow.xaml @@ -1,183 +1,183 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -