diff --git a/libs/cua-driver-rs/crates/cua-driver/src/autostart.rs b/libs/cua-driver-rs/crates/cua-driver/src/autostart.rs index 8ed6e4dbd1..a270a0570b 100644 --- a/libs/cua-driver-rs/crates/cua-driver/src/autostart.rs +++ b/libs/cua-driver-rs/crates/cua-driver/src/autostart.rs @@ -162,43 +162,84 @@ Register-ScheduledTask -TaskName 'cua-driver-serve' -Action $action -Trigger $tr "#; pub fn enable(exe: &str) -> Result<()> { - // Pass the binary path via env var so the script doesn't need - // shell-quoting acrobatics for paths with spaces or odd chars. + // First attempt: register directly. Works when called from an + // already-elevated context (install.ps1's self-elevated child shell, + // or a user running cua-driver autostart enable from an admin + // PowerShell). Pass the binary path via env var so the script + // doesn't need shell-quoting acrobatics for paths with spaces. let out = Command::new("powershell") .args(["-NoProfile", "-NonInteractive", "-Command", REGISTER_PS]) .env("CUA_DRIVER_AS_EXE", exe) .output() .map_err(|e| anyhow!("failed to invoke powershell: {e}"))?; - if !out.status.success() { - let stderr = String::from_utf8_lossy(&out.stderr); - // Detect the most common cause — non-elevated caller can't - // register a RunLevel=Highest task — and surface an actionable - // message instead of the raw PowerShell stack trace. - let stderr_lower = stderr.to_lowercase(); - let looks_like_access_denied = stderr_lower.contains("access is denied") - || stderr_lower.contains("0x80070005") - || stderr_lower.contains("permission") - || stderr_lower.contains("requires elevation"); - if looks_like_access_denied { - return Err(anyhow!( - "Register-ScheduledTask failed: this task is registered with \ - RunLevel=Highest so the daemon can drive UWP / AppContainer apps \ - (Calculator, modern Settings, Photos, ...). Registering a Highest \ - task itself requires admin. Re-run from an elevated PowerShell, or \ - run install.ps1 with -AutoStart from an elevated session. See \ - https://github.com/trycua/cua/issues/1602 for context.\n\ - \n\ - Raw error: {}", - stderr.trim() - )); - } + if out.status.success() { + return Ok(()); + } + + let stderr = String::from_utf8_lossy(&out.stderr); + let stderr_lower = stderr.to_lowercase(); + let looks_like_access_denied = stderr_lower.contains("access is denied") + || stderr_lower.contains("0x80070005") + || stderr_lower.contains("permission") + || stderr_lower.contains("requires elevation"); + + if !looks_like_access_denied { return Err(anyhow!( "PowerShell Register-ScheduledTask failed (exit {}): {}", out.status.code().unwrap_or(-1), stderr.trim() )); } - Ok(()) + + // Access-denied — caller is at Medium IL but the task wants Highest. + // Self-elevate via ShellExecute "runas" verb, which fires the UAC + // prompt. The elevated child runs the same registration via + // powershell -Command, with the registered binary path injected as + // an env var (durable across the elevation boundary). + eprintln!("cua-driver: registering autostart at RunLevel=Highest needs admin."); + eprintln!("cua-driver: triggering UAC prompt — accept it to register the task."); + + let elevated_status = { + use std::os::windows::process::CommandExt; + // CREATE_NO_WINDOW = 0x08000000 — keep the elevated child's + // console out of the foreground; PowerShell already runs hidden + // via Start-Process -Verb RunAs's WindowStyle Hidden. + const CREATE_NO_WINDOW: u32 = 0x08000000; + + // PowerShell incantation: Start-Process -Verb RunAs to elevate, + // run our own exe with `autostart enable` so the registration + // happens INSIDE the elevated process (where it'll succeed on + // the first attempt and not re-enter this branch). -Wait so we + // can capture the child's exit code. + let inner = format!( + "& \"{}\" autostart enable", + exe.replace('"', "`\"") + ); + let outer = format!( + "$p = Start-Process -FilePath '{}' -ArgumentList @('-NoProfile','-NonInteractive','-Command',{}) -Verb RunAs -Wait -PassThru; exit $p.ExitCode", + "powershell.exe", + // Embed the inner command as a single-quoted PowerShell string + // (PS escapes ' as '' inside ''-strings). + format!("'{}'", inner.replace('\'', "''")) + ); + Command::new("powershell") + .args(["-NoProfile", "-NonInteractive", "-Command", &outer]) + .creation_flags(CREATE_NO_WINDOW) + .status() + .map_err(|e| anyhow!("failed to spawn elevation helper: {e}"))? + }; + + if elevated_status.success() { + Ok(()) + } else { + Err(anyhow!( + "self-elevation for autostart registration failed (exit {}). The UAC \ + prompt was probably dismissed. Re-run `cua-driver autostart enable` \ + and accept the prompt, or run install.ps1 -AutoStart which has the \ + same self-elevation flow. See https://github.com/trycua/cua/issues/1602.", + elevated_status.code().unwrap_or(-1) + )) + } } pub fn disable() -> Result<()> {