fix: la jointure Windows n'executait jamais le script PowerShell

Le script etait envoye sur stdin de `powershell -Command -`, qui analyse
l'entree ligne par ligne. Le `}` fermant le try/catch n'etait suivi d'aucun
saut de ligne : le parseur gardait l'instruction incomplete en attente et la
jetait a la fermeture de stdin. PowerShell rendait la main avec le code 0
sans avoir appele Add-Computer, et l'utilisateur ne voyait qu'un « echec de
la jointure (code retour 0) » sans explication.

- Transmission du script d'un seul bloc via `-EncodedCommand` (base64
  UTF-16LE), ce qui supprime toute analyse ligne par ligne et propage
  correctement les codes de sortie. Le mot de passe reste hors de la ligne
  de commande : il ne transite que par l'environnement du sous-processus.
- Report de la sortie brute de PowerShell quand aucun marqueur n'est trouve,
  pour qu'un echec inattendu reste diagnosticable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-08 23:09:54 +02:00
co-authored by Claude Opus 5
parent bc080fd76a
commit 3d4d6de0f8
2 changed files with 51 additions and 9 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## [Non publié]
### Fixed
- La jointure de domaine sous Windows échouait sans aucun message, avec un code
retour 0. Le script PowerShell était transmis sur l'entrée standard de
`powershell -Command -`, qui analyse stdin ligne par ligne : le bloc
`try`/`catch` final, dépourvu de saut de ligne terminal, restait en attente
dans le parseur et n'était jamais exécuté. PowerShell rendait donc la main
sans avoir tenté la jointure. Le script est désormais transmis d'un bloc via
`-EncodedCommand`.
- En l'absence de marqueur de succès comme d'échec, le message d'erreur se
réduisait à « code retour 0 », impossible à diagnostiquer. La sortie brute de
PowerShell est maintenant reportée à l'utilisateur (le mot de passe ne
transitant que par l'environnement, il n'y apparaît jamais).
## [0.1.3] - 2026-09-08
### Fixed
+36 -9
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import base64
import os
import shutil
import subprocess
@@ -19,6 +20,20 @@ def _ps_quote(value: str) -> str:
return value.replace("'", "''")
def _encode_command(script: str) -> str:
"""Encode un script pour `powershell -EncodedCommand` (base64 d'UTF-16LE).
Le script est ainsi transmis comme un bloc unique et atomique. L'alternative
`-Command -` fait analyser l'entrée standard ligne par ligne : la dernière
instruction d'un bloc `try`/`catch` restait en attente dans le parseur et
n'était jamais exécutée à la fermeture de stdin — PowerShell rendait alors
la main avec le code 0 sans avoir rien fait, et la jointure echouait sans
le moindre message. Le mot de passe reste hors du script (variable
d'environnement) : la ligne de commande ne l'expose pas.
"""
return base64.b64encode(script.encode("utf-16-le")).decode("ascii")
class WindowsAddComputerBackend(DomainJoinBackend):
def is_available(self) -> tuple[bool, str]:
if shutil.which("powershell") is None and shutil.which("powershell.exe") is None:
@@ -76,8 +91,16 @@ class WindowsAddComputerBackend(DomainJoinBackend):
try:
process = subprocess.Popen(
["powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "-"],
stdin=subprocess.PIPE,
[
"powershell",
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-EncodedCommand",
_encode_command(script),
],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
@@ -90,12 +113,6 @@ class WindowsAddComputerBackend(DomainJoinBackend):
finally:
env[_PASSWORD_ENV_VAR] = ""
try:
process.stdin.write(script)
process.stdin.close()
except BrokenPipeError:
pass
output_lines: list[str] = []
if process.stdout:
for line in process.stdout:
@@ -114,5 +131,15 @@ class WindowsAddComputerBackend(DomainJoinBackend):
)
failure_detail = next((l for l in output_lines if l.strip().startswith(_FAILURE_PREFIX)), None)
message = failure_detail or f"Échec de la jointure au domaine (code retour {returncode})."
if failure_detail:
message = failure_detail
else:
# Sans ce report de la sortie brute, un echec inattendu se resumait a
# « code retour 0 », indiagnosticable. Le mot de passe n'apparait
# jamais dans cette sortie : il ne transite que par l'environnement.
detail = full_output.strip() or "aucune sortie produite par PowerShell"
message = (
f"Échec de la jointure au domaine (code retour {returncode}).\n"
f"Sortie de PowerShell : {detail}"
)
return JoinResult(success=False, message=message)