<#!
.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.'
}
Comments