Skip to content

Commit 4410103

Browse files
Add release smoke test workflow
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fc2bb08 commit 4410103

7 files changed

Lines changed: 1058 additions & 0 deletions
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Release Smoke Test
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
release-repository:
7+
description: 'GitHub repository to download the release from'
8+
required: true
9+
default: 'Devolutions/UniGetUI'
10+
release-tag:
11+
description: 'Release tag to test. Leave empty for the latest release.'
12+
required: false
13+
default: ''
14+
installer-asset-name:
15+
description: 'Installer asset name in the GitHub release'
16+
required: true
17+
default: 'UniGetUI.Installer.x64.exe'
18+
max-stage:
19+
description: 'Last smoke-test stage to run'
20+
required: true
21+
type: choice
22+
default: 'install-launch'
23+
options:
24+
- rdp
25+
- rdp-client
26+
- remoting-server
27+
- remote-command
28+
- install-launch
29+
30+
jobs:
31+
release-smoke-test:
32+
name: Windows release smoke test
33+
runs-on: windows-latest
34+
timeout-minutes: 90
35+
permissions:
36+
contents: read
37+
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v6
41+
42+
- name: Run release smoke test
43+
shell: pwsh
44+
env:
45+
GITHUB_TOKEN: ${{ github.token }}
46+
RELEASE_REPOSITORY: ${{ inputs['release-repository'] }}
47+
RELEASE_TAG: ${{ inputs['release-tag'] }}
48+
INSTALLER_ASSET_NAME: ${{ inputs['installer-asset-name'] }}
49+
MAX_STAGE: ${{ inputs['max-stage'] }}
50+
run: |
51+
.\testing\release-smoke\Invoke-ReleaseSmokeTest.ps1 `
52+
-ReleaseRepository $env:RELEASE_REPOSITORY `
53+
-ReleaseTag $env:RELEASE_TAG `
54+
-InstallerAssetName $env:INSTALLER_ASSET_NAME `
55+
-MaxStage $env:MAX_STAGE `
56+
-ArtifactsDir '${{ github.workspace }}\artifacts\release-smoke'
57+
58+
- name: Cleanup release smoke test
59+
if: always()
60+
shell: pwsh
61+
run: |
62+
.\testing\release-smoke\Invoke-ReleaseSmokeTest.ps1 `
63+
-CleanupOnly `
64+
-ArtifactsDir '${{ github.workspace }}\artifacts\release-smoke'
65+
66+
- name: Upload release smoke artifacts
67+
if: always()
68+
uses: actions/upload-artifact@v7
69+
with:
70+
name: release-smoke-test
71+
path: artifacts\release-smoke
72+
if-no-files-found: warn
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
[CmdletBinding(DefaultParameterSetName = 'Enable')]
2+
param(
3+
[Parameter(ParameterSetName = 'Enable')]
4+
[Parameter(ParameterSetName = 'Cleanup')]
5+
[string] $StatePath = (Join-Path $env:RUNNER_TEMP 'unigetui-release-smoke-rdp-state.json'),
6+
7+
[Parameter(ParameterSetName = 'Cleanup')]
8+
[switch] $Cleanup
9+
)
10+
11+
Set-StrictMode -Version Latest
12+
$ErrorActionPreference = 'Stop'
13+
14+
$terminalServerPath = 'HKLM:\System\CurrentControlSet\Control\Terminal Server'
15+
$rdpTcpPath = Join-Path $terminalServerPath 'WinStations\RDP-Tcp'
16+
$rdpGroupName = 'Remote Desktop Users'
17+
18+
function Get-RegistryValue {
19+
param(
20+
[Parameter(Mandatory)]
21+
[string] $Path,
22+
23+
[Parameter(Mandatory)]
24+
[string] $Name
25+
)
26+
27+
$property = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
28+
if ($null -eq $property) {
29+
return $null
30+
}
31+
32+
return $property.$Name
33+
}
34+
35+
function Write-JsonFile {
36+
param(
37+
[Parameter(Mandatory)]
38+
[string] $Path,
39+
40+
[Parameter(Mandatory)]
41+
[object] $Value
42+
)
43+
44+
$directory = Split-Path -Path $Path -Parent
45+
New-Item -Path $directory -ItemType Directory -Force | Out-Null
46+
$Value | ConvertTo-Json -Depth 8 | Set-Content -Path $Path -Encoding utf8NoBOM
47+
}
48+
49+
function Test-TcpPort {
50+
param(
51+
[Parameter(Mandatory)]
52+
[string] $HostName,
53+
54+
[Parameter(Mandatory)]
55+
[int] $Port
56+
)
57+
58+
$client = [System.Net.Sockets.TcpClient]::new()
59+
try {
60+
$connect = $client.BeginConnect($HostName, $Port, $null, $null)
61+
if (-not $connect.AsyncWaitHandle.WaitOne([TimeSpan]::FromSeconds(1))) {
62+
return $false
63+
}
64+
65+
$client.EndConnect($connect)
66+
return $true
67+
}
68+
catch {
69+
return $false
70+
}
71+
finally {
72+
$client.Dispose()
73+
}
74+
}
75+
76+
function Wait-TcpPort {
77+
param(
78+
[Parameter(Mandatory)]
79+
[string] $HostName,
80+
81+
[Parameter(Mandatory)]
82+
[int] $Port,
83+
84+
[int] $TimeoutSeconds = 30
85+
)
86+
87+
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
88+
do {
89+
if (Test-TcpPort -HostName $HostName -Port $Port) {
90+
return
91+
}
92+
93+
Start-Sleep -Seconds 1
94+
} while ((Get-Date) -lt $deadline)
95+
96+
throw "Timed out waiting for $HostName`:$Port to accept TCP connections."
97+
}
98+
99+
if ($Cleanup) {
100+
if (-not (Test-Path $StatePath)) {
101+
return
102+
}
103+
104+
$state = Get-Content -Path $StatePath -Raw | ConvertFrom-Json
105+
106+
if ($null -ne $state.fDenyTSConnections) {
107+
Set-ItemProperty -Path $terminalServerPath -Name 'fDenyTSConnections' -Value ([int] $state.fDenyTSConnections)
108+
}
109+
110+
if ($null -ne $state.UserAuthentication) {
111+
Set-ItemProperty -Path $rdpTcpPath -Name 'UserAuthentication' -Value ([int] $state.UserAuthentication)
112+
}
113+
114+
foreach ($rule in @($state.FirewallRules)) {
115+
if ($null -ne $rule.Name -and $null -ne $rule.Enabled) {
116+
Set-NetFirewallRule -Name $rule.Name -Enabled $rule.Enabled -ErrorAction SilentlyContinue
117+
}
118+
}
119+
120+
if ($state.AddedToRemoteDesktopUsers) {
121+
Remove-LocalGroupMember -Group $rdpGroupName -Member $state.LocalUserName -ErrorAction SilentlyContinue
122+
}
123+
124+
Remove-Item -Path $StatePath -Force -ErrorAction SilentlyContinue
125+
return
126+
}
127+
128+
$localUserName = $env:USERNAME
129+
if ([string]::IsNullOrWhiteSpace($localUserName)) {
130+
throw 'USERNAME is not set; cannot configure a local RDP user.'
131+
}
132+
133+
$passwordBytes = [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(24)
134+
$temporaryPassword = 'RdpSmoke!' + [Convert]::ToBase64String($passwordBytes) + 'aA1!'
135+
Write-Host "::add-mask::$temporaryPassword"
136+
137+
$currentMembers = @(Get-LocalGroupMember -Group $rdpGroupName -ErrorAction SilentlyContinue | ForEach-Object { $_.Name })
138+
$memberNames = @($localUserName, "$env:COMPUTERNAME\$localUserName")
139+
$wasRdpMember = [bool]($currentMembers | Where-Object { $memberNames -contains $_ } | Select-Object -First 1)
140+
141+
$state = [pscustomobject]@{
142+
LocalUserName = $localUserName
143+
DomainUserName = "$env:COMPUTERNAME\$localUserName"
144+
fDenyTSConnections = Get-RegistryValue -Path $terminalServerPath -Name 'fDenyTSConnections'
145+
UserAuthentication = Get-RegistryValue -Path $rdpTcpPath -Name 'UserAuthentication'
146+
FirewallRules = @(Get-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue | Select-Object -Property Name, Enabled)
147+
AddedToRemoteDesktopUsers = (-not $wasRdpMember)
148+
}
149+
Write-JsonFile -Path $StatePath -Value $state
150+
151+
$securePassword = ConvertTo-SecureString -String $temporaryPassword -AsPlainText -Force
152+
Set-LocalUser -Name $localUserName -Password $securePassword
153+
154+
if (-not $wasRdpMember) {
155+
Add-LocalGroupMember -Group $rdpGroupName -Member $localUserName
156+
}
157+
158+
Set-ItemProperty -Path $terminalServerPath -Name 'fDenyTSConnections' -Value 0
159+
Set-ItemProperty -Path $rdpTcpPath -Name 'UserAuthentication' -Value 0
160+
Set-Service -Name TermService -StartupType Automatic
161+
Start-Service -Name TermService
162+
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' | Out-Null
163+
Wait-TcpPort -HostName '127.0.0.1' -Port 3389
164+
165+
[pscustomobject]@{
166+
UserName = $localUserName
167+
DomainUserName = "$env:COMPUTERNAME\$localUserName"
168+
Password = $temporaryPassword
169+
HostName = '127.0.0.1'
170+
Port = 3389
171+
StatePath = $StatePath
172+
} | ConvertTo-Json -Compress

0 commit comments

Comments
 (0)