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
+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)