using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Text; using Microsoft.Win32; namespace StudioE5.SetupWizard; public partial class MainForm : Form { private readonly InstallerState _state; private readonly Panel _contentPanel; private readonly Button _btnBack; private readonly Button _btnNext; private readonly Button _btnCancel; private readonly Label _titleLabel; private readonly Label _subtitleLabel; private readonly Label _statusLabel; private WizardStep _currentStep = WizardStep.Welcome; private PrerequisiteResult? _lastCheck; public MainForm(bool startInUninstallMode = false) { _state = InstallerState.Load(); var version = GetVersion(); Text = startInUninstallMode ? $"Désinstallation studioE5 v{version}" : $"Installateur studioE5 Agent v{version}"; Size = new Size(700, 520); StartPosition = FormStartPosition.CenterScreen; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; _titleLabel = new Label { Left = 24, Top = 18, Width = 640, Height = 32, Font = new Font("Segoe UI", 14, FontStyle.Bold), Text = $"Installateur studioE5 Agent v{version}" }; _subtitleLabel = new Label { Left = 24, Top = 50, Width = 640, Height = 24, Font = new Font("Segoe UI", 9), ForeColor = Color.Gray }; _statusLabel = new Label { Left = 24, Top = 76, Width = 640, Height = 24, Font = new Font("Segoe UI", 9, FontStyle.Bold), ForeColor = Color.DodgerBlue, Visible = false }; _contentPanel = new Panel { Left = 24, Top = 100, Width = 640, Height = 304, BorderStyle = BorderStyle.None }; var separator = new Panel { Left = 0, Top = 416, Width = 700, Height = 1, BackColor = Color.LightGray, Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom }; _btnBack = new Button { Left = 360, Top = 440, Width = 100, Height = 30, Text = "< Précédent", Visible = false }; _btnBack.Click += (s, e) => GoBack(); _btnNext = new Button { Left = 470, Top = 440, Width = 100, Height = 30, Text = "Suivant >" }; _btnNext.Click += (s, e) => GoNext(); _btnCancel = new Button { Left = 584, Top = 440, Width = 80, Height = 30, Text = "Annuler" }; _btnCancel.Click += (s, e) => Application.Exit(); Controls.Add(_titleLabel); Controls.Add(_subtitleLabel); Controls.Add(_statusLabel); Controls.Add(_contentPanel); Controls.Add(separator); Controls.Add(_btnBack); Controls.Add(_btnNext); Controls.Add(_btnCancel); if (startInUninstallMode) { GoToStep(WizardStep.Uninstall); return; } ResumeAfterReboot(); } private void ResumeAfterReboot() { // Si aucune étape n'a été faite, c'est une nouvelle installation (pas une reprise). if (IsFreshInstall()) { GoToStep(WizardStep.Welcome); return; } // Reprise après redémarrage : on saute à la première étape non terminée. if (!_state.WslFeaturesEnabled || !_state.WslPackageInstalled || !_state.VirtualEnvironmentInstalled || !_state.WslDefaultVersionSet || !_state.WslKernelUpdated) { GoToStep(WizardStep.InstallVirtualEnvironment); return; } if (!_state.PodmanInstalled) { GoToStep(WizardStep.InstallPodman); return; } if (!_state.PodmanConfigured) { GoToStep(WizardStep.ConfigurePodman); return; } if (!_state.AgentInstalled) { GoToStep(WizardStep.InstallAgent); return; } GoToStep(WizardStep.Welcome); } private bool IsFreshInstall() { return _state.Step == WizardStep.Welcome && !_state.WslFeaturesEnabled && !_state.WslPackageInstalled && !_state.VirtualEnvironmentInstalled && !_state.WslDefaultVersionSet && !_state.WslKernelUpdated && !_state.PodmanInstalled && !_state.PodmanConfigured && !_state.AgentInstalled; } private void GoBack() { switch (_currentStep) { case WizardStep.Prerequisites: GoToStep(WizardStep.Welcome); break; case WizardStep.InstallVirtualEnvironment: case WizardStep.RestartRequired: GoToStep(WizardStep.Prerequisites); break; case WizardStep.InstallPodman: GoToStep(WizardStep.Prerequisites); break; case WizardStep.ConfigurePodman: GoToStep(WizardStep.InstallPodman); break; case WizardStep.InstallAgent: GoToStep(WizardStep.ConfigurePodman); break; } } private void GoNext() { switch (_currentStep) { case WizardStep.Welcome: GoToStep(WizardStep.Prerequisites); break; case WizardStep.Prerequisites: if (_lastCheck?.VirtualEnvironmentInstalled != true) GoToStep(WizardStep.InstallVirtualEnvironment); else if (_lastCheck?.PodmanInstalled != true) GoToStep(WizardStep.InstallPodman); else if (_lastCheck?.PodmanMachineReady != true) GoToStep(WizardStep.ConfigurePodman); else GoToStep(WizardStep.InstallAgent); break; case WizardStep.InstallVirtualEnvironment: InstallVirtualEnvironment(); break; case WizardStep.RestartRequired: Application.Exit(); break; case WizardStep.InstallPodman: InstallPodman(); break; case WizardStep.ConfigurePodman: ConfigurePodman(); break; case WizardStep.InstallAgent: InstallAgent(); break; case WizardStep.Finished: Application.Exit(); break; case WizardStep.Uninstall: RunUninstall(); break; } } private void GoToStep(WizardStep step) { _currentStep = step; _state.Step = step; _state.Save(); _contentPanel.Controls.Clear(); switch (step) { case WizardStep.Welcome: ShowWelcome(); break; case WizardStep.Prerequisites: ShowPrerequisites(); break; case WizardStep.InstallVirtualEnvironment: ShowInstallVirtualEnvironment(); break; case WizardStep.RestartRequired: ShowRestartRequired(); break; case WizardStep.InstallPodman: ShowInstallPodman(); break; case WizardStep.ConfigurePodman: ShowConfigurePodman(); break; case WizardStep.InstallAgent: ShowInstallAgent(); break; case WizardStep.Finished: ShowFinished(); break; case WizardStep.Uninstall: ShowUninstall(); break; } } private void ShowWelcome() { var version = GetVersion(); _titleLabel.Text = "Bienvenue dans l'installateur studioE5"; _subtitleLabel.Text = $"Version {version}"; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 200, Text = $"Version {version}\n\n" + "L'installation se déroule en plusieurs étapes :\n\n" + "1. Vérification des prérequis\n" + "2. Installation de l'environnement virtuel (si nécessaire)\n" + "3. Installation de Podman\n" + "4. Configuration de Podman\n" + "5. Installation de studioE5 Agent\n\n" + "Cliquez sur 'Suivant' pour commencer.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = false; _btnNext.Text = "Suivant >"; _btnNext.Enabled = true; _btnCancel.Text = "Annuler"; } private void ShowPrerequisites() { _titleLabel.Text = "Vérification des prérequis"; _subtitleLabel.Text = "Assurez-vous que votre poste est prêt avant de continuer."; _lastCheck = PrerequisiteChecker.Check(); var result = _lastCheck; var text = $"Système d'exploitation : {(result.WindowsCompatible ? "✅ Compatible" : "❌ Non compatible (Windows 10 2004+ requis)")}\n" + $"Mémoire vive (RAM) : {(result.RamMB >= 8192 ? "✅" : result.RamMB >= 4096 ? "⚠️" : "❌")} {result.RamMB} Mo (8 Go recommandés)\n" + $"Espace disque disponible : {(result.FreeDiskMB >= 10240 ? "✅" : result.FreeDiskMB >= 5120 ? "⚠️" : "❌")} {result.FreeDiskMB} Mo (10 Go recommandés)\n" + $"Environnement virtuel : {(result.VirtualEnvironmentInstalled ? "✅ Installé" : "❌ Non installé")}\n" + $"Podman : {(result.PodmanInstalled ? "✅ Installé" : "❌ Non installé")}\n" + $"Machine Podman : {(result.PodmanMachineReady ? "✅ Prête" : "❌ Non prête")}\n\n"; if (result.AllReady) { text += "Tous les prérequis sont satisfaits. Vous pouvez continuer."; } else { text += "Ordre d'installation recommandé :\n" + "1. Installer l'environnement virtuel\n" + "2. Installer Podman\n" + "3. Configurer Podman\n" + "4. Installer studioE5 Agent"; } var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 240, Text = text, Font = new Font("Consolas", 10), ForeColor = Color.Black }; _contentPanel.Controls.Add(label); _btnBack.Visible = true; _btnNext.Text = GetPrerequisitesNextButtonText(result); _btnNext.Enabled = true; } private string GetPrerequisitesNextButtonText(PrerequisiteResult result) { if (result.AllReady) return "Suivant >"; if (!result.VirtualEnvironmentInstalled) return "Installer l'environnement virtuel"; if (!result.PodmanInstalled) return "Installer Podman"; if (!result.PodmanMachineReady) return "Configurer Podman"; return "Suivant >"; } private void ShowInstallVirtualEnvironment() { _titleLabel.Text = "Installation de l'environnement virtuel"; _subtitleLabel.Text = "L'environnement virtuel permet de faire tourner Podman sur Windows."; var progress = GetWslInstallationProgress(); var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 160, Text = "L'environnement virtuel (WSL2) n'est pas installé.\n\n" + "L'assistant va effectuer les opérations suivantes :\n" + "1. Activer les fonctionnalités Windows requises\n" + "2. Mettre à jour le noyau WSL2\n" + "3. Installer WSL2 si nécessaire\n" + "4. Définir WSL2 comme version par défaut\n" + "5. Vérifier que WSL2 est opérationnel\n\n" + "Selon votre version de Windows, certaines étapes peuvent être implicites.\n" + "Un redémarrage de l'ordinateur sera probablement nécessaire. L'installateur se relancera automatiquement.", Font = new Font("Segoe UI", 10) }; var progressLabel = new Label { Left = 0, Top = 190, Width = _contentPanel.Width, Height = 80, Text = progress, Font = new Font("Consolas", 9), ForeColor = Color.DarkSlateGray }; _contentPanel.Controls.Add(label); _contentPanel.Controls.Add(progressLabel); _btnBack.Visible = true; _btnNext.Text = progress.Contains("Terminé") ? "Continuer" : "Installer"; _btnNext.Enabled = true; } private string GetWslInstallationProgress() { var wsl2Operational = PrerequisiteChecker.IsWSL2Ready(); var steps = new List { $"[{( _state.WslFeaturesEnabled ? "x" : " " )}] Fonctionnalités Windows activées", $"[{( _state.WslPackageInstalled ? "x" : " " )}] Package WSL2 installé", $"[{( _state.WslKernelUpdated ? "x" : " " )}] Noyau WSL2 mis à jour", $"[{( _state.VirtualEnvironmentInstalled ? "x" : " " )}] WSL2 installé", $"[{( _state.WslDefaultVersionSet ? "x" : " " )}] WSL2 défini comme version par défaut", $"[{( wsl2Operational ? "x" : " " )}] WSL2 opérationnel" }; if (_state.WslFeaturesEnabled && _state.WslPackageInstalled && _state.WslKernelUpdated && _state.VirtualEnvironmentInstalled && _state.WslDefaultVersionSet && wsl2Operational) steps.Add("\nTerminé. Cliquez sur Continuer."); return string.Join("\n", steps); } private async void InstallVirtualEnvironment() { _btnNext.Enabled = false; _btnBack.Enabled = false; UseWaitCursor = true; try { // Si WSL2 est déjà prêt, on marque toutes les étapes comme faites et on continue. if (PrerequisiteChecker.IsWSL2Ready()) { MarkWslInstallationComplete(); SetStatus(null); GoToStep(WizardStep.InstallPodman); return; } // Étape 1 : activer les fonctionnalités Windows requises par WSL2. if (!_state.WslFeaturesEnabled) { SetStatus("Activation des fonctionnalités Windows requises... Cela peut prendre plusieurs minutes."); var rebootRequired = await Task.Run(() => { var r = false; r |= EnableWindowsFeature("Microsoft-Windows-Subsystem-Linux"); r |= EnableWindowsFeature("VirtualMachinePlatform"); // Fonctionnalités Hyper-V optionnelles mais utiles pour WSL2. // Elles peuvent ne pas exister sur Windows Home : on ignore les erreurs. TryEnableWindowsFeature("Microsoft-Hyper-V-All"); TryEnableWindowsFeature("Microsoft-Hyper-V"); TryEnableWindowsFeature("HypervisorPlatform"); return r; }); _state.WslFeaturesEnabled = true; _state.Save(); if (rebootRequired) { // Au moins une fonctionnalité nécessite un redémarrage. RegisterRunOnce(); GoToStep(WizardStep.RestartRequired); return; } } // Étape 2 : installer le package WSL2 complet offline si disponible. // Le package Microsoft.WSL.msixbundle remplace l'installation via le Store // et inclut le noyau WSL2. if (!_state.WslPackageInstalled) { SetStatus("Installation du package WSL2..."); var packageInstalled = await Task.Run(() => TryInstallWslAppxBundle()); if (packageInstalled) { _state.WslPackageInstalled = true; _state.WslKernelUpdated = true; // Le bundle inclut le noyau. _state.Save(); if (CheckWsl2ReadyAndContinue()) return; } } // Étape 3 : installer/mettre à jour le noyau WSL2 si ce n'est pas déjà fait. // Fallback sur le MSI noyau bundlé ou sur Windows Update (wsl --update). if (!_state.WslKernelUpdated) { SetStatus("Mise à jour du noyau WSL2..."); await Task.Run(() => TryInstallWslKernelMsi()); _state.WslKernelUpdated = true; _state.Save(); if (CheckWsl2ReadyAndContinue()) return; } // Étape 4 : définir WSL2 comme version par défaut. if (!_state.WslDefaultVersionSet) { SetStatus("Définition de WSL2 comme version par défaut..."); await Task.Run(() => RunCommand("wsl.exe", "--set-default-version 2", "Définition de WSL2 comme version par défaut")); _state.WslDefaultVersionSet = true; _state.Save(); } SetStatus(null); GoToStep(WizardStep.InstallPodman); } catch (Exception ex) { MessageBox.Show($"Erreur lors de l'installation de l'environnement virtuel : {ex.Message}", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { _btnNext.Enabled = true; _btnBack.Enabled = true; UseWaitCursor = false; } } private void MarkWslInstallationComplete() { _state.WslFeaturesEnabled = true; _state.WslPackageInstalled = true; _state.WslKernelUpdated = true; _state.VirtualEnvironmentInstalled = true; _state.WslDefaultVersionSet = true; _state.Save(); } private bool CheckWsl2ReadyAndContinue() { if (!PrerequisiteChecker.IsWSL2Ready()) return false; MarkWslInstallationComplete(); SetStatus(null); GoToStep(WizardStep.InstallPodman); return true; } private bool TryInstallWslAppxBundle() { var resourcesDir = Path.Combine(AppContext.BaseDirectory, "Resources"); if (!Directory.Exists(resourcesDir)) return false; var bundlePath = Directory.EnumerateFiles(resourcesDir, "Microsoft.WSL_*.msixbundle").FirstOrDefault(); if (string.IsNullOrEmpty(bundlePath)) return false; SetStatus("Installation du package WSL2 depuis le bundle local..."); var script = $"Add-AppxPackage -Path \"{bundlePath}\""; RunCommand( "powershell.exe", $"-ExecutionPolicy Bypass -Command \"{script}\"", "Installation du package WSL2 (Add-AppxPackage)"); return true; } private void TryInstallWslKernelMsi() { var kernelMsiPath = Path.Combine(AppContext.BaseDirectory, "Resources", "wsl_update_x64.msi"); if (File.Exists(kernelMsiPath)) { // Mode offline : installation du noyau WSL2 depuis le MSI bundlé. SetStatus("Installation du noyau WSL2 depuis le package local..."); RunCommand("msiexec.exe", $"/i \"{kernelMsiPath}\" /qn /norestart", "Installation du noyau WSL2"); return; } // Mode online : mise à jour via Windows Update. SetStatus("Téléchargement et mise à jour du noyau WSL2 (nécessite Internet)..."); RunCommand("wsl.exe", "--update", "Mise à jour du noyau WSL2", additionalSuccessCodes: new[] { 3010 }); } private void SetStatus(string? text) { if (string.IsNullOrEmpty(text)) { _statusLabel.Visible = false; _statusLabel.Text = string.Empty; } else { _statusLabel.Text = text; _statusLabel.Visible = true; } } private bool EnableWindowsFeature(string featureName) { // 3010 = succès, mais un redémarrage est nécessaire. // L'activation des fonctionnalités Windows peut prendre plusieurs minutes // (voire plus de 10 min sur certaines machines), on allonge donc le timeout. var exitCode = RunCommand( "dism.exe", $"/online /enable-feature /featurename:{featureName} /all /norestart", $"Activation de la fonctionnalité Windows {featureName}", additionalSuccessCodes: new[] { 3010 }, timeoutSeconds: 1200); return exitCode == 3010; } private void TryEnableWindowsFeature(string featureName) { try { // 3010 = succès, mais un redémarrage est nécessaire. var exitCode = RunCommand( "dism.exe", $"/online /enable-feature /featurename:{featureName} /all /norestart", $"Activation de la fonctionnalité Windows {featureName}", additionalSuccessCodes: new[] { 3010 }, timeoutSeconds: 1200); // Si la fonctionnalité a été activée avec succès et nécessite un redémarrage, // on ne force pas ici : les fonctionnalités obligatoires gèrent déjà le redémarrage. } catch { // Fonctionnalité optionnelle ou indisponible sur cette édition de Windows : on ignore. } } private void ShowRestartRequired() { _titleLabel.Text = "Redémarrage nécessaire"; _subtitleLabel.Text = "Les fonctionnalités Windows ont été activées."; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 120, Text = "Veuillez redémarrer votre ordinateur pour finaliser l'activation des fonctionnalités Windows.\n\n" + "L'installateur se relancera automatiquement après le redémarrage pour continuer l'installation de WSL2.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = false; _btnNext.Text = "Redémarrer maintenant"; _btnNext.Enabled = true; _btnCancel.Text = "Redémarrer plus tard"; } private void ShowInstallPodman() { _titleLabel.Text = "Installation de Podman"; _subtitleLabel.Text = "Podman est le moteur de conteneurs utilisé par studioE5."; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 120, Text = "Podman va être installé sur votre poste.\n\n" + "Cliquez sur 'Installer' pour lancer l'installation silencieuse.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = true; _btnNext.Text = "Installer Podman"; _btnNext.Enabled = true; } private async void InstallPodman() { _btnNext.Enabled = false; _btnBack.Enabled = false; UseWaitCursor = true; try { var msiPath = Path.Combine(AppContext.BaseDirectory, "Resources", "podman-installer-windows-amd64.msi"); if (!File.Exists(msiPath)) throw new FileNotFoundException("Le fichier podman-installer-windows-amd64.msi est introuvable. Vérifiez qu'il est bien inclus dans le package."); SetStatus("Installation de Podman en cours..."); await Task.Run(() => RunCommand("msiexec.exe", $"/i \"{msiPath}\" /qn /norestart", "Installation de Podman en cours...")); _state.PodmanInstalled = true; _state.Save(); // Installation de Docker Compose standalone pour que l'agent studioE5 // puisse utiliser la commande `docker-compose` avec Podman. SetStatus("Installation de Docker Compose..."); await Task.Run(() => InstallDockerCompose()); SetStatus(null); GoToStep(WizardStep.ConfigurePodman); } catch (Exception ex) { MessageBox.Show($"Erreur lors de l'installation de Podman : {ex.Message}", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { _btnNext.Enabled = true; _btnBack.Enabled = true; UseWaitCursor = false; } } private void InstallDockerCompose() { var composeSourcePath = Path.Combine(AppContext.BaseDirectory, "Resources", "docker-compose-windows-x86_64.exe"); if (!File.Exists(composeSourcePath)) { // Docker Compose n'est pas bundlé : on ignore silencieusement. // L'utilisateur devra l'installer manuellement. return; } // Cible : même dossier que podman.exe, qui est déjà dans le PATH. var podmanPath = PrerequisiteChecker.GetPodmanExePath(); if (string.IsNullOrEmpty(podmanPath)) { // Fallback : dossier dédié studioE5-agent. var fallbackDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "studioE5-agent", "bin"); Directory.CreateDirectory(fallbackDir); var fallbackPath = Path.Combine(fallbackDir, "docker-compose.exe"); File.Copy(composeSourcePath, fallbackPath, overwrite: true); AddToPath(fallbackDir); return; } var podmanDir = Path.GetDirectoryName(podmanPath); if (!string.IsNullOrEmpty(podmanDir)) { var targetPath = Path.Combine(podmanDir, "docker-compose.exe"); File.Copy(composeSourcePath, targetPath, overwrite: true); } } private static void AddToPath(string directory) { try { const string keyPath = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; using var key = Registry.LocalMachine.OpenSubKey(keyPath, true); if (key == null) return; var currentPath = key.GetValue("Path", "", RegistryValueOptions.DoNotExpandEnvironmentNames) as string ?? ""; if (currentPath.Contains(directory, StringComparison.OrdinalIgnoreCase)) return; var newPath = currentPath.TrimEnd(';') + ";" + directory; key.SetValue("Path", newPath, RegistryValueKind.ExpandString); } catch { // ignored } } private void EnsureWslConfig() { try { var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var wslConfigPath = Path.Combine(userProfile, ".wslconfig"); const string desiredContent = "[wsl2]\nmemory=8GB\nprocessors=4\n"; var currentContent = File.Exists(wslConfigPath) ? File.ReadAllText(wslConfigPath) : ""; var needsUpdate = !currentContent.Contains("memory=8GB", StringComparison.OrdinalIgnoreCase) || !currentContent.Contains("processors=4", StringComparison.OrdinalIgnoreCase); if (needsUpdate) { File.WriteAllText(wslConfigPath, desiredContent); } // Appliquer la configuration en arrêtant WSL. // La nouvelle configuration sera prise en compte au prochain démarrage. RunCommand("wsl.exe", "--shutdown", "Arrêt de WSL2 pour appliquer la configuration", timeoutSeconds: 60); } catch { // On ignore les erreurs : si WSL n'est pas encore installé, la configuration // sera prise en compte au premier démarrage. } } private void ShowConfigurePodman() { _titleLabel.Text = "Configuration de Podman"; _subtitleLabel.Text = "Initialisation de la machine Podman."; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 120, Text = "La machine virtuelle Podman va être créée et démarrée.\n\n" + "Cette opération peut prendre plusieurs minutes la première fois.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = true; _btnNext.Text = "Configurer Podman"; _btnNext.Enabled = true; } private async void ConfigurePodman() { _btnNext.Enabled = false; _btnBack.Enabled = false; UseWaitCursor = true; try { var podmanPath = PrerequisiteChecker.GetPodmanExePath(); if (string.IsNullOrEmpty(podmanPath)) throw new FileNotFoundException("podman.exe est introuvable. Vérifiez que Podman est bien installé."); // Configurer WSL pour allouer suffisamment de ressources à la machine Podman. // Par défaut WSL2 n'utilise que 2 Go de RAM, ce qui est insuffisant pour Prestashop. SetStatus("Configuration des ressources WSL2..."); await Task.Run(() => EnsureWslConfig()); var machineImagePath = GetPodmanMachineImagePath(); if (!string.IsNullOrEmpty(machineImagePath)) { SetStatus("Initialisation de la machine Podman depuis l'image locale..."); await Task.Run(() => RunCommand(podmanPath, $"machine init --image \"{machineImagePath}\"", "Initialisation de la machine Podman (offline)")); } else { SetStatus("Initialisation de la machine Podman (téléchargement depuis Internet)..."); await Task.Run(() => RunCommand(podmanPath, "machine init", "Initialisation de la machine Podman")); } SetStatus("Démarrage de la machine Podman..."); await Task.Run(() => RunCommand(podmanPath, "machine start", "Démarrage de la machine Podman...")); _state.PodmanConfigured = true; _state.Save(); SetStatus(null); GoToStep(WizardStep.InstallAgent); } catch (Exception ex) { MessageBox.Show($"Erreur lors de la configuration de Podman : {ex.Message}", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { _btnNext.Enabled = true; _btnBack.Enabled = true; UseWaitCursor = false; } } private static string? GetPodmanMachineImagePath() { var resourcesDir = Path.Combine(AppContext.BaseDirectory, "Resources"); if (!Directory.Exists(resourcesDir)) return null; return Directory.EnumerateFiles(resourcesDir, "podman-machine.*.wsl.tar.zst").FirstOrDefault(); } private void ShowInstallAgent() { _titleLabel.Text = "Installation de studioE5 Agent"; _subtitleLabel.Text = "L'environnement est prêt. Il ne reste plus qu'à installer l'agent."; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 120, Text = "Cliquez sur 'Installer studioE5 Agent' pour lancer le package d'installation.\n\n" + "Une fois l'installation terminée, fermez cet assistant.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = true; _btnNext.Text = "Installer studioE5 Agent"; _btnNext.Enabled = true; } private void InstallAgent() { try { var setupPath = Path.Combine(AppContext.BaseDirectory, "Resources", "studioE5-agent-setup.exe"); if (!File.Exists(setupPath)) throw new FileNotFoundException("Le fichier studioE5-agent-setup.exe est introuvable. Vérifiez qu'il est bien inclus dans le package."); var psi = new ProcessStartInfo(setupPath) { UseShellExecute = true, Verb = "runas" }; Process.Start(psi); _state.AgentInstalled = true; _state.Save(); GoToStep(WizardStep.Finished); } catch (Exception ex) { MessageBox.Show($"Erreur lors du lancement de l'installation de l'agent : {ex.Message}", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void ShowFinished() { _titleLabel.Text = "Installation terminée"; _subtitleLabel.Text = "studioE5 Agent est prêt à être utilisé."; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 120, Text = "L'installation est terminée.\n\n" + "Vous pouvez lancer studioE5 Agent depuis le menu Démarrer ou le bureau.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = false; _btnNext.Text = "Terminer"; _btnCancel.Text = "Fermer"; } private void ShowUninstall() { _titleLabel.Text = "Désinstallation de studioE5"; _subtitleLabel.Text = "Suppression complète de studioE5 Agent et des composants associés."; var label = new Label { Left = 0, Top = 20, Width = _contentPanel.Width, Height = 200, Text = "Cette opération va :\n\n" + "- Arrêter studioE5 Agent\n" + "- Désinstaller studioE5 Agent\n" + "- Supprimer les données élèves\n" + "- Désinstaller Podman\n" + "- Proposer de désactiver l'environnement virtuel\n\n" + "Cliquez sur 'Désinstaller' pour commencer.", Font = new Font("Segoe UI", 10) }; _contentPanel.Controls.Add(label); _btnBack.Visible = false; _btnNext.Text = "Désinstaller"; _btnCancel.Text = "Annuler"; } private void RunUninstall() { _btnNext.Enabled = false; try { // 1. Kill agent RunCommand("taskkill.exe", "/f /im studioE5-agent.exe", "Arrêt de studioE5 Agent..."); // 2. Uninstall agent via Inno Setup uninstaller var agentUninstaller = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "studioE5-agent", "unins000.exe"); if (File.Exists(agentUninstaller)) RunCommand(agentUninstaller, "/SILENT", "Désinstallation de studioE5 Agent..."); // 3. Delete student data var dataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "studioE5-agent"); if (Directory.Exists(dataDir)) { Directory.Delete(dataDir, true); } // 4. Uninstall Podman var podmanMsiPath = Path.Combine(AppContext.BaseDirectory, "Resources", "podman-installer-windows-amd64.msi"); if (File.Exists(podmanMsiPath)) { RunCommand("msiexec.exe", $"/x \"{podmanMsiPath}\" /qn /norestart", "Désinstallation de Podman..."); } else { MessageBox.Show("Le MSI de Podman n'a pas été trouvé dans le package. Veuillez désinstaller Podman manuellement via Ajouter/Supprimer des programmes.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); } // 5. Optionally disable WSL2 var result = MessageBox.Show("Voulez-vous désactiver l'environnement virtuel (WSL2) ?", "Environnement virtuel", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { RunCommand("wsl.exe", "--uninstall", "Désactivation de l'environnement virtuel..."); } InstallerState.Delete(); MessageBox.Show("Désinstallation terminée.", "Terminé", MessageBoxButtons.OK, MessageBoxIcon.Information); Application.Exit(); } catch (Exception ex) { MessageBox.Show($"Erreur lors de la désinstallation : {ex.Message}", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { _btnNext.Enabled = true; } } private int RunCommand(string fileName, string arguments, string description, int[]? additionalSuccessCodes = null, int timeoutSeconds = 300) { var psi = new ProcessStartInfo(fileName, arguments) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; using var process = Process.Start(psi); if (process == null) throw new InvalidOperationException($"Impossible de démarrer {fileName}"); // Lire stdout/stderr en parallèle pour éviter le blocage du processus // lorsque ses buffers de sortie sont pleins. var stdoutTask = Task.Run(() => process.StandardOutput.ReadToEnd()); var stderrTask = Task.Run(() => process.StandardError.ReadToEnd()); if (!process.WaitForExit(timeoutSeconds * 1000)) { try { process.Kill(); } catch { /* ignored */ } throw new TimeoutException($"{description} a dépassé le délai de {timeoutSeconds} secondes."); } Task.WaitAll(stdoutTask, stderrTask); var exitCode = process.ExitCode; if (exitCode != 0 && !(additionalSuccessCodes?.Contains(exitCode) ?? false)) { throw new InvalidOperationException($"{description} a échoué (code {exitCode}) : {stderrTask.Result}"); } return exitCode; } private static string GetVersion() { try { var versionPath = Path.Combine(AppContext.BaseDirectory, "VERSION"); if (File.Exists(versionPath)) { var version = File.ReadAllText(versionPath).Trim(); if (!string.IsNullOrWhiteSpace(version)) return version; } } catch { // ignored } return "0.0.0"; } private void RegisterRunOnce() { var executablePath = Application.ExecutablePath; var key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce", true); key?.SetValue("StudioE5SetupWizard", $"\"{executablePath}\""); } }