Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/plaud_tools/tray/process_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
# future Python drops the attribute.
_CREATE_BREAKAWAY_FROM_JOB: int = getattr(subprocess, "CREATE_BREAKAWAY_FROM_JOB", 0x01000000)

# On a stock machine the default PowerShell execution policy (``Restricted``)
# refuses to run any script, and an interactive prompt would hang forever with
# stdio wired to DEVNULL. Every launch needs both flags to run reliably from
# the no-console frozen tray; only the updater passed them explicitly, so the
# uninstall-helper and toast launches could still fail on a stock policy
# (#142). Injected here (once) so every call site gets them for free.
_REQUIRED_FLAGS: tuple[str, ...] = ("-NonInteractive", "-ExecutionPolicy", "Bypass")


def launch_hidden_powershell(
args: list[str],
Expand All @@ -61,7 +69,15 @@ def launch_hidden_powershell(
this): ``CREATE_BREAKAWAY_FROM_JOB`` is requested, with a same-args retry
(flag dropped) if the enclosing Job Object forbids it -- so the operation
is never worse off than launching without the flag at all.

``-NonInteractive -ExecutionPolicy Bypass`` are injected right after
``args[0]`` (the powershell.exe path) unless already present, so every
caller is immune to a stock ``Restricted`` execution policy without
having to remember the flags itself (#142).
"""
if "-NonInteractive" not in args:
args = [args[0], *_REQUIRED_FLAGS, *args[1:]]

base_flags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
kwargs: dict[str, object] = {
"stdin": subprocess.DEVNULL,
Expand Down
5 changes: 2 additions & 3 deletions src/plaud_tools/tray/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,12 @@ def _launch_updater(ps_path: Path) -> subprocess.Popen[bytes]:
and update.ps1 then waits for the tray to exit before replacing its files.
Delegates to :func:`process_launch.launch_hidden_powershell` (#142) for the
safe-stdio + job-breakaway launch semantics shared with the uninstaller.
``-NonInteractive -ExecutionPolicy Bypass`` are injected by the shared
helper itself, so they aren't repeated here.
"""
args = [
_POWERSHELL_EXE,
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-WindowStyle",
"Hidden",
"-File",
Expand Down
46 changes: 46 additions & 0 deletions tests/test_process_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,52 @@ def fake_popen(args, creationflags=0, **kwargs):
assert len(calls) == 1


def test_execution_policy_flags_injected_when_missing(monkeypatch):
"""A stock machine's default ``Restricted`` execution policy refuses to run
any script; every call site must get ``-NonInteractive -ExecutionPolicy
Bypass`` even if it forgot to pass them itself (#142).
"""
seen: dict[str, list[str]] = {}

def fake_popen(args, **kwargs):
seen["args"] = args
return _FakeProc()

monkeypatch.setattr(process_launch.subprocess, "Popen", fake_popen)

launch_hidden_powershell(["powershell.exe", "-WindowStyle", "Hidden", "-Command", "Write-Host hi"])

assert seen["args"] == [
"powershell.exe",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-WindowStyle",
"Hidden",
"-Command",
"Write-Host hi",
]


def test_execution_policy_flags_not_duplicated(monkeypatch):
"""A caller that already passes the flags (the updater, historically)
must not get them injected a second time.
"""
seen: dict[str, list[str]] = {}

def fake_popen(args, **kwargs):
seen["args"] = args
return _FakeProc()

monkeypatch.setattr(process_launch.subprocess, "Popen", fake_popen)

original = ["powershell.exe", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", "x.ps1"]
launch_hidden_powershell(original)

assert seen["args"] == original
assert seen["args"].count("-NonInteractive") == 1


def test_cwd_is_forwarded(monkeypatch):
seen: dict[str, object] = {}

Expand Down
Loading