-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pwsh): initial devolutions gateway updater tool (#472)
Co-authored-by: Marc-André Moreau <[email protected]>
- Loading branch information
1 parent
91e2e50
commit d1f5e20
Showing
2 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |