Skip to content

Commit

Permalink
feat(pwsh): initial devolutions gateway updater tool (#472)
Browse files Browse the repository at this point in the history
Co-authored-by: Marc-André Moreau <[email protected]>
  • Loading branch information
awakecoding and Marc-André Moreau authored Jun 16, 2023
1 parent 91e2e50 commit d1f5e20
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 0 deletions.
255 changes: 255 additions & 0 deletions tools/updater/GatewayUpdater.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@

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 }
}
55 changes: 55 additions & 0 deletions tools/updater/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Devolutions Gateway Updater

The Devolutions Gateway Updater is a tool to automatically check, download and install new versions of Devolutions Gateway, making it easier to keep a large number of Devolutions Gateway servers up-to-date without manual work. It is a simple, adaptable PowerShell script meant to be registered as a scheduled task that runs once a day.

## Installing

Open an elevated PowerShell terminal, move to the directory containing the GatewayUpdater.ps1 script, and then run it with the 'install' parameter:

```powershell
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

You can wait for the scheduled task to run automatically at 3AM, or manually trigger it to see if it works:

```powershell
& schtasks.exe /Run /TN "Devolutions Gateway Updater"
```

You can then query the status of the 'Devolutions Gateway Updater' scheduled task:

```powershell
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

To uninstall the Devolutions Gateway Updater, run the GatewayUpdater.ps1 script with the 'uninstall' parameter:

```powershell
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'

0 comments on commit d1f5e20

Please sign in to comment.