From 3d4d6de0f8064e22500a8420d959a29733e8390d Mon Sep 17 00:00:00 2001 From: Johnny Date: Tue, 8 Sep 2026 23:09:54 +0200 Subject: [PATCH] fix: la jointure Windows n'executait jamais le script PowerShell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 15 ++++++++ domainjoin/backend/windows_addcomputer.py | 45 ++++++++++++++++++----- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b0d72..60d430f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/domainjoin/backend/windows_addcomputer.py b/domainjoin/backend/windows_addcomputer.py index d36fc16..d2214ed 100644 --- a/domainjoin/backend/windows_addcomputer.py +++ b/domainjoin/backend/windows_addcomputer.py @@ -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)