Notre site necessite Javascript pour fonctionner correctement !

PowerShell : ExitCode vide avec Start-Process -PassThru, pourquoi un succès passe pour un échec


La commande a réussi, mais le script conclut à une erreur :
le code n'est pas anormal, il est perdu.
La cause, et les correctifs testés

Article publié le 1er novembre 2026

Votre script PowerShell lance une commande longue, attend qu'elle se termine, contrôle le code de retour et s'arrête sur un message incompréhensible :

wsl --import a rendu le code

Il n'y a rien après le mot « code ». Pourtant, la commande a bien fonctionné. Sur la migration de poste qui nous a fait tomber sur ce problème, en novembre 2026, la distribution Linux était présente en version 2, la sortie annonçait une opération réussie et le fichier d'erreurs du script était vide.

La commande n'a rendu aucun code anormal : PowerShell a perdu le code. Avec certains paramètres de Start-Process, l'objet renvoyé par -PassThru ne peut plus lire ExitCode une fois le processus terminé. La propriété reste vide, et le test classique -ne 0 prend ce vide pour une erreur. La correction tient en une ligne, à condition de la placer au bon endroit.

⚠️ Avertissement. Comme indiqué dans nos mentions légales, la société BSC Informatique ne pourra être tenue responsable des conséquences éventuelles suite à l'application de cet article. Essayez vos scripts sur des données sans importance avant de les utiliser sur un poste en service. Les commandes wsl de cet article portent sur des distributions : adaptez les noms et les chemins à votre cas.

Sommaire

À retenir en trente secondes

SituationCe qu'il faut faire
ExitCode vide après -PassThruAjouter $null = $p.Handle juste après Start-Process
Vous n'avez pas besoin de faire autre chose pendant l'exécutionAppeler la commande avec & et lire $LASTEXITCODE
Le script vient de s'arrêter sur un code videContrôler le résultat réel, ne rien relancer, ne rien supprimer
$p.WaitForExit() n'a rien changéC'est normal : seul, il ne conserve pas le code
Vous testez -not $p.ExitCodeDanger : un vrai échec passe pour un succès, voir la section 3

1. Reproduire le problème en quelques lignes

Ouvrez Windows PowerShell et collez ceci. La commande rend volontairement le code 7.

$p = Start-Process cmd.exe -ArgumentList '/c', 'exit 7' -PassThru -NoNewWindow
Start-Sleep -Seconds 1
"Code : [$($p.ExitCode)]"

Vous obtenez Code : []. Remplacez -NoNewWindow par -WindowStyle Hidden : vous obtenez Code : [7].

Tout est là. Le problème ne vient ni de -PassThru, ni de la commande lancée. Il vient de la manière dont Start-Process crée le processus.

2. Pourquoi le code disparaît

Sous Windows, le code de retour d'un processus terminé reste lisible tant qu'un programme garde un handle sur ce processus, c'est-à-dire une référence ouverte vers lui. Quand plus personne n'en détient, le code disparaît avec lui.

Start-Process a deux façons de démarrer un programme :

Le code de Windows PowerShell 5.1 n'est pas public, mais il se comporte de la même façon. Voici ce que nous avons mesuré sur la version 5.1.26100.9818, avec une commande rendant le code 7 et une attente sur HasExited :

Paramètres de Start-Process avec -PassThruExitCode lu ensuite
-WindowStyle Hidden7
-WorkingDirectory seul7
-NoNewWindowvide
-RedirectStandardOutput, -RedirectStandardErrorvide
-RedirectStandardError et -WindowStyle Hiddenvide
-UseNewEnvironmentvide
-LoadUserProfilevide

Masquer la fenêtre ne protège donc de rien : une simple redirection suffit à perdre le code. C'est le montage le plus courant dans un script d'administration, puisqu'on veut justement garder une trace des erreurs dans un fichier.

Pourquoi aucune erreur ne s'affiche. .NET lève bien une exception, mais PowerShell l'avale quand on lit une propriété, et il renvoie $null. Set-StrictMode -Version Latest n'y change rien. Pour la voir, appelez directement la méthode cachée derrière la propriété :

$p.get_ExitCode()

Sur un Windows en français, le message est explicite : « Le processus n'ayant pas été démarré par cet objet, les informations demandées ne peuvent pas être déterminées. »

3. Pourquoi un succès devient un échec, et l'inverse

Dans PowerShell, $null n'est pas égal à 0. Le test $null -ne 0 est donc vrai. Le script conclut à une erreur alors que le code n'a simplement jamais été lu.

Ce serait presque rassurant si le défaut allait toujours dans ce sens. Or tout dépend de la façon d'écrire le test :

Test écrit dans le scriptCommande réussie, code perduCommande échouée, code perdu
if ($p.ExitCode -ne 0) { échec }Échec, à tortÉchec, par chance
if ($p.ExitCode -eq 0) { succès }Échec, à tortÉchec, par chance
if (-not $p.ExitCode) { succès }Succès, par chanceSuccès, à tort
if ([int]$p.ExitCode -eq 0) { succès }Succès, par chanceSuccès, à tort

Les deux dernières lignes sont les plus dangereuses. La commande a échoué, le script continue, et rien ne le signale. [int]$null vaut en effet 0.

Les variables automatiques ne vous sauveront pas non plus. Après un Start-Process dont le programme a rendu le code 7, $? reste à True, car la commande PowerShell a bien lancé le programme. Quant à $LASTEXITCODE, il n'est pas mis à jour : dans notre essai, il gardait la valeur 3 de la commande précédente.

4. Le script vient de s'arrêter : que faire tout de suite

Ne relancez pas l'étape. Si elle a réussi, vous allez au mieux obtenir une erreur, au pire faire le travail une seconde fois par-dessus le premier.

  1. Contrôlez le résultat réel, pas le code. Pour une importation WSL, tapez wsl --list --verbose et vérifiez que la distribution apparaît, avec la bonne version. Pour une copie, comparez les fichiers. Pour une installation, lancez le programme.
  2. Lisez les journaux du script. Un fichier d'erreurs vide et une sortie normale vont dans le même sens.
  3. Si le résultat est bon, reprenez après cette étape, sans la refaire. Corrigez le script avant de le relancer en entier.

N'essayez surtout pas de repartir de zéro avec wsl --unregister. Microsoft prévient que toutes les données, les réglages et les logiciels de la distribution sont alors perdus définitivement. Sur cette migration, l'importation avait réussi : cette commande aurait détruit un travail terminé, pour corriger une erreur qui n'existait pas.

5. Les trois façons de fiabiliser le contrôle

MéthodeQuand la choisirSa limite
& et $LASTEXITCODELe script attend la commande sans rien faire d'autreAucun suivi pendant l'exécution
-Wait -PassThruVous gardez Start-Process sans rien faire d'autreAttend aussi les processus enfants
$null = $p.HandleVous voulez afficher un suivi ou imposer un délaiÀ placer tout de suite, sinon sans effet

Solution 1 : l'appel direct, à préférer quand c'est possible. Microsoft le rappelle dans sa documentation : Start-Process ne devrait servir que lorsque vous avez besoin de contrôler la façon dont la commande s'exécute.

& wsl.exe --import Debian C:\WSL\Debian C:\Sauvegarde\debian.tar --version 2
if ($LASTEXITCODE -ne 0) { throw "wsl --import a rendu le code $LASTEXITCODE" }

Un piège sous PowerShell 5.1 : n'ajoutez pas 2>&1 si le script tourne avec $ErrorActionPreference = 'Stop'. Dans notre essai, une commande qui écrivait une ligne sur sa sortie d'erreur a déclenché une exception alors qu'elle rendait le code 0. C'est exactement le même faux échec, par un autre chemin.

Solution 2 : -Wait. Sur 5.1, -Wait -PassThru rend bien le code, même avec des redirections. Sa contrepartie est documentée : -Wait attend tout l'arbre de processus, descendants compris. Avec une commande qui laissait tourner un programme en arrière-plan, notre script a attendu 6,1 secondes, contre un retour immédiat avec la solution 3. Une commande qui laisse un service derrière elle peut ainsi bloquer votre script.

Solution 3 : conserver le handle. Il suffit de lire la propriété Handle pour que l'objet le garde en mémoire.

$p = Start-Process wsl.exe -ArgumentList '--status' -PassThru -NoNewWindow `
    -RedirectStandardOutput C:\Temp\sortie.txt -RedirectStandardError C:\Temp\erreurs.txt
$null = $p.Handle
$p.WaitForExit()
$p.ExitCode

Deux précisions, vérifiées chacune par un essai :

6. Une fonction réutilisable, avec un vrai contrôle du résultat

Cette fonction regroupe les bonnes pratiques : journaux séparés, handle conservé, suivi toutes les 30 secondes et refus de conclure si le code reste introuvable. Elle s'est comportée correctement dans nos essais, avec les codes 0, 5, 7 et -1, sur une commande de 35 secondes et sur de vraies commandes wsl.exe.

function Lancer-Commande {
    param(
        [Parameter(Mandatory)][string]$Programme,
        [string[]]$Arguments,
        [Parameter(Mandatory)][string]$Journal
    )
    $p = Start-Process -FilePath $Programme -ArgumentList $Arguments -PassThru -NoNewWindow `
        -RedirectStandardOutput "$Journal.sortie.txt" -RedirectStandardError "$Journal.erreurs.txt"
    $null = $p.Handle
    while (-not $p.WaitForExit(30000)) {
        Write-Host "En cours depuis $([int]((Get-Date) - $p.StartTime).TotalMinutes) min"
    }
    if ($null -eq $p.ExitCode) { throw "Code de retour indisponible pour $Programme" }
    return $p.ExitCode
}

Le test $null -eq $p.ExitCode est la vraie garantie : un code perdu arrête le script avec un message clair, au lieu de se faire passer pour un échec ou pour un succès.

Le dossier du journal doit exister avant l'appel. Si un chemin passé dans $Arguments contient une espace, ajoutez-lui des guillemets, car Start-Process assemble les arguments en une seule ligne.

Ajoutez ensuite un contrôle du résultat réel. Le code dit ce que la commande pense avoir fait, pas ce qui existe :

$env:WSL_UTF8 = '1'
$code = Lancer-Commande -Programme wsl.exe -Journal C:\Temp\wsl-import -Arguments `
    '--import', 'Debian', 'C:\WSL\Debian', 'C:\Sauvegarde\debian.tar', '--version', '2'
$presente = (wsl.exe --list --quiet) -contains 'Debian'
if ($code -ne 0 -or -not $presente) { throw "Import non confirmé : code $code, présente : $presente" }

N'oubliez pas la ligne WSL_UTF8. Sous PowerShell 5.1, wsl.exe sort son texte en UTF-16, et chaque lettre capturée est suivie d'un caractère nul. Dans notre essai, sans cette variable, -match, -like et Select-String ne trouvaient pas une distribution pourtant installée, et seul -contains répondait juste. Votre vérification peut donc se tromper à son tour. Avec WSL_UTF8=1, tous ces tests répondent juste, et les journaux redirigés passent eux aussi de l'UTF-16 à l'UTF-8. Sans elle, relisez-les avec Get-Content -Encoding Unicode.

7. Un code non nul n'est pas toujours un échec

Une fois le code fiable, reste à l'interpréter. Le zéro pour un succès n'est qu'une convention : Microsoft rappelle que chaque programme fixe la sienne.

CommandeCodeCe qu'il veut dire
dism /enable-feature3010Terminé, un redémarrage est nécessaire
robocopy1Des fichiers ont été copiés sans erreur, voir notre article sur robocopy
tar -xf archive -C C:\1Cas vu sur la même migration : l'archive contenait une entrée ./, tous les fichiers étaient pourtant posés
wsl --terminate sur un nom inconnu-1Vrai échec, ici une distribution introuvable

Le cas de tar montre bien la règle : lisez la documentation de l'outil, et contrôlez le résultat. Sur cette migration, c'est la taille de chaque fichier qui a tranché, pas le code.

8. Et sous PowerShell 7 ?

Le défaut est signalé depuis la bêta de PowerShell 6 dans le dépôt officiel. Il concernait précisément -NoNewWindow et les paramètres -RedirectStandard*, et uniquement Windows. PowerShell 7.4.0 l'avait même étendu au cas de -Wait.

Une correction a été intégrée fin 2023 pour la version 7.4.1, et l'auteur du signalement l'a confirmée sur la 7.4.2. Le code actuel lit désormais process.Handle juste après la création du processus. Nous n'avons pas essayé PowerShell 7 sur notre poste : ce point repose sur le dépôt GitHub.

En pratique, powershell.exe reste la version 5.1 livrée avec Windows, et pwsh.exe la version 7 installée à part. Sur la 5.1 de notre poste, en novembre 2026, le défaut existe toujours. Si votre script doit tourner sur les deux, gardez la ligne $null = $p.Handle : elle se contente de lire une propriété.

Besoin d'un coup de main ?

Une question sur votre script ? Écrivez-nous, avec la ligne Start-Process utilisée et le résultat de $PSVersionTable.PSVersion.

Questions fréquentes

Pourquoi $p.ExitCode est-il vide après Start-Process -PassThru ?

Avec -NoNewWindow ou une redirection, l'objet renvoyé ne détient pas de handle sur le processus. Une fois celui-ci terminé, le code n'est plus lisible, et PowerShell masque l'erreur en renvoyant $null.

Comment récupérer le code de retour avec Start-Process ?

Tapez $null = $p.Handle juste après Start-Process, puis lisez $p.ExitCode une fois le processus terminé. Vous pouvez aussi utiliser -Wait -PassThru.

Start-Process met-il à jour $LASTEXITCODE ?

Non, dans notre essai la variable a gardé sa valeur précédente. Elle n'est alimentée que si vous lancez le programme directement, par exemple avec &.

Set-StrictMode signale-t-il le problème ?

Non. Même en -Version Latest, la propriété renvoie un vide sans erreur. Seul $p.get_ExitCode() montre l'exception.

Comment savoir si wsl --import a réussi ?

Lisez son code avec & et $LASTEXITCODE, puis vérifiez que la distribution apparaît dans wsl --list --verbose. Dans un script, réglez $env:WSL_UTF8 = '1' avant d'analyser cette liste.

Le problème existe-t-il sous PowerShell 7 ?

D'après le dépôt officiel, il est corrigé depuis la branche 7.4.1. Il subsiste sous Windows PowerShell 5.1, où nous l'avons reproduit en novembre 2026.

BSC Informatique, EURL BSC INFORMATIQUE, 18 Avenue Olbius Riquier, 83400 Hyères, 04 94 27 65 95.


Migration ou script bloqué ? Dépannage à Hyères

Windows, migrations et postes de travail,
pour les particuliers et les professionnels.

Tél. 04 94 27 65 95


04 94 27 65 95Envoyer une demande
Un ordinateur en panne ?
Diagnostic sous 24 à 48 h.
Réparateur labellisé QualiRépar.
04 94 27 65 95Envoyer une demande