# RustDesk unattended-install bootstrap (help.apps.61ut.com) # For an ALWAYS-ON machine (e.g. Dad's PC) that should accept remote support without anyone clicking Accept. # # What it does: # 1. Downloads the pinned, OFFICIAL SIGNED RustDesk 1.4.9 installer. # 2. Silently installs it. # 3. Points it at our self-hosted server (host + key baked in below). # 4. PROMPTS you for a unattended-access password for THIS machine and sets it. # # Security note (AI-2293): this script is served from a public, unauthenticated page and is # re-fetched fresh on every run, so it must NEVER embed a password. It asks for one interactively # instead, so every machine gets its own password and nothing secret is ever world-readable. # # Run it (right-click -> Run with PowerShell, OR from an elevated PowerShell): # Set-ExecutionPolicy -Scope Process Bypass -Force; .\install-unattended.ps1 $ErrorActionPreference = 'Stop' # ---- pinned version + server config (constant; from AI-2288) ---- $Version = '1.4.9' $ExeName = "rustdesk-$Version-x86_64.exe" $BaseUrl = 'https://help.apps.61ut.com/dl' # RustDesk 1.4.x --config expects the PLAIN "host=...,key=..." string (NOT base64). # Source: src/core_main.rs --config handler -> get_custom_server_from_string(), which # looks for a literal "host=" and splits on ",". A base64 blob has no "host=", so it is # silently ignored -- that was the earlier bug where config never applied. $ConfigStr = 'host=relay.apps.61ut.com,key=RWkwPhQUFcKkn0CtdMWpTJH0oageh+XfZ4qbKaBHMD8=' # ---- per-machine unattended password: PROMPT for it, never embed it ---- # Read it as a SecureString so it is not echoed to the console, and only convert to plaintext # at the moment we hand it to rustdesk.exe --password. Require a non-empty, >= 8 char value. $UnattendedPassword = $null while ([string]::IsNullOrEmpty($UnattendedPassword)) { Write-Host "" Write-Host "Choose an unattended-access password for THIS computer." -ForegroundColor Cyan Write-Host "Make it unique to this machine (do not reuse the same one everywhere)." -ForegroundColor Cyan $sec1 = Read-Host -AsSecureString "Enter password (min 8 chars, hidden)" $sec2 = Read-Host -AsSecureString "Re-enter password to confirm" $bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec1) $bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec2) try { $p1 = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr1) $p2 = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr2) if ($p1 -ne $p2) { Write-Host "Passwords did not match. Try again." -ForegroundColor Yellow } elseif ($p1.Length -lt 8) { Write-Host "Password must be at least 8 characters. Try again." -ForegroundColor Yellow } else { $UnattendedPassword = $p1 } } finally { # zero the plaintext copies we no longer need [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) Remove-Variable -Name p1, p2 -ErrorAction SilentlyContinue } } # ---- download the official signed installer ---- $Dest = Join-Path $env:TEMP $ExeName Write-Host "Downloading $ExeName ..." Invoke-WebRequest -Uri "$BaseUrl/$ExeName" -OutFile $Dest -UseBasicParsing # ---- silent install ---- # NOTE: do NOT use -Wait here. --silent-install elevates and hands the actual install # to a SEPARATE process, then the launched exe stays resident (tray/service), so # Start-Process -Wait would block forever (the reported hang). Launch it WITHOUT -Wait # and instead poll until the installed rustdesk.exe appears, with a timeout. Write-Host "Installing RustDesk $Version silently ..." Start-Process -FilePath $Dest -ArgumentList '--silent-install' $Rd = $null $deadline = (Get-Date).AddMinutes(3) while ((Get-Date) -lt $deadline) { $candidates = @( (Join-Path $env:ProgramFiles 'RustDesk\rustdesk.exe'), (Join-Path ${env:ProgramFiles(x86)} 'RustDesk\rustdesk.exe') ) $found = $candidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1 if ($found) { $Rd = $found; break } Start-Sleep -Seconds 2 } if (-not $Rd) { throw "RustDesk did not finish installing within 3 minutes (looked in Program Files)." } # NB: print the folder, not the full exe path -- the literal "\rustdesk.exe" contains a # backslash-r that some consoles render as a carriage return, mangling the line. Write-Host ("Installed to: " + (Split-Path $Rd -Parent)) # --- CRITICAL (RustDesk 1.4.x, upstream bug #13023) ------------------------------------- # --silent-install AUTO-LAUNCHES RustDesk. A running RustDesk overwrites its config on # startup and ignores --config/--password applied to a live instance, so the naive # "install then --config then --password" sequence silently does nothing. We must STOP # RustDesk completely, apply config + password while it is stopped, write the config file # directly as a belt-and-suspenders, then start the service so it registers pre-configured. function Stop-RustDesk { # stop the service if present, then kill any stray GUI/tray processes Get-Service -Name 'RustDesk' -ErrorAction SilentlyContinue | Where-Object { $_.Status -ne 'Stopped' } | ForEach-Object { Stop-Service -Name 'RustDesk' -Force -ErrorAction SilentlyContinue } Get-Process -Name 'rustdesk' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 } # Run a one-shot rustdesk CLI command with a hard timeout so a misbehaving flag can # never hang the whole install (defensive after the --silent-install -Wait hang). function Invoke-RdCli([string]$argline) { $p = Start-Process -FilePath $Rd -ArgumentList $argline -WindowStyle Hidden -PassThru if (-not $p.WaitForExit(20000)) { try { $p.Kill() } catch { } Write-Host " (command took too long and was stopped; continuing)" -ForegroundColor Yellow } } Write-Host "Stopping RustDesk so configuration will stick ..." Stop-RustDesk # ---- apply our server config (rendezvous + relay + key) while stopped ---- Write-Host "Applying server configuration ..." Invoke-RdCli "--config `"$ConfigStr`"" Start-Sleep -Seconds 1 # Belt-and-suspenders: also write the values straight into RustDesk2.toml so a config # reset on first launch can't drop them. RustDesk reads the SERVICE config from # %ProgramData%\RustDesk\config and the USER config from %APPDATA%\RustDesk\config. $Key = 'RWkwPhQUFcKkn0CtdMWpTJH0oageh+XfZ4qbKaBHMD8=' $Host_ = 'relay.apps.61ut.com' $toml = @" rendezvous_server = '$Host_' nat_type = 1 serial = 0 [options] custom-rendezvous-server = '$Host_' relay-server = '$Host_' key = '$Key' "@ # Write to every location RustDesk may read from: the current user's %APPDATA%, the # machine %ProgramData%, and the LocalService profile the SYSTEM-run service uses. $cfgBases = @( "$env:APPDATA\RustDesk\config", "$env:ProgramData\RustDesk\config", "$env:WinDir\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config" ) foreach ($base in $cfgBases) { try { New-Item -ItemType Directory -Force -Path $base | Out-Null Set-Content -Path (Join-Path $base 'RustDesk2.toml') -Value $toml -Encoding UTF8 -Force } catch { } } # ---- set the unattended-access password you chose above (while stopped) ---- Write-Host "Setting unattended password ..." Invoke-RdCli "--password `"$UnattendedPassword`"" # drop the plaintext password from memory now that it has been applied Remove-Variable -Name UnattendedPassword -ErrorAction SilentlyContinue # ---- enable the ALWAYS-ON background service ---- # This is what makes RustDesk keep accepting connections when the window is CLOSED and # nobody is logged in. Without it, RustDesk only runs while the GUI window is open, so # closing the window ends remote access (the reported symptom). # rustdesk.exe --install-service installs + starts the SYSTEM service (src/core_main.rs # --install-service -> platform::install_service(): sc create RustDesk start=auto + a # startup tray shortcut). Run elevated (the .bat already self-elevates). Write-Host "Enabling always-on background service ..." Invoke-RdCli "--install-service" Start-Sleep -Seconds 3 # Best-effort: also make sure the service is actually running now. Never let this abort # the run (Start-Service can throw while the service is still settling right after # creation, and $ErrorActionPreference='Stop' would kill the script before the summary). try { $svc = Get-Service -Name 'RustDesk' -ErrorAction SilentlyContinue if ($svc -and $svc.Status -ne 'Running') { Start-Service -Name 'RustDesk' -ErrorAction Stop } } catch { } try { Start-Process -FilePath $Rd -WindowStyle Minimized -ErrorAction Stop } catch { } Start-Sleep -Seconds 5 # Report whether the always-on service is present + running. $svcOk = $false try { $svc = Get-Service -Name 'RustDesk' -ErrorAction SilentlyContinue if ($svc -and $svc.Status -eq 'Running') { $svcOk = $true } } catch { } # ---- verify the config actually took ---- $applied = $false foreach ($base in $cfgBases) { $f = Join-Path $base 'RustDesk2.toml' if ((Test-Path $f) -and (Select-String -Path $f -Pattern ([regex]::Escape($Host_)) -Quiet)) { $applied = $true } } Write-Host "" if ($applied) { Write-Host "[OK] Server settings applied (points at $Host_)." -ForegroundColor Green } else { Write-Host "[!] Could not confirm the server settings were written." -ForegroundColor Yellow Write-Host " Open RustDesk -> the (three dots) menu -> Network, and set ID Server = $Host_." -ForegroundColor Yellow } if ($svcOk) { Write-Host "[OK] Always-on service is running -- this PC stays reachable with the window closed." -ForegroundColor Green } else { Write-Host "[!] Could not confirm the always-on service is running." -ForegroundColor Yellow Write-Host " In RustDesk, open Settings and toggle the service on (or leave the RustDesk window open)." -ForegroundColor Yellow } Write-Host "" Write-Host "All set. You can close the RustDesk window; it keeps running in the background." -ForegroundColor Green Write-Host "Tell the helper this PC's RustDesk ID (shown in the RustDesk window)." -ForegroundColor Green