mirror of https://github.com/stascorp/rdpwrap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
780 lines
33 KiB
780 lines
33 KiB
name: Build and Release
|
|
|
|
on:
|
|
push:
|
|
branches: [main, master]
|
|
paths:
|
|
- 'msi/rdpwrap.ini'
|
|
- 'msi/rdpwrap-arm-kb.ini'
|
|
- 'msi/**'
|
|
- 'src-x86-x64-Fusix/**'
|
|
- 'src-csharp/**'
|
|
- '.github/workflows/build-and-release.yml'
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Release tag (e.g. v2026.4.2). Leave blank to auto-generate from date.'
|
|
required: false
|
|
default: ''
|
|
prerelease:
|
|
description: 'Mark as pre-release'
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
|
|
concurrency:
|
|
group: release
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Job dependency / parallelism map
|
|
#
|
|
# build-dll ───────────────► build-csharp ──────────► build-msi ──┐
|
|
# build-offsetfinder ───────────────────────────────────────────► release
|
|
# download-sergiye ───────────────────────────────────────────► release
|
|
# build-dll ─┘
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
jobs:
|
|
|
|
# ── Job 1: Build rdpwrap.dll (x64, x86, ARM64) ────────────────────────────
|
|
# No job dependencies — starts immediately.
|
|
build-dll:
|
|
name: Build rdpwrap DLL
|
|
runs-on: windows-2022
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup MSBuild
|
|
uses: microsoft/setup-msbuild@v3
|
|
|
|
- name: Build rdpwrap.dll (x64, Win32, ARM64)
|
|
shell: pwsh
|
|
run: |
|
|
foreach ($platform in @('x64', 'Win32', 'ARM64')) {
|
|
msbuild src-x86-x64-Fusix/RDPWrap.vcxproj `
|
|
/p:Configuration=Release `
|
|
/p:Platform="$platform" `
|
|
/p:PlatformToolset=v143 `
|
|
/p:WindowsTargetPlatformVersion=10.0 `
|
|
/v:minimal
|
|
}
|
|
Copy-Item src-x86-x64-Fusix/Release/x64/rdpwrap.dll .\rdpwrap_x64.dll
|
|
Copy-Item src-x86-x64-Fusix/Release/x86/rdpwrap.dll .\rdpwrap_x86.dll
|
|
Copy-Item src-x86-x64-Fusix/Release/arm64/rdpwrap.dll .\rdpwrap_arm64.dll
|
|
Write-Host "Built DLLs:"
|
|
Get-Item .\rdpwrap_x64.dll, .\rdpwrap_x86.dll, .\rdpwrap_arm64.dll | Format-Table Name, Length
|
|
|
|
- name: Upload rdpwrap DLLs
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: rdpwrap-dlls
|
|
path: |
|
|
rdpwrap_x64.dll
|
|
rdpwrap_x86.dll
|
|
rdpwrap_arm64.dll
|
|
if-no-files-found: error
|
|
|
|
|
|
# ── Job 2: Build RDPWrapOffsetFinder (x64, x86) — parallel to build-dll ───
|
|
# No job dependencies — starts immediately.
|
|
build-offsetfinder:
|
|
name: Build OffsetFinder
|
|
runs-on: windows-2022
|
|
outputs:
|
|
finder_ver: ${{ steps.finder.outputs.finder_ver }}
|
|
|
|
steps:
|
|
- name: Checkout repository (with submodules)
|
|
uses: actions/checkout@v6
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Setup MSBuild
|
|
uses: microsoft/setup-msbuild@v3
|
|
|
|
- name: Build RDPWrapOffsetFinder from submodule
|
|
id: finder
|
|
shell: pwsh
|
|
run: |
|
|
$version = (git -C src-csharp/RDPOffsetFinder describe --tags --always).Trim()
|
|
Write-Host "Submodule version: $version"
|
|
|
|
foreach ($cfg in @(
|
|
@{ Platform = 'x64'; ZydisCfg = 'Release MD DLL'; ZydisBin = 'ReleaseX64'; Arch = 'x64' }
|
|
@{ Platform = 'Win32'; ZydisCfg = 'Release MD DLL'; ZydisBin = 'ReleaseX86'; Arch = 'x86' }
|
|
)) {
|
|
# Build Zydis DLL
|
|
msbuild src-csharp/RDPOffsetFinder/zydis/msvc/zydis/Zydis.vcxproj `
|
|
/p:Configuration="$($cfg.ZydisCfg)" `
|
|
/p:Platform="$($cfg.Platform)" `
|
|
/p:PlatformToolset=v143 `
|
|
/v:minimal
|
|
|
|
# Build via .sln so $(SolutionDir) resolves to the submodule root
|
|
# where zydis/ lives - vcxproj AdditionalIncludeDirectories and
|
|
# AdditionalDependencies both use $(SolutionDir)\zydis\... and
|
|
# fail with C1083 when the vcxproj is targeted directly.
|
|
msbuild src-csharp/RDPOffsetFinder/RDPWrapOffsetFinder.sln `
|
|
/t:RDPWrapOffsetFinder `
|
|
/p:Configuration=Release `
|
|
/p:Platform="$($cfg.Platform)" `
|
|
/p:PlatformToolset=v143 `
|
|
/v:minimal
|
|
|
|
$arch = $cfg.Arch
|
|
$exeSrc = "src-csharp/RDPOffsetFinder/RDPWrapOffsetFinder/$($cfg.Platform)/Release/RDPWrapOffsetFinder.exe"
|
|
$dllSrc = "src-csharp/RDPOffsetFinder/zydis/msvc/bin/$($cfg.ZydisBin)/Zydis.dll"
|
|
Copy-Item $exeSrc ".\RDPWrapOffsetFinder_$arch.exe"
|
|
Copy-Item $dllSrc ".\Zydis_$arch.dll"
|
|
}
|
|
|
|
Write-Host "Built finder tools (version $version):"
|
|
Get-Item .\RDPWrapOffsetFinder_*.exe, .\Zydis_*.dll | Format-Table Name, Length
|
|
|
|
echo "finder_ver=$version" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Upload OffsetFinder binaries
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: offsetfinder-binaries
|
|
path: |
|
|
RDPWrapOffsetFinder_x64.exe
|
|
RDPWrapOffsetFinder_x86.exe
|
|
Zydis_x64.dll
|
|
Zydis_x86.dll
|
|
if-no-files-found: error
|
|
|
|
|
|
# ── Job 3: Download rdpWrapper GUI from sergiye — parallel to build-dll ────
|
|
# No job dependencies — starts immediately.
|
|
download-sergiye:
|
|
name: Download sergiye rdpWrapper
|
|
runs-on: windows-2022
|
|
outputs:
|
|
wrapper_ver: ${{ steps.wrapper.outputs.wrapper_ver }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Download rdpWrapper (sergiye)
|
|
id: wrapper
|
|
shell: pwsh
|
|
run: |
|
|
$apiUrl = "https://api.github.com/repos/sergiye/rdpWrapper/releases/latest"
|
|
$release = Invoke-RestMethod -Uri $apiUrl -Headers @{ "User-Agent" = "rdpwrap-ci" }
|
|
|
|
foreach ($arch in @('x64', 'x86')) {
|
|
$asset = $release.assets |
|
|
Where-Object { $_.name -eq "rdpWrapper_$arch.exe" } |
|
|
Select-Object -First 1
|
|
if (-not $asset) {
|
|
throw "Could not find rdpWrapper_$arch.exe in sergiye release $($release.tag_name)"
|
|
}
|
|
Write-Host "Downloading $($asset.browser_download_url)"
|
|
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile ".\rdpWrapper_$arch.exe"
|
|
}
|
|
|
|
Write-Host "Wrapper exes:"
|
|
Get-Item .\rdpWrapper_x64.exe, .\rdpWrapper_x86.exe | Format-Table Name, Length
|
|
echo "wrapper_ver=$($release.tag_name)" >> $env:GITHUB_OUTPUT
|
|
|
|
# ── SHA-256 audit log for third-party binaries ──────────────────────────
|
|
# Records the hash of every downloaded sergiye binary in the workflow log.
|
|
# This provides a tamper-evident paper trail without blocking the release.
|
|
- name: Audit SHA-256 of downloaded binaries
|
|
shell: pwsh
|
|
run: |
|
|
$files = Get-Item .\rdpWrapper_x64.exe, .\rdpWrapper_x86.exe
|
|
Write-Host "SHA-256 audit (third-party downloads from sergiye/rdpWrapper):"
|
|
foreach ($f in $files) {
|
|
$hash = (Get-FileHash $f.FullName -Algorithm SHA256).Hash
|
|
Write-Host " $($f.Name) $hash"
|
|
}
|
|
|
|
# ── Verify hashes against pinned values in tools/sergiye-hashes.json ────
|
|
# If the file contains a non-empty 'release' key, hashes must match exactly.
|
|
# If 'release' is empty this step is skipped (bootstrap scenario).
|
|
- name: Verify sergiye binary hashes
|
|
shell: pwsh
|
|
run: |
|
|
$pinFile = 'tools/sergiye-hashes.json'
|
|
$pin = Get-Content $pinFile -Raw | ConvertFrom-Json
|
|
if ([string]::IsNullOrEmpty($pin.release)) {
|
|
Write-Host "Hash pinning not yet configured (tools/sergiye-hashes.json 'release' is empty) — skipping."
|
|
Write-Host "To enable pinning: download the binaries, compute hashes with Get-FileHash, and update $pinFile."
|
|
} else {
|
|
Write-Host "Verifying against pinned release: $($pin.release)"
|
|
$pass = $true
|
|
foreach ($name in @('rdpWrapper_x64.exe', 'rdpWrapper_x86.exe')) {
|
|
$expected = $pin.$name
|
|
$actual = (Get-FileHash ".\$name" -Algorithm SHA256).Hash
|
|
if ($actual -ne $expected) {
|
|
Write-Error "Hash mismatch for $name!"
|
|
Write-Error " Expected: $expected"
|
|
Write-Error " Actual: $actual"
|
|
$pass = $false
|
|
} else {
|
|
Write-Host " $name OK ($actual)"
|
|
}
|
|
}
|
|
if (-not $pass) { throw "One or more sergiye binary hashes did not match the pinned values." }
|
|
}
|
|
|
|
- name: Upload sergiye wrappers
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: sergiye-wrappers
|
|
path: |
|
|
rdpWrapper_x64.exe
|
|
rdpWrapper_x86.exe
|
|
if-no-files-found: error
|
|
|
|
|
|
# ── Job 4: Build C# tools (FD + SC) — waits for build-dll ────────────────
|
|
# Waits for build-dll so the DLLs can be embedded into single-file EXEs.
|
|
build-csharp:
|
|
name: Build C# Tools
|
|
runs-on: windows-2022
|
|
needs: [build-dll]
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Download rdpwrap DLLs
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: rdpwrap-dlls
|
|
path: .
|
|
|
|
# Stage embedded resources for RDPWInst offline/bundled mode.
|
|
# RDPWInst.csproj embeds these files when present (Condition="Exists(...)").
|
|
# Copying them here ensures the single-file EXE can install without network.
|
|
- name: Stage RDPWInst embedded resources
|
|
shell: pwsh
|
|
run: |
|
|
$res = 'src-csharp/RDPWInst/Resources'
|
|
New-Item -ItemType Directory -Path $res -Force | Out-Null
|
|
Copy-Item .\rdpwrap_x86.dll "$res\rdpw32.dll" -Force
|
|
Copy-Item .\rdpwrap_x64.dll "$res\rdpw64.dll" -Force
|
|
Copy-Item msi\rdpwrap.ini "$res\rdpwrap.ini" -Force
|
|
Copy-Item LICENSE "$res\license.txt" -Force
|
|
Write-Host "Staged resources:"
|
|
Get-ChildItem $res | Format-Table Name, Length
|
|
|
|
- name: Setup .NET SDK
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '10.x'
|
|
|
|
- name: Cache NuGet packages
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.nuget/packages
|
|
key: nuget-${{ runner.os }}-${{ hashFiles('**/Directory.Build.props', '**/*.csproj') }}
|
|
restore-keys: |
|
|
nuget-${{ runner.os }}-
|
|
|
|
# Compute version once so it's consistent across all publish and sign steps
|
|
- name: Get build version
|
|
id: build_ver
|
|
shell: pwsh
|
|
run: |
|
|
$v = Get-Date -Format "yyyy.M.d"
|
|
echo "version=$v" >> $env:GITHUB_OUTPUT
|
|
Write-Host "Build version: $v"
|
|
|
|
# ── Build framework-dependent variants ──────────────────────────────────
|
|
- name: Publish C# tools (x64, x86, arm64)
|
|
shell: pwsh
|
|
env:
|
|
DOTNET_NOLOGO: true
|
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
|
run: |
|
|
$projects = @('RDPWInst', 'RDPConf', 'RDPCheck')
|
|
$rids = [ordered]@{ x64 = 'win-x64'; x86 = 'win-x86'; arm64 = 'win-arm64' }
|
|
$ver = '${{ steps.build_ver.outputs.version }}'
|
|
foreach ($proj in $projects) {
|
|
foreach ($plat in $rids.Keys) {
|
|
$rid = $rids[$plat]
|
|
dotnet publish "src-csharp/$proj/$proj.csproj" `
|
|
-c Release `
|
|
-r $rid `
|
|
-p:Platform=$plat `
|
|
-p:PublishSingleFile=true `
|
|
-p:SelfContained=false `
|
|
-p:Version=$ver `
|
|
-p:AssemblyVersion="$ver.0" `
|
|
-p:FileVersion="$ver.0" `
|
|
--output "dist_cs\$plat\$proj"
|
|
Copy-Item "dist_cs\$plat\$proj\$proj.exe" ".\${proj}_${plat}.exe"
|
|
}
|
|
}
|
|
Write-Host "C# tool executables:"
|
|
Get-Item .\RDPWInst_*.exe, .\RDPConf_*.exe, .\RDPCheck_*.exe | Format-Table Name, Length
|
|
|
|
# ── Build self-contained variants (no .NET runtime required) ─────────────
|
|
# Self-contained exes are larger (~60 MB) but work without a .NET install.
|
|
# Suffixed with _sc to distinguish from the framework-dependent versions.
|
|
- name: Publish self-contained C# tools (x64, x86, arm64)
|
|
shell: pwsh
|
|
env:
|
|
DOTNET_NOLOGO: true
|
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
|
run: |
|
|
$projects = @('RDPWInst', 'RDPConf', 'RDPCheck')
|
|
$rids = [ordered]@{ x64 = 'win-x64'; x86 = 'win-x86'; arm64 = 'win-arm64' }
|
|
$ver = '${{ steps.build_ver.outputs.version }}'
|
|
foreach ($proj in $projects) {
|
|
foreach ($plat in $rids.Keys) {
|
|
$rid = $rids[$plat]
|
|
dotnet publish "src-csharp/$proj/$proj.csproj" `
|
|
-c Release `
|
|
-r $rid `
|
|
-p:Platform=$plat `
|
|
-p:PublishSingleFile=true `
|
|
-p:SelfContained=true `
|
|
-p:Version=$ver `
|
|
-p:AssemblyVersion="$ver.0" `
|
|
-p:FileVersion="$ver.0" `
|
|
--output "dist_cs_sc\$plat\$proj"
|
|
Copy-Item "dist_cs_sc\$plat\$proj\$proj.exe" ".\${proj}_${plat}_sc.exe"
|
|
}
|
|
}
|
|
Write-Host "Self-contained executables:"
|
|
Get-Item .\RDPWInst_*_sc.exe, .\RDPConf_*_sc.exe, .\RDPCheck_*_sc.exe | Format-Table Name, Length
|
|
|
|
# ── Optional: code-sign all C# executables ──────────────────────────────
|
|
# Enable by setting repository variable USE_CERT_SIGNING=true and adding
|
|
# secrets CODESIGN_CERT_BASE64 (PFX as base64) and CODESIGN_CERT_PASSWORD.
|
|
- name: Sign C# executables
|
|
if: vars.USE_CERT_SIGNING == 'true'
|
|
env:
|
|
CODESIGN_CERT_BASE64: ${{ secrets.CODESIGN_CERT_BASE64 }}
|
|
CODESIGN_CERT_PASSWORD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
|
|
shell: pwsh
|
|
run: |
|
|
$pfxPath = "$env:RUNNER_TEMP\codesign.pfx"
|
|
[IO.File]::WriteAllBytes($pfxPath,
|
|
[Convert]::FromBase64String($env:CODESIGN_CERT_BASE64))
|
|
$signtool = (Resolve-Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" |
|
|
Sort-Object | Select-Object -Last 1).Path
|
|
# Sign both framework-dependent and self-contained variants (glob picks up all _*.exe files)
|
|
$exes = Get-Item .\RDPWInst_*.exe, .\RDPConf_*.exe, .\RDPCheck_*.exe
|
|
foreach ($exe in $exes) {
|
|
& $signtool sign /fd SHA256 /f $pfxPath `
|
|
/p $env:CODESIGN_CERT_PASSWORD `
|
|
/tr http://timestamp.digicert.com /td SHA256 $exe.FullName
|
|
}
|
|
Remove-Item $pfxPath -Force
|
|
|
|
- name: Upload C# tools
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: csharp-tools
|
|
path: |
|
|
RDPWInst_*.exe
|
|
RDPConf_*.exe
|
|
RDPCheck_*.exe
|
|
if-no-files-found: error
|
|
|
|
|
|
# ── Job 5: Build MSI packages — waits for build-csharp (implies build-dll) ─
|
|
build-msi:
|
|
name: Build MSI Packages
|
|
runs-on: windows-2022
|
|
needs: [build-csharp]
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Download rdpwrap DLLs
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: rdpwrap-dlls
|
|
path: .
|
|
|
|
- name: Download C# tools
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: csharp-tools
|
|
path: .
|
|
|
|
- name: Setup .NET SDK
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '10.x'
|
|
|
|
- name: Cache NuGet packages (WiX restore)
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.nuget/packages
|
|
key: nuget-${{ runner.os }}-${{ hashFiles('msi/global.json', 'msi/*.wixproj') }}
|
|
restore-keys: |
|
|
nuget-${{ runner.os }}-
|
|
|
|
# Get version (same formula as build-csharp for filename consistency)
|
|
- name: Get build version
|
|
id: build_ver
|
|
shell: pwsh
|
|
run: |
|
|
$v = Get-Date -Format "yyyy.M.d"
|
|
echo "version=$v" >> $env:GITHUB_OUTPUT
|
|
Write-Host "Build version: $v"
|
|
|
|
# ── Build MSI installers (WiX v5, x64 / x86 / arm64) ──────────────────
|
|
- name: Build MSI packages
|
|
shell: pwsh
|
|
run: |
|
|
$ver = '${{ steps.build_ver.outputs.version }}'
|
|
foreach ($arch in @('x64', 'x86', 'arm64')) {
|
|
# Stage arch-specific inputs next to the .wixproj
|
|
Copy-Item ".\rdpwrap_$arch.dll" "msi\rdpwrap_$arch.dll" -Force
|
|
Copy-Item ".\RDPWInst_$arch.exe" "msi\RDPWInst_$arch.exe" -Force
|
|
Copy-Item ".\RDPConf_$arch.exe" "msi\RDPConf_$arch.exe" -Force
|
|
Copy-Item ".\RDPCheck_$arch.exe" "msi\RDPCheck_$arch.exe" -Force
|
|
|
|
dotnet build msi/RDPWInst.wixproj `
|
|
-c Release `
|
|
/p:Platform=$arch `
|
|
/p:OutputName="RDPWrapper-$arch" `
|
|
/p:PackageVersion=$ver `
|
|
/p:OutputPath="$PWD/msi_out/$arch"
|
|
if ($LASTEXITCODE -ne 0) { throw "WiX build failed for $arch" }
|
|
|
|
$msiSrc = Get-Item "msi_out\$arch\RDPWrapper-$arch.msi" -ErrorAction SilentlyContinue
|
|
if (-not $msiSrc) {
|
|
$msiSrc = Get-ChildItem "msi\bin" -Recurse -Filter "RDPWrapper-$arch.msi" |
|
|
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
|
}
|
|
if (-not $msiSrc) { throw "Could not locate RDPWrapper-$arch.msi after WiX build" }
|
|
Copy-Item $msiSrc.FullName ".\RDPWrapper-${ver}-$arch.msi"
|
|
Write-Host "Produced: RDPWrapper-${ver}-$arch.msi ($([math]::Round($msiSrc.Length/1MB,1)) MB)"
|
|
}
|
|
Write-Host "MSI packages:"
|
|
Get-Item .\RDPWrapper-*-*.msi | Format-Table Name, Length
|
|
|
|
# ── Optional: sign the MSI packages ────────────────────────────────────
|
|
# Enable by setting repository variable USE_CERT_SIGNING=true and adding
|
|
# secrets CODESIGN_CERT_BASE64 (PFX as base64) and CODESIGN_CERT_PASSWORD.
|
|
- name: Sign MSI packages
|
|
if: vars.USE_CERT_SIGNING == 'true'
|
|
env:
|
|
CODESIGN_CERT_BASE64: ${{ secrets.CODESIGN_CERT_BASE64 }}
|
|
CODESIGN_CERT_PASSWORD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
|
|
shell: pwsh
|
|
run: |
|
|
$pfxPath = "$env:RUNNER_TEMP\codesign.pfx"
|
|
[IO.File]::WriteAllBytes($pfxPath,
|
|
[Convert]::FromBase64String($env:CODESIGN_CERT_BASE64))
|
|
$signtool = (Resolve-Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe" |
|
|
Sort-Object | Select-Object -Last 1).Path
|
|
foreach ($msi in (Get-Item .\RDPWrapper-*-*.msi)) {
|
|
& $signtool sign /fd SHA256 /f $pfxPath `
|
|
/p $env:CODESIGN_CERT_PASSWORD `
|
|
/tr http://timestamp.digicert.com /td SHA256 `
|
|
$msi.FullName
|
|
}
|
|
Remove-Item $pfxPath -Force
|
|
|
|
- name: Upload MSI packages
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: msi-packages
|
|
path: RDPWrapper-*-*.msi
|
|
if-no-files-found: error
|
|
|
|
|
|
# ── Job 6: Publish GitHub Release — waits for all build jobs ──────────────
|
|
release:
|
|
name: Publish Release
|
|
runs-on: windows-2022
|
|
needs: [build-dll, build-csharp, build-offsetfinder, download-sergiye, build-msi]
|
|
# Attach to the 'release' GitHub Environment so repository admins can add
|
|
# required-reviewer gates in Settings → Environments → release.
|
|
# If the environment doesn't exist yet it will be created without gates.
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Download rdpwrap DLLs
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: rdpwrap-dlls
|
|
path: .
|
|
|
|
- name: Download C# tools
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: csharp-tools
|
|
path: .
|
|
|
|
- name: Download OffsetFinder binaries
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: offsetfinder-binaries
|
|
path: .
|
|
|
|
- name: Download sergiye wrappers
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: sergiye-wrappers
|
|
path: .
|
|
|
|
- name: Download MSI packages
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: msi-packages
|
|
path: .
|
|
|
|
- name: Verify all release assets are present
|
|
shell: pwsh
|
|
run: |
|
|
$required = @(
|
|
'rdpwrap_x64.dll', 'rdpwrap_x86.dll', 'rdpwrap_arm64.dll',
|
|
'RDPWInst_x64.exe', 'RDPWInst_x86.exe', 'RDPWInst_arm64.exe',
|
|
'RDPConf_x64.exe', 'RDPConf_x86.exe', 'RDPConf_arm64.exe',
|
|
'RDPCheck_x64.exe', 'RDPCheck_x86.exe', 'RDPCheck_arm64.exe',
|
|
'RDPWrapOffsetFinder_x64.exe', 'RDPWrapOffsetFinder_x86.exe',
|
|
'Zydis_x64.dll', 'Zydis_x86.dll',
|
|
'rdpWrapper_x64.exe', 'rdpWrapper_x86.exe'
|
|
)
|
|
$missing = $required | Where-Object { -not (Test-Path $_) }
|
|
if ($missing) {
|
|
throw "Missing required release assets: $($missing -join ', ')"
|
|
}
|
|
Write-Host "All required assets present."
|
|
Get-ChildItem *.exe, *.dll, *.msi | Format-Table Name, Length
|
|
|
|
# ── Extract the date string used for tagging and the updated field ───────
|
|
- name: Get release metadata
|
|
id: meta
|
|
shell: pwsh
|
|
run: |
|
|
$date = Get-Date -Format "yyyy.MM.dd.HHmm"
|
|
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm UTC"
|
|
|
|
# Pull the Updated= line out of the INI to surface in release notes
|
|
$iniContent = Get-Content msi/rdpwrap.ini -Raw
|
|
$iniDate = if ($iniContent -match 'Updated=([^\r\n]+)') { $Matches[1] } else { 'unknown' }
|
|
|
|
$inputTag = '${{ inputs.tag }}'.Trim()
|
|
$tagName = if ($inputTag -ne '') { $inputTag } else { "ini-$date" }
|
|
$outputName = if ($inputTag -ne '') { "Release $inputTag" } else { "INI Update $date" }
|
|
|
|
echo "date=$date" >> $env:GITHUB_OUTPUT
|
|
echo "stamp=$stamp" >> $env:GITHUB_OUTPUT
|
|
echo "inidate=$iniDate" >> $env:GITHUB_OUTPUT
|
|
echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT
|
|
echo "release_name=$outputName" >> $env:GITHUB_OUTPUT
|
|
|
|
# ── Validate the INI has required sections and mandatory per-version keys ─
|
|
- name: Validate INI
|
|
shell: pwsh
|
|
run: |
|
|
$ini = Get-Content msi/rdpwrap.ini -Raw
|
|
$lines = Get-Content msi/rdpwrap.ini
|
|
|
|
# 1) Check mandatory top-level sections exist
|
|
foreach ($section in @('[Main]', '[SLPolicy]', '[PatchCodes]')) {
|
|
if ($ini -notmatch [regex]::Escape($section)) {
|
|
throw "INI validation failed: missing required section $section"
|
|
}
|
|
}
|
|
|
|
# 2) Parse every Windows-version section and verify it contains the
|
|
# mandatory keys LocalOnlyPatch and SLInitHook.
|
|
$sectionKeys = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.List[string]]]::new()
|
|
$currentSection = $null
|
|
|
|
foreach ($line in $lines) {
|
|
if ($line -match '^\[(\d[\d\.]+)\]') {
|
|
$currentSection = $Matches[1]
|
|
$sectionKeys[$currentSection] = [System.Collections.Generic.List[string]]::new()
|
|
} elseif ($null -ne $currentSection -and $line -match '^([A-Za-z][A-Za-z0-9_]*)[\s]*=') {
|
|
$sectionKeys[$currentSection].Add($Matches[1])
|
|
}
|
|
}
|
|
|
|
$mandatoryKeys = @('LocalOnlyPatch', 'SLInitHook')
|
|
$errors = [System.Collections.Generic.List[string]]::new()
|
|
|
|
foreach ($sec in $sectionKeys.Keys) {
|
|
foreach ($key in $mandatoryKeys) {
|
|
if (-not $sectionKeys[$sec].Contains($key)) {
|
|
$errors.Add(" Section [$sec] is missing mandatory key: $key")
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($errors.Count -gt 0) {
|
|
Write-Error "INI mandatory-key check failed ($($errors.Count) issue(s)):"
|
|
$errors | ForEach-Object { Write-Error $_ }
|
|
throw "INI validation failed — see errors above."
|
|
}
|
|
|
|
$sectionCount = $sectionKeys.Count
|
|
Write-Host "INI is valid. Windows-version sections: $sectionCount (all contain LocalOnlyPatch + SLInitHook)"
|
|
|
|
# ── Auto-generate changelog from merged PRs since last release ───────────
|
|
- name: Generate changelog
|
|
id: changelog
|
|
shell: pwsh
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
# Get the tag of the previous release so we can scope the PR list
|
|
$prevTag = (gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>$null) ?? ''
|
|
if ($prevTag) {
|
|
Write-Host "Generating changelog since: $prevTag"
|
|
$since = gh release view $prevTag --json publishedAt --jq .publishedAt
|
|
$prs = gh pr list --state merged --base main --search "merged:>=$since" `
|
|
--json number,title,author --limit 50 | ConvertFrom-Json
|
|
} else {
|
|
Write-Host "No previous release found; skipping per-PR changelog."
|
|
$prs = @()
|
|
}
|
|
if ($prs.Count -gt 0) {
|
|
$lines = $prs | ForEach-Object { "- #$($_.number) $($_.title) (@$($_.author.login))" }
|
|
$body = $lines -join "`n"
|
|
} else {
|
|
$body = "_No merged pull requests found since the previous release._"
|
|
}
|
|
# Write multi-line output safely
|
|
$delim = [guid]::NewGuid().ToString('N')
|
|
echo "prs<<$delim" >> $env:GITHUB_OUTPUT
|
|
echo $body >> $env:GITHUB_OUTPUT
|
|
echo $delim >> $env:GITHUB_OUTPUT
|
|
|
|
# ── Assemble the three user-facing distribution bundles ──────────────────
|
|
- name: Create distribution bundles
|
|
shell: pwsh
|
|
run: |
|
|
# RDPWrapper.zip - complete install package (all arches, framework-dependent)
|
|
$d = ".\bundle_wrapper"
|
|
New-Item -ItemType Directory -Path $d -Force | Out-Null
|
|
Copy-Item .\rdpwrap_x64.dll "$d\rdpwrap_x64.dll"
|
|
Copy-Item .\rdpwrap_x86.dll "$d\rdpwrap_x86.dll"
|
|
Copy-Item .\rdpwrap_arm64.dll "$d\rdpwrap_arm64.dll"
|
|
Copy-Item .\rdpWrapper_x64.exe "$d\rdpWrapper_x64.exe"
|
|
Copy-Item .\rdpWrapper_x86.exe "$d\rdpWrapper_x86.exe"
|
|
# Framework-dependent C# tools (require .NET 10 Desktop Runtime)
|
|
foreach ($proj in @('RDPWInst', 'RDPConf', 'RDPCheck')) {
|
|
foreach ($arch in @('x64', 'x86', 'arm64')) {
|
|
$src = ".\${proj}_${arch}.exe"
|
|
if (Test-Path $src) { Copy-Item $src "$d\${proj}_${arch}.exe" }
|
|
}
|
|
}
|
|
Copy-Item msi\rdpwrap.ini "$d\rdpwrap.ini"
|
|
Compress-Archive -Path "$d\*" -DestinationPath ".\RDPWrapper.zip" -Force
|
|
Remove-Item $d -Recurse -Force
|
|
|
|
# RDPWrapper-SelfContained.zip - apps bundled with .NET runtime (~60 MB each)
|
|
$sc = ".\bundle_sc"
|
|
New-Item -ItemType Directory -Path $sc -Force | Out-Null
|
|
foreach ($proj in @('RDPWInst', 'RDPConf', 'RDPCheck')) {
|
|
foreach ($arch in @('x64', 'x86', 'arm64')) {
|
|
$src = ".\${proj}_${arch}_sc.exe"
|
|
if (Test-Path $src) { Copy-Item $src "$sc\${proj}_${arch}_sc.exe" }
|
|
}
|
|
}
|
|
Copy-Item msi\rdpwrap.ini "$sc\rdpwrap.ini"
|
|
Compress-Archive -Path "$sc\*" -DestinationPath ".\RDPWrapper-SelfContained.zip" -Force
|
|
Remove-Item $sc -Recurse -Force
|
|
|
|
# RDPWrapOffsetFinder.zip - offset finder with per-arch subfolders
|
|
$f = ".\bundle_finder"
|
|
foreach ($arch in @('x64', 'x86')) {
|
|
$dir = "$f\$arch"
|
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
|
Copy-Item ".\RDPWrapOffsetFinder_$arch.exe" "$dir\RDPWrapOffsetFinder.exe"
|
|
Copy-Item ".\Zydis_$arch.dll" "$dir\Zydis.dll"
|
|
}
|
|
Compress-Archive -Path "$f\*" -DestinationPath ".\RDPWrapOffsetFinder.zip" -Force
|
|
Remove-Item $f -Recurse -Force
|
|
|
|
Write-Host "Distribution bundles:"
|
|
Get-Item .\RDPWrapper.zip, .\RDPWrapper-SelfContained.zip, .\RDPWrapOffsetFinder.zip | Format-Table Name, Length
|
|
|
|
# ── Create/update a versioned GitHub Release with all assets ─────────────
|
|
- name: Publish release
|
|
# Pinned to v2 tag SHA for supply-chain safety.
|
|
# Bump this SHA when upgrading: https://github.com/softprops/action-gh-release/releases
|
|
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
|
with:
|
|
tag_name: "${{ steps.meta.outputs.tag_name }}"
|
|
name: "${{ steps.meta.outputs.release_name }}"
|
|
prerelease: ${{ inputs.prerelease || false }}
|
|
make_latest: true
|
|
body: |
|
|
## RDP Wrapper - Automated Release
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| INI `Updated` date | `${{ steps.meta.outputs.inidate }}` |
|
|
| Published | ${{ steps.meta.outputs.stamp }} |
|
|
| rdpWrapper (sergiye) | ${{ needs.download-sergiye.outputs.wrapper_ver }} |
|
|
| RDPWrapOffsetFinder (llccd) | ${{ needs.build-offsetfinder.outputs.finder_ver }} |
|
|
|
|
> **Framework-dependent tools** (default) require **.NET 10 Desktop Runtime** on the target machine.
|
|
> Download: https://dotnet.microsoft.com/download/dotnet/10.0
|
|
> For environments without a runtime install, use the `*_sc.exe` files from `RDPWrapper-SelfContained.zip`.
|
|
|
|
### Downloads
|
|
|
|
| File | Contents | Use |
|
|
|---|---|---|
|
|
| `RDPWrapper.zip` | rdpwrap DLLs, C# tools (x64/x86/arm64), rdpWrapper GUI, rdpwrap.ini | **Main install package** — extract and run `RDPWInst_x64.exe -i -o` (or matching arch) |
|
|
| `RDPWrapper-SelfContained.zip` | Same C# tools bundled with .NET runtime (no separate runtime install needed) | Use when .NET 10 Desktop Runtime cannot be deployed |
|
|
| `RDPWrapper-<ver>-x64.msi` / `x86` / `arm64` | Windows Installer packages — GUI-friendly, self-registering uninstall | Preferred for managed/enterprise deployments |
|
|
| `RDPWrapOffsetFinder.zip` | RDPWrapOffsetFinder.exe + Zydis.dll for x64 and x86 | Generate offsets for an unknown termsrv.dll version manually |
|
|
|
|
### Merged pull requests
|
|
|
|
${{ steps.changelog.outputs.prs }}
|
|
|
|
### Individual loose files (used by the automated installer)
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `rdpwrap.ini` | Offset database fetched automatically by the installer |
|
|
| `RDPWrapOffsetFinder_x64.exe` / `Zydis_x64.dll` | Loose x64 binaries downloaded by `TryAutoGenerateOffsets` |
|
|
| `RDPWrapOffsetFinder_x86.exe` / `Zydis_x86.dll` | Loose x86 binaries downloaded by `TryAutoGenerateOffsets` |
|
|
files: |
|
|
RDPWrapper.zip
|
|
RDPWrapper-SelfContained.zip
|
|
RDPWrapper-*-x64.msi
|
|
RDPWrapper-*-x86.msi
|
|
RDPWrapper-*-arm64.msi
|
|
RDPWrapOffsetFinder.zip
|
|
msi/rdpwrap.ini
|
|
msi/rdpwrap-arm-kb.ini
|
|
rdpwrap_x64.dll
|
|
rdpwrap_x86.dll
|
|
rdpwrap_arm64.dll
|
|
RDPWInst_x64.exe
|
|
RDPWInst_x86.exe
|
|
RDPWInst_arm64.exe
|
|
RDPConf_x64.exe
|
|
RDPConf_x86.exe
|
|
RDPConf_arm64.exe
|
|
RDPCheck_x64.exe
|
|
RDPCheck_x86.exe
|
|
RDPCheck_arm64.exe
|
|
RDPWrapOffsetFinder_x64.exe
|
|
RDPWrapOffsetFinder_x86.exe
|
|
Zydis_x64.dll
|
|
Zydis_x86.dll
|