using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text.Json; using EngineeringSync.Setup.ViewModels; using Microsoft.Win32; namespace EngineeringSync.Setup.Services; /// /// Führt alle Installationsschritte aus. Gibt Fortschritt und Log-Meldungen /// über Events zurück, damit die UI in Echtzeit aktualisiert werden kann. /// public class InstallerService(WizardState state) { public event Action? Progress; public event Action? LogMessage; private void Report(int percent, string step, string? log = null) { Progress?.Invoke(percent, step); LogMessage?.Invoke(log ?? step); } /// Hauptinstallationsablauf – läuft auf einem Background-Thread. public async Task InstallAsync() { await Task.Run(async () => { // 1. Installationsverzeichnis anlegen Report(5, "Installationsverzeichnis anlegen..."); Directory.CreateDirectory(state.InstallPath); await Delay(); // 2. Programmdateien kopieren Report(15, "Programmdateien werden kopiert..."); await CopyApplicationFilesAsync(); // 3. Datenbank-Verzeichnis anlegen Report(30, "Datenbankverzeichnis anlegen..."); var dbDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "EngineeringSync"); Directory.CreateDirectory(dbDir); Report(33, "Datenbankverzeichnis angelegt.", $"Datenbank-Pfad: {dbDir}"); await Delay(); // 4. Erstkonfigurations-Datei schreiben Report(40, "Erstkonfiguration wird gespeichert..."); WriteFirstRunConfig(dbDir); await Delay(); // 5. Bestehenden Dienst stoppen & deinstallieren (Upgrade-Szenario) Report(50, "Vorherige Installation wird geprüft..."); await StopAndRemoveExistingServiceAsync(); await Delay(); // 6. Windows Service registrieren Report(60, "Windows-Dienst wird registriert..."); var serviceExe = Path.Combine(state.InstallPath, "EngineeringSync.Service.exe"); await RegisterWindowsServiceAsync(serviceExe); await Delay(); // 7. Autostart für TrayApp if (state.AutoStartTrayApp) { Report(70, "TrayApp-Autostart wird konfiguriert..."); ConfigureTrayAppAutostart(); await Delay(); } // 8. Verknüpfungen erstellen Report(78, "Verknüpfungen werden erstellt..."); if (state.CreateDesktopShortcut) CreateShortcut(ShortcutTarget.Desktop); if (state.CreateStartMenuEntry) CreateShortcut(ShortcutTarget.StartMenu); await Delay(); // 9. Add/Remove Programs Eintrag Report(85, "Deinstallations-Eintrag wird angelegt..."); RegisterUninstallEntry(); await Delay(); // 10. Dienst starten if (state.AutoStartService) { Report(92, "Windows-Dienst wird gestartet..."); await StartServiceAsync(); } Report(100, "Installation abgeschlossen.", "✓ EngineeringSync wurde erfolgreich installiert."); }); } // ── Hilfsmethoden ───────────────────────────────────────────────── private async Task CopyApplicationFilesAsync() { var sourceDir = Path.GetFullPath(AppContext.BaseDirectory).TrimEnd(Path.DirectorySeparatorChar); var targetDir = Path.GetFullPath(state.InstallPath).TrimEnd(Path.DirectorySeparatorChar); // Wenn das Setup bereits aus dem Zielverzeichnis läuft (z.B. nach Inno-Setup-Installation), // wurden die Dateien bereits kopiert – nichts zu tun. if (string.Equals(sourceDir, targetDir, StringComparison.OrdinalIgnoreCase)) { LogMessage?.Invoke(" Dateien bereits installiert (Inno-Setup) - Kopieren wird übersprungen."); return; } var patterns = new[] { "*.exe", "*.dll", "*.json", "*.pdb" }; var allFiles = patterns .SelectMany(p => Directory.GetFiles(sourceDir, p)) .Distinct() .ToList(); for (int i = 0; i < allFiles.Count; i++) { var src = allFiles[i]; var dest = Path.Combine(targetDir, Path.GetFileName(src)); File.Copy(src, dest, overwrite: true); LogMessage?.Invoke($" Kopiert: {Path.GetFileName(src)}"); await Task.Delay(30); // Kurze Pause für UI-Responsivität } } private void WriteFirstRunConfig(string dbDir) { var config = new { FirstRun = new { ProjectName = state.ProjectName, EngineeringPath = state.EngineeringPath, SimulationPath = state.SimulationPath, FileExtensions = state.WatchAllFiles ? "*" : state.FileExtensions, WatchAllFiles = state.WatchAllFiles }, Backup = new // NEU { BackupEnabled = state.BackupEnabled, BackupPath = state.BackupUseCustomPath ? state.BackupCustomPath : (string?)null, MaxBackupsPerFile = state.MaxBackupsPerFile } }; var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); var configPath = Path.Combine(state.InstallPath, "firstrun-config.json"); File.WriteAllText(configPath, json); LogMessage?.Invoke($" Konfiguration geschrieben: {configPath}"); } private async Task StopAndRemoveExistingServiceAsync() { try { var stopResult = await RunProcessAsync("sc", "stop EngineeringSync"); if (stopResult.exitCode == 0) { LogMessage?.Invoke(" Bestehender Dienst gestoppt."); await Task.Delay(1500); // Warten bis Dienst wirklich gestoppt } await RunProcessAsync("sc", "delete EngineeringSync"); LogMessage?.Invoke(" Bestehender Dienst entfernt."); } catch { /* Kein vorheriger Dienst – OK */ } } private async Task RegisterWindowsServiceAsync(string exePath) { if (!File.Exists(exePath)) { LogMessage?.Invoke($" WARNUNG: Service-EXE nicht gefunden: {exePath}"); LogMessage?.Invoke(" (Im Entwicklungsmodus – Service-Registrierung übersprungen)"); return; } var startType = state.AutoStartService ? "auto" : "demand"; var args = $"create EngineeringSync binPath= \"{exePath}\" " + $"start= {startType} " + $"DisplayName= \"EngineeringSync Watcher Service\""; var (exitCode, output, error) = await RunProcessAsync("sc", args); if (exitCode == 0) LogMessage?.Invoke(" ✓ Windows-Dienst registriert."); else { LogMessage?.Invoke($" FEHLER bei sc create (Code {exitCode}): {error}"); throw new InvalidOperationException($"Service-Registrierung fehlgeschlagen: {error}"); } } private void ConfigureTrayAppAutostart() { var trayExe = Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe"); if (!File.Exists(trayExe)) { LogMessage?.Invoke(" TrayApp nicht gefunden – Autostart übersprungen."); return; } using var key = Registry.CurrentUser.OpenSubKey( @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", writable: true)!; key.SetValue("EngineeringSync.TrayApp", $"\"{trayExe}\""); LogMessage?.Invoke(" ✓ TrayApp-Autostart in Registry eingetragen."); } private enum ShortcutTarget { Desktop, StartMenu } private void CreateShortcut(ShortcutTarget target) { var trayExe = Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe"); var dir = target == ShortcutTarget.Desktop ? Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory) : Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), "Programs", "EngineeringSync"); Directory.CreateDirectory(dir); var linkPath = Path.Combine(dir, "EngineeringSync.lnk"); CreateWindowsShortcut(linkPath, trayExe, state.InstallPath, "EngineeringSync Tray App"); LogMessage?.Invoke($" ✓ Verknüpfung erstellt: {linkPath}"); } private static void CreateWindowsShortcut(string linkPath, string targetPath, string workingDir, string description) { // WScript.Shell COM-Interop für Verknüpfungs-Erstellung var type = Type.GetTypeFromProgID("WScript.Shell")!; dynamic shell = Activator.CreateInstance(type)!; var shortcut = shell.CreateShortcut(linkPath); shortcut.TargetPath = targetPath; shortcut.WorkingDirectory = workingDir; shortcut.Description = description; shortcut.Save(); Marshal.FinalReleaseComObject(shortcut); Marshal.FinalReleaseComObject(shell); } private void RegisterUninstallEntry() { using var key = Registry.LocalMachine.CreateSubKey( @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\EngineeringSync"); var uninstallExe = Path.Combine(state.InstallPath, "EngineeringSync.Setup.exe"); key.SetValue("DisplayName", "EngineeringSync"); key.SetValue("DisplayVersion", "1.0.0"); key.SetValue("Publisher", "EngineeringSync"); key.SetValue("InstallLocation", state.InstallPath); key.SetValue("UninstallString", $"\"{uninstallExe}\" /uninstall"); key.SetValue("NoModify", 1, RegistryValueKind.DWord); key.SetValue("NoRepair", 1, RegistryValueKind.DWord); key.SetValue("EstimatedSize", 45000, RegistryValueKind.DWord); key.SetValue("DisplayIcon", Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe")); LogMessage?.Invoke(" ✓ Deinstallations-Eintrag in Registry angelegt."); } private async Task StartServiceAsync() { var (exitCode, _, error) = await RunProcessAsync("sc", "start EngineeringSync"); if (exitCode == 0) LogMessage?.Invoke(" ✓ Windows-Dienst gestartet."); else LogMessage?.Invoke($" WARNUNG: Dienst konnte nicht gestartet werden ({error})"); } public void LaunchTrayApp() { var trayExe = Path.Combine(state.InstallPath, "EngineeringSync.TrayApp.exe"); if (File.Exists(trayExe)) Process.Start(new ProcessStartInfo(trayExe) { UseShellExecute = true }); } private static async Task<(int exitCode, string output, string error)> RunProcessAsync( string fileName, string arguments) { using var process = new Process { StartInfo = new ProcessStartInfo { FileName = fileName, Arguments = arguments, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; process.Start(); var output = await process.StandardOutput.ReadToEndAsync(); var error = await process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); return (process.ExitCode, output.Trim(), error.Trim()); } private static Task Delay() => Task.Delay(400); }