0 vote
It would be good if there was a way to update many gateways from one console. Currently you have to update every system on which a gateway is installed and update it manually if the version of the Devolution Server changes. With many gateways, this is of course a considerable investment of time and makes it impractical.
Perhaps it would be possible for you to implement this option on the Devolutions server for the individual gateways?
Hi
Indeed, with the popularity of our Gateway we'll put this at the top of list. Normally such a change would be in the next planned release (October), but we've agreed to add it ASAP. It may not be in our first beta, but it should appear in this 2023.2 cycle.
Best Regards,
Maurice
Hi,
This is indeed a recurring feature request. I am unsure about the best approach at this point - since installing the Devolutions Gateway requires more rights than to simply run it (the service account itself has fewer rights, but the MSI installer requires elevated rights) it would be difficult to make a simple self-update mechanism.
This means we should probably look into a way to either install it with fewer rights (like a user-local installation, like those programs that don't require elevated rights to update) or we'd need to allow remote execution through the Devolutions Gateway with supplied credentials to run a PowerShell command to download the newer MSI, install it, then re-launch the service.
The PowerShell remoting approach would be the most flexible - is that something that could work for you? Maybe this could be built into RDM instead of the console, such that you could leverage your existing connection entries, etc.
I'm throwing a few ideas out there, it still isn't clear in my mind which approach would be the simplest to build quickly
Best regards,
Marc-André Moreau
Hi,
I have written a simple Devolutions Gateway Updater tool as a PowerShell script to be registered as a scheduled task that runs once a day to check for updates + download and install them automatically.
I am including a copy of the instructions and script here, such that we can remember the starting point. Please take a look at it, and tell me if this fits your needs, and what you'd like done differently. I don't expect it to be final, this is just a first (working) version to collect feedback and better align the development of a proper way to update a large number of Devolutions Gateway servers:
Installing Updater
Open an elevated PowerShell terminal, move to the directory containing the GatewayUpdater.ps1 script, and then run it with the 'install' parameter:
PS > .\GatewayUpdater.ps1 install TaskPath TaskName State -------- -------- ----- \ Devolutions Gateway Updater Ready Updater script installed to 'C:\Program Files\Devolutions\Gateway Updater\GatewayUpdater.ps1' and registered as 'Devolutions Gateway Updater' scheduled task
The GatewayUpdater.ps1 script will be copied to "$Env:ProgramFiles\Devolutions\Gateway Updater" and registered as a scheduled task named 'Devolutions Gateway Updater" that runs once per day at 3AM.
Running Updater
You can wait for the scheduled task to run automatically at 3AM, or manually trigger it to see if it works:
& schtasks.exe /Run /TN "Devolutions Gateway Updater"
You can then query the status of the 'Devolutions Gateway Updater' scheduled task:
PS > schtasks.exe /Query /TN "Devolutions Gateway Updater" Folder: \ TaskName Next Run Time Status ======================================== ====================== =============== Devolutions Gateway Updater 2023-06-17 3:00:00 AM Ready
The updater checks if a new version of Devolutions Gateway has been published, and then proceeds to automatically download the installer, check its file hash before running it silently.
Uninstalling Updater
To uninstall the Devolutions Gateway Updater, run the GatewayUpdater.ps1 script with the 'uninstall' parameter:
PS > .\GatewayUpdater.ps1 uninstall Folder: \ TaskName Next Run Time Status ======================================== ====================== =============== Devolutions Gateway Updater 2023-06-17 3:00:00 AM Ready SUCCESS: The scheduled task "Devolutions Gateway Updater" was successfully deleted.
This will unregister the scheduled task, and delete the GatewayUpdater.ps1 script from 'C:\Program Files\Devolutions\Gateway Updater'
GatewayUpdater.ps1 script:
function Install-DGatewayUpdater
{
[CmdletBinding()]
param (
)
$InstallPath = "$Env:ProgramFiles\Devolutions\Gateway Updater"
$ScriptPath = Join-Path $InstallPath "GatewayUpdater.ps1"
New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null
Copy-Item -Path $PSCommandPath -Destination $ScriptPath -Force
Register-DGatewayUpdater -ScriptPath $ScriptPath
$TaskName = "Devolutions Gateway Updater"
Write-Host "Updater script installed to '$ScriptPath' and registered as '$TaskName' scheduled task"
}
function Uninstall-DGatewayUpdater
{
[CmdletBinding()]
param (
)
Unregister-DGatewayUpdater
$InstallPath = "$Env:ProgramFiles\Devolutions\Gateway Updater"
$ScriptPath = Join-Path $InstallPath "GatewayUpdater.ps1"
Remove-Item $ScriptPath -ErrorAction SilentlyContinue -Force | Out-Null
}
function Register-DGatewayUpdater
{
[CmdletBinding()]
param (
[string] $ScriptPath
)
if ([string]::IsNullOrEmpty($ScriptPath)) {
$ScriptPath = $PSCommandPath
}
Unregister-DGatewayUpdater
$TaskName = "Devolutions Gateway Updater"
$TaskUser = "NT AUTHORITY\SYSTEM"
$TaskPrincipal = New-ScheduledTaskPrincipal -UserID $TaskUser -LogonType ServiceAccount -RunLevel Highest
$TaskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`""
$TaskTrigger = New-ScheduledTaskTrigger -Daily -At 3AM
Register-ScheduledTask -TaskName $TaskName -Action $TaskAction -Trigger $TaskTrigger -Principal $TaskPrincipal
}
function Unregister-DGatewayUpdater
{
[CmdletBinding()]
param (
)
$TaskName = "Devolutions Gateway Updater"
& schtasks.exe /Query /TN $TaskName 2>$null
$TaskExists = [bool] ($LASTEXITCODE -eq 0)
if ($TaskExists) {
& schtasks.exe /Delete /TN $TaskName /F
}
}
function Get-DGatewayInstalledVersion
{
[CmdletBinding()]
param(
)
$UninstallReg = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' `
| ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -Match 'Devolutions Gateway' }
if ($UninstallReg) {
$DGatewayVersion = '20' + $UninstallReg.DisplayVersion
}
$DGatewayVersion
}
function Get-DGatewayPackageLocation
{
[CmdletBinding()]
param(
[string] $RequiredVersion
)
$VersionQuad = '';
$ProductsUrl = "https://devolutions.net/productinfo.htm"
$ProductsHtm = Invoke-RestMethod -Uri $ProductsUrl -Method 'GET' -ContentType 'text/plain'
$VersionMatches = $($ProductsHtm | Select-String -AllMatches -Pattern "Gatewaybin.Version=(\S+)").Matches
$LatestVersion = $VersionMatches.Groups[1].Value
if ($RequiredVersion) {
if ($RequiredVersion -Match "^\d+`.\d+`.\d+$") {
$RequiredVersion = $RequiredVersion + ".0"
}
$VersionQuad = $RequiredVersion
} else {
$VersionQuad = $LatestVersion
}
$VersionMatches = $($VersionQuad | Select-String -AllMatches -Pattern "(\d+)`.(\d+)`.(\d+)`.(\d+)").Matches
$VersionMajor = $VersionMatches.Groups[1].Value
$VersionMinor = $VersionMatches.Groups[2].Value
$VersionPatch = $VersionMatches.Groups[3].Value
$VersionTriple = "${VersionMajor}.${VersionMinor}.${VersionPatch}"
$GatewayUrlMatches = $($ProductsHtm | Select-String -AllMatches -Pattern "(Gateway\S+).Url=(\S+)").Matches
$GatewayHashMatches = $($ProductsHtm | Select-String -AllMatches -Pattern "(Gateway\S+).hash=(\S+)").Matches
$GatewayMsiUrl = $GatewayUrlMatches | Where-Object { $_.Groups[1].Value -eq 'Gatewaybin' }
$GatewayMsiHash = $GatewayHashMatches | Where-Object { $_.Groups[1].Value -eq 'Gatewaybin' }
if ($GatewayMsiUrl) {
$DownloadUrl = $GatewayMsiUrl.Groups[2].Value
$DownloadHash = $GatewayMsiHash.Groups[2].Value
}
if ($RequiredVersion) {
$DownloadUrl = $DownloadUrl -Replace $LatestVersion, $RequiredVersion
}
[PSCustomObject]@{
Url = $DownloadUrl
Hash = $DownloadHash
Version = $VersionTriple
}
}
function Get-DGatewayPackageFile
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $DownloadUrl,
[Parameter(Mandatory = $true)]
[string] $DownloadHash,
[string] $DownloadPath
)
$FileName = [System.IO.Path]::GetFileName($DownloadUrl)
if ([string]::IsNullOrEmpty($DownloadPath)) {
$TempPath = [System.IO.Path]::GetTempPath()
$DownloadPath = Join-Path -Path $TempPath -ChildPath $FileName
}
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($DownloadUrl, $DownloadPath)
$FileHash = (Get-FileHash -Path $DownloadPath -Algorithm SHA256).Hash
if ($FileHash -ne $DownloadHash) {
throw "$FileName hash mismatch: Actual: $FileHash, Expected: $DownloadHash"
}
$DownloadPath
}
function Install-DGatewayPackage
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string] $InstallerPath,
[switch] $Quiet,
[switch] $Force
)
$Display = '/passive'
if ($Quiet) {
$Display = '/quiet'
}
$TempPath = Join-Path $([System.IO.Path]::GetTempPath()) "dgateway-${Version}"
New-Item -ItemType Directory -Path $TempPath -ErrorAction SilentlyContinue | Out-Null
$InstallLogFile = Join-Path $TempPath 'DGateway_Install.log'
$MsiArgs = @(
'/i', "`"$InstallerPath`"",
$Display,
'/norestart',
'/log', "`"$InstallLogFile`""
)
Start-Process 'msiexec.exe' -ArgumentList $MsiArgs -Wait -NoNewWindow
Remove-Item -Path $InstallLogFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $TempPath -Force -Recurse
}
function Uninstall-DGatewayPackage
{
[CmdletBinding()]
param(
[switch] $Quiet
)
$UninstallReg = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' `
| ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -Match 'Devolutions Gateway' }
if ($UninstallReg) {
$UninstallString = $($UninstallReg.UninstallString `
-Replace 'msiexec.exe', '' -Replace '/I', '' -Replace '/X', '').Trim()
$Display = '/passive'
if ($Quiet) {
$Display = '/quiet'
}
$MsiArgs = @(
'/X', $UninstallString, $Display
)
Start-Process 'msiexec.exe' -ArgumentList $MsiArgs -Wait
}
}
function Invoke-DGatewayUpdater
{
[CmdletBinding()]
param(
)
$CurrentVersion = Get-DGatewayInstalledVersion
$Package = Get-DGatewayPackageLocation
if ($CurrentVersion -ne $Package.Version) {
$DownloadPath = Get-DGatewayPackageFile -DownloadUrl $Package.Url -DownloadHash $Package.Hash
if ($DownloadPath) {
if ([Version] $Package.Version -lt [Version] $CurrentVersion) {
Uninstall-DGatewayPackage -Quiet
}
Install-DGatewayPackage -InstallerPath $DownloadPath -Quiet
}
}
}
$CmdVerbs = @('run', 'install', 'uninstall', 'register', 'unregister')
if ($args.Count -lt 1) {
$CmdVerb = "run"
$CmdArgs = @()
} else {
$CmdVerb = $args[0]
$CmdArgs = $args[1..$args.Count]
}
if ($CmdVerbs -NotContains $CmdVerb) {
throw "invalid verb $CmdVerb, use one of: [$($CmdVerbs -Join ',')]"
}
switch ($CmdVerb) {
"run" { Invoke-DGatewayUpdater @CmdArgs }
"install" { Install-DGatewayUpdater @CmdArgs }
"uninstall" { Uninstall-DGatewayUpdater @CmdArgs }
"register" { Register-DGatewayUpdater @CmdArgs }
"unregister" { Unregister-DGatewayUpdater @CmdArgs }
}
I hope this helps,
Best regards,
Marc-André Moreau
Thank you Marc-André for producing this code, it is appreciated.
It looks like this would be distributed and run at each gateway host. Am I correct? Or are there plans to run this centrally, which could be a timesaver.
Looking at the current releases out there, we are at 2023.3.16 DSRVR. Do I understand that if we run this updated it would pick up the 2024 code base? Something like 2024.1.3. Would this be a conflict with the 2023.3.16 schema? We have to watch our RDM upgrades so as to not have schema issues, especially between the 2023 and 2024 releases. If so, could we restrict the Devolutions Gateway Updater to certain releases?
With best regards and thank you again for this project.
Phil
Hi,
A cleaned up copy of the same updater script is on the Devolutions Gateway GitHub repository and it is also referred to in our documentation.
Yes, it would need to be installed on each Devolutions Gateway host. I wrote it in a day, as we've had a couple of requests from customers, but I wanted to provide a quick V1 for feedback. I suspected that the true request would be for centralized update management, because it is not a trivial thing to implement properly.
Devolutions Gateway backward and forward compatibility is very good, but you need to use the latest version for the latest features to work, obviously. Devolutions Gateway 2024.1 will definitely work with RDM and DVLS 2023.3. If you intend to just keep it to the very latest version, maybe the current script is good enough.
Implementing a self-updating mechanism is tricky for multiple reasons: Devolutions Gateway is the service that would receive the "update yourself" call, but we can't assume it runs with sufficient rights to launch an installer elevated. I'd have to check if it would be possible to trigger the scheduled task running elevated to do the update process though, and write the desired version to a JSON file to be picked up.
We'd obviously need to do the proper checks to ensure it only downloads Devolutions Gateway installers from our CDN and signed by us, but beyond that, there's the issue of obtaining the installer file. In your case, can we assume Devolutions Gateway has Internet access to the Devolutions CDN? Some customers run it without Internet access, and I'd have to come up with a method for pushing the the installer files if that's something you need.
Anyway, I'm just throwing a couple of ideas out there, maybe this could be a good hackathon project. You're not the first to ask about centralized updates, it just isn't very straightforward to implement properly and especially in a secure manner.
Best regards,
Marc-André Moreau
This would be a great feature.
I think the easiest way would be:
Similar to how windows updates work