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 504e4e9e31..8ed6e4dbd1 100644 --- a/libs/cua-driver-rs/crates/cua-driver/src/autostart.rs +++ b/libs/cua-driver-rs/crates/cua-driver/src/autostart.rs @@ -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 @@ -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 @@ -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), diff --git a/libs/cua-driver/scripts/install.ps1 b/libs/cua-driver/scripts/install.ps1 index 48fc6f8900..cd8f017fbc 100644 --- a/libs/cua-driver/scripts/install.ps1 +++ b/libs/cua-driver/scripts/install.ps1 @@ -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." } } @@ -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)' @@ -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 "" } } @@ -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 @@ -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 }