Writing
Deploying RustDesk with Microsoft Intune
How I turned an MSI installation into a configured, detectable, and operationally safer Win32 application
Remotely installing an application is relatively straightforward. Deploying it consistently, configuring it for different Windows execution contexts, validating the result, and allowing Microsoft Intune to detect the final state correctly is a broader engineering problem.
That was the challenge I encountered while preparing RustDesk for distribution to managed Windows endpoints.
RustDesk would be used as a remote-support client connected to self-hosted infrastructure. Sending the MSI to computers was therefore not enough. Each endpoint also needed the correct HBBS, HBBR, and API addresses, the server public key, configuration for service and user contexts, operational logs, and a reliable Intune detection rule.
To solve this, I created RustDeskIntuneDeployment, a PowerShell-based package for installing, configuring, detecting, and removing RustDesk through Microsoft Intune Win32 applications.
The source code is available on GitHub:
The problem
A manual RustDesk installation can be configured through the application interface. That model does not scale in a managed environment.
Devices need to receive the same approved configuration without local intervention. Installation is also normally performed by the Intune Management Extension as SYSTEM, while the interactive client and Windows service may read configuration from different contexts.
This introduced several design questions:
- How should the MSI be installed silently and its return codes handled?
- Where should configuration be written so that the service, system processes, and users can find it?
- How should existing users and users who sign in after deployment both be supported?
- How can Intune avoid reporting success when only the executable exists but configuration failed?
- What evidence is required to troubleshoot failed installations?
- How can the package avoid embedding dangerous secrets?
The objective was no longer simply to “install RustDesk.” It became:
Deliver a configured and verifiable state, not just execute an installer.
Package architecture
The project contains three primary scripts:
| Script | Responsibility |
|---|---|
install.ps1 | Installs the MSI, distributes configuration, validates files, installs or starts the service, writes logs, and creates a completion marker. |
detect.ps1 | Confirms that the executable exists, a machine-level configuration contains the expected server, and the marker exists. |
uninstall.ps1 | Locates the RustDesk MSI product and requests a silent uninstall. |
The deployment flow can be summarized as follows:
Administrator
│
├── trusted MSI
├── PowerShell scripts
└── configuration values
│
▼
.intunewin package
│
▼
Microsoft Intune
│
▼
Intune Management Extension
│
▼
Windows endpoint
├── RustDesk application
├── Windows service
├── RustDesk2.toml files
├── installation logs
└── detection marker
The RustDesk server infrastructure remains outside the package scope. The project configures clients to connect to approved services, but it does not deploy or manage HBBS, HBBR, the API, the web console, or server-side access controls.
Installing the MSI silently
The installer locates the first *.msi file in the source directory and invokes Windows Installer with silent parameters.
$MsiArgs = @(
"/i"
"`"$($Msi.FullName)`""
"/qn"
"/norestart"
"INSTALLFOLDER=`"$InstallFolder`""
"CREATEDESKTOPSHORTCUTS=`"N`""
"INSTALLPRINTER=`"N`""
"/l*v"
"`"$MsiLog`""
)
The script accepts exit codes 0 and 3010. The latter indicates a successful installation that may require a restart.
Any other code stops execution. This prevents the workflow from continuing into configuration when the MSI was not installed correctly.
There is also an operational implication: because the installer selects the first MSI it finds, the source directory should contain only the intended installer. Before packaging, the publisher signature, version, SHA-256 hash, provenance, and redistribution terms should be reviewed.
Distributing configuration across multiple contexts
A central part of the project was recognizing that a single file under one user profile would not cover every runtime scenario.
The installer generates RustDesk2.toml with the server values and writes it to several locations:
- the
LocalServiceprofile; - the system profile used by
SYSTEMprocesses; C:\ProgramData\RustDesk\config;- the Windows default-user profile;
- existing user profiles discovered through the Registry.
Existing profiles are discovered under:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
The script expands the registered profile paths and excludes shared or template profiles such as Public, Default, and All Users.
This duplication is deliberate. The service and interactive client may read configuration in different contexts, and behavior can vary by version and installation method.
The default profile supports users who have not signed in yet. Existing profiles receive a direct copy during deployment.
Validating the written configuration
The script does not assume an operation succeeded merely because it did not throw an exception.
After writing the files, it checks each destination for the expected hostname:
if (Select-String -Path $ConfigFile -SimpleMatch $ExpectedHost -Quiet) {
$ValidConfigs.Add($ConfigFile)
}
Installation fails if no file contains the configured server.
This validation is simple but important. Without it, Intune could receive a success code even if files were empty, written incorrectly, or contained an unexpected configuration.
Installing and verifying the service
After locating the executable, the script checks whether the RustDesk service exists.
When required, it attempts to install the service with:
& $RustDeskExe --install-service
It then starts the service and reads its properties through CIM.
The installation reaches its final stage only after the service can be confirmed. The package therefore does not treat executable presence alone as sufficient evidence of a functional deployment.
Logs and the completion marker
The project separates two types of logs:
- a PowerShell transcript covering orchestration and configuration decisions;
- a verbose Windows Installer log.
With an organization name such as orgname, the paths resemble:
C:\ProgramData\orgname\IntuneLogs\RustDesk-Intune-Install.log
C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\RustDesk-msi-install.log
At the end of the workflow, the installer creates a marker at:
C:\ProgramData\orgname\IntuneMarkers\RustDesk-Configured.marker
The marker records information including the date, server, executable path, detected version, and validated configuration files.
It is removed at the beginning of a reinstall or repair. This reduces the risk that a failed deployment leaves stale evidence and produces a detection false positive.
The marker is not cryptographic attestation. It is operational evidence that the custom installation workflow reached its final stage.
Microsoft Intune detection
The custom detection script requires three conditions:
- a RustDesk executable in a recognized path;
- at least one machine-level configuration containing the expected server;
- the completion marker.
if ($ExeExists -and $ConfigOk -and (Test-Path $MarkerFile)) {
exit 0
}
exit 1
This is more reliable than checking only for an executable or MSI product.
However, the rule is designed to confirm package state, not complete remote-support health.
It does not currently validate:
- whether the service is running;
- every configured port;
- the configured public key;
- every user-profile copy;
- connectivity to HBBS, HBBR, or the API;
- successful remote sessions;
- support-operator authorization.
That distinction matters. An Intune detection rule should not be confused with availability monitoring or end-to-end security validation.
Recommended Intune configuration
The source directory is packaged with the Microsoft Win32 Content Prep Tool and uploaded as a Win32 application.
The recommended commands are:
Install:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File install.ps1
Uninstall:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File uninstall.ps1
The main settings include:
| Field | Value |
|---|---|
| Install behavior | System |
| Architecture | 64-bit |
| Restart behavior | No specific action |
| Detection rule | Custom script: detect.ps1 |
| Run detection as 32-bit | No |
Rollout should begin with a small, representative pilot group. After validation, assignments can progress to support and IT users, selected departments, and finally broader deployment.
Security model
A remote-support client has significant endpoint impact. I therefore treated the package as privileged automation.
The main controls include:
- executing only an MSI obtained from a trusted source;
- validating signature, version, and hash before packaging;
- reviewing addresses and ports before distribution;
- including only the server public key;
- never including the RustDesk server private key;
- restricting and monitoring access to remote-support infrastructure;
- using deployment rings;
- protecting and sanitizing logs and markers before publication;
- separating deployment authorization from authorization to initiate remote sessions.
The installer includes an optional permanent-password block, but it is disabled by default.
A shared plaintext password embedded in a package distributed to every endpoint would create a broadly exposed credential that is difficult to rotate. A production design should use unique, centrally governed credentials or a dedicated secret-provisioning mechanism.
Known limitations
Documenting limitations is part of the project.
The current uninstall script queries Win32_Product to locate the MSI. This class can trigger Windows Installer consistency checks and is not ideal for larger environments.
A future version should locate the product through Registry uninstall keys and explicitly validate the msiexec return code.
The current removal flow also focuses on the MSI product. It does not automatically delete every copied configuration file, log, marker, or server-side RustDesk record.
There is no continuous reconciliation of the complete configuration either. The project is a managed application package, not a permanent compliance agent. Detection may trigger reinstall when its checked conditions no longer exist, but it does not continuously compare every value in RustDesk2.toml.
What I learned
This project reinforced that endpoint deployment is a problem of state, context, and trust.
An installer can return success while the service remains absent. The executable can exist while pointing to the wrong infrastructure. Configuration can work for one user but not under SYSTEM. Intune can report success with a superficial detection rule even when the package’s real objective was not achieved.
The solution was to decompose deployment into verifiable stages:
- validate the source artifact;
- install silently;
- locate the actual executable;
- write configuration to the required contexts;
- verify the written content;
- confirm the service;
- produce logs;
- create final evidence;
- detect the minimum expected conditions.
More importantly, the project demonstrates that remote-support automation must be designed alongside security controls. Distributing the client is only one part of the solution. Identity, authorization, auditing, secret protection, server infrastructure, and operator governance remain separate responsibilities.
Next steps
The main planned improvements include:
- replacing
Win32_Productwith Registry-based discovery; - validating the uninstall exit code;
- making detection more granular for ports and the public key;
- adding automated tests for configuration generation and validation;
- defining an explicit cleanup or retention policy for logs and markers;
- evaluating a remediation model for configuration drift;
- publishing sanitized evidence from a pilot deployment.
The current project already provides a reusable foundation for turning RustDesk into a managed Win32 application with silent installation, multi-context configuration, operational logging, and custom detection.
Source code
The complete implementation, architecture documentation, configuration guidance, and deployment procedure are available on GitHub: