<#! .SYNOPSIS Connects to a Linux computer in RustDesk and reconnects after its graphical-login handoff. .DESCRIPTION RustDesk may initially connect to a LightDM (or similar) login screen. When the user signs in, the remote RustDesk server can be replaced by the per-user server. Its built-in retry can occur before that replacement server is ready. This wrapper opens the normal RustDesk connection, watches the remote machine's loginctl sessions over key-based SSH, and issues one fresh RustDesk connection after the requested user becomes active on the specified graphical seat. .NOTES Requires Windows PowerShell 5.1 or later, RustDesk, OpenSSH's ssh.exe, and non-interactive/key-based SSH access to the remote machine. #> [CmdletBinding()] param( # RustDesk ID, relay address, or direct address such as 192.168.1.25:21118. [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$RustDeskTarget, # An SSH host alias, hostname, or user@hostname accepted by ssh.exe. [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SshTarget, # Defaults to the current Windows username, normalised for typical Linux use. [ValidateNotNullOrEmpty()] [string]$SessionUser = $(if ($env:USERNAME) { $env:USERNAME.ToLowerInvariant() } else { '' }), [ValidateNotNullOrEmpty()] [string]$Seat = 'seat0', [ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })] [string]$RustDeskPath = (Join-Path $env:ProgramFiles 'RustDesk\RustDesk.exe'), [ValidateRange(30, 900)] [int]$LoginWatchWindowSeconds = 300, [ValidateRange(1, 30)] [int]$PollIntervalSeconds = 3, [ValidateRange(1, 60)] [int]$SessionStartupGraceSeconds = 7, # Optional plain-text diagnostic log path. It contains timestamps only. [string]$LogPath ) $sshCommand = Get-Command ssh.exe -ErrorAction Stop $seatPattern = '^\s*\S+\s+\d+\s+' + [regex]::Escape($SessionUser) + '\s+' + [regex]::Escape($Seat) + '\s' function Write-ConnectionLog { param([Parameter(Mandatory = $true)][string]$Message) if (-not $LogPath) { return } try { Add-Content -LiteralPath $LogPath -Value ('{0:yyyy-MM-dd HH:mm:ss} {1}' -f (Get-Date), $Message) } catch { # Logging is diagnostic only and must not prevent a remote connection. } } function Test-RemoteDesktopSession { # SSH-created sessions have no seat. A graphical Linux desktop session is # explicitly associated with a seat, normally seat0. $sessions = & $sshCommand.Source -o BatchMode=yes -o ConnectTimeout=3 -o LogLevel=ERROR $SshTarget 'loginctl list-sessions --no-legend' 2>$null $matchingSessions = @($sessions | Where-Object { $_ -match $seatPattern }) return ($LASTEXITCODE -eq 0 -and $matchingSessions.Count -gt 0) } function Start-RustDeskConnection { Start-Process -FilePath $RustDeskPath -ArgumentList "--connect $RustDeskTarget" } $wasOnDesktop = Test-RemoteDesktopSession $deadline = [DateTime]::UtcNow.AddSeconds($LoginWatchWindowSeconds) $reconnectIssued = $false Write-ConnectionLog "Opened initial connection to $RustDeskTarget; waiting for $SessionUser on $Seat." Start-RustDeskConnection # A connection made while the desktop is already signed in does not cross the # display-manager-to-user-session boundary. The initial RustDesk connection is # already the right one, so exit instead of keeping a watcher alive. if ($wasOnDesktop) { Write-ConnectionLog "$SessionUser was already active on $Seat; no post-login reconnection is needed." return } while ([DateTime]::UtcNow -lt $deadline) { $isOnDesktop = Test-RemoteDesktopSession if (-not $wasOnDesktop -and $isOnDesktop) { Write-ConnectionLog "Detected $SessionUser on $Seat; waiting $SessionStartupGraceSeconds seconds for the user-session RustDesk server." Start-Sleep -Seconds $SessionStartupGraceSeconds Start-RustDeskConnection Write-ConnectionLog 'Issued post-login RustDesk reconnection.' $reconnectIssued = $true break } $wasOnDesktop = $isOnDesktop Start-Sleep -Seconds $PollIntervalSeconds } if (-not $reconnectIssued) { Write-ConnectionLog 'Stopped watching without detecting a new graphical-session login.' }