Skip to content

feat: add Windows TermService protocol provider - #2381

Merged
Marc-André Moreau (mamoreau-devolutions) merged 13 commits into
masterfrom
ironrdp-termsrv
Nov 9, 2026
Merged

Marc-André Moreau (mamoreau-devolutions) merged 13 commits into
masterfrom
ironrdp-termsrv

Conversation

@mamoreau-devolutions

@mamoreau-devolutions Marc-André Moreau (mamoreau-devolutions) commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Add a side-by-side WTS protocol provider and companion service to connect IronRDP clients to authenticated Windows desktops.

Provide an indirect display driver, native desktop capture and input, clipboard redirection, and native device and dynamic-channel bridges. Include experimental RemoteApp support, authenticated control IPC, and Windows deployment, signing, and diagnostics tools. Place the server and an unsupported MSI packaging prototype under server/termsrv, apart from the reusable crates.

Co-authored-by: Cursor cursoragent@cursor.com
Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

@mamoreau-devolutions

Marc-André Moreau (mamoreau-devolutions) commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Current PR change size against the current merge base: Rust runtime/source +25,596/-0 (including inline tests); dedicated Rust tests +707/-0; Pester tests +2,478/-0; other files +11,910/-0, including manifests, lockfile, documentation, deployment/build scripts, and MSI tooling. The added WdfDriverStubUm.lib binary is excluded from line counts.

Properties marked UNTESTED::

  • IDD native updates must finish or abort during stop without waiting for parent cleanup (server/termsrv/crates/ironrdp-idd/src/remote.rs:238).
  • Cursor capture must use the assigned interactive desktop (server/termsrv/crates/ironrdp-termsrv/src/cursor.rs:130).
  • Native premultiplied BGRA is preserved; translucent RDP rendering remains unverified (cursor.rs:370).
  • Repeated cursor capture must release copied bitmaps/DCs without destroying the shared cursor (cursor.rs:477).
  • Concurrent users must not share clipboard state; distinct logged-on users are needed to verify isolation (server/termsrv/crates/ironrdp-termsrv/src/main.rs:5013).
  • Private GUID compatibility requires native shell startup checks across Windows upgrades (server/termsrv/crates/ironrdp-wtsprotocol-provider/src/wts_com.rs:284).
  • Close must mark the connection closed before clearing its callback slot, preventing concurrent registration from restoring it; both lock orders need checking (wts_com.rs:2125).
  • Forced TermService recovery must recheck the dedicated host before killing its PID; SCM/PID races need target validation (server/termsrv/scripts/TestVm.psm1:216).
  • PSRemoting reconnect after reboot and target-side IDD loading are not exercised locally (server/termsrv/crates/ironrdp-termsrv/scripts/deploy-testvm-psremoting.ps1:333).
  • Provider registration and the companion-owned listener need target verification before the remote deployment order changes (deploy-testvm-psremoting.ps1:353).
  • The PSRemoting finally path that restores TermService is not covered by the local restoration tests (deploy-testvm-psremoting.ps1:793).
  • Successful E2E collection sessions must stay alive through IDD and bitmap downloads; every FromSession transfer needs a recheck when the loop changes (server/termsrv/crates/ironrdp-termsrv/scripts/e2e-test-screenshot.ps1:1318).
  • RunEx must use the selected session on the target Windows build (server/termsrv/crates/ironrdp-termsrv/scripts/capture-irdp-session-screenshot.ps1:312).
  • The provider must connect to the restarted companion; native control-pipe readiness and listener recovery need checks when startup order changes (server/termsrv/crates/ironrdp-idd/scripts/restart-rds-services.ps1:102).
  • PSDirect must remain available after a native service failure (server/termsrv/crates/ironrdp-wtsprotocol-provider/scripts/deploy-hyperv-psdirect.ps1:250).

Validation: the 124 Pester tests under server/termsrv/tests pass on PowerShell 7 and Windows PowerShell 5.1. The previous head failed the Linux and macOS lints because the Windows-only provider crate declared dependencies it does not use on those platforms; they are now declared for Windows only. The reworked deployment and E2E scripts have not been run against a live VM yet.

Review fixes in 9cc6b69

The WDF build script now parses the COFF archive and the defined WdfBindInfo symbol, validates the referenced record, and patches only its build field. The D0 test no longer uses a wall-clock pass threshold; the fake initializer records the async request while completion remains withheld. Fixed-input cases now use named mutant markers where no independent oracle defines the behavior.

Local validation passed: cargo fmt, targeted Clippy, targeted Cargo tests, and all four standalone build.rs tests. The five changed test cases passed three consecutive clean runs.

Diff-scoped mutation evidence (~15 minutes):

Test Oracle Fault challenged Result
power_entry_does_not_wait_for_async_adapter_completion mutant Skip async initialization Caught at request assertion
helper_accepts_a_frame_at_the_16_mib_limit mutant Reject the exact maximum Caught at frame read
pointer_timestamps_remain_monotonic_after_a_previous_frame mutant Lower the base timestamp when QPC ties Caught at strict ordering assertion
open_virtual_channel_keeps_the_static_flag mutant Skip serializing/deserializing the static flag Caught at command equality
boundary_dimensions_survive_publication mutant Write height at the wrong offset Caught at readback
patcher_does_not_patch_a_version_shaped_decoy mutant Accept any COFF symbol as WdfBindInfo Caught at error assertion
patcher_changes_the_named_bind_info_build_field_only mutant Patch the adjacent version field Caught at byte equality
patcher_uses_the_x86_bind_info_layout mutant Use the 64-bit x86 record offset Caught at offset assertion
bundled_stub_bind_info_matches_its_wdf_version reference Checked-in WDF stub library Retained as an independent artifact oracle

No bugs or surviving challenged faults were found. cargo-mutants was skipped for the D0 source because candidate discovery yielded no mutants in adapter.rs; the changed tests were instead challenged with targeted domain patches. A full IDD run under the D0 mutant was inconclusive because the expected failure poisoned the shared fixture mutex, so the D0 test was rerun in isolation. No broader mutation scope was run.

Note

Human-tuned, LLM-assisted content.

@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) marked this pull request as ready for review November 6, 2026 14:30
Copilot AI balanced review requested due to automatic review settings November 6, 2026 14:30
@github-actions github-actions Bot added kind/protocol Affects RDP or related protocol behavior kind/technical-debt Internal cleanup work risk/medium Behavioral change that does not substantially alter a core public API size/XXL Size: 1300 or more counted lines or 50 or more files needs-author-action The pull request author is the current next actor labels Nov 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical issues persist plaintext RDP credentials, risk disabling native RDP or terminating unrelated services, and register a companion executable that cannot start as a Windows service.

15 open issues
What changed in this PR

Adds a side-by-side Windows TermService protocol provider and companion process for connecting IronRDP clients to authenticated Windows desktops, with indirect display driver integration and deployment tooling.

Changes:

  • Adds provider lifecycle management, session tracking, experimental RemoteApp authorization, and shared control/display IPC.
  • Introduces indirect display driver support and native pipe and camera components.
  • Provides Windows installation, rollback, signing, diagnostics, connection helpers, and MSI packaging.

This pull request requires changes. Unresolved critical and moderate findings block approval, including credential exposure, unsafe TermService restart handling, and invalid Windows service registration. Review was static; Windows runtime validation was not performed.

File Description
package/​CevicheServiceWindowsManaged/​README.md Documents MSI builds, installation, and first-run setup.
package/​CevicheServiceWindowsManaged/​Program.cs Defines MSI artifacts, service registration, and upgrades.
package/​CevicheServiceWindowsManaged/​Includes.cs Defines installer and service identifiers.
package/​CevicheServiceWindowsManaged/​CevicheServiceWindowsManaged.csproj Defines the WixSharp packaging project.
package/​CevicheServiceWindowsManaged/​build-ceviche-service-msi.ps1 Configures artifacts and invokes MSI packaging.
crates/​ironrdp-wtsprotocol-provider/​src/​session_registry.rs Tracks connection-to-session assignments.
crates/​ironrdp-wtsprotocol-provider/​src/​published_apps.rs Resolves and authorizes published RemoteApp applications.
crates/​ironrdp-wtsprotocol-provider/​src/​manager.rs Creates listeners with shared session tracking.
crates/​ironrdp-wtsprotocol-provider/​src/​listener.rs Creates and identifies protocol connections.
crates/​ironrdp-wtsprotocol-provider/​src/​lib.rs Exposes the Windows provider API.
crates/​ironrdp-wtsprotocol-provider/​src/​connection.rs Implements connection lifecycle transitions and regression tests.
crates/​ironrdp-wtsprotocol-provider/​scripts/​wait-termservice-ready.ps1 Polls for service and socket readiness.
crates/​ironrdp-wtsprotocol-provider/​scripts/​verify-side-by-side.ps1 Verifies listener and COM registration.
crates/​ironrdp-wtsprotocol-provider/​scripts/​uninstall-side-by-side.ps1 Removes provider registration.
crates/​ironrdp-wtsprotocol-provider/​scripts/​smoke-test-side-by-side.ps1 Checks deployment configuration and connectivity prerequisites.
crates/​ironrdp-wtsprotocol-provider/​scripts/​smoke-test-control-pipe-local.ps1 Exercises local control IPC operations.
crates/​ironrdp-wtsprotocol-provider/​scripts/​side-by-side-defaults.ps1 Centralizes listener defaults and transport checks.
crates/​ironrdp-wtsprotocol-provider/​scripts/​restore-side-by-side-state.ps1 Restores saved registry state.
crates/​ironrdp-wtsprotocol-provider/​scripts/​preflight-side-by-side.ps1 Checks host prerequisites and port conflicts.
crates/​ironrdp-wtsprotocol-provider/​scripts/​new-side-by-side-mstsc-file.ps1 Generates an MSTSC connection file.
crates/​ironrdp-wtsprotocol-provider/​scripts/​install-side-by-side.ps1 Registers the provider and manages TermService restarts.
crates/​ironrdp-wtsprotocol-provider/​scripts/​first-run-side-by-side.ps1 Coordinates installation, verification, and rollback.
crates/​ironrdp-wtsprotocol-provider/​scripts/​configure-side-by-side-firewall.ps1 Manages listener firewall rules.
crates/​ironrdp-wtsprotocol-provider/​scripts/​collect-side-by-side-diagnostics.ps1 Collects deployment and service diagnostics.
crates/​ironrdp-wtsprotocol-provider/​scripts/​check-vm-side-by-side-eligibility.ps1 Checks Hyper-V guest rollout prerequisites.
crates/​ironrdp-wtsprotocol-provider/​scripts/​build-provider-dll.ps1 Builds and locates the provider DLL.
crates/​ironrdp-wtsprotocol-provider/​scripts/​backup-side-by-side-state.ps1 Exports registry backups and a manifest.
crates/​ironrdp-wtsprotocol-provider/​README.md Documents provider capabilities and integration limits.
crates/​ironrdp-wtsprotocol-provider/​Cargo.toml Defines the provider crate and Windows dependencies.
crates/​ironrdp-wtsprotocol-ipc/​tests/​display.rs Tests display-request publication and validation.
crates/​ironrdp-wtsprotocol-ipc/​tests/​commands.rs Tests virtual-channel command compatibility.
crates/​ironrdp-wtsprotocol-ipc/​src/​lib.rs Defines control messages and JSON framing helpers.
crates/​ironrdp-wtsprotocol-ipc/​src/​display.rs Defines session-specific display-size records.
crates/​ironrdp-wtsprotocol-ipc/​Cargo.toml Defines shared IPC dependencies.
crates/​ironrdp-termsrv/​src/​native_pipe.rs Implements cancellable overlapped message-pipe I/O.
crates/​ironrdp-termsrv/​src/​native_camera.rs Tracks camera negotiation and device-channel authorization.
crates/​ironrdp-termsrv/​scripts/​connect-msrdpex.ps1 Creates and launches an MsRdpEx connection.
crates/​ironrdp-termsrv/​scripts/​connect-agent.ps1 Launches an agent connection using environment credentials.
crates/​ironrdp-termsrv/​scripts/​confirm-rdp-type10-logon.ps1 Correlates Windows remote-logon and session events.
crates/​ironrdp-termsrv/​Cargo.toml Defines companion runtime dependencies.
crates/​ironrdp-termsrv/​AGENTS.md Documents Windows VM debugging and security precautions.
crates/​ironrdp-idd/​src/​lib.rs Adds driver entry points, callback wiring, and tracing.
crates/​ironrdp-idd/​scripts/​verify-test-signing.ps1 Checks guest test-signing status.
crates/​ironrdp-idd/​scripts/​sign-driver.ps1 Signs and verifies driver package artifacts.
crates/​ironrdp-idd/​scripts/​restart-vm.ps1 Reboots guests and waits for remoting readiness.
crates/​ironrdp-idd/​scripts/​restart-rds-services.ps1 Restarts RDS and companion services.
crates/​ironrdp-idd/​scripts/​install-idd-remote.ps1 Installs the driver and provisions display-directory ACLs.
crates/​ironrdp-idd/​scripts/​get-test-certificates.ps1 Retrieves and caches test-signing certificates.
crates/​ironrdp-idd/​scripts/​find-wdk-tools.ps1 Locates WDK headers, libraries, and signing tools.
crates/​ironrdp-idd/​scripts/​ensure-rdsh-role.ps1 Checks or installs the RDS Session Host role.
crates/​ironrdp-idd/​scripts/​deploy-idd.ps1 Provisions target-only test-signing trust.
crates/​ironrdp-idd/​scripts/​collect-idd-diagnostics.ps1 Collects remote driver diagnostics into an archive.
crates/​ironrdp-idd/​scripts/​check-windows-edition.ps1 Reports guest Windows edition and suitability.
crates/​ironrdp-idd/​README.md Documents driver builds, signing, and deployment.
crates/​ironrdp-idd/​IronRdpIdd.inf Defines UMDF/IddCx driver installation metadata.
crates/​ironrdp-idd/​Cargo.toml Defines driver features and Windows dependencies.
crates/​ironrdp-idd/​.gitignore Excludes generated driver and signing artifacts.
Cargo.lock Locks new crates and dependency versions.
.gitignore Excludes local smoke-test and diagnostic artifacts.

Review effort: Balanced

Comment thread server/termsrv/package/msi/Program.cs Resolved
@github-actions github-actions Bot added needs-author-action The pull request author is the current next actor and removed needs-author-action The pull request author is the current next actor labels Nov 6, 2026
@github-actions github-actions Bot removed the needs-author-action The pull request author is the current next actor label Nov 7, 2026
@mamoreau-devolutions

Copy link
Copy Markdown
Contributor Author

Update in 6116d817 (cleanup af4922b2): production Rust/C# code +447/-752; dedicated tests +601/-126; other files +172/-151 (deployment/build scripts, documentation, package metadata, and ignore rules).

Validation: Linux-target Clippy for ironrdp-termsrv passes with -D warnings; 39 focused Pester tests pass; the affected Rust crate tests, cargo fmt --check, installer Compile target, and git diff --check pass. CI is rerunning on af4922b.

Note

LLM-assisted content (no human feedback).

@github-actions github-actions Bot added the needs-author-action The pull request author is the current next actor label Nov 7, 2026
@github-actions github-actions Bot added risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/cross-cutting Spans multiple architectural boundaries needs-author-action The pull request author is the current next actor and removed needs-author-action The pull request author is the current next actor risk/medium Behavioral change that does not substantially alter a core public API labels Nov 7, 2026
@github-actions github-actions Bot added risk/medium Behavioral change that does not substantially alter a core public API needs-author-action The pull request author is the current next actor and removed risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/cross-cutting Spans multiple architectural boundaries labels Nov 7, 2026
@github-actions github-actions Bot added needs-author-action The pull request author is the current next actor and removed needs-author-action The pull request author is the current next actor labels Nov 8, 2026
@mamoreau-devolutions

Marc-André Moreau (mamoreau-devolutions) commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

File and line breakdown

The current PR diff contains 93 files, with 38,578 additions and no deletions.

Category Files Lines added Breakdown by directory
Production code 26 26,466 crates/ironrdp-idd and src: 9 files, +5,113; crates/ironrdp-termsrv/src: 8, +13,581; crates/ironrdp-wtsprotocol-ipc/src: 2, +369; crates/ironrdp-wtsprotocol-provider/src: 7, +7,403
Tests 21 3,093 crates/ironrdp-idd/src/adapter_tests.rs: +518; crates/ironrdp-wtsprotocol-ipc/tests: 2 files, +222; server/termsrv/tests: 18, +2,353
Other files 46 9,019 Repository root: 3 files, +73; server/termsrv: README, +113; crates/ironrdp-idd docs/config: 3, +175; IDD scripts: 9, +934; crates/ironrdp-termsrv docs/config: 2, +319; TermSrv crate scripts: 12, +5,757; IPC config: 1, +22; provider docs/config: 3, +262; provider scripts: 9, +913; server/termsrv/scripts: 3, +451

“Other files” includes documentation, manifests, and developer scripts.

Note

Human-tuned, LLM-assisted content.

@github-actions github-actions Bot removed the needs-author-action The pull request author is the current next actor label Nov 9, 2026
@github-actions github-actions Bot added the needs-author-action The pull request author is the current next actor label Nov 9, 2026
Add a side-by-side WTS protocol provider and companion service to
connect IronRDP clients to authenticated Windows desktops.

Provide an indirect display driver, native desktop capture and input,
clipboard redirection, and native device and dynamic-channel bridges.
Include experimental RemoteApp support, authenticated control IPC,
and Windows deployment, signing, diagnostics, and MSI packaging tools.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move the experimental Windows TermService server, its four crates and
the MSI prototype under server/termsrv, apart from the reusable IronRDP
crates. They remain workspace members, so the usual checks still cover
them.

Share one script foundation across deployment, E2E and diagnostics:

- Deployment goes through one script that starts the companion before
  TermService, checks that the companion owns the listener, and
  restores the previous TermService lifecycle when it fails.
- A common PowerShell module handles credentials, remoting, readiness
  and lifecycle for the test VM.
- Each E2E observation uses its own agent endpoint and accepts only
  evidence produced by the current run.
- Logoff and reset act only after confirming that the requested account
  owns the session.
- First-run packaging produces a configuration, a guide and an RDP
  file.
- Diagnostics are collected into one bundle that keeps partial results.
- Pester tests cover the scripts.

Mark the MSI service mode unsupported, since the executable does not
implement SCM service hosting.

Declare the provider's dependencies for Windows only. The crate is
empty elsewhere, so its unused dependencies fail the
unused_crate_dependencies lint on Linux and macOS.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve TermService startup state on failures and avoid killing shared service hosts. Keep configured ports consistent through deferred listener startup, and handle optional installer artifacts without stale MSI outputs.

Add regression coverage for lifecycle recovery, readiness ordering, fragmented control-pipe reads, and listener command compatibility. Use a non-macro stderr write in the unsupported-platform stub so non-Windows Clippy passes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep local installer restore and compile outputs out of the repository.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fix Windows-only Clippy failures in the provider and IDD fallback paths.\nKeep test and no-link behavior unchanged while documenting the intentional cfg split.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the integer suffix syntax required by Windows Clippy.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bound ABI size conversions and reject EDID buffers that cannot fit the
 native size field. Keep mutable EDID storage alive during IddCx calls and
 document WDF dispatch safety requirements.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Parse the COFF archive and patch only a defined WdfBindInfo build field.
Reject malformed, ambiguous, or incompatible records instead of guessing at
byte offsets.

Keep test oracles aligned with each fixed-input case. Replace the D0
entry wall-clock threshold with a check that async initialization was
requested while completion was withheld.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve RDP input state, keep TLS keys non-exportable, and require trusted WinRM HTTPS. Centralize IPC framing, remove unsupported packaging and the bundled WDF binary, and simplify provider cleanup and diagnostics.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove superseded provider-only first-run, configuration-package, and
Hyper-V deployment paths, along with thin wrappers and duplicate probes
whose checks are covered by the supported PSRemoting deployment,
installer validation, diagnostics, and end-to-end harness.

Document direct provider registration and agent connection commands so
the remaining setup paths do not depend on removed wrappers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Select the test VM through IRONRDP_TEST_VM_HOSTNAME or an explicit
parameter instead of a fixed hostname, and make the deployment root,
remote IDD path, and Windows Kits root configurable.

Derive build artifact paths from cargo metadata so CARGO_TARGET_DIR and
CARGO_BUILD_TARGET are honored. Pin the server certificate over
authenticated PSRemoting into a temporary known-certificate store, and
allow an offline directory for the signing test certificates.

Check IDD tool prerequisites before deployment and document the
prerequisites and overrides.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep setup and debugging instructions in the TermSrv README instead of agent-specific files.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added needs-author-action The pull request author is the current next actor and removed needs-author-action The pull request author is the current next actor kind/technical-debt Internal cleanup work labels Nov 9, 2026
Use core::iter::from_fn in the keyboard synchronization tests so
they pass the workspace Clippy lint without changing behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot removed the needs-author-action The pull request author is the current next actor label Nov 9, 2026
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit cc63ef7 into master Nov 9, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/2 Two automated reviews completed kind/protocol Affects RDP or related protocol behavior risk/medium Behavioral change that does not substantially alter a core public API size/XXL Size: 1300 or more counted lines or 50 or more files

Development

Successfully merging this pull request may close these issues.

3 participants