Skip to content
Merged
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
93 changes: 67 additions & 26 deletions libs/cua-driver-rs/crates/cua-driver/src/autostart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
Loading