diff --git a/Setup/Installer.cs b/Setup/Installer.cs
deleted file mode 100644
index 4383a8b..0000000
--- a/Setup/Installer.cs
+++ /dev/null
@@ -1,388 +0,0 @@
-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",
- };
-}