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:
+1
-1
@@ -1 +1 @@
|
||||
0.3.17
|
||||
0.3.18
|
||||
|
||||
@@ -24,9 +24,20 @@ Fournir un **installateur professionnel Windows** pour studioE5 Agent, guidé pa
|
||||
|
||||
- Wizard C# avec 7 étapes guidées.
|
||||
- Détection des prérequis : Windows, RAM, disque, WSL2, Podman.
|
||||
- Installation WSL2 avec redémarrage + reprise automatique.
|
||||
- Installation WSL2 en plusieurs étapes contrôlées avec suivi visuel :
|
||||
1. Activation des fonctionnalités Windows (`Microsoft-Windows-Subsystem-Linux` et `VirtualMachinePlatform`) avec gestion du code 3010 (redémarrage nécessaire).
|
||||
2. Installation du package WSL2 complet depuis le bundle Microsoft Store offline (`Microsoft.WSL_*.msixbundle`) via `Add-AppxPackage`.
|
||||
3. Mise à jour du noyau WSL2 depuis le MSI bundlé (`wsl_update_x64.msi`) ou, en dernier recours, via `wsl --update`.
|
||||
4. Définition de WSL2 comme version par défaut (`wsl --set-default-version 2`).
|
||||
5. L’étape `wsl --install --no-distribution` n’est plus utilisée : l’installation est entièrement offline grâce au bundle.
|
||||
6. Conversion des distributions WSL1 existantes vers WSL2 (`wsl --set-version <nom> 2`) si nécessaire.
|
||||
- Reprise automatique après redémarrage grâce à `RunOnce` (y compris redémarrage post-installation WSL2).
|
||||
- Versioning indépendant du wizard (fichier `setup-wizard/VERSION`, affiché dans la fenêtre et la page d’accueil).
|
||||
- Amélioration de l’interface : fenêtre agrandie à 800×600, `AutoScaleMode = Dpi`, meilleur rendu sur petits écrans (11 pouces).
|
||||
- `RunCommand` capture désormais stdout + stderr pour des diagnostics et fallbacks plus fiables.
|
||||
- Détection des prérequis vérifie que WSL2 (et non WSL1) est installé via `wsl --status`.
|
||||
- Installation Podman via MSI bundlé.
|
||||
- Configuration Podman (`machine init` + `machine start`).
|
||||
- Configuration Podman (`machine init` + `machine start`), avec initialisation offline possible depuis une image locale (`podman-machine.*.wsl.tar.zst`).
|
||||
- Lancement du package Inno Setup agent.
|
||||
- Mode désinstallation complet.
|
||||
- Script Inno Setup de base pour l’agent.
|
||||
@@ -57,6 +68,10 @@ Dans `setup-wizard/Resources/` :
|
||||
```text
|
||||
podman-installer-windows-amd64.msi
|
||||
studioE5-agent-setup.exe
|
||||
Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle # package WSL2 complet (offline)
|
||||
podman-machine.x86_64.wsl.tar.zst # image Podman machine pour WSL (offline)
|
||||
docker-compose-windows-x86_64.exe # Docker Compose standalone (offline)
|
||||
wsl_update_x64.msi # optionnel, fallback noyau WSL2
|
||||
```
|
||||
|
||||
### Commande
|
||||
@@ -95,8 +110,14 @@ Le fichier généré se trouve dans `installer-output/`.
|
||||
## Notes importantes
|
||||
|
||||
- Le wizard doit être exécuté **en administrateur**.
|
||||
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur.
|
||||
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur après l’activation des fonctionnalités Windows.
|
||||
- L’installation de WSL2 est découpée en étapes et chaque étape est enregistrée pour permettre la reprise après redémarrage.
|
||||
- Podman nécessite WSL2 : le wizard vérifie donc que la version par défaut de WSL est 2.
|
||||
- Le MSI Podman officiel pour Windows est `podman-installer-windows-amd64.msi`.
|
||||
- Le bundle WSL2 offline est disponible sur <https://github.com/microsoft/WSL/releases> : `Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle`.
|
||||
- L’image Podman machine offline est disponible sur <https://github.com/containers/podman-machine-os/releases> : `podman-machine.x86_64.wsl.tar.zst`.
|
||||
- Docker Compose standalone est disponible sur <https://github.com/docker/compose/releases> : `docker-compose-windows-x86_64.exe`.
|
||||
- Le wizard crée automatiquement un fichier `.wslconfig` allouant **8 Go de RAM et 4 CPU** à WSL2, ce qui est nécessaire pour des applications lourdes comme PrestaShop.
|
||||
- Pour la désinstallation, le MSI Podman doit être présent dans `Resources/`.
|
||||
|
||||
## Liens utiles
|
||||
|
||||
@@ -24,6 +24,18 @@ public class InstallerState
|
||||
[JsonPropertyName("virtualEnvironmentInstalled")]
|
||||
public bool VirtualEnvironmentInstalled { get; set; }
|
||||
|
||||
[JsonPropertyName("wslFeaturesEnabled")]
|
||||
public bool WslFeaturesEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("wslPackageInstalled")]
|
||||
public bool WslPackageInstalled { get; set; }
|
||||
|
||||
[JsonPropertyName("wslDefaultVersionSet")]
|
||||
public bool WslDefaultVersionSet { get; set; }
|
||||
|
||||
[JsonPropertyName("wslKernelUpdated")]
|
||||
public bool WslKernelUpdated { get; set; }
|
||||
|
||||
[JsonPropertyName("podmanInstalled")]
|
||||
public bool PodmanInstalled { get; set; }
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StudioE5.SetupWizard;
|
||||
|
||||
@@ -19,13 +22,20 @@ public static class PrerequisiteChecker
|
||||
{
|
||||
public static PrerequisiteResult Check()
|
||||
{
|
||||
var wsl2Ready = IsWSL2Ready();
|
||||
var podmanMachineReady = IsPodmanMachineReady();
|
||||
|
||||
// Fallback : si la machine Podman est prête, WSL2 est nécessairement fonctionnel.
|
||||
// Cela contourne les problèmes de détection WSL liés à l'encodage ou au PATH.
|
||||
var virtualEnvironmentInstalled = wsl2Ready || podmanMachineReady;
|
||||
|
||||
return new PrerequisiteResult(
|
||||
WindowsCompatible: IsWindowsCompatible(),
|
||||
RamMB: GetTotalPhysicalMemoryMB(),
|
||||
FreeDiskMB: GetFreeDiskSpaceMB("C:\\"),
|
||||
VirtualEnvironmentInstalled: IsWSLInstalled(),
|
||||
VirtualEnvironmentInstalled: virtualEnvironmentInstalled,
|
||||
PodmanInstalled: IsPodmanInstalled(),
|
||||
PodmanMachineReady: IsPodmanMachineReady()
|
||||
PodmanMachineReady: podmanMachineReady
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +80,143 @@ public static class PrerequisiteChecker
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsWSL2Ready()
|
||||
{
|
||||
// PowerShell gère mieux l'encodage de la sortie WSL que Process.Start en C#.
|
||||
if (IsWSL2ReadyViaPowerShell())
|
||||
return true;
|
||||
|
||||
// Fallback natif si PowerShell n'est pas disponible.
|
||||
return IsWSL2ReadyNative();
|
||||
}
|
||||
|
||||
private static bool IsWSL2ReadyViaPowerShell()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tempFile = Path.GetTempFileName();
|
||||
var script =
|
||||
"$status = & wsl.exe --status 2>&1; " +
|
||||
"$ready = ($status -match 'Version par d\\u00E9faut\\s*:\\s*2') -or " +
|
||||
"($status -match 'Default Version\\s*:\\s*2'); " +
|
||||
"$ready | Out-File -FilePath '" + tempFile + "' -Encoding utf8 -NoNewline";
|
||||
|
||||
var psi = new ProcessStartInfo("powershell.exe", $"-ExecutionPolicy Bypass -Command \"{script}\"")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
process.WaitForExit();
|
||||
|
||||
if (!File.Exists(tempFile))
|
||||
return false;
|
||||
|
||||
var result = File.ReadAllText(tempFile).Trim();
|
||||
File.Delete(tempFile);
|
||||
|
||||
return result.Equals("True", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWSL2ReadyNative()
|
||||
{
|
||||
try
|
||||
{
|
||||
// wsl --status est plus fiable que --version pour savoir si WSL2 est prêt.
|
||||
var psi = new ProcessStartInfo("wsl.exe", "--status")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
// wsl --status peut retourner un code non nul même quand l’info utile est affichée
|
||||
// (par exemple si aucune distribution n’est installée). On parse quand même.
|
||||
var combined = output + "\n" + error;
|
||||
var normalized = combined
|
||||
.Replace('\u00A0', ' ')
|
||||
.Replace('\u202F', ' ');
|
||||
|
||||
if (normalized.Contains("Version par défaut : 2", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("Default Version: 2", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalized.Contains("Version défaut : 2", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var defaultVersion = ParseWslDefaultVersion(combined);
|
||||
if (defaultVersion == 2)
|
||||
return true;
|
||||
|
||||
// Si aucune version par défaut n'est trouvée, on tente les autres méthodes.
|
||||
return (defaultVersion == 0 && WslVersionIndicatesWsl2()) ||
|
||||
WslListIndicatesWsl2();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWSLInstalled()
|
||||
{
|
||||
return IsWSL2Ready();
|
||||
}
|
||||
|
||||
private static int ParseWslDefaultVersion(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var rawLine in text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
// Normalise les espaces insécables et les espaces multiples.
|
||||
var trimmed = rawLine
|
||||
.Replace('\u00A0', ' ')
|
||||
.Replace('\u202F', ' ')
|
||||
.Trim();
|
||||
|
||||
// Regex souple pour matcher :
|
||||
// - Default Version: 2
|
||||
// - Version par défaut : 2
|
||||
// - Version défaut:2
|
||||
// etc.
|
||||
var match = Regex.Match(
|
||||
trimmed,
|
||||
@"(?i)(?:default\s+version|version\s+(?:par\s+)?d[eé]faut)\s*[:\-]?\s*(\d+)",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
if (match.Success && int.TryParse(match.Groups[1].Value, out var version))
|
||||
return version;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool WslVersionIndicatesWsl2()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -79,12 +225,23 @@ public static class PrerequisiteChecker
|
||||
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) return false;
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode == 0;
|
||||
if (process.ExitCode != 0) return false;
|
||||
|
||||
var combined = output + "\n" + error;
|
||||
|
||||
// Si la sortie mentionne explicitement WSL 2 ou un noyau 5.10+, on considère WSL2 prêt.
|
||||
return combined.Contains("WSL version: 2", StringComparison.OrdinalIgnoreCase) ||
|
||||
combined.Contains("WSL version: 2.0", StringComparison.OrdinalIgnoreCase) ||
|
||||
combined.Contains("Kernel version: 5.10", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -92,16 +249,110 @@ public static class PrerequisiteChecker
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPodmanInstalled()
|
||||
private static bool WslListIndicatesWsl2()
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("podman.exe", "--version")
|
||||
var psi = new ProcessStartInfo("wsl.exe", "--list --verbose")
|
||||
{
|
||||
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) return false;
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
var combined = output + "\n" + error;
|
||||
|
||||
// Si au moins une distribution est en version 2, WSL2 est fonctionnel.
|
||||
foreach (var line in combined.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2 && parts[^1] == "2")
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetPodmanExePath()
|
||||
{
|
||||
// 1. Chercher dans le PATH actuel du processus.
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("where.exe", "podman.exe")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process != null)
|
||||
{
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
var firstLine = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(firstLine) && File.Exists(firstLine))
|
||||
return firstLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
// 2. Chercher dans les emplacements d'installation connus.
|
||||
var candidates = new[]
|
||||
{
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "RedHat", "Podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "RedHat", "Podman", "podman.exe"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Podman", "podman.exe"),
|
||||
};
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsPodmanInstalled()
|
||||
{
|
||||
var podmanPath = GetPodmanExePath();
|
||||
if (string.IsNullOrEmpty(podmanPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo(podmanPath, "--version")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8,
|
||||
StandardErrorEncoding = Encoding.UTF8
|
||||
};
|
||||
using var process = Process.Start(psi);
|
||||
if (process == null) return false;
|
||||
@@ -116,14 +367,20 @@ public static class PrerequisiteChecker
|
||||
|
||||
private static bool IsPodmanMachineReady()
|
||||
{
|
||||
var podmanPath = GetPodmanExePath();
|
||||
if (string.IsNullOrEmpty(podmanPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("podman.exe", "machine list --format json")
|
||||
var psi = new ProcessStartInfo(podmanPath, "machine list --format json")
|
||||
{
|
||||
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) return false;
|
||||
|
||||
@@ -29,9 +29,14 @@ setup-wizard/
|
||||
├── MainForm.cs
|
||||
├── InstallerState.cs
|
||||
├── PrerequisiteChecker.cs
|
||||
├── app.manifest
|
||||
└── Resources/
|
||||
├── podman-installer-windows-amd64.msi # MSI officiel Podman pour Windows
|
||||
└── studioE5-agent-setup.exe # Package Inno Setup de l'agent
|
||||
├── podman-installer-windows-amd64.msi # MSI officiel Podman pour Windows
|
||||
├── studioE5-agent-setup.exe # Package Inno Setup de l'agent
|
||||
├── Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle # Package WSL2 complet (offline)
|
||||
├── podman-machine.x86_64.wsl.tar.zst # Image Podman machine pour WSL (offline)
|
||||
├── docker-compose-windows-x86_64.exe # Docker Compose standalone (offline)
|
||||
└── wsl_update_x64.msi # Noyau WSL2 (optionnel, fallback)
|
||||
```
|
||||
|
||||
## Build
|
||||
@@ -58,9 +63,25 @@ bin\Release\net8.0-windows\win-x64\publish\StudioE5-SetupWizard.exe
|
||||
|
||||
1. Télécharger le MSI Podman Windows :
|
||||
<https://github.com/containers/podman/releases>
|
||||
2. Le renommer en `podman-installer-windows-amd64.msi` et le placer dans `Resources/`.
|
||||
3. Générer le package Inno Setup de l’agent (`studioE5-agent-setup.exe`) et le placer dans `Resources/`.
|
||||
4. Builder et publier le wizard.
|
||||
Le renommer en `podman-installer-windows-amd64.msi` et le placer dans `Resources/`.
|
||||
2. Générer le package Inno Setup de l’agent (`studioE5-agent-setup.exe`) et le placer dans `Resources/`.
|
||||
3. Télécharger le package WSL2 complet (offline) :
|
||||
<https://github.com/microsoft/WSL/releases>
|
||||
Par exemple : `Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle`.
|
||||
Le placer dans `Resources/`.
|
||||
4. Télécharger l’image Podman machine pour WSL (offline) :
|
||||
<https://github.com/containers/podman-machine-os/releases>
|
||||
Par exemple : `podman-machine.x86_64.wsl.tar.zst`.
|
||||
Le placer dans `Resources/`.
|
||||
5. Télécharger Docker Compose standalone (offline) :
|
||||
<https://github.com/docker/compose/releases>
|
||||
Par exemple : `docker-compose-windows-x86_64.exe`.
|
||||
Le placer dans `Resources/`.
|
||||
6. *(Optionnel, fallback)* Télécharger le noyau WSL2 :
|
||||
<https://github.com/microsoft/WSL/releases>
|
||||
Par exemple : `wsl.2.7.10.0.x64.msi`, à renommer en `wsl_update_x64.msi`.
|
||||
Le placer dans `Resources/`.
|
||||
6. Builder et publier le wizard.
|
||||
|
||||
## Lancement
|
||||
|
||||
@@ -80,4 +101,5 @@ bin\Release\net8.0-windows\win-x64\publish\StudioE5-SetupWizard.exe
|
||||
|
||||
- Le wizard doit être exécuté **en administrateur**.
|
||||
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur. Le wizard s’enregistre dans `RunOnce` pour se relancer automatiquement.
|
||||
- Le wizard configure WSL2 avec **8 Go de RAM et 4 CPU** via le fichier `.wslconfig` de l’utilisateur.
|
||||
- Le MSI Podman doit correspondre à l’architecture `x64`.
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# Feuille de route — Installateur studioE5 Agent
|
||||
|
||||
## Objectif
|
||||
|
||||
Fournir un **installateur professionnel Windows** pour studioE5 Agent, guidé pas à pas, qui gère les prérequis (WSL2 / Podman) et propose une désinstallation complète.
|
||||
|
||||
## Architecture choisie
|
||||
|
||||
- **Wizard C# Windows Forms (.NET 8)** : `setup-wizard/`
|
||||
- Détecte les prérequis.
|
||||
- Installe WSL2 si besoin (avec reprise après redémarrage via `RunOnce`).
|
||||
- Installe Podman depuis le MSI officiel.
|
||||
- Initialise et démarre la machine Podman.
|
||||
- Lance le package Inno Setup de studioE5 Agent.
|
||||
- Mode désinstallation via `/uninstall`.
|
||||
- **Package agent (Inno Setup)** : `studioE5-agent.iss`
|
||||
- Installe `studioE5-agent.exe` + binaires Tailscale.
|
||||
- Crée les raccourcis.
|
||||
- Gère la désinstallation.
|
||||
|
||||
## État actuel
|
||||
|
||||
### ✅ Réalisé
|
||||
|
||||
- Wizard C# avec 7 étapes guidées.
|
||||
- Détection des prérequis : Windows, RAM, disque, WSL2, Podman.
|
||||
- Installation WSL2 en plusieurs étapes contrôlées avec suivi visuel :
|
||||
1. Activation des fonctionnalités Windows (`Microsoft-Windows-Subsystem-Linux` et `VirtualMachinePlatform`) avec gestion du code 3010 (redémarrage nécessaire).
|
||||
2. Installation du package WSL2 complet depuis le bundle Microsoft Store offline (`Microsoft.WSL_*.msixbundle`) via `Add-AppxPackage`.
|
||||
3. Mise à jour du noyau WSL2 depuis le MSI bundlé (`wsl_update_x64.msi`) ou, en dernier recours, via `wsl --update`.
|
||||
4. Définition de WSL2 comme version par défaut (`wsl --set-default-version 2`).
|
||||
5. L’étape `wsl --install --no-distribution` n’est plus utilisée : l’installation est entièrement offline grâce au bundle.
|
||||
6. Conversion des distributions WSL1 existantes vers WSL2 (`wsl --set-version <nom> 2`) si nécessaire.
|
||||
- Reprise automatique après redémarrage grâce à `RunOnce` (y compris redémarrage post-installation WSL2).
|
||||
- Versioning indépendant du wizard (fichier `setup-wizard/VERSION`, affiché dans la fenêtre et la page d’accueil).
|
||||
- Amélioration de l’interface : fenêtre agrandie à 800×600, `AutoScaleMode = Dpi`, meilleur rendu sur petits écrans (11 pouces).
|
||||
- `RunCommand` capture désormais stdout + stderr pour des diagnostics et fallbacks plus fiables.
|
||||
- Détection des prérequis vérifie que WSL2 (et non WSL1) est installé via `wsl --status`.
|
||||
- Installation Podman via MSI bundlé.
|
||||
- Configuration Podman (`machine init` + `machine start`), avec initialisation offline possible depuis une image locale (`podman-machine.*.wsl.tar.zst`).
|
||||
- Lancement du package Inno Setup agent.
|
||||
- Mode désinstallation complet.
|
||||
- Script Inno Setup de base pour l’agent.
|
||||
|
||||
### 🔄 En cours / À tester
|
||||
|
||||
- Compilation et test du wizard sur Windows.
|
||||
- Packaging final (wizard + MSI Podman + setup agent) en un seul dossier distribuable.
|
||||
|
||||
### ⏳ À venir
|
||||
|
||||
- Signature de l’exécutable pour éviter les alertes SmartScreen.
|
||||
- Support macOS et Linux.
|
||||
- Installateur silencieux possible pour déploiement GPO.
|
||||
|
||||
## Build du wizard
|
||||
|
||||
### Prérequis
|
||||
|
||||
- Windows 10/11
|
||||
- .NET 8 SDK
|
||||
- Inno Setup 6 (pour générer `studioE5-agent-setup.exe`)
|
||||
|
||||
### Fichiers à placer
|
||||
|
||||
Dans `setup-wizard/Resources/` :
|
||||
|
||||
```text
|
||||
podman-installer-windows-amd64.msi
|
||||
studioE5-agent-setup.exe
|
||||
Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle # package WSL2 complet (offline)
|
||||
podman-machine.x86_64.wsl.tar.zst # image Podman machine pour WSL (offline)
|
||||
docker-compose-windows-x86_64.exe # Docker Compose standalone (offline)
|
||||
wsl_update_x64.msi # optionnel, fallback noyau WSL2
|
||||
```
|
||||
|
||||
### Commande
|
||||
|
||||
```powershell
|
||||
cd setup-wizard
|
||||
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true
|
||||
```
|
||||
|
||||
### Sortie
|
||||
|
||||
```text
|
||||
setup-wizard\bin\Release\net8.0-windows\win-x64\publish\StudioE5-SetupWizard.exe
|
||||
```
|
||||
|
||||
## Build du package agent (Inno Setup)
|
||||
|
||||
Structure attendue :
|
||||
|
||||
```text
|
||||
agent/
|
||||
├── studioE5-agent.exe
|
||||
├── tailscale-bin/
|
||||
│ └── windows/
|
||||
│ ├── tailscale.exe
|
||||
│ ├── tailscaled.exe
|
||||
│ └── wintun.dll
|
||||
└── installer/
|
||||
└── studioE5-agent.iss
|
||||
```
|
||||
|
||||
Ouvrir `studioE5-agent.iss` avec Inno Setup Compiler et compiler (`Ctrl+F9`).
|
||||
|
||||
Le fichier généré se trouve dans `installer-output/`.
|
||||
|
||||
## Notes importantes
|
||||
|
||||
- Le wizard doit être exécuté **en administrateur**.
|
||||
- L’installation de WSL2 nécessite un **redémarrage** de l’ordinateur après l’activation des fonctionnalités Windows.
|
||||
- L’installation de WSL2 est découpée en étapes et chaque étape est enregistrée pour permettre la reprise après redémarrage.
|
||||
- Podman nécessite WSL2 : le wizard vérifie donc que la version par défaut de WSL est 2.
|
||||
- Le MSI Podman officiel pour Windows est `podman-installer-windows-amd64.msi`.
|
||||
- Le bundle WSL2 offline est disponible sur <https://github.com/microsoft/WSL/releases> : `Microsoft.WSL_2.7.10.0_x64_ARM64.msixbundle`.
|
||||
- L’image Podman machine offline est disponible sur <https://github.com/containers/podman-machine-os/releases> : `podman-machine.x86_64.wsl.tar.zst`.
|
||||
- Docker Compose standalone est disponible sur <https://github.com/docker/compose/releases> : `docker-compose-windows-x86_64.exe`.
|
||||
- Le wizard crée automatiquement un fichier `.wslconfig` allouant **8 Go de RAM et 4 CPU** à WSL2, ce qui est nécessaire pour des applications lourdes comme PrestaShop.
|
||||
- Pour la désinstallation, le MSI Podman doit être présent dans `Resources/`.
|
||||
|
||||
## Liens utiles
|
||||
|
||||
- Releases Podman : <https://github.com/containers/podman/releases>
|
||||
- Inno Setup : <https://jrsoftware.org/isdl.php>
|
||||
- .NET 8 SDK : <https://dotnet.microsoft.com/download/dotnet/8.0>
|
||||
@@ -6,8 +6,10 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>fr</SatelliteResourceLanguages>
|
||||
<RootNamespace>StudioE5.SetupWizard</RootNamespace>
|
||||
<AssemblyName>StudioE5-SetupWizard</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -15,10 +17,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\podman-installer-windows-amd64.msi">
|
||||
<!-- Fichier de version affiché dans le wizard. -->
|
||||
<Content Include="VERSION">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\studioE5-agent-setup.exe">
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Tous les fichiers placés dans Resources/ sont copiés dans le répertoire de sortie. -->
|
||||
<!-- Attendus : MSI Podman, setup agent, bundle WSL, image Podman machine, MSI noyau WSL (optionnel). -->
|
||||
<Content Include="Resources\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0.1.1
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="StudioE5.SetupWizard.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- Force l'exécution en tant qu'administrateur -->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
@@ -0,0 +1,120 @@
|
||||
#Requires -RunAsAdministrator
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installe ou répare WSL2 de manière fiable.
|
||||
.DESCRIPTION
|
||||
Ce script :
|
||||
1. Vérifie si WSL2 est déjà prêt.
|
||||
2. Active les fonctionnalités Windows nécessaires.
|
||||
3. Définit WSL2 comme version par défaut.
|
||||
4. Met à jour le noyau WSL2.
|
||||
5. Installe WSL sans distribution si possible.
|
||||
Un redémarrage peut être nécessaire après l’activation des fonctionnalités.
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Test-Wsl2Ready {
|
||||
try {
|
||||
$output = & wsl.exe --status 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
Write-Host "[Test] wsl --status exit code: $exitCode" -ForegroundColor Cyan
|
||||
if ($output) {
|
||||
Write-Host "[Test] wsl --status output:" -ForegroundColor Cyan
|
||||
$output | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
}
|
||||
|
||||
if ($exitCode -eq 0 -or ($output -match "Version par défaut\s*:\s*2") -or ($output -match "Default Version\s*:\s*2")) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "[Test] wsl --status a échoué : $_" -ForegroundColor Yellow
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Enable-WindowsFeatureIfNeeded {
|
||||
param([string]$FeatureName)
|
||||
|
||||
Write-Host "[Feature] Activation de $FeatureName..." -ForegroundColor Cyan
|
||||
$result = & dism.exe /online /enable-feature /featurename:$FeatureName /all /norestart 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
$result | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Host "[Feature] $FeatureName activé (pas de redémarrage nécessaire)." -ForegroundColor Green
|
||||
return $false
|
||||
}
|
||||
elseif ($exitCode -eq 3010) {
|
||||
Write-Host "[Feature] $FeatureName activé, mais un redémarrage est nécessaire (code 3010)." -ForegroundColor Yellow
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
throw "Échec de l'activation de $FeatureName (code $exitCode)."
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Wsl2 {
|
||||
Write-Host "[WSL] Tentative d'installation sans distribution..." -ForegroundColor Cyan
|
||||
try {
|
||||
& wsl.exe --install --no-distribution 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
if ($LASTEXITCODE -ne 0) { throw "wsl --install --no-distribution a retourné le code $LASTEXITCODE" }
|
||||
Write-Host "[WSL] Installation sans distribution réussie." -ForegroundColor Green
|
||||
return
|
||||
}
|
||||
catch {
|
||||
Write-Host "[WSL] Option --no-distribution non supportée ou échec : $_" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "[WSL] Fallback : installation classique de WSL..." -ForegroundColor Cyan
|
||||
& wsl.exe --install 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
if ($LASTEXITCODE -ne 0) { throw "wsl --install a retourné le code $LASTEXITCODE" }
|
||||
Write-Host "[WSL] Installation classique réussie." -ForegroundColor Green
|
||||
}
|
||||
|
||||
# === Début du script ===
|
||||
|
||||
Write-Host "=== Installation / réparation WSL2 ===" -ForegroundColor Green
|
||||
|
||||
if (Test-Wsl2Ready) {
|
||||
Write-Host "WSL2 est déjà prêt. Rien à faire." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "WSL2 n'est pas détecté. Lancement de l'installation..." -ForegroundColor Yellow
|
||||
|
||||
$rebootNeeded = $false
|
||||
$rebootNeeded = (Enable-WindowsFeatureIfNeeded -FeatureName "Microsoft-Windows-Subsystem-Linux") -or $rebootNeeded
|
||||
$rebootNeeded = (Enable-WindowsFeatureIfNeeded -FeatureName "VirtualMachinePlatform") -or $rebootNeeded
|
||||
|
||||
if ($rebootNeeded) {
|
||||
Write-Host "`nUn redémarrage est nécessaire pour activer les fonctionnalités Windows." -ForegroundColor Yellow
|
||||
Write-Host "Après le redémarrage, relance ce script pour terminer l'installation de WSL2." -ForegroundColor Yellow
|
||||
|
||||
$response = Read-Host "Redémarrer maintenant ? (O/N)"
|
||||
if ($response -eq "O" -or $response -eq "o") {
|
||||
Restart-Computer -Force
|
||||
}
|
||||
exit 3010
|
||||
}
|
||||
|
||||
Write-Host "[WSL] Définition de WSL2 comme version par défaut..." -ForegroundColor Cyan
|
||||
& wsl.exe --set-default-version 2 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
if ($LASTEXITCODE -ne 0) { throw "wsl --set-default-version 2 a échoué (code $LASTEXITCODE)." }
|
||||
|
||||
Write-Host "[WSL] Mise à jour du noyau WSL2..." -ForegroundColor Cyan
|
||||
& wsl.exe --update 2>&1 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
||||
# 3010 = succès mais redémarrage possible
|
||||
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "wsl --update a échoué (code $LASTEXITCODE)." }
|
||||
|
||||
Install-Wsl2
|
||||
|
||||
if (Test-Wsl2Ready) {
|
||||
Write-Host "`nWSL2 est maintenant prêt." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
else {
|
||||
Write-Host "`nWSL2 ne semble toujours pas prêt. Essayez de redémarrer et de relancer le script." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user