# author: https://github.com/bradhawkins85 # modified: added RustDesk (ScoutIT-RemoteSupport) silent install with service verification and ID output $ErrorActionPreference = 'SilentlyContinue' $innosetup = 'tacticalagent-v2.10.0-windows-amd64.exe' $api = '"https://apirmm.scoutitsystems.com"' $clientid = '1' $siteid = '1' $agenttype = '"workstation"' $power = 0 $rdp = 0 $ping = 0 $auth = '"f77cac29dc421abacbfc1882c2700b655f7a4bcf6500aaec040208dc2a5bdbff"' $downloadlink = 'https://agents.tacticalrmm.com/api/v2/agents/?version=2.10.0&arch=amd64&token=f3c1cbee-7d48-4f2a-8f0a-7cc33bb42802&plat=windows&api=apirmm.scoutitsystems.com' $apilink = $downloadlink.split('/') # ── RustDesk / ScoutIT Remote Support config ────────────────────────────────── $rustdeskMsi = 'estudio-scoutit-remotesupport.msi' $rustdeskDl = 'https://scoutit.help/estudio-rust' $rustdeskInstallDir = 'C:\Program Files\ScoutIT-RemoteSupport' $rustdeskService = 'ScoutIT-RemoteSupport' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # ── Helper: Install RustDesk ────────────────────────────────────────────────── function Install-RustDesk { $msiPath = "$env:SystemRoot\Temp\$rustdeskMsi" # Download Write-Host "Downloading ScoutIT Remote Support client..." Try { Invoke-WebRequest -Uri $rustdeskDl -OutFile $msiPath -UseBasicParsing } Catch { Write-Warning "RustDesk download failed: $($_.Exception.Message)" return } # Verify the downloaded file is a valid MSI (OLE compound doc magic bytes: D0 CF 11 E0) $fileSize = (Get-Item $msiPath).Length Write-Host "Downloaded file size: $fileSize bytes" $magic = [System.IO.File]::ReadAllBytes($msiPath) | Select-Object -First 4 $magicHex = ($magic | ForEach-Object { $_.ToString('X2') }) -join ' ' Write-Host "File header bytes: $magicHex" if ($magicHex -ne 'D0 CF 11 E0') { Write-Warning "Downloaded file is not a valid MSI (expected D0 CF 11 E0, got $magicHex). Check the download URL." Remove-Item $msiPath -Force -ErrorAction SilentlyContinue return } Write-Host "File verified as valid MSI." # Silent MSI install $msiLog = "$env:SystemRoot\Temp\estudio-scoutit-remotesupport-install.log" Write-Host "Installing ScoutIT Remote Support client silently (log: $msiLog)..." Try { Start-Process -FilePath "msiexec.exe" ` -ArgumentList "/i `"$msiPath`" /qn /norestart /l*v `"$msiLog`" CREATEDESKTOPSHORTCUTS=`"N`" INSTALLPRINTER=`"N`"" ` -Wait Start-Sleep -Seconds 10 } Catch { Write-Warning "RustDesk MSI install failed: $($_.Exception.Message)" return } Finally { if (Test-Path $msiPath) { Remove-Item -Path $msiPath -Force } } # Locate the installed exe # First try: registry InstallLocation from the MSI uninstall entry $uninstallKeys = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) $rustdeskExe = $null foreach ($key in $uninstallKeys) { $entry = Get-ItemProperty $key -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'scoutit|remotesupport|rustdesk' -or $_.Publisher -match 'scoutit|rustdesk' } | Select-Object -First 1 if ($entry -and $entry.InstallLocation) { $candidate = Get-ChildItem $entry.InstallLocation -Filter '*.exe' -ErrorAction SilentlyContinue | Select-Object -First 1 if ($candidate) { $rustdeskExe = $candidate.FullName Write-Host "Found exe via registry: $rustdeskExe" break } } } # Second try: search only within directories whose name matches expected install folder if (-not $rustdeskExe) { Write-Host "Searching for RustDesk exe in matching install directories..." $found = Get-ChildItem 'C:\Program Files', 'C:\Program Files (x86)' -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'rustdesk|scoutit|remotesupport' } | ForEach-Object { Get-ChildItem $_.FullName -Filter '*.exe' -ErrorAction SilentlyContinue } | Select-Object -First 1 if ($found) { $rustdeskExe = $found.FullName Write-Host "Found exe at: $rustdeskExe" } } # Last resort: list what the MSI actually installed so we can identify the exe if (-not $rustdeskExe) { Write-Warning "Could not locate RustDesk exe. Listing Program Files for diagnosis:" Get-ChildItem 'C:\Program Files', 'C:\Program Files (x86)' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-5) } | ForEach-Object { Write-Host " $($_.FullName)" } Write-Warning "Could not locate RustDesk exe. Skipping service and ID steps. Check MSI log: $msiLog" return } $exeDir = Split-Path $rustdeskExe # Install service if not present $svc = Get-Service -Name $rustdeskService -ErrorAction SilentlyContinue if ($null -eq $svc) { Write-Host "Registering ScoutIT Remote Support service..." Start-Process -FilePath $rustdeskExe -ArgumentList '--install-service' -Wait Start-Sleep -Seconds 20 $svc = Get-Service -Name $rustdeskService -ErrorAction SilentlyContinue } # Ensure service is running if ($null -ne $svc) { $attempts = 0 while ($svc.Status -ne 'Running' -and $attempts -lt 5) { Write-Host "Starting ScoutIT Remote Support service (attempt $($attempts + 1))..." Start-Service -Name $rustdeskService -ErrorAction SilentlyContinue Start-Sleep -Seconds 5 $svc.Refresh() $attempts++ } if ($svc.Status -eq 'Running') { Write-Host "ScoutIT Remote Support service is running." } else { Write-Warning "ScoutIT Remote Support service did not reach Running state." } } else { Write-Warning "Service '$rustdeskService' not found after install." } # Get and output the RustDesk ID (visible in RMM job logs) Push-Location $exeDir $rustdeskId = & $rustdeskExe --get-id 2>&1 Pop-Location Write-Output "................................................." Write-Output "ScoutIT Remote Support ID: $rustdeskId" Write-Output "................................................." } # ── Tactical RMM ───────────────────────────────────────────────────────────── $serviceName = 'tacticalrmm' If (Get-Service $serviceName -ErrorAction SilentlyContinue) { Write-Host "Tactical RMM Is Already Installed" } Else { $OutPath = $env:TMP $output = $innosetup $installArgs = @('-m install --api ', "$api", '--client-id', $clientid, '--site-id', $siteid, '--agent-type', "$agenttype", '--auth', "$auth") if ($power) { $installArgs += "--power" } if ($rdp) { $installArgs += "--rdp" } if ($ping) { $installArgs += "--ping" } Try { $DefenderStatus = Get-MpComputerStatus | Select-Object AntivirusEnabled if ($DefenderStatus -match "True") { Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*' Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*' Add-MpPreference -ExclusionPath 'C:\ProgramData\TacticalRMM\*' } } Catch { # pass } $X = 0 do { Write-Output "Waiting for network" Start-Sleep -s 5 $X += 1 } until (($connectresult = Test-NetConnection $apilink[2] -Port 443 | Where-Object { $_.TcpTestSucceeded }) -or $X -eq 3) if ($connectresult.TcpTestSucceeded -eq $true) { Try { Invoke-WebRequest -Uri $downloadlink -OutFile "$OutPath\$output" Start-Process -FilePath "$OutPath\$output" -ArgumentList '/VERYSILENT /SUPPRESSMSGBOXES' -Wait Write-Host "Extracting..." Start-Sleep -s 5 Start-Process -FilePath "C:\Program Files\TacticalAgent\tacticalrmm.exe" -ArgumentList $installArgs -Wait # ── Install RustDesk after Tactical RMM ── Install-RustDesk exit 0 } Catch { $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName Write-Error -Message "$ErrorMessage $FailedItem" exit 1 } Finally { if (Test-Path "$OutPath\$output") { Remove-Item -Path "$OutPath\$output" -Force } } } else { Write-Output "Unable to connect to server" } }