<# MyTechie RustDesk Auto-Installer Version: 2.4.0 RustDesk: 1.4.9 Purpose: Install and configure RustDesk for MyTechie remote support. #> [CmdletBinding()] param( [switch]$Elevated, [switch]$VerifyOnly, [switch]$SelfTest ) Set-StrictMode -Version 2.0 $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" $InstallerVersion = "2.4.0" $RustDeskVersion = "1.4.9" $ScriptUrl = "https://mytechie.com.au/remote/windows.ps1" $SupportUrl = "https://mytechie.com.au/remote/" $RustDeskConfig = "0nI9MnVR9Uc2I1dGVWNQRUZn1UU3Q0cQ1UV6NVUoljQkdUTWV0YZVWdhlnYShjI6ISeltmIsIyNxETMyoTdh5SbvNmLllGajVGd51mLlR3btVmciojI5FGblJnIsIiNxETMyoTdh5SbvNmLllGajVGd51mLlR3btVmciojI0N3boJye" $Assets = @{ AMD64 = @{ File = "rustdesk-1.4.9-x86_64.exe" Sha256 = "eaedeb0088e687bf46f7c46a9c6ea5493ce51f3134dfd6acbedb47b5b9136274" } ARM64 = @{ File = "rustdesk-1.4.9-aarch64.exe" Sha256 = "c717bf52fdd601c58419e46c503176dd87187d174ebf8b3b5854ca381e8e9145" } } function Write-Step { param([int]$Number, [string]$Message) Write-Host ("[{0}/5] {1}" -f $Number, $Message) -ForegroundColor Cyan } function Stop-Setup { param([string]$Code, [string]$Message) Write-Host "" Write-Host "Setup failed ($Code)." -ForegroundColor Red Write-Host $Message -ForegroundColor Red Write-Host "Leave this window open and contact your MyTechie technician: $SupportUrl" -ForegroundColor Yellow exit 1 } function Test-Administrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Get-NativeArchitecture { try { $architecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } catch { $architecture = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE } } switch -Regex ($architecture.ToUpperInvariant()) { "^(AMD64|X64)$" { return "AMD64" } "^(ARM64)$" { return "ARM64" } default { Stop-Setup "WIN-ARCH" "Unsupported Windows architecture: $architecture" } } } function New-SupportPassword { $alphabet = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789" $bytes = New-Object byte[] 16 $rng = [Security.Cryptography.RandomNumberGenerator]::Create() try { $rng.GetBytes($bytes) } finally { $rng.Dispose() } return -join ($bytes | ForEach-Object { $alphabet[[int]$_ % $alphabet.Length] }) } function Get-FileSha256 { param([string]$Path) $getFileHash = Get-Command Get-FileHash -ErrorAction SilentlyContinue if ($getFileHash) { return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() } $stream = [IO.File]::OpenRead($Path) $sha256 = [Security.Cryptography.SHA256]::Create() try { $digest = $sha256.ComputeHash($stream) return -join ($digest | ForEach-Object { $_.ToString("x2") }) } finally { $sha256.Dispose() $stream.Dispose() } } function Test-RustDeskRegistryEntry { param([object]$Entry) $displayName = $Entry.PSObject.Properties["DisplayName"] $installLocation = $Entry.PSObject.Properties["InstallLocation"] return ( $null -ne $displayName -and $null -ne $installLocation -and [string]$displayName.Value -like "RustDesk*" -and -not [string]::IsNullOrWhiteSpace([string]$installLocation.Value) ) } function Find-RustDeskExecutable { $candidates = @( (Join-Path $env:ProgramFiles "RustDesk\rustdesk.exe"), $(if (${env:ProgramFiles(x86)}) { Join-Path ${env:ProgramFiles(x86)} "RustDesk\rustdesk.exe" }) ) $registryRoots = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" ) foreach ($root in $registryRoots) { $entry = Get-ItemProperty $root -ErrorAction SilentlyContinue | Where-Object { Test-RustDeskRegistryEntry -Entry $_ } | Select-Object -First 1 if ($entry) { $installLocation = $entry.PSObject.Properties["InstallLocation"].Value $candidates += Join-Path $installLocation "rustdesk.exe" } } foreach ($candidate in $candidates) { if ($candidate -and (Test-Path -LiteralPath $candidate)) { return (Resolve-Path -LiteralPath $candidate).Path } } return $null } function Download-VerifiedInstaller { param( [hashtable]$Asset, [string]$Destination ) $url = "https://github.com/rustdesk/rustdesk/releases/download/$RustDeskVersion/$($Asset.File)" for ($attempt = 1; $attempt -le 3; $attempt++) { try { Invoke-WebRequest -Uri $url -OutFile $Destination -UseBasicParsing -Headers @{ "User-Agent" = "MyTechie-RustDesk-Installer/$InstallerVersion" } break } catch { if ($attempt -eq 3) { Stop-Setup "WIN-DOWNLOAD" "RustDesk could not be downloaded after three attempts." } Write-Host "Download attempt $attempt failed. Retrying..." -ForegroundColor Yellow Start-Sleep -Seconds 3 } } $actualHash = Get-FileSha256 -Path $Destination if ($actualHash -ne $Asset.Sha256) { Remove-Item -LiteralPath $Destination -Force -ErrorAction SilentlyContinue Stop-Setup "WIN-INTEGRITY" "The RustDesk download did not match the approved SHA-256 digest." } Write-Host "Verified RustDesk $RustDeskVersion ($($Asset.File))." -ForegroundColor Green } if ($SelfTest) { $missingProperties = [pscustomobject]@{ Publisher = "Example" } $validEntry = [pscustomobject]@{ DisplayName = "RustDesk" InstallLocation = "C:\Program Files\RustDesk" } $emptyLocation = [pscustomobject]@{ DisplayName = "RustDesk" InstallLocation = " " } $unrelatedEntry = [pscustomobject]@{ DisplayName = "Example Application" InstallLocation = "C:\Program Files\Example" } if (Test-RustDeskRegistryEntry -Entry $missingProperties) { Write-Error "Registry self-test accepted an entry without DisplayName." exit 1 } if (-not (Test-RustDeskRegistryEntry -Entry $validEntry)) { Write-Error "Registry self-test rejected a valid RustDesk entry." exit 1 } if (Test-RustDeskRegistryEntry -Entry $emptyLocation) { Write-Error "Registry self-test accepted an empty install location." exit 1 } if (Test-RustDeskRegistryEntry -Entry $unrelatedEntry) { Write-Error "Registry self-test accepted an unrelated application." exit 1 } Write-Host "Windows registry compatibility self-test passed." exit 0 } Write-Host "" Write-Host "=== MyTechie Remote Support Installer v$InstallerVersion ===" -ForegroundColor Cyan Write-Host "" if (-not $VerifyOnly -and -not (Test-Administrator)) { if ($Elevated) { Stop-Setup "WIN-ELEVATION" "Administrator access was not granted." } Write-Host "Windows will ask for administrator permission." -ForegroundColor Yellow $bootstrap = Join-Path ([IO.Path]::GetTempPath()) ("mytechie-rustdesk-{0}.ps1" -f [Guid]::NewGuid()) try { Invoke-WebRequest -Uri $ScriptUrl -OutFile $bootstrap -UseBasicParsing -Headers @{ "User-Agent" = "MyTechie-RustDesk-Bootstrap/$InstallerVersion" } $arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$bootstrap`" -Elevated" $process = Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments -Wait -PassThru exit $process.ExitCode } catch { Stop-Setup "WIN-ELEVATION" "The administrator setup window could not be opened." } finally { Remove-Item -LiteralPath $bootstrap -Force -ErrorAction SilentlyContinue } } $architecture = Get-NativeArchitecture $asset = $Assets[$architecture] $tempDirectory = Join-Path ([IO.Path]::GetTempPath()) ("mytechie-rustdesk-{0}" -f [Guid]::NewGuid()) $installerPath = Join-Path $tempDirectory $asset.File try { if ($VerifyOnly) { Write-Step 1 "Checking the approved RustDesk package" New-Item -ItemType Directory -Path $tempDirectory -Force | Out-Null Download-VerifiedInstaller -Asset $asset -Destination $installerPath Write-Host "Verification complete. No software was installed." -ForegroundColor Green exit 0 } Write-Step 1 "Checking this Windows device" Write-Host "Architecture: $architecture" $rustDeskExe = Find-RustDeskExecutable if ($rustDeskExe) { Write-Host "RustDesk is already installed. The existing app will be configured." } else { Write-Step 2 "Downloading and verifying RustDesk $RustDeskVersion" New-Item -ItemType Directory -Path $tempDirectory -Force | Out-Null Download-VerifiedInstaller -Asset $asset -Destination $installerPath Write-Host "Installing RustDesk..." $installProcess = Start-Process -FilePath $installerPath -ArgumentList "--silent-install" -Wait -PassThru if ($installProcess.ExitCode -ne 0) { Stop-Setup "WIN-INSTALL" "RustDesk returned installer exit code $($installProcess.ExitCode)." } for ($attempt = 1; $attempt -le 20; $attempt++) { $rustDeskExe = Find-RustDeskExecutable if ($rustDeskExe) { break } Start-Sleep -Seconds 2 } if (-not $rustDeskExe) { Stop-Setup "WIN-INSTALL" "RustDesk finished installing, but rustdesk.exe could not be found." } } Write-Step 3 "Configuring the MyTechie relay and support password" $supportPassword = New-SupportPassword $configOutput = (& $rustDeskExe --config $RustDeskConfig 2>&1 | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or $configOutput -match "required|failed|disabled") { Stop-Setup "WIN-CONFIG" "RustDesk rejected the MyTechie server configuration." } $passwordOutput = (& $rustDeskExe --password $supportPassword 2>&1 | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or $passwordOutput -match "required|failed|disabled") { Stop-Setup "WIN-PASSWORD" "RustDesk could not set the support access password." } Write-Step 4 "Starting the RustDesk service" $service = Get-Service -Name "rustdesk" -ErrorAction SilentlyContinue if (-not $service) { $serviceOutput = (& $rustDeskExe --install-service 2>&1 | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or $serviceOutput -match "failed") { Stop-Setup "WIN-SERVICE" "The RustDesk Windows service could not be installed." } Start-Sleep -Seconds 2 $service = Get-Service -Name "rustdesk" -ErrorAction SilentlyContinue } if (-not $service) { Stop-Setup "WIN-SERVICE" "The RustDesk Windows service is missing after installation." } if ($service.Status -eq "Running") { Restart-Service -Name "rustdesk" -Force -ErrorAction Stop } else { Start-Service -Name "rustdesk" -ErrorAction Stop } Write-Step 5 "Waiting for this device's RustDesk ID" $rustDeskId = "" for ($attempt = 1; $attempt -le 15; $attempt++) { $idOutput = (& $rustDeskExe --get-id 2>$null | Out-String).Trim() $match = [regex]::Match($idOutput, "\b\d{6,}\b") if ($match.Success) { $rustDeskId = $match.Value break } Write-Host "Waiting for registration... ($attempt/15)" Start-Sleep -Seconds 2 } Start-Process -FilePath $rustDeskExe if (-not $rustDeskId) { Stop-Setup "WIN-ID" "RustDesk is installed, but its ID is not ready. Open RustDesk from the Start menu and tell your technician the ID shown there." } Write-Host "" Write-Host "=========================================" -ForegroundColor Green Write-Host " MyTechie Remote Support Setup Complete" -ForegroundColor Green Write-Host "=========================================" -ForegroundColor Green Write-Host "" Write-Host "RustDesk ID: $rustDeskId" -ForegroundColor White Write-Host "Access password: $supportPassword" -ForegroundColor White Write-Host "" Write-Host "Share these details only with your MyTechie technician." -ForegroundColor Cyan Write-Host "This password remains active until it is changed, the installer is rerun, or RustDesk is removed." -ForegroundColor Yellow Write-Host "" if ([Environment]::UserInteractive) { [void](Read-Host "Press Enter after you have shared the ID and password") } } finally { if (Test-Path -LiteralPath $tempDirectory) { Remove-Item -LiteralPath $tempDirectory -Recurse -Force -ErrorAction SilentlyContinue } }