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
38 changes: 36 additions & 2 deletions libs/cua-driver-rs/crates/cua-driver/src/autostart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ mod platform {
/// install.ps1 surfaces any divergence; the moment install.ps1 changes
/// shape, this script needs the same edit.
///
/// **RunLevel = Highest** (since 2026-05-21): the daemon is registered to
/// run at the user's elevated/admin token rather than the filtered
/// standard-user token. This is what lets the daemon drive UWP /
/// AppContainer apps (Calculator, modern Settings, Photos, …) — at
/// Medium IL the cross-AppContainer UIA RPC returns a stub (~1 element
/// instead of the full tree, see #1602 / #1601). High IL crosses that
/// boundary cleanly. Trade-off: `Register-ScheduledTask -RunLevel
/// Highest` requires the caller to already be at High IL, so this
/// function emits an actionable error when invoked from a non-elevated
/// shell. Users opt into autostart via the installer's `-AutoStart`
/// flag or `cua-driver autostart enable`, both of which prompt for
/// elevation when needed.
///
/// **Account-name format**: on domain-joined machines USERDOMAIN holds the
/// AD domain name (e.g. CORP) and the principal must be `CORP\username`.
/// On workgroup machines USERDOMAIN holds either the literal string
Expand All @@ -134,10 +147,10 @@ if ($env:USERDOMAIN -and $env:USERDOMAIN -ne 'WORKGROUP' -and $env:USERDOMAIN -n
$user = "$domain\$env:USERNAME"
$action = New-ScheduledTaskAction -Execute $env:CUA_DRIVER_AS_EXE -Argument 'serve' -WorkingDirectory $env:USERPROFILE
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
$principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Limited
$principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Hours 0)
Unregister-ScheduledTask -TaskName 'cua-driver-serve' -Confirm:$false -ErrorAction SilentlyContinue
Register-ScheduledTask -TaskName 'cua-driver-serve' -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'cua-driver-rs: serve daemon, auto-start at interactive logon' | Out-Null
Register-ScheduledTask -TaskName 'cua-driver-serve' -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'cua-driver-rs: serve daemon, auto-start at interactive logon, RunLevel=Highest for UWP/AppContainer support' | Out-Null

# Note: the uiAccess'd worker (`cua-driver-uia.exe`) does NOT get its own
# scheduled task. uiAccess PEs can only be launched via ShellExecute, and
Expand All @@ -158,6 +171,27 @@ Register-ScheduledTask -TaskName 'cua-driver-serve' -Action $action -Trigger $tr
.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()
));
}
return Err(anyhow!(
"PowerShell Register-ScheduledTask failed (exit {}): {}",
out.status.code().unwrap_or(-1),
Expand Down
58 changes: 50 additions & 8 deletions libs/cua-driver/scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -478,15 +478,53 @@ function Ensure-Junction([string]$linkPath, [string]$targetPath) {
# itself owns the platform-specific registration logic so the install
# scripts and the runtime stay in lock-step — when the verb's behavior
# changes, this script picks it up automatically with no edit needed.
function Test-IsElevated {
# Returns $true if the current process is running at High IL (admin token).
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object System.Security.Principal.WindowsPrincipal($id)
return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Register-CuaDriverAutostart {
param([Parameter(Mandatory = $true)][string]$InstalledBinary)

if (-not (Test-Path -LiteralPath $InstalledBinary)) {
throw "binary not found at $InstalledBinary"
}
& $InstalledBinary autostart enable
if ($LASTEXITCODE -ne 0) {
throw "cua-driver autostart enable failed (exit $LASTEXITCODE)"

# The autostart task is registered with RunLevel=Highest so the daemon runs
# at the user's elevated/admin token. This is what lets cua-driver drive
# UWP / AppContainer apps (Calculator, modern Settings, Photos) — at the
# default Medium IL token, the cross-AppContainer UIA RPC truncates the
# tree to ~1 element (see issue 1602 / 1601). Registering a RunLevel=Highest
# task itself requires admin, so we self-elevate if needed and run only
# the registration step in an elevated PowerShell window. The rest of the
# install (file extraction, junction creation, User PATH update) stays
# unelevated as before.
if (Test-IsElevated) {
& $InstalledBinary autostart enable
if ($LASTEXITCODE -ne 0) {
throw "cua-driver autostart enable failed (exit $LASTEXITCODE)"
}
return
}

Write-Host ""
Write-Host "Auto-start at logon needs admin one time to register the" -ForegroundColor Yellow
Write-Host "Scheduled Task with RunLevel=Highest. A UAC prompt will appear." -ForegroundColor Yellow
Write-Host "The task itself runs silently at every logon afterwards." -ForegroundColor Yellow
Write-Host ""

$elevCmd = "& `"$InstalledBinary`" autostart enable; `$ec = `$LASTEXITCODE; if (`$ec -ne 0) { Read-Host 'cua-driver autostart enable failed; press Enter to close' }; exit `$ec"
try {
$proc = Start-Process -FilePath "powershell.exe" `
-ArgumentList "-NoProfile","-ExecutionPolicy","Bypass","-Command",$elevCmd `
-Verb RunAs -Wait -PassThru -ErrorAction Stop
if ($proc.ExitCode -ne 0) {
throw "cua-driver autostart enable failed in elevated session (exit $($proc.ExitCode))"
}
} catch {
throw "elevation cancelled or failed: $($_.Exception.Message). Re-run install.ps1 -AutoStart from an elevated PowerShell to retry."
}
}

Expand Down Expand Up @@ -958,7 +996,7 @@ if ($AutoStart) {
Write-Host "Registering auto-start (cua-driver autostart enable)..." -ForegroundColor Cyan
try {
Register-CuaDriverAutostart -InstalledBinary $installedBinary
Write-Host " cua-driver serve will auto-start at every interactive logon." -ForegroundColor Green
Write-Host " cua-driver serve will auto-start at every interactive logon (RunLevel=Highest)." -ForegroundColor Green
Write-Host ' In a new PowerShell window, manage with:'
Write-Host ' cua-driver autostart kick (run now without re-logging)'
Write-Host ' cua-driver autostart status (inspect the task)'
Expand All @@ -968,8 +1006,8 @@ if ($AutoStart) {
}
catch {
Write-Host " Failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host ' Install otherwise succeeded; in a new shell run: cua-driver autostart enable'
Write-Host " In THIS shell, use: $installedBinary autostart enable"
Write-Host ' Install otherwise succeeded; from an elevated shell run: cua-driver autostart enable'
Write-Host " In THIS shell (if already elevated), use: $installedBinary autostart enable"
Write-Host ""
}
}
Expand All @@ -978,8 +1016,8 @@ else {
Write-Host "Auto-start at logon (Windows equivalent of macOS LaunchAgent):" -ForegroundColor Cyan
Write-Host " Run cua-driver serve automatically every time you sign in." -ForegroundColor Cyan
Write-Host "" -ForegroundColor Cyan
Write-Host " In a new PowerShell window:" -ForegroundColor Cyan
Write-Host " cua-driver autostart enable (register the task)" -ForegroundColor Cyan
Write-Host " In a new PowerShell window (will prompt for admin once to register):" -ForegroundColor Cyan
Write-Host " cua-driver autostart enable (register the task at RunLevel=Highest)" -ForegroundColor Cyan
Write-Host " cua-driver autostart kick (start now without re-logging)" -ForegroundColor Cyan
Write-Host " cua-driver autostart status (inspect)" -ForegroundColor Cyan
Write-Host " cua-driver autostart disable (remove)" -ForegroundColor Cyan
Expand All @@ -988,6 +1026,10 @@ else {
Write-Host " $installedBinary" -ForegroundColor Cyan
Write-Host "" -ForegroundColor Cyan
Write-Host " Or re-run this installer with -AutoStart for the same result." -ForegroundColor Cyan
Write-Host "" -ForegroundColor Cyan
Write-Host " Without auto-start, the daemon runs at the user's default token IL." -ForegroundColor Cyan
Write-Host " That's fine for Win32 + Chromium apps. UWP / AppContainer apps" -ForegroundColor Cyan
Write-Host " (Calculator, modern Settings, Photos) need the elevated autostart task." -ForegroundColor Cyan
}


Expand Down
Loading