feat: installation offline complete, HTTPS registry, 8Go WSL, v0.3.18

- Wizard: installation 100% offline (WSL bundle, Podman MSI, machine image, docker-compose)
- Wizard: suppression de wsl --install --no-distribution
- Wizard: .wslconfig avec 8Go RAM / 4 CPU
- Wizard: operations asynchrones pour eviter le freeze UI
- Wizard: detection automatique de podman.exe
- Wizard: version 0.1.1
- Agent: passage en v0.3.18
- Serveur: registry PrestaShop en HTTPS via gitea.alfrednobel.edudeploy.com
- Caddy: config gitea.alfrednobel.edudeploy.com
- Docs: mise a jour SUIVI_INSTALLER.md, README.md, seed.ts
This commit is contained in:
EduBox Dev
2026-07-02 22:52:28 +00:00
parent 3c519629d2
commit fc61404271
15 changed files with 1094 additions and 62 deletions
+485 -38
View File
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Microsoft.Win32;
namespace StudioE5.SetupWizard;
@@ -13,6 +15,7 @@ public partial class MainForm : Form
private readonly Button _btnCancel;
private readonly Label _titleLabel;
private readonly Label _subtitleLabel;
private readonly Label _statusLabel;
private WizardStep _currentStep = WizardStep.Welcome;
private PrerequisiteResult? _lastCheck;
@@ -20,7 +23,8 @@ public partial class MainForm : Form
public MainForm(bool startInUninstallMode = false)
{
_state = InstallerState.Load();
Text = startInUninstallMode ? "Désinstallation studioE5" : "Installateur studioE5 Agent";
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;
@@ -34,7 +38,7 @@ public partial class MainForm : Form
Width = 640,
Height = 32,
Font = new Font("Segoe UI", 14, FontStyle.Bold),
Text = "Installateur studioE5 Agent"
Text = $"Installateur studioE5 Agent v{version}"
};
_subtitleLabel = new Label
@@ -47,12 +51,23 @@ public partial class MainForm : Form
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 = 84,
Top = 100,
Width = 640,
Height = 320,
Height = 304,
BorderStyle = BorderStyle.None
};
@@ -99,6 +114,7 @@ public partial class MainForm : Form
Controls.Add(_titleLabel);
Controls.Add(_subtitleLabel);
Controls.Add(_statusLabel);
Controls.Add(_contentPanel);
Controls.Add(separator);
Controls.Add(_btnBack);
@@ -116,18 +132,30 @@ public partial class MainForm : Form
private void ResumeAfterReboot()
{
// If we are resuming after a reboot, skip directly to the relevant step.
if (_state.VirtualEnvironmentInstalled && !_state.PodmanInstalled)
// 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.PodmanInstalled && !_state.PodmanConfigured)
if (!_state.PodmanConfigured)
{
GoToStep(WizardStep.ConfigurePodman);
return;
}
if (_state.PodmanConfigured && !_state.AgentInstalled)
if (!_state.AgentInstalled)
{
GoToStep(WizardStep.InstallAgent);
return;
@@ -136,6 +164,19 @@ public partial class MainForm : Form
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)
@@ -167,10 +208,14 @@ public partial class MainForm : Form
GoToStep(WizardStep.Prerequisites);
break;
case WizardStep.Prerequisites:
if (_lastCheck?.VirtualEnvironmentInstalled == true)
GoToStep(WizardStep.InstallPodman);
else
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();
@@ -237,8 +282,9 @@ public partial class MainForm : Form
private void ShowWelcome()
{
var version = GetVersion();
_titleLabel.Text = "Bienvenue dans l'installateur studioE5";
_subtitleLabel.Text = "Cet assistant va vous guider pour installer studioE5 Agent sur votre poste.";
_subtitleLabel.Text = $"Version {version}";
var label = new Label
{
@@ -246,7 +292,8 @@ public partial class MainForm : Form
Top = 20,
Width = _contentPanel.Width,
Height = 200,
Text = "L'installation se déroule en plusieurs étapes :\n\n" +
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" +
@@ -306,46 +353,174 @@ public partial class MainForm : Form
_contentPanel.Controls.Add(label);
_btnBack.Visible = true;
_btnNext.Text = result.AllReady ? "Suivant >" : (result.VirtualEnvironmentInstalled ? "Installer Podman" : "Installer l'environnement virtuel");
_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 = 120,
Height = 160,
Text = "L'environnement virtuel (WSL2) n'est pas installé.\n\n" +
"Cliquez sur 'Installer' pour lancer l'installation.\n" +
"Un redémarrage de l'ordinateur sera nécessaire. L'installateur se relancera automatiquement.",
"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 = "Installer";
_btnNext.Text = progress.Contains("Terminé") ? "Continuer" : "Installer";
_btnNext.Enabled = true;
}
private void InstallVirtualEnvironment()
private string GetWslInstallationProgress()
{
var wsl2Operational = PrerequisiteChecker.IsWSL2Ready();
var steps = new List<string>
{
$"[{( _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
{
RunCommand("wsl.exe", "--install --no-distribution", "Installation de l'environnement virtuel en cours...");
_state.VirtualEnvironmentInstalled = true;
_state.Save();
RegisterRunOnce();
GoToStep(WizardStep.RestartRequired);
// 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)
{
@@ -355,13 +530,122 @@ public partial class MainForm : Form
{
_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 = "L'environnement virtuel a été installé.";
_subtitleLabel.Text = "Les fonctionnalités Windows ont été activées.";
var label = new Label
{
@@ -369,8 +653,8 @@ public partial class MainForm : Form
Top = 20,
Width = _contentPanel.Width,
Height = 120,
Text = "Veuillez redémarrer votre ordinateur pour finaliser l'installation de l'environnement virtuel.\n\n" +
"L'installateur se relancera automatiquement après le redémarrage pour continuer l'installation.",
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)
};
@@ -405,10 +689,11 @@ public partial class MainForm : Form
_btnNext.Enabled = true;
}
private void InstallPodman()
private async void InstallPodman()
{
_btnNext.Enabled = false;
_btnBack.Enabled = false;
UseWaitCursor = true;
try
{
@@ -416,9 +701,17 @@ public partial class MainForm : Form
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.");
RunCommand("msiexec.exe", $"/i \"{msiPath}\" /qn /norestart", "Installation de Podman en cours...");
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)
@@ -429,6 +722,91 @@ public partial class MainForm : Form
{
_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.
}
}
@@ -455,17 +833,40 @@ public partial class MainForm : Form
_btnNext.Enabled = true;
}
private void ConfigurePodman()
private async void ConfigurePodman()
{
_btnNext.Enabled = false;
_btnBack.Enabled = false;
UseWaitCursor = true;
try
{
RunCommand("podman.exe", "machine init", "Initialisation de la machine Podman...");
RunCommand("podman.exe", "machine start", "Démarrage de la machine Podman...");
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)
@@ -476,9 +877,19 @@ public partial class MainForm : Form
{
_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";
@@ -631,26 +1042,62 @@ public partial class MainForm : Form
}
}
private void RunCommand(string fileName, string arguments, string description)
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
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}");
process.WaitForExit();
if (process.ExitCode != 0)
// 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))
{
var error = process.StandardError.ReadToEnd();
throw new InvalidOperationException($"{description} a échoué (code {process.ExitCode}) : {error}");
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()