Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bbde1dfe2 |
No files matched your search
@@ -3,3 +3,5 @@
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
/tests/*
|
||||||
|
!/tests/test_update_manifests.py
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#requires -Version 5.1
|
#requires -Version 5.1
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Aktualisiert Scoop und alle benutzerspezifischen Pakete; elexis* bleibt gesperrt.
|
Aktualisiert Scoop und alle benutzerspezifischen Pakete; elexis* bleibt gesperrt.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Fuehrt ausschliesslich den Update-Lauf aus. Die Aufgabe wird von update-bucket.ps1
|
Führt ausschliesslich den Update-Lauf aus. Die Aufgabe wird von update-bucket.ps1
|
||||||
eingerichtet und startet dieses Skript verborgen. Der letzte Lauf steht in
|
eingerichtet und startet dieses Skript verborgen. Der letzte Lauf steht in
|
||||||
auto-update.log neben diesem Skript. Globale Pakete werden nicht aktualisiert.
|
auto-update.log neben diesem Skript. Globale Pakete werden nicht aktualisiert.
|
||||||
#>
|
#>
|
||||||
@@ -25,7 +25,7 @@ $exitCode = 0
|
|||||||
try {
|
try {
|
||||||
Start-Transcript -LiteralPath (Join-Path $PSScriptRoot 'auto-update.log') -Force | Out-Null
|
Start-Transcript -LiteralPath (Join-Path $PSScriptRoot 'auto-update.log') -Force | Out-Null
|
||||||
$transcriptStarted = $true
|
$transcriptStarted = $true
|
||||||
# Ein absoluter Scoop-Pfad und die shims im PATH funktionieren auch ohne Profil.
|
# Ein absoluter Scoop-Pfad und die Shims im PATH funktionieren auch ohne Profil.
|
||||||
$env:PATH = (Join-Path $ScoopRoot 'shims') + [IO.Path]::PathSeparator + $env:PATH
|
$env:PATH = (Join-Path $ScoopRoot 'shims') + [IO.Path]::PathSeparator + $env:PATH
|
||||||
$installed = @(& $scoopCommand list)
|
$installed = @(& $scoopCommand list)
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'Installierte Scoop-Pakete konnten nicht ermittelt werden.' }
|
if ($LASTEXITCODE -ne 0) { throw 'Installierte Scoop-Pakete konnten nicht ermittelt werden.' }
|
||||||
@@ -33,7 +33,7 @@ try {
|
|||||||
if ($package.Name -notlike 'elexis*' -or $package.Info -match 'Global install|Install failed') { continue }
|
if ($package.Name -notlike 'elexis*' -or $package.Info -match 'Global install|Install failed') { continue }
|
||||||
& $scoopCommand hold $package.Name
|
& $scoopCommand hold $package.Name
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "Updatesperre fuer '$($package.Name)' fehlgeschlagen. Kein Update ausgefuehrt."
|
throw "Updatesperre für '$($package.Name)' fehlgeschlagen. Kein Update ausgeführt."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& $scoopCommand update
|
& $scoopCommand update
|
||||||
|
|||||||
+113
-19
@@ -1,13 +1,14 @@
|
|||||||
#requires -Version 5.1
|
#requires -Version 5.1
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Fragt nach und richtet optional die taeglichen Scoop-Auto-Updates ein.
|
Fragt nach und richtet optional die täglichen Scoop-Auto-Updates ein.
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Eigenstaendig ausfuehrbar, ohne Bucket-Migration. Bei y wird auto-update.ps1
|
Eigenständig ausführbar, ohne Bucket-Migration. Bei y wird auto-update.ps1
|
||||||
heruntergeladen und eine Aufgabe fuer taeglich 03:00 Uhr erstellt.
|
heruntergeladen und eine Aufgabe für täglich 03:00 Uhr erstellt.
|
||||||
Die Aufgabe startet das Updateskript verborgen und holt verpasste Termine nach,
|
Die Aufgabe startet das Updateskript verborgen und holt verpasste Termine nach,
|
||||||
sobald die Benutzersitzung und eine Netzwerkverbindung verfuegbar sind.
|
sobald die Benutzersitzung und eine Netzwerkverbindung verfügbar sind.
|
||||||
Bei n wird nichts eingerichtet; bestehende Aufgaben bleiben unveraendert.
|
Bestehende Aufgaben können nach Rückfrage repariert oder samt Updateskript
|
||||||
|
entfernt werden. Werden beide Fragen verneint, bleibt alles unverändert.
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\scripts\setup-auto-update.ps1
|
.\scripts\setup-auto-update.ps1
|
||||||
#>
|
#>
|
||||||
@@ -20,9 +21,48 @@ $ErrorActionPreference = 'Stop'
|
|||||||
$ScoopRoot = [IO.Path]::GetFullPath($ScoopRoot)
|
$ScoopRoot = [IO.Path]::GetFullPath($ScoopRoot)
|
||||||
$repository = 'https://gitea.medelexis.ch/public/scoop'
|
$repository = 'https://gitea.medelexis.ch/public/scoop'
|
||||||
|
|
||||||
|
$userId = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||||
|
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||||
|
$taskName = 'Medelexis AG - Scoop AutoUpdate'
|
||||||
|
$legacyTaskName = "Medelexis-Scoop-AutoUpdate-$userSid"
|
||||||
|
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||||
|
$existingTasks = @(
|
||||||
|
foreach ($name in @($taskName, $legacyTaskName)) {
|
||||||
|
Get-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
$autoUpdateRemoved = $false
|
||||||
|
if ($existingTasks.Count -gt 0) {
|
||||||
do {
|
do {
|
||||||
$autoUpdateAnswer = (Read-Host 'Scoop Auto-Update einschalten? [y/n]').Trim().ToLowerInvariant()
|
$autoUpdateAnswer = (Read-Host 'Bestehende Scoop-Auto-Update-Aufgabe reparieren (ersetzen)? [y/n]').Trim().ToLowerInvariant()
|
||||||
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
||||||
|
if ($autoUpdateAnswer -eq 'n') {
|
||||||
|
do {
|
||||||
|
$removeAnswer = (Read-Host 'Scoop-Auto-Update entfernen? [y/n]').Trim().ToLowerInvariant()
|
||||||
|
} while ($removeAnswer -notin @('y', 'n'))
|
||||||
|
if ($removeAnswer -eq 'y') {
|
||||||
|
foreach ($existingTask in $existingTasks) {
|
||||||
|
if ($existingTask.State -eq 'Running') {
|
||||||
|
Stop-ScheduledTask -TaskName $existingTask.TaskName
|
||||||
|
}
|
||||||
|
Unregister-ScheduledTask -TaskName $existingTask.TaskName -Confirm:$false
|
||||||
|
}
|
||||||
|
# Nur die zugehörigen Dateien entfernen, niemals den Ordner rekursiv.
|
||||||
|
foreach ($fileName in @('auto-update.ps1', 'auto-update-hidden.exe', 'auto-update.log')) {
|
||||||
|
$filePath = Join-Path $autoUpdateDirectory $fileName
|
||||||
|
if (Test-Path -LiteralPath $filePath -PathType Leaf) {
|
||||||
|
Remove-Item -LiteralPath $filePath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$autoUpdateRemoved = $true
|
||||||
|
Write-Host 'Scoop-Auto-Update entfernt (Aufgabe und zugehörige Dateien).'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
$autoUpdateAnswer = (Read-Host 'Scoop-Auto-Update einschalten? [y/n]').Trim().ToLowerInvariant()
|
||||||
|
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
||||||
|
}
|
||||||
|
|
||||||
if ($autoUpdateAnswer -eq 'y') {
|
if ($autoUpdateAnswer -eq 'y') {
|
||||||
$scoopCommand = Join-Path $ScoopRoot 'shims\scoop.ps1'
|
$scoopCommand = Join-Path $ScoopRoot 'shims\scoop.ps1'
|
||||||
@@ -41,16 +81,67 @@ if ($autoUpdateAnswer -eq 'y') {
|
|||||||
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
||||||
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
||||||
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
||||||
throw 'Das heruntergeladene Auto-Update-Skript ist kein gueltiges Updateskript.'
|
throw 'Das heruntergeladene Auto-Update-Skript ist kein gültiges Updateskript.'
|
||||||
}
|
}
|
||||||
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
||||||
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
||||||
$userId = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
# Bei einer Reparatur zuerst eine laufende Aufgabe anhalten,
|
||||||
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
# damit ihr Starter ersetzt werden kann.
|
||||||
$taskName = "Medelexis-Scoop-AutoUpdate-$userSid"
|
foreach ($existingTask in $existingTasks) {
|
||||||
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
if ($existingTask.State -eq 'Running') {
|
||||||
$arguments = '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "{0}" -ScoopRoot "{1}"' -f $autoUpdateScript, $ScoopRoot
|
Stop-ScheduledTask -TaskName $existingTask.TaskName
|
||||||
$action = New-ScheduledTaskAction -Execute $powershell -Argument $arguments
|
}
|
||||||
|
}
|
||||||
|
# Ein GUI-Programm startet PowerShell ohne Konsole, auch wenn Windows
|
||||||
|
# Terminal als Standardterminal eingerichtet ist. Es wartet auf das Ende
|
||||||
|
# des Updates und reicht den Exitcode weiter (Fehlerstatus / IgnoreNew).
|
||||||
|
$launcherPath = Join-Path $autoUpdateDirectory 'auto-update-hidden.exe'
|
||||||
|
$launcherSource = @'
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
public static class ScoopAutoUpdateLauncher {
|
||||||
|
[STAThread]
|
||||||
|
public static int Main(string[] args) {
|
||||||
|
if (args.Length != 2) return 2;
|
||||||
|
try {
|
||||||
|
var start = new ProcessStartInfo();
|
||||||
|
start.FileName = System.IO.Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"),
|
||||||
|
@"System32\WindowsPowerShell\v1.0\powershell.exe");
|
||||||
|
start.Arguments = "-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File \"" +
|
||||||
|
args[0] + "\" -ScoopRoot \"" + args[1] + "\"";
|
||||||
|
start.UseShellExecute = false;
|
||||||
|
start.CreateNoWindow = true;
|
||||||
|
start.WindowStyle = ProcessWindowStyle.Hidden;
|
||||||
|
using (var child = Process.Start(start)) {
|
||||||
|
child.WaitForExit();
|
||||||
|
return child.ExitCode;
|
||||||
|
}
|
||||||
|
} catch { return 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
$compiledLauncher = Join-Path $autoUpdateDirectory ('auto-update-{0}.exe' -f [guid]::NewGuid())
|
||||||
|
$launcherSourcePath = [IO.Path]::ChangeExtension($compiledLauncher, '.cs')
|
||||||
|
# Der .NET-Framework-Compiler erzeugt eine Windows-Anwendung auch unter
|
||||||
|
# PowerShell 7, wo Add-Type -OutputType WindowsApplication nicht existiert.
|
||||||
|
$compiler = @(
|
||||||
|
(Join-Path $env:SystemRoot 'Microsoft.NET\Framework64\v4.0.30319\csc.exe'),
|
||||||
|
(Join-Path $env:SystemRoot 'Microsoft.NET\Framework\v4.0.30319\csc.exe')
|
||||||
|
) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
||||||
|
if (-not $compiler) { throw 'Der Windows-.NET-Framework-Compiler wurde nicht gefunden.' }
|
||||||
|
try {
|
||||||
|
[IO.File]::WriteAllText($launcherSourcePath, $launcherSource, [Text.Encoding]::UTF8)
|
||||||
|
$compilerOutput = & $compiler /nologo /target:winexe "/out:$compiledLauncher" $launcherSourcePath 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $compiledLauncher -PathType Leaf)) {
|
||||||
|
throw "Der fensterlose Starter konnte nicht erstellt werden: $($compilerOutput | Out-String)"
|
||||||
|
}
|
||||||
|
Move-Item -LiteralPath $compiledLauncher -Destination $launcherPath -Force
|
||||||
|
} finally {
|
||||||
|
foreach ($temporaryFile in @($compiledLauncher, $launcherSourcePath)) {
|
||||||
|
if (Test-Path -LiteralPath $temporaryFile) { Remove-Item -LiteralPath $temporaryFile }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$action = New-ScheduledTaskAction -Execute $launcherPath -Argument ('"{0}" "{1}"' -f $autoUpdateScript, $ScoopRoot.TrimEnd('\'))
|
||||||
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
|
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
|
||||||
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
||||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
||||||
@@ -58,12 +149,15 @@ if ($autoUpdateAnswer -eq 'y') {
|
|||||||
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
||||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||||
-Principal $principal -Settings $settings -Force `
|
-Principal $principal -Settings $settings -Force `
|
||||||
-Description 'Scoop taeglich um 03:00 aktualisieren; verpasste Termine nachholen; elexis* auf hold setzen.' | Out-Null
|
-Description 'Tägliche Aktualisierung der installierten Elemente (Elexis ausgeschlossen)' | Out-Null
|
||||||
Write-Host "Scoop Auto-Update eingeschaltet: $taskName (taeglich 03:00, verpasste Termine nachholen)."
|
# Die frühere Aufgabe dieses Benutzers entfernen, damit nur ein Update läuft.
|
||||||
Write-Host "Ausfuehrung verborgen unter $userId, sobald der Benutzer angemeldet ist; kein Aufwecken des PCs."
|
if (Get-ScheduledTask -TaskName $legacyTaskName -ErrorAction SilentlyContinue) {
|
||||||
|
Unregister-ScheduledTask -TaskName $legacyTaskName -Confirm:$false
|
||||||
|
}
|
||||||
|
Write-Host "Scoop-Auto-Update eingeschaltet: $taskName"
|
||||||
} finally {
|
} finally {
|
||||||
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
||||||
}
|
}
|
||||||
} else {
|
} elseif (-not $autoUpdateRemoved) {
|
||||||
Write-Host 'Auto-Update nicht eingerichtet. Eine eventuell bestehende Aufgabe bleibt unveraendert.'
|
Write-Host 'Keine Änderung an Scoop-Auto-Update vorgenommen.'
|
||||||
}
|
}
|
||||||
+107
-14
@@ -9,7 +9,7 @@ Ein passender bestehender Bucket wird umbenannt. Danach werden elexis und streic
|
|||||||
Andere Buckets wie main und extras bleiben bestehen.
|
Andere Buckets wie main und extras bleiben bestehen.
|
||||||
Alte Paketnamen mit Unterstrich werden auf passende Manifestnamen mit Bindestrich umgestellt.
|
Alte Paketnamen mit Unterstrich werden auf passende Manifestnamen mit Bindestrich umgestellt.
|
||||||
ee-iso/ee_iso und ee-cli werden deinstalliert. MyElexis CLI wird neu installiert.
|
ee-iso/ee_iso und ee-cli werden deinstalliert. MyElexis CLI wird neu installiert.
|
||||||
Mit -Apply wird abschliessend gefragt, ob taegliche Auto-Updates um 03:00 Uhr
|
Mit -Apply wird abschliessend gefragt, ob tägliche Auto-Updates um 03:00 Uhr
|
||||||
eingerichtet werden sollen. Das separate Skript wird aus dem Repository geladen.
|
eingerichtet werden sollen. Das separate Skript wird aus dem Repository geladen.
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
.\scripts\update-bucket.ps1
|
.\scripts\update-bucket.ps1
|
||||||
@@ -269,9 +269,48 @@ Write-Host "Fertig: $($changes.Count) install.json-Dateien auf medelexis umgeste
|
|||||||
Write-Host "Paketnamen umgestellt: $($renames.Count)."
|
Write-Host "Paketnamen umgestellt: $($renames.Count)."
|
||||||
Write-Host 'Von elexis/streichtech/medelexis bleibt nur medelexis bestehen. Prüfung: scoop status --local'
|
Write-Host 'Von elexis/streichtech/medelexis bleibt nur medelexis bestehen. Prüfung: scoop status --local'
|
||||||
|
|
||||||
|
$userId = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||||
|
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||||
|
$taskName = 'Medelexis AG - Scoop AutoUpdate'
|
||||||
|
$legacyTaskName = "Medelexis-Scoop-AutoUpdate-$userSid"
|
||||||
|
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||||
|
$existingTasks = @(
|
||||||
|
foreach ($name in @($taskName, $legacyTaskName)) {
|
||||||
|
Get-ScheduledTask -TaskName $name -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
$autoUpdateRemoved = $false
|
||||||
|
if ($existingTasks.Count -gt 0) {
|
||||||
do {
|
do {
|
||||||
$autoUpdateAnswer = (Read-Host 'Scoop Auto-Update einschalten? [y/n]').Trim().ToLowerInvariant()
|
$autoUpdateAnswer = (Read-Host 'Bestehende Scoop-Auto-Update-Aufgabe reparieren (ersetzen)? [y/n]').Trim().ToLowerInvariant()
|
||||||
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
||||||
|
if ($autoUpdateAnswer -eq 'n') {
|
||||||
|
do {
|
||||||
|
$removeAnswer = (Read-Host 'Scoop-Auto-Update entfernen? [y/n]').Trim().ToLowerInvariant()
|
||||||
|
} while ($removeAnswer -notin @('y', 'n'))
|
||||||
|
if ($removeAnswer -eq 'y') {
|
||||||
|
foreach ($existingTask in $existingTasks) {
|
||||||
|
if ($existingTask.State -eq 'Running') {
|
||||||
|
Stop-ScheduledTask -TaskName $existingTask.TaskName
|
||||||
|
}
|
||||||
|
Unregister-ScheduledTask -TaskName $existingTask.TaskName -Confirm:$false
|
||||||
|
}
|
||||||
|
# Nur die zugehörigen Dateien entfernen, niemals den Ordner rekursiv.
|
||||||
|
foreach ($fileName in @('auto-update.ps1', 'auto-update-hidden.exe', 'auto-update.log')) {
|
||||||
|
$filePath = Join-Path $autoUpdateDirectory $fileName
|
||||||
|
if (Test-Path -LiteralPath $filePath -PathType Leaf) {
|
||||||
|
Remove-Item -LiteralPath $filePath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$autoUpdateRemoved = $true
|
||||||
|
Write-Host 'Scoop-Auto-Update entfernt (Aufgabe und zugehörige Dateien).'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
$autoUpdateAnswer = (Read-Host 'Scoop-Auto-Update einschalten? [y/n]').Trim().ToLowerInvariant()
|
||||||
|
} while ($autoUpdateAnswer -notin @('y', 'n'))
|
||||||
|
}
|
||||||
|
|
||||||
if ($autoUpdateAnswer -eq 'y') {
|
if ($autoUpdateAnswer -eq 'y') {
|
||||||
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||||
@@ -286,16 +325,67 @@ if ($autoUpdateAnswer -eq 'y') {
|
|||||||
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
||||||
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
||||||
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
||||||
throw 'Das heruntergeladene Auto-Update-Skript ist kein gueltiges Updateskript.'
|
throw 'Das heruntergeladene Auto-Update-Skript ist kein gültiges Updateskript.'
|
||||||
}
|
}
|
||||||
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
||||||
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
||||||
$userId = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
# Bei einer Reparatur zuerst eine laufende Aufgabe anhalten,
|
||||||
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
# damit ihr Starter ersetzt werden kann.
|
||||||
$taskName = "Medelexis-Scoop-AutoUpdate-$userSid"
|
foreach ($existingTask in $existingTasks) {
|
||||||
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
if ($existingTask.State -eq 'Running') {
|
||||||
$arguments = '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "{0}" -ScoopRoot "{1}"' -f $autoUpdateScript, $ScoopRoot
|
Stop-ScheduledTask -TaskName $existingTask.TaskName
|
||||||
$action = New-ScheduledTaskAction -Execute $powershell -Argument $arguments
|
}
|
||||||
|
}
|
||||||
|
# Ein GUI-Programm startet PowerShell ohne Konsole, auch wenn Windows
|
||||||
|
# Terminal als Standardterminal eingerichtet ist. Es wartet auf das Ende
|
||||||
|
# des Updates und reicht den Exitcode weiter (Fehlerstatus / IgnoreNew).
|
||||||
|
$launcherPath = Join-Path $autoUpdateDirectory 'auto-update-hidden.exe'
|
||||||
|
$launcherSource = @'
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
public static class ScoopAutoUpdateLauncher {
|
||||||
|
[STAThread]
|
||||||
|
public static int Main(string[] args) {
|
||||||
|
if (args.Length != 2) return 2;
|
||||||
|
try {
|
||||||
|
var start = new ProcessStartInfo();
|
||||||
|
start.FileName = System.IO.Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"),
|
||||||
|
@"System32\WindowsPowerShell\v1.0\powershell.exe");
|
||||||
|
start.Arguments = "-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File \"" +
|
||||||
|
args[0] + "\" -ScoopRoot \"" + args[1] + "\"";
|
||||||
|
start.UseShellExecute = false;
|
||||||
|
start.CreateNoWindow = true;
|
||||||
|
start.WindowStyle = ProcessWindowStyle.Hidden;
|
||||||
|
using (var child = Process.Start(start)) {
|
||||||
|
child.WaitForExit();
|
||||||
|
return child.ExitCode;
|
||||||
|
}
|
||||||
|
} catch { return 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
$compiledLauncher = Join-Path $autoUpdateDirectory ('auto-update-{0}.exe' -f [guid]::NewGuid())
|
||||||
|
$launcherSourcePath = [IO.Path]::ChangeExtension($compiledLauncher, '.cs')
|
||||||
|
# Der .NET-Framework-Compiler erzeugt eine Windows-Anwendung auch unter
|
||||||
|
# PowerShell 7, wo Add-Type -OutputType WindowsApplication nicht existiert.
|
||||||
|
$compiler = @(
|
||||||
|
(Join-Path $env:SystemRoot 'Microsoft.NET\Framework64\v4.0.30319\csc.exe'),
|
||||||
|
(Join-Path $env:SystemRoot 'Microsoft.NET\Framework\v4.0.30319\csc.exe')
|
||||||
|
) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
||||||
|
if (-not $compiler) { throw 'Der Windows-.NET-Framework-Compiler wurde nicht gefunden.' }
|
||||||
|
try {
|
||||||
|
[IO.File]::WriteAllText($launcherSourcePath, $launcherSource, [Text.Encoding]::UTF8)
|
||||||
|
$compilerOutput = & $compiler /nologo /target:winexe "/out:$compiledLauncher" $launcherSourcePath 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $compiledLauncher -PathType Leaf)) {
|
||||||
|
throw "Der fensterlose Starter konnte nicht erstellt werden: $($compilerOutput | Out-String)"
|
||||||
|
}
|
||||||
|
Move-Item -LiteralPath $compiledLauncher -Destination $launcherPath -Force
|
||||||
|
} finally {
|
||||||
|
foreach ($temporaryFile in @($compiledLauncher, $launcherSourcePath)) {
|
||||||
|
if (Test-Path -LiteralPath $temporaryFile) { Remove-Item -LiteralPath $temporaryFile }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$action = New-ScheduledTaskAction -Execute $launcherPath -Argument ('"{0}" "{1}"' -f $autoUpdateScript, $ScoopRoot.TrimEnd('\'))
|
||||||
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
|
$trigger = New-ScheduledTaskTrigger -Daily -At '03:00'
|
||||||
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
||||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
||||||
@@ -303,12 +393,15 @@ if ($autoUpdateAnswer -eq 'y') {
|
|||||||
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
||||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||||
-Principal $principal -Settings $settings -Force `
|
-Principal $principal -Settings $settings -Force `
|
||||||
-Description 'Scoop taeglich um 03:00 aktualisieren; verpasste Termine nachholen; elexis* auf hold setzen.' | Out-Null
|
-Description 'Tägliche Aktualisierung der installierten Elemente (Elexis ausgeschlossen)' | Out-Null
|
||||||
Write-Host "Scoop Auto-Update eingeschaltet: $taskName (taeglich 03:00, verpasste Termine nachholen)."
|
# Die frühere Aufgabe dieses Benutzers entfernen, damit nur ein Update läuft.
|
||||||
Write-Host "Ausfuehrung verborgen unter $userId, sobald der Benutzer angemeldet ist; kein Aufwecken des PCs."
|
if (Get-ScheduledTask -TaskName $legacyTaskName -ErrorAction SilentlyContinue) {
|
||||||
|
Unregister-ScheduledTask -TaskName $legacyTaskName -Confirm:$false
|
||||||
|
}
|
||||||
|
Write-Host "Scoop-Auto-Update eingeschaltet: $taskName"
|
||||||
} finally {
|
} finally {
|
||||||
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
||||||
}
|
}
|
||||||
} else {
|
} elseif (-not $autoUpdateRemoved) {
|
||||||
Write-Host 'Auto-Update nicht eingerichtet. Eine eventuell bestehende Aufgabe bleibt unveraendert.'
|
Write-Host 'Keine Änderung an Scoop-Auto-Update vorgenommen.'
|
||||||
}
|
}
|
||||||
+135
-38
@@ -1,4 +1,4 @@
|
|||||||
#requires -Version 5.1
|
#requires -Version 5.1
|
||||||
# Isolated behavioural tests: no real downloads, Scoop updates or scheduled tasks.
|
# Isolated behavioural tests: no real downloads, Scoop updates or scheduled tasks.
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$projectRoot = Split-Path $PSScriptRoot -Parent
|
$projectRoot = Split-Path $PSScriptRoot -Parent
|
||||||
@@ -11,6 +11,14 @@ $oldFailure = $env:SCOOP_AUTO_UPDATE_TEST_FAILURE
|
|||||||
function Assert-True($Condition, [string] $Message) {
|
function Assert-True($Condition, [string] $Message) {
|
||||||
if (-not $Condition) { throw $Message }
|
if (-not $Condition) { throw $Message }
|
||||||
}
|
}
|
||||||
|
function Assert-HiddenTask($Task, [string] $Runner, [string] $ScoopRoot) {
|
||||||
|
Assert-True ($Task.TaskName -eq 'Medelexis AG - Scoop AutoUpdate') 'Unexpected task name.'
|
||||||
|
$description = 'T' + [char]0x00e4 + 'gliche Aktualisierung der installierten Elemente (Elexis ausgeschlossen)'
|
||||||
|
Assert-True ($Task.Description -ceq $description) 'Unexpected task description.'
|
||||||
|
$launcher = Join-Path (Split-Path $Runner -Parent) 'auto-update-hidden.exe'
|
||||||
|
Assert-True ($Task.Action.Execute -eq $launcher -and (Test-Path -LiteralPath $launcher)) 'Task must use the GUI launcher.'
|
||||||
|
Assert-True ($Task.Action.Argument -eq ('"{0}" "{1}"' -f $Runner, $ScoopRoot)) 'Expected quoted runner and Scoop root.'
|
||||||
|
}
|
||||||
|
|
||||||
# These stand-ins capture scheduling settings without changing Windows.
|
# These stand-ins capture scheduling settings without changing Windows.
|
||||||
function New-ScheduledTaskAction { param($Execute, $Argument) [pscustomobject]$PSBoundParameters }
|
function New-ScheduledTaskAction { param($Execute, $Argument) [pscustomobject]$PSBoundParameters }
|
||||||
@@ -25,13 +33,32 @@ function New-ScheduledTaskSettingsSet {
|
|||||||
function Register-ScheduledTask {
|
function Register-ScheduledTask {
|
||||||
param($TaskName, $Action, $Trigger, $Principal, $Settings, [switch]$Force, $Description)
|
param($TaskName, $Action, $Trigger, $Principal, $Settings, [switch]$Force, $Description)
|
||||||
$global:AutoUpdateTestTask = [pscustomobject]$PSBoundParameters
|
$global:AutoUpdateTestTask = [pscustomobject]$PSBoundParameters
|
||||||
|
$global:AutoUpdateTestTasks[$TaskName] = $global:AutoUpdateTestTask
|
||||||
|
}
|
||||||
|
function Get-ScheduledTask {
|
||||||
|
param($TaskName, $ErrorAction)
|
||||||
|
if ($global:AutoUpdateTestTasks.ContainsKey($TaskName)) { $global:AutoUpdateTestTasks[$TaskName] }
|
||||||
|
}
|
||||||
|
function Unregister-ScheduledTask {
|
||||||
|
param($TaskName, [switch]$Confirm)
|
||||||
|
$expected = 'Medelexis-Scoop-AutoUpdate-' + [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||||
|
Assert-True ($TaskName -in @($expected, 'Medelexis AG - Scoop AutoUpdate') -and -not $Confirm) 'Only remove the matching auto-update task.'
|
||||||
|
$global:AutoUpdateTestRemovedTask = $TaskName
|
||||||
|
$global:AutoUpdateTestTasks.Remove($TaskName)
|
||||||
|
}
|
||||||
|
function Stop-ScheduledTask {
|
||||||
|
param($TaskName)
|
||||||
|
$global:AutoUpdateTestStoppedTask = $TaskName
|
||||||
|
$global:AutoUpdateTestTasks[$TaskName].State = 'Ready'
|
||||||
}
|
}
|
||||||
function Read-Host {
|
function Read-Host {
|
||||||
param($Prompt)
|
param($Prompt)
|
||||||
|
$global:AutoUpdateTestPrompts.Add($Prompt)
|
||||||
$global:AutoUpdateTestAnswers.Dequeue()
|
$global:AutoUpdateTestAnswers.Dequeue()
|
||||||
}
|
}
|
||||||
function Invoke-WebRequest {
|
function Invoke-WebRequest {
|
||||||
param($Uri, $OutFile, [switch]$UseBasicParsing)
|
param($Uri, $OutFile, [switch]$UseBasicParsing)
|
||||||
|
$global:AutoUpdateTestDownloads++
|
||||||
Assert-True ($Uri -in @('https://example.test/scoop/raw/branch/master/scripts/auto-update.ps1',
|
Assert-True ($Uri -in @('https://example.test/scoop/raw/branch/master/scripts/auto-update.ps1',
|
||||||
'https://gitea.medelexis.ch/public/scoop/raw/branch/master/scripts/auto-update.ps1')) 'Unexpected download URL.'
|
'https://gitea.medelexis.ch/public/scoop/raw/branch/master/scripts/auto-update.ps1')) 'Unexpected download URL.'
|
||||||
if ($global:AutoUpdateTestInvalidDownload) {
|
if ($global:AutoUpdateTestInvalidDownload) {
|
||||||
@@ -46,7 +73,7 @@ throw 'The downloaded runner must not execute during setup.'
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
# Spaces in the installation path exercise the scheduled command's quoting.
|
# Spaces in the installation path exercise the scheduled command's quoting.
|
||||||
$scoopRoot = Join-Path $fixtureRoot 'scoop with spaces'
|
$scoopRoot = Join-Path $fixtureRoot ('scoop with spaces ' + [char]0x00e4)
|
||||||
$shims = Join-Path $scoopRoot 'shims'
|
$shims = Join-Path $scoopRoot 'shims'
|
||||||
New-Item -ItemType Directory -Path $shims -Force | Out-Null
|
New-Item -ItemType Directory -Path $shims -Force | Out-Null
|
||||||
$scriptPath = Join-Path $fixtureRoot 'auto-update.ps1'
|
$scriptPath = Join-Path $fixtureRoot 'auto-update.ps1'
|
||||||
@@ -85,66 +112,136 @@ exit 0
|
|||||||
$commands = @(Get-Content $commandLog)
|
$commands = @(Get-Content $commandLog)
|
||||||
Assert-True ($commands[-1] -eq $failure) "Must abort immediately after $failure."
|
Assert-True ($commands[-1] -eq $failure) "Must abort immediately after $failure."
|
||||||
}
|
}
|
||||||
# Exercise the actual opt-in/download block without running the bucket migration.
|
# Test all entry points without migrating buckets or changing real tasks.
|
||||||
$tokens = $null
|
$tokens = $null
|
||||||
$errors = $null
|
$errors = $null
|
||||||
$bucketAst = [Management.Automation.Language.Parser]::ParseFile((Join-Path $projectRoot 'scripts\update-bucket.ps1'), [ref]$tokens, [ref]$errors)
|
$bucketAst = [Management.Automation.Language.Parser]::ParseFile((Join-Path $projectRoot 'scripts\update-bucket.ps1'), [ref]$tokens, [ref]$errors)
|
||||||
Assert-True ($errors.Count -eq 0) 'Invalid bucket script syntax.'
|
Assert-True ($errors.Count -eq 0) 'Invalid bucket script syntax.'
|
||||||
$optInBlock = [scriptblock]::Create((($bucketAst.EndBlock.Statements | Select-Object -Last 2 | ForEach-Object { $_.Extent.Text }) -join "`n"))
|
$setupStatements = @($bucketAst.EndBlock.Statements)
|
||||||
|
$setupStart = 0
|
||||||
|
while ($setupStart -lt $setupStatements.Count -and -not $setupStatements[$setupStart].Extent.Text.StartsWith('$userId =')) { $setupStart++ }
|
||||||
|
Assert-True ($setupStart -lt $setupStatements.Count) 'Setup block missing in bucket migration.'
|
||||||
|
$optInBlock = [scriptblock]::Create((($setupStatements[$setupStart..($setupStatements.Count - 1)] | ForEach-Object { $_.Extent.Text }) -join "`n"))
|
||||||
$repository = 'https://example.test/scoop'
|
$repository = 'https://example.test/scoop'
|
||||||
Remove-Item -LiteralPath $commandLog
|
Remove-Item -LiteralPath $commandLog
|
||||||
$global:AutoUpdateTestAnswers = [Collections.Generic.Queue[string]]::new()
|
$global:AutoUpdateTestAnswers = [Collections.Generic.Queue[string]]::new()
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('n')
|
$global:AutoUpdateTestPrompts = [Collections.Generic.List[string]]::new()
|
||||||
& $optInBlock
|
$global:AutoUpdateTestTasks = @{}
|
||||||
|
$global:AutoUpdateTestDownloads = 0
|
||||||
$downloadDirectory = Join-Path $scoopRoot 'scripts\medelexis'
|
$downloadDirectory = Join-Path $scoopRoot 'scripts\medelexis'
|
||||||
Assert-True (-not (Test-Path $downloadDirectory)) 'Declining must not download or set up anything.'
|
$downloadedPath = Join-Path $downloadDirectory 'auto-update.ps1'
|
||||||
|
$launcherPath = Join-Path $downloadDirectory 'auto-update-hidden.exe'
|
||||||
|
$taskName = 'Medelexis AG - Scoop AutoUpdate'
|
||||||
|
$legacyName = 'Medelexis-Scoop-AutoUpdate-' + [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||||
|
$setupEntries = @($optInBlock, (Join-Path $projectRoot 'scripts\setup-auto-update.ps1'))
|
||||||
|
foreach ($entry in $setupEntries) {
|
||||||
|
$global:AutoUpdateTestPrompts.Clear()
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||||
|
& $entry -ScoopRoot $scoopRoot
|
||||||
|
Assert-True ($global:AutoUpdateTestPrompts.Count -eq 1 -and $global:AutoUpdateTestPrompts[0] -eq 'Scoop-Auto-Update einschalten? [y/n]') 'Missing task must offer normal setup.'
|
||||||
|
Assert-True ($global:AutoUpdateTestTasks.Count -eq 0) 'Declining initial setup must not register a task.'
|
||||||
|
|
||||||
|
$global:AutoUpdateTestInvalidDownload = $false
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('invalid')
|
$global:AutoUpdateTestAnswers.Enqueue('invalid')
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('Y')
|
$global:AutoUpdateTestAnswers.Enqueue('Y')
|
||||||
& $optInBlock
|
& $entry -ScoopRoot $scoopRoot
|
||||||
$downloadedPath = Join-Path $downloadDirectory 'auto-update.ps1'
|
$task = $global:AutoUpdateTestTasks[$taskName]
|
||||||
Assert-True (Test-Path $downloadedPath) 'Expected downloaded runner.'
|
Assert-HiddenTask $task $downloadedPath $scoopRoot
|
||||||
$task = $global:AutoUpdateTestTask
|
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00' -and $task.Settings.StartWhenAvailable) 'Expected daily 03:00 with catch-up.'
|
||||||
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00') 'Expected daily 03:00 schedule.'
|
Assert-True ($task.Settings.RunOnlyIfNetworkAvailable -and $task.Settings.MultipleInstances -eq 'IgnoreNew') 'Expected network and overlap settings.'
|
||||||
Assert-True ($task.Settings.StartWhenAvailable -and $task.Settings.RunOnlyIfNetworkAvailable) 'Expected catch-up and network settings.'
|
|
||||||
Assert-True ($task.Settings.MultipleInstances -eq 'IgnoreNew') 'Expected overlap protection.'
|
|
||||||
Assert-True ($task.Principal.LogonType -eq 'Interactive' -and $task.Principal.RunLevel -eq 'Limited') 'Expected normal user session.'
|
Assert-True ($task.Principal.LogonType -eq 'Interactive' -and $task.Principal.RunLevel -eq 'Limited') 'Expected normal user session.'
|
||||||
Assert-True ($task.Action.Argument.Contains('-WindowStyle Hidden') -and $task.Action.Argument.Contains('-NonInteractive')) 'Expected background execution.'
|
Assert-True (-not (Test-Path $commandLog)) 'Setup must not execute Scoop or migrations.'
|
||||||
Assert-True ($task.Action.Argument.Contains('-File "' + $downloadedPath + '"') -and $task.Action.Argument.Contains('-ScoopRoot "' + $scoopRoot + '"')) 'Expected quoted paths pointing to the downloaded runner.'
|
|
||||||
Assert-True (-not (Test-Path $commandLog)) 'Setup must not execute Scoop.'
|
# No/no must leave task, runner and launcher byte-for-byte unchanged.
|
||||||
$taskName = $task.TaskName
|
$downloads = $global:AutoUpdateTestDownloads
|
||||||
|
$runnerBefore = (Get-FileHash -LiteralPath $downloadedPath).Hash
|
||||||
|
$launcherBefore = (Get-FileHash -LiteralPath $launcherPath).Hash
|
||||||
|
$global:AutoUpdateTestPrompts.Clear()
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||||
|
& $entry -ScoopRoot $scoopRoot
|
||||||
|
Assert-True ($global:AutoUpdateTestPrompts.Count -eq 2 -and $global:AutoUpdateTestPrompts[0] -like '*reparieren*' -and $global:AutoUpdateTestPrompts[1] -like '*entfernen*') 'Expected repair then removal questions.'
|
||||||
|
Assert-True ($global:AutoUpdateTestTasks[$taskName] -eq $task -and $global:AutoUpdateTestDownloads -eq $downloads) 'No/no must preserve the task and avoid downloading.'
|
||||||
|
Assert-True ((Get-FileHash -LiteralPath $downloadedPath).Hash -eq $runnerBefore -and (Get-FileHash -LiteralPath $launcherPath).Hash -eq $launcherBefore) 'No/no must preserve files.'
|
||||||
|
|
||||||
|
# Repair downloads and replaces the existing task, without duplicating it.
|
||||||
|
$global:AutoUpdateTestPrompts.Clear()
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('y')
|
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||||
& $optInBlock
|
& $entry -ScoopRoot $scoopRoot
|
||||||
Assert-True ($global:AutoUpdateTestTask.TaskName -eq $taskName -and $global:AutoUpdateTestTask.Force) 'Repeated setup must replace the same task.'
|
Assert-True ($global:AutoUpdateTestPrompts.Count -eq 1 -and $global:AutoUpdateTestPrompts[0] -like '*reparieren*') 'Repair must ask only the repair question.'
|
||||||
$originalDownload = [IO.File]::ReadAllText($downloadedPath)
|
Assert-True ($global:AutoUpdateTestDownloads -eq $downloads + 1 -and $global:AutoUpdateTestTasks.Count -eq 1) 'Repair must download and replace the existing task.'
|
||||||
Remove-Variable AutoUpdateTestTask -Scope Global
|
Assert-HiddenTask $global:AutoUpdateTestTasks[$taskName] $downloadedPath $scoopRoot
|
||||||
|
|
||||||
|
# Failed repair must retain the prior runner and task.
|
||||||
|
$task = $global:AutoUpdateTestTasks[$taskName]
|
||||||
$global:AutoUpdateTestInvalidDownload = $true
|
$global:AutoUpdateTestInvalidDownload = $true
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('y')
|
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||||
$rejected = $false
|
$rejected = $false
|
||||||
try { & $optInBlock } catch { $rejected = $true }
|
try { & $entry -ScoopRoot $scoopRoot } catch { $rejected = $true }
|
||||||
Assert-True $rejected 'Invalid download must be rejected.'
|
Assert-True $rejected 'Invalid download must be rejected.'
|
||||||
Assert-True ([IO.File]::ReadAllText($downloadedPath) -ceq $originalDownload) 'Invalid download must preserve the existing script.'
|
Assert-True ($global:AutoUpdateTestTasks[$taskName] -eq $task -and (Get-FileHash -LiteralPath $downloadedPath).Hash -eq $runnerBefore) 'Failed repair must preserve the existing runner and task.'
|
||||||
Assert-True ($null -eq (Get-Variable AutoUpdateTestTask -Scope Global -ErrorAction SilentlyContinue)) 'Invalid download must not register a task.'
|
|
||||||
Assert-True (@(Get-ChildItem $downloadDirectory -Filter '*.download').Count -eq 0) 'Temporary downloads must be removed.'
|
Assert-True (@(Get-ChildItem $downloadDirectory -Filter '*.download').Count -eq 0) 'Temporary downloads must be removed.'
|
||||||
# The additional standalone installer must offer the same setup without migration.
|
|
||||||
$standaloneSetup = Join-Path $projectRoot 'scripts\setup-auto-update.ps1'
|
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('n')
|
|
||||||
& $standaloneSetup -ScoopRoot $scoopRoot
|
|
||||||
Assert-True ($null -eq (Get-Variable AutoUpdateTestTask -Scope Global -ErrorAction SilentlyContinue)) 'Standalone n must not register a task.'
|
|
||||||
$global:AutoUpdateTestInvalidDownload = $false
|
$global:AutoUpdateTestInvalidDownload = $false
|
||||||
|
|
||||||
|
# Also recognize the earlier task name, and remove only managed files.
|
||||||
|
$global:AutoUpdateTestTasks.Clear()
|
||||||
|
$global:AutoUpdateTestTasks[$legacyName] = [pscustomobject]@{ TaskName = $legacyName; State = 'Running' }
|
||||||
|
Set-Content -LiteralPath (Join-Path $downloadDirectory 'auto-update.log') -Value 'old log'
|
||||||
|
Set-Content -LiteralPath (Join-Path $downloadDirectory 'unrelated.txt') -Value 'keep'
|
||||||
|
$downloads = $global:AutoUpdateTestDownloads
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||||
$global:AutoUpdateTestAnswers.Enqueue('y')
|
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||||
& $standaloneSetup -ScoopRoot $scoopRoot
|
& $entry -ScoopRoot $scoopRoot
|
||||||
$task = $global:AutoUpdateTestTask
|
Assert-True ($global:AutoUpdateTestTasks.Count -eq 0 -and $global:AutoUpdateTestStoppedTask -eq $legacyName) 'Remove must stop and unregister the existing legacy task.'
|
||||||
Assert-True ($task.TaskName -eq $taskName -and $task.Force) 'Standalone setup must use the same task.'
|
Assert-True (-not (Test-Path $downloadedPath) -and -not (Test-Path $launcherPath) -and -not (Test-Path (Join-Path $downloadDirectory 'auto-update.log'))) 'Remove must delete the runner, launcher and log.'
|
||||||
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00' -and $task.Settings.StartWhenAvailable) 'Standalone setup must schedule daily 03:00 with catch-up.'
|
Assert-True ((Test-Path (Join-Path $downloadDirectory 'unrelated.txt')) -and $global:AutoUpdateTestDownloads -eq $downloads) 'Remove must preserve unrelated files and avoid downloading.'
|
||||||
Assert-True ($task.Action.Argument.Contains('-WindowStyle Hidden') -and $task.Action.Argument.Contains('-File "' + $downloadedPath + '"')) 'Standalone task must invoke the downloaded runner hidden.'
|
|
||||||
Assert-True (-not (Test-Path $commandLog)) 'Standalone setup must not run Scoop or migrations.'
|
# Repair migrates the legacy name to the requested task name.
|
||||||
Write-Host 'PASS: schedule, repeat setup, hold filtering, command order, logging, failures, opt-in and download setup.'
|
$global:AutoUpdateTestTasks[$legacyName] = [pscustomobject]@{ TaskName = $legacyName; State = 'Ready' }
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||||
|
& $entry -ScoopRoot $scoopRoot
|
||||||
|
Assert-True ($global:AutoUpdateTestTasks.Count -eq 1 -and $global:AutoUpdateTestTasks.ContainsKey($taskName)) 'Repair must migrate the legacy task.'
|
||||||
|
# Removal of the current name must offer the same behaviour.
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('n')
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||||
|
& $entry -ScoopRoot $scoopRoot
|
||||||
|
Assert-True ($global:AutoUpdateTestTasks.Count -eq 0 -and -not (Test-Path $downloadedPath)) 'Remove must also handle the current task name.'
|
||||||
|
}
|
||||||
|
# Prepare one local launcher for the real hidden-process smoke test below.
|
||||||
|
$global:AutoUpdateTestAnswers.Enqueue('y')
|
||||||
|
& (Join-Path $projectRoot 'scripts\setup-auto-update.ps1') -ScoopRoot $scoopRoot
|
||||||
|
$task = $global:AutoUpdateTestTasks[$taskName]
|
||||||
|
|
||||||
|
# Run only a harmless local fixture through the actual GUI launcher. No Scoop
|
||||||
|
# or scheduler calls: verify quoting, hidden child window and exit propagation.
|
||||||
|
@'
|
||||||
|
param([string] $ScoopRoot)
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class AutoUpdateWindowTest {
|
||||||
|
[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
|
||||||
|
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr handle);
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
$visible = [AutoUpdateWindowTest]::IsWindowVisible([AutoUpdateWindowTest]::GetConsoleWindow())
|
||||||
|
Set-Content -LiteralPath (Join-Path $ScoopRoot 'hidden-test.txt') -Value ([string]$visible)
|
||||||
|
exit 23
|
||||||
|
'@ | Set-Content -LiteralPath $downloadedPath -Encoding UTF8
|
||||||
|
$process = Start-Process -FilePath $task.Action.Execute -ArgumentList $task.Action.Argument -WindowStyle Hidden -PassThru
|
||||||
|
if (-not $process.WaitForExit(20000)) {
|
||||||
|
$process.Kill()
|
||||||
|
throw 'Hidden launcher did not finish within 20 seconds.'
|
||||||
|
}
|
||||||
|
Assert-True ($process.ExitCode -eq 23) 'Launcher must forward the runner exit code.'
|
||||||
|
Assert-True ((Get-Content -LiteralPath (Join-Path $scoopRoot 'hidden-test.txt')) -eq 'False') 'Runner must have no visible console window.'
|
||||||
|
Write-Host 'PASS: install, repair, no/no, removal, legacy tasks, hidden launcher, schedule, hold filtering and failures.'
|
||||||
} finally {
|
} finally {
|
||||||
$env:SCOOP = $oldScoop
|
$env:SCOOP = $oldScoop
|
||||||
$env:PATH = $oldPath
|
$env:PATH = $oldPath
|
||||||
$env:SCOOP_AUTO_UPDATE_TEST_ROOT = $oldFixture
|
$env:SCOOP_AUTO_UPDATE_TEST_ROOT = $oldFixture
|
||||||
$env:SCOOP_AUTO_UPDATE_TEST_FAILURE = $oldFailure
|
$env:SCOOP_AUTO_UPDATE_TEST_FAILURE = $oldFailure
|
||||||
Remove-Variable AutoUpdateTestTask, AutoUpdateTestAnswers, AutoUpdateTestInvalidDownload -Scope Global -ErrorAction SilentlyContinue
|
Remove-Variable AutoUpdateTestTask, AutoUpdateTestAnswers, AutoUpdateTestInvalidDownload, AutoUpdateTestRemovedTask, AutoUpdateTestTasks, AutoUpdateTestDownloads, AutoUpdateTestPrompts, AutoUpdateTestStoppedTask -Scope Global -ErrorAction SilentlyContinue
|
||||||
# Delete only this test's unique directory, after checking its absolute parent.
|
# Delete only this test's unique directory, after checking its absolute parent.
|
||||||
if ((Test-Path -LiteralPath $fixtureRoot) -and
|
if ((Test-Path -LiteralPath $fixtureRoot) -and
|
||||||
[IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($fixtureRoot)) -eq [IO.Path]::GetFullPath($PSScriptRoot) -and
|
[IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($fixtureRoot)) -eq [IO.Path]::GetFullPath($PSScriptRoot) -and
|
||||||
|
|||||||
Reference in new issue
Block a user