Skip to content
Draft
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
121 changes: 121 additions & 0 deletions .github/agentic-rdp/Connect-AgentSession.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string] $UserName,

[Parameter(Mandatory)]
[string] $Password,

[string] $HostName = '127.0.0.1',

[int] $Port = 3389,

[string] $DesktopSize = '1920x1080',

[string] $AgentPath = (Join-Path $env:GITHUB_WORKSPACE 'target\release\ironrdp-agent.exe'),

[string] $Endpoint = "pipe:ironrdp-agent-ci-$PID",

[string] $ArtifactsDir = (Join-Path $env:GITHUB_WORKSPACE 'artifacts\agentic-rdp'),

[string] $StatePath = (Join-Path $ArtifactsDir 'agent-session.json')
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Invoke-Agent {
param(
[Parameter(ValueFromRemainingArguments)]
[string[]] $Arguments
)

& $AgentPath --endpoint $Endpoint --no-spawn-daemon @Arguments
}

function Get-SessionStatus {
param(
[Parameter(Mandatory)]
[string] $SessionId
)

$statusJson = Invoke-Agent status --session $SessionId
$statusJson | Set-Content -Path (Join-Path $ArtifactsDir 'agent-session-status.json') -Encoding utf8NoBOM
return ($statusJson | ConvertFrom-Json)
Comment on lines +36 to +44
}

function Assert-DesktopSize {
param(
[Parameter(Mandatory)]
[object] $Status,

[Parameter(Mandatory)]
[int] $ExpectedWidth,

[Parameter(Mandatory)]
[int] $ExpectedHeight
)

if ([int] $Status.width -ne $ExpectedWidth -or [int] $Status.height -ne $ExpectedHeight) {
throw "RDP framebuffer is $($Status.width)x$($Status.height), expected ${ExpectedWidth}x${ExpectedHeight}"
}
}

New-Item -Path $ArtifactsDir -ItemType Directory -Force | Out-Null

if ($DesktopSize -notmatch '^(?<width>[1-9][0-9]*)[xX](?<height>[1-9][0-9]*)$') {
throw "DesktopSize must use WxH format, got '$DesktopSize'"
}

$expectedWidth = [int] $Matches.width
$expectedHeight = [int] $Matches.height
$passwordEnvironmentVariable = 'IRONRDP_AGENT_LOCAL_RDP_PASSWORD'
$env:IRONRDP_AGENT_LOCAL_RDP_PASSWORD = $Password

try {
$destination = "${HostName}:$Port"
$connectJson = Invoke-Agent connect $destination `
--username $UserName `
--password-env $passwordEnvironmentVariable `
--desktop-size $DesktopSize `
--no-credssp `
--autologon `
--compression-enabled false `
--color-depth 16 `
--no-server-pointer
Comment on lines +80 to +85
$connectJson | Set-Content -Path (Join-Path $ArtifactsDir 'agent-connect.json') -Encoding utf8NoBOM

$connect = $connectJson | ConvertFrom-Json
$sessionId = [string] $connect.session_id

Comment on lines +86 to +90
Invoke-Agent wait-frame --session $sessionId --timeout-ms 120000 | Out-Null
$status = Get-SessionStatus -SessionId $sessionId

if ([int] $status.width -ne $expectedWidth -or [int] $status.height -ne $expectedHeight) {
$beforeResizeFrame = [uint64] $status.frame_sequence
Invoke-Agent resize --session $sessionId --width $expectedWidth --height $expectedHeight --scale 100 | Out-Null
Invoke-Agent wait-frame --session $sessionId --timeout-ms 60000 --after-frame $beforeResizeFrame | Out-Null
$status = Get-SessionStatus -SessionId $sessionId
}

Assert-DesktopSize -Status $status -ExpectedWidth $expectedWidth -ExpectedHeight $expectedHeight

$state = [pscustomobject]@{
SessionId = $sessionId
Endpoint = $Endpoint
HostName = $HostName
Port = $Port
UserName = $UserName
RequestedDesktopSize = $DesktopSize
Width = $status.width
Height = $status.height
FrameSequence = $status.frame_sequence
StatePath = $StatePath
}

$state | ConvertTo-Json -Depth 6 | Set-Content -Path $StatePath -Encoding utf8NoBOM
$state | ConvertTo-Json -Compress
}
finally {
Remove-Item Env:\IRONRDP_AGENT_LOCAL_RDP_PASSWORD -ErrorAction SilentlyContinue
}
172 changes: 172 additions & 0 deletions .github/agentic-rdp/Enable-LocalRdp.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
[CmdletBinding(DefaultParameterSetName = 'Enable')]
param(
[Parameter(ParameterSetName = 'Enable')]
[Parameter(ParameterSetName = 'Cleanup')]
[string] $StatePath = (Join-Path $env:RUNNER_TEMP 'ironrdp-agentic-rdp-state.json'),

[Parameter(ParameterSetName = 'Cleanup')]
[switch] $Cleanup
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$terminalServerPath = 'HKLM:\System\CurrentControlSet\Control\Terminal Server'
$rdpTcpPath = Join-Path $terminalServerPath 'WinStations\RDP-Tcp'
$rdpGroupName = 'Remote Desktop Users'

function Get-RegistryValue {
param(
[Parameter(Mandatory)]
[string] $Path,

[Parameter(Mandatory)]
[string] $Name
)

$property = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
if ($null -eq $property) {
return $null
}

return $property.$Name
}

function Write-JsonFile {
param(
[Parameter(Mandatory)]
[string] $Path,

[Parameter(Mandatory)]
[object] $Value
)

$directory = Split-Path -Path $Path -Parent
New-Item -Path $directory -ItemType Directory -Force | Out-Null
$Value | ConvertTo-Json -Depth 8 | Set-Content -Path $Path -Encoding utf8NoBOM
}

function Test-TcpPort {
param(
[Parameter(Mandatory)]
[string] $HostName,

[Parameter(Mandatory)]
[int] $Port
)

$client = [System.Net.Sockets.TcpClient]::new()
try {
$connect = $client.BeginConnect($HostName, $Port, $null, $null)
if (-not $connect.AsyncWaitHandle.WaitOne([TimeSpan]::FromSeconds(1))) {
return $false
}

$client.EndConnect($connect)
return $true
}
catch {
return $false
}
finally {
$client.Dispose()
}
}

function Wait-TcpPort {
param(
[Parameter(Mandatory)]
[string] $HostName,

[Parameter(Mandatory)]
[int] $Port,

[int] $TimeoutSeconds = 30
)

$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
do {
if (Test-TcpPort -HostName $HostName -Port $Port) {
return
}

Start-Sleep -Seconds 1
} while ((Get-Date) -lt $deadline)

throw "Timed out waiting for $HostName`:$Port to accept TCP connections"
}

if ($Cleanup) {
if (-not (Test-Path $StatePath)) {
return
}

$state = Get-Content -Path $StatePath -Raw | ConvertFrom-Json

if ($null -ne $state.fDenyTSConnections) {
Set-ItemProperty -Path $terminalServerPath -Name 'fDenyTSConnections' -Value ([int] $state.fDenyTSConnections)
}

if ($null -ne $state.UserAuthentication) {
Set-ItemProperty -Path $rdpTcpPath -Name 'UserAuthentication' -Value ([int] $state.UserAuthentication)
}

foreach ($rule in @($state.FirewallRules)) {
if ($null -ne $rule.Name -and $null -ne $rule.Enabled) {
Set-NetFirewallRule -Name $rule.Name -Enabled $rule.Enabled -ErrorAction SilentlyContinue
}
}

if ($state.AddedToRemoteDesktopUsers) {
Remove-LocalGroupMember -Group $rdpGroupName -Member $state.LocalUserName -ErrorAction SilentlyContinue
}

Remove-Item -Path $StatePath -Force -ErrorAction SilentlyContinue
return
}

$localUserName = $env:USERNAME
if ([string]::IsNullOrWhiteSpace($localUserName)) {
throw 'USERNAME is not set; cannot configure a local RDP user'
}

$passwordBytes = [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(24)
$temporaryPassword = 'RdpAgent!' + [Convert]::ToBase64String($passwordBytes) + 'aA1!'
Write-Host "::add-mask::$temporaryPassword"

$currentMembers = @(Get-LocalGroupMember -Group $rdpGroupName -ErrorAction SilentlyContinue | ForEach-Object { $_.Name })
$memberNames = @($localUserName, "$env:COMPUTERNAME\$localUserName")
$wasRdpMember = [bool]($currentMembers | Where-Object { $memberNames -contains $_ } | Select-Object -First 1)

$state = [pscustomobject]@{
LocalUserName = $localUserName
DomainUserName = "$env:COMPUTERNAME\$localUserName"
fDenyTSConnections = Get-RegistryValue -Path $terminalServerPath -Name 'fDenyTSConnections'
UserAuthentication = Get-RegistryValue -Path $rdpTcpPath -Name 'UserAuthentication'
FirewallRules = @(Get-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue | Select-Object -Property Name, Enabled)
AddedToRemoteDesktopUsers = (-not $wasRdpMember)
}
Write-JsonFile -Path $StatePath -Value $state

$securePassword = ConvertTo-SecureString -String $temporaryPassword -AsPlainText -Force
Set-LocalUser -Name $localUserName -Password $securePassword

if (-not $wasRdpMember) {
Add-LocalGroupMember -Group $rdpGroupName -Member $localUserName
}

Set-ItemProperty -Path $terminalServerPath -Name 'fDenyTSConnections' -Value 0
Set-ItemProperty -Path $rdpTcpPath -Name 'UserAuthentication' -Value 0
Set-Service -Name TermService -StartupType Automatic
Start-Service -Name TermService
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' | Out-Null
Wait-TcpPort -HostName '127.0.0.1' -Port 3389

[pscustomobject]@{
UserName = $localUserName
DomainUserName = "$env:COMPUTERNAME\$localUserName"
Password = $temporaryPassword
HostName = '127.0.0.1'
Port = 3389
StatePath = $StatePath
} | ConvertTo-Json -Compress
Loading
Loading