Compare commits
7
Commits
53f3f6bd45
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04ca6b1cb7 | ||
|
|
ac62c5476d | ||
|
|
ddbc3fa48e | ||
|
|
4bbde1dfe2 | ||
|
|
ee6d5b9b1e | ||
|
|
6c1c651839 | ||
|
|
795a056564 |
No files matched your search
+2
-2
@@ -1,7 +1,7 @@
|
||||
*.bak
|
||||
*.exe
|
||||
scripts/*
|
||||
!scripts/update-manifests.py
|
||||
.vscode/*
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
/tests/*
|
||||
!/tests/test_update_manifests.py
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": "20261020T080626Z",
|
||||
"version": "20261109T091718Z",
|
||||
"description": "MyElexis CLI",
|
||||
"url": "https://download.medelexis.ch/myelexis-cli/windows/myelexis-cli-1.0.0-SNAPSHOT-runner.exe#/myelexis-cli.exe",
|
||||
"hash": "a397bae3e6c340f40b4b0a35ea22bafd3c30a02990e0b26bde7957a803902078",
|
||||
"hash": "b266ad9f0b01bebd0c15134096e011933aac6080af2b26a68b8467dbcc6e60b2",
|
||||
"bin": [
|
||||
[
|
||||
"myelexis-cli.exe",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Scoop Medelexis AG - Intern
|
||||
|
||||
Damit die PowerShell-Scripte ausgeführt werden können, muss im Terminal-Fenster zuerst `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass` eingegeben und ausgeführt werden.
|
||||
|
||||
**Scripts:**
|
||||
|
||||
* Install -> installiert Scoop und fügt die Buckets extras und medelexis hinzu
|
||||
* update-bucket -> Migriert bestehende Buckets und Pakete auf medelexis *(kann mehrfach ausgeführt werden)
|
||||
(ohne -Apply wird nur ein Probelauf durchgeführt. Wenn das Update angewendet werden soll, muss "update-bucket.ps1 -Apply" verwendet werden.)*
|
||||
* setup-auto-update -> Richtet eine tägliche Scoop-Update-Aufgabe ein, repariert oder entfernt sie.
|
||||
|
||||
Andere Scripts sind System-Scripts und nicht für manuelle Ausführung gedacht.
|
||||
@@ -0,0 +1,49 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Aktualisiert Scoop und alle benutzerspezifischen Pakete; elexis* bleibt gesperrt.
|
||||
.DESCRIPTION
|
||||
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
|
||||
auto-update.log neben diesem Skript. Globale Pakete werden nicht aktualisiert.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ScoopRoot = [IO.Path]::GetFullPath($ScoopRoot)
|
||||
$env:SCOOP = $ScoopRoot
|
||||
$scoopCommand = Join-Path $ScoopRoot 'shims\scoop.ps1'
|
||||
if (-not (Test-Path -LiteralPath $scoopCommand -PathType Leaf)) {
|
||||
throw "Scoop wurde nicht gefunden: $scoopCommand"
|
||||
}
|
||||
|
||||
$transcriptStarted = $false
|
||||
$exitCode = 0
|
||||
try {
|
||||
Start-Transcript -LiteralPath (Join-Path $PSScriptRoot 'auto-update.log') -Force | Out-Null
|
||||
$transcriptStarted = $true
|
||||
# Ein absoluter Scoop-Pfad und die Shims im PATH funktionieren auch ohne Profil.
|
||||
$env:PATH = (Join-Path $ScoopRoot 'shims') + [IO.Path]::PathSeparator + $env:PATH
|
||||
$installed = @(& $scoopCommand list)
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Installierte Scoop-Pakete konnten nicht ermittelt werden.' }
|
||||
foreach ($package in $installed) {
|
||||
if ($package.Name -notlike 'elexis*' -or $package.Info -match 'Global install|Install failed') { continue }
|
||||
& $scoopCommand hold $package.Name
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Updatesperre für '$($package.Name)' fehlgeschlagen. Kein Update ausgeführt."
|
||||
}
|
||||
}
|
||||
& $scoopCommand update
|
||||
if ($LASTEXITCODE -ne 0) { throw 'scoop update fehlgeschlagen.' }
|
||||
& $scoopCommand update '*'
|
||||
if ($LASTEXITCODE -ne 0) { throw 'scoop update * fehlgeschlagen.' }
|
||||
} catch {
|
||||
Write-Error $_ -ErrorAction Continue
|
||||
$exitCode = 1
|
||||
} finally {
|
||||
if ($transcriptStarted) { Stop-Transcript | Out-Null }
|
||||
}
|
||||
exit $exitCode
|
||||
@@ -1,4 +1,4 @@
|
||||
# 1. SCOOP CHECK & INSTALL
|
||||
# 1. SCOOP CHECK & INSTALL
|
||||
$bucketList = scoop bucket list 2>$null | Out-String
|
||||
|
||||
if ($bucketList) {
|
||||
@@ -29,12 +29,12 @@ else {
|
||||
}
|
||||
|
||||
# 3. BUCKET CHECKS
|
||||
if ($bucketList -match "elexis") {
|
||||
Write-Host "elexis Bucket ist bereits vorhanden." -ForegroundColor White
|
||||
if ($bucketList -match '(?m)^\s*medelexis\s') {
|
||||
Write-Host "Medelexis-Bucket ist bereits vorhanden." -ForegroundColor White
|
||||
}
|
||||
else {
|
||||
scoop bucket add elexis https://gitea.medelexis.ch/public/scoop
|
||||
Write-Host "elexis Bucket hinzugefügt." -ForegroundColor Green
|
||||
scoop bucket add medelexis https://gitea.medelexis.ch/public/scoop
|
||||
Write-Host "Medelexis-Bucket hinzugefügt." -ForegroundColor Green
|
||||
}
|
||||
|
||||
if ($bucketList -match "extras") {
|
||||
@@ -0,0 +1,163 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fragt nach und richtet optional die täglichen Scoop-Auto-Updates ein.
|
||||
.DESCRIPTION
|
||||
Eigenständig ausführbar, ohne Bucket-Migration. Bei y wird auto-update.ps1
|
||||
heruntergeladen und eine Aufgabe für täglich 03:00 Uhr erstellt.
|
||||
Die Aufgabe startet das Updateskript verborgen und holt verpasste Termine nach,
|
||||
sobald die Benutzersitzung und eine Netzwerkverbindung verfügbar sind.
|
||||
Bestehende Aufgaben können nach Rückfrage repariert oder samt Updateskript
|
||||
entfernt werden. Werden beide Fragen verneint, bleibt alles unverändert.
|
||||
.EXAMPLE
|
||||
.\scripts\setup-auto-update.ps1
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ScoopRoot = [IO.Path]::GetFullPath($ScoopRoot)
|
||||
$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 {
|
||||
$autoUpdateAnswer = (Read-Host 'Bestehende Scoop-Auto-Update-Aufgabe reparieren (ersetzen)? [y/n]').Trim().ToLowerInvariant()
|
||||
} 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') {
|
||||
$scoopCommand = Join-Path $ScoopRoot 'shims\scoop.ps1'
|
||||
if (-not (Test-Path -LiteralPath $scoopCommand -PathType Leaf)) {
|
||||
throw "Scoop wurde nicht gefunden: $scoopCommand"
|
||||
}
|
||||
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||
New-Item -ItemType Directory -Path $autoUpdateDirectory -Force | Out-Null
|
||||
$autoUpdateScript = Join-Path $autoUpdateDirectory 'auto-update.ps1'
|
||||
$downloadPath = Join-Path $autoUpdateDirectory ('auto-update-{0}.download' -f [guid]::NewGuid())
|
||||
try {
|
||||
$autoUpdateUrl = "$repository/raw/branch/master/scripts/auto-update.ps1"
|
||||
Invoke-WebRequest -Uri $autoUpdateUrl -OutFile $downloadPath -UseBasicParsing
|
||||
$parseTokens = $null
|
||||
$parseErrors = $null
|
||||
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
||||
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
||||
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
||||
throw 'Das heruntergeladene Auto-Update-Skript ist kein gültiges Updateskript.'
|
||||
}
|
||||
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
||||
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
||||
# Bei einer Reparatur zuerst eine laufende Aufgabe anhalten,
|
||||
# damit ihr Starter ersetzt werden kann.
|
||||
foreach ($existingTask in $existingTasks) {
|
||||
if ($existingTask.State -eq 'Running') {
|
||||
Stop-ScheduledTask -TaskName $existingTask.TaskName
|
||||
}
|
||||
}
|
||||
# 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'
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
||||
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force `
|
||||
-Description 'Tägliche Aktualisierung der installierten Elemente (Elexis ausgeschlossen)' | Out-Null
|
||||
# Die frühere Aufgabe dieses Benutzers entfernen, damit nur ein Update läuft.
|
||||
if (Get-ScheduledTask -TaskName $legacyTaskName -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName $legacyTaskName -Confirm:$false
|
||||
}
|
||||
Write-Host "Scoop-Auto-Update eingeschaltet: $taskName"
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
||||
}
|
||||
} elseif (-not $autoUpdateRemoved) {
|
||||
Write-Host 'Keine Änderung an Scoop-Auto-Update vorgenommen.'
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
#requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Stellt bestehende Scoop-Installationen von elexis/streichtech auf medelexis um.
|
||||
.DESCRIPTION
|
||||
Ohne -Apply nur ein Probelauf. Keine Sicherungen oder Git-Pushes.
|
||||
Bearbeitet alle Versionsverzeichnisse der benutzerspezifischen Scoop-Installation.
|
||||
Ein passender bestehender Bucket wird umbenannt. Danach werden elexis und streichtech entfernt.
|
||||
Andere Buckets wie main und extras bleiben bestehen.
|
||||
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.
|
||||
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.
|
||||
.EXAMPLE
|
||||
.\scripts\update-bucket.ps1
|
||||
.EXAMPLE
|
||||
.\scripts\update-bucket.ps1 -Apply
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch] $Apply,
|
||||
[string] $ScoopRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repository = 'https://gitea.medelexis.ch/public/scoop'
|
||||
$targetName = 'medelexis'
|
||||
$target = Join-Path $ScoopRoot "buckets\$targetName"
|
||||
$apps = Join-Path $ScoopRoot 'apps'
|
||||
if (-not (Test-Path -LiteralPath $apps -PathType Container)) {
|
||||
throw "Kein Scoop-Apps-Verzeichnis gefunden: $apps"
|
||||
}
|
||||
|
||||
function Test-MedelexisRepository([string] $Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Container)) { return $false }
|
||||
$remote = & git -C $Path remote get-url origin 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { return $false }
|
||||
try { $uri = [Uri] ([string] $remote).Trim() } catch { return $false }
|
||||
return ($uri.Scheme -eq 'https' -and $uri.Host -eq 'gitea.medelexis.ch' -and
|
||||
$uri.AbsolutePath.TrimEnd('/').Replace('.git', '') -eq '/public/scoop')
|
||||
}
|
||||
|
||||
function Get-PackageRenames([string] $BucketPath) {
|
||||
if (-not $BucketPath -or -not (Test-Path -LiteralPath $BucketPath)) { return }
|
||||
foreach ($app in Get-ChildItem -LiteralPath $apps -Directory) {
|
||||
if ($app.Name -in @('ee-iso', 'ee_iso')) { continue }
|
||||
if (-not $app.Name.Contains('_')) { continue }
|
||||
$newName = $app.Name.Replace('_', '-')
|
||||
$newManifest = Join-Path $BucketPath "bucket\$newName.json"
|
||||
if (-not (Test-Path -LiteralPath $newManifest)) {
|
||||
$newManifest = Join-Path $BucketPath "$newName.json"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $newManifest)) { continue }
|
||||
$current = Join-Path $app.FullName 'current'
|
||||
$installFile = Join-Path $current 'install.json'
|
||||
if (-not (Test-Path -LiteralPath $installFile)) { continue }
|
||||
$install = [IO.File]::ReadAllText($installFile) | ConvertFrom-Json
|
||||
if ($install.bucket -notin @('elexis', 'streichtech', 'medelexis')) { continue }
|
||||
$destination = Join-Path $apps $newName
|
||||
if (Test-Path -LiteralPath $destination) {
|
||||
throw "'$($app.Name)' und '$newName' sind beide installiert. Keine automatische Zusammenführung."
|
||||
}
|
||||
$link = Get-Item -LiteralPath $current -Force
|
||||
if ($link.LinkType -ne 'Junction') {
|
||||
throw "Für '$($app.Name)' wird eine Scoop-current-Junction erwartet. Keine automatische Umbenennung."
|
||||
}
|
||||
$versionPath = [IO.Path]::GetFullPath([string] @($link.Target)[0])
|
||||
if ((Split-Path $versionPath -Parent) -ne $app.FullName) {
|
||||
throw "Die current-Verknüpfung von '$($app.Name)' zeigt ausserhalb des Paketordners."
|
||||
}
|
||||
$persistSource = Join-Path $ScoopRoot "persist\$($app.Name)"
|
||||
$persistTarget = Join-Path $ScoopRoot "persist\$newName"
|
||||
if ((Test-Path -LiteralPath $persistSource) -and (Test-Path -LiteralPath $persistTarget)) {
|
||||
throw "Beide Persistenzordner für '$($app.Name)' und '$newName' existieren. Keine automatische Zusammenführung."
|
||||
}
|
||||
Write-Host "Paket umbenennen: $($app.Name) -> $newName"
|
||||
[pscustomobject]@{
|
||||
OldName = $app.Name; NewName = $newName; Source = $app.FullName
|
||||
Destination = $destination; Version = (Split-Path $versionPath -Leaf)
|
||||
PersistSource = $persistSource; PersistTarget = $persistTarget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$installCli = (Test-Path -LiteralPath (Join-Path $apps 'ee-cli')) -or
|
||||
-not (Test-Path -LiteralPath (Join-Path $apps 'myelexis-cli'))
|
||||
$obsoleteApps = @(@('ee-iso', 'ee_iso', 'ee-cli') | Where-Object {
|
||||
Test-Path -LiteralPath (Join-Path $apps $_)
|
||||
})
|
||||
if ($installCli -and (Test-Path -LiteralPath (Join-Path $apps 'myelexis-cli'))) {
|
||||
$obsoleteApps += 'myelexis-cli'
|
||||
}
|
||||
foreach ($obsolete in $obsoleteApps) {
|
||||
Write-Host "Veraltetes Paket deinstallieren: $obsolete"
|
||||
}
|
||||
if ($installCli) { Write-Host 'MyElexis CLI wird aus medelexis neu installiert.' }
|
||||
|
||||
$renameSource = $null
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
if (-not (Test-MedelexisRepository $target)) {
|
||||
throw 'Der vorhandene Bucket medelexis zeigt auf ein anderes Repository. Keine Änderung.'
|
||||
}
|
||||
Write-Host 'Bucket medelexis ist bereits korrekt eingerichtet.'
|
||||
} else {
|
||||
foreach ($oldName in @('elexis', 'streichtech')) {
|
||||
$candidate = Join-Path $ScoopRoot "buckets\$oldName"
|
||||
if (Test-MedelexisRepository $candidate) {
|
||||
$renameSource = $candidate
|
||||
Write-Host "Bucket umbenennen: $oldName -> $targetName"
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $renameSource) { Write-Host "Bucket hinzufügen: $targetName -> $repository" }
|
||||
}
|
||||
|
||||
$changes = @(
|
||||
foreach ($app in Get-ChildItem -LiteralPath $apps -Directory) {
|
||||
if ($app.Name -in $obsoleteApps) { continue }
|
||||
foreach ($version in Get-ChildItem -LiteralPath $app.FullName -Directory) {
|
||||
# current und andere Verknüpfungen nicht nochmals bearbeiten.
|
||||
if ($version.Attributes -band [IO.FileAttributes]::ReparsePoint) { continue }
|
||||
$file = Join-Path $version.FullName 'install.json'
|
||||
if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { continue }
|
||||
$original = [IO.File]::ReadAllText($file)
|
||||
$data = $original | ConvertFrom-Json
|
||||
if ($data.bucket -notin @('elexis', 'streichtech')) { continue }
|
||||
$pattern = '(?i)("bucket"\s*:\s*)"(?:elexis|streichtech)"'
|
||||
$updated = [regex]::Replace($original, $pattern, '${1}"medelexis"')
|
||||
if (($updated | ConvertFrom-Json).bucket -ne $targetName) {
|
||||
throw "Bucket-Zuordnung konnte nicht angepasst werden: $file"
|
||||
}
|
||||
Write-Host "$($app.Name) / $($version.Name): $($data.bucket) -> $targetName"
|
||||
[pscustomobject]@{ File = $file; Original = $original; Updated = $updated; App = $app.Name }
|
||||
}
|
||||
}
|
||||
)
|
||||
$manifestRoot = $(if (Test-Path -LiteralPath $target) { $target } else { $renameSource })
|
||||
$renames = @(Get-PackageRenames $manifestRoot)
|
||||
|
||||
if (-not $Apply) {
|
||||
foreach ($oldName in @('elexis', 'streichtech')) {
|
||||
if (Test-Path -LiteralPath (Join-Path $ScoopRoot "buckets\$oldName")) {
|
||||
Write-Host "Alter Bucket wird nach der Umstellung entfernt oder umbenannt: $oldName"
|
||||
}
|
||||
}
|
||||
Write-Host "Probelauf: $($changes.Count) install.json-Dateien würden geändert."
|
||||
Write-Host "Probelauf: $($renames.Count) Paketnamen würden umgestellt."
|
||||
Write-Host "Probelauf: $(@($obsoleteApps).Count) alte Pakete würden deinstalliert."
|
||||
Write-Host 'Mit -Apply wird optional ein Auto-Update um 03:00 Uhr mit Nachholen eingerichtet (y/n).'
|
||||
Write-Host 'Mit -Apply anwenden. Es werden keine Sicherungen erstellt.'
|
||||
return
|
||||
}
|
||||
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'ScoopRoot muss der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $target)) {
|
||||
if ($renameSource) {
|
||||
$bucketRoot = (Resolve-Path -LiteralPath (Join-Path $ScoopRoot 'buckets')).Path
|
||||
$sourcePath = (Resolve-Path -LiteralPath $renameSource).Path
|
||||
$targetPath = [IO.Path]::GetFullPath($target)
|
||||
if ((Split-Path $sourcePath -Parent) -ne $bucketRoot -or
|
||||
(Split-Path $targetPath -Parent) -ne $bucketRoot) {
|
||||
throw 'Bucket-Pfade liegen ausserhalb des erwarteten Verzeichnisses. Keine Umbenennung.'
|
||||
}
|
||||
[IO.Directory]::Move($sourcePath, $targetPath)
|
||||
} else {
|
||||
# scoop verwendet dieselbe Root-Variable für benutzerdefinierte Installationen.
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne
|
||||
[IO.Path]::GetFullPath($(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })).TrimEnd('\')) {
|
||||
throw 'ScoopRoot weicht von der aktiven Scoop-Installation ab; Bucket bitte dort zuerst einrichten.'
|
||||
}
|
||||
& scoop bucket add $targetName $repository
|
||||
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $target)) {
|
||||
throw 'Bucket konnte nicht hinzugefügt werden. Installationsmetadaten bleiben unverändert.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Falls der Ziel-Bucket erst hinzugefügt wurde, stehen seine Manifestnamen jetzt bereit.
|
||||
if (-not $manifestRoot) { $renames = @(Get-PackageRenames $target) }
|
||||
|
||||
# Vor dem ersten Schreiben prüfen, ob während des Probelaufs Dateien geändert wurden.
|
||||
foreach ($change in $changes) {
|
||||
if ([IO.File]::ReadAllText($change.File) -cne $change.Original) {
|
||||
throw 'Installationsmetadaten wurden zwischenzeitlich geändert. Skript bitte erneut starten.'
|
||||
}
|
||||
}
|
||||
foreach ($change in $changes) {
|
||||
[IO.File]::WriteAllText($change.File, $change.Updated, [Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
if ($installCli -and -not (Test-Path -LiteralPath (Join-Path $target 'bucket\myelexis-cli.json')) -and
|
||||
-not (Test-Path -LiteralPath (Join-Path $target 'myelexis-cli.json'))) {
|
||||
throw 'Manifest myelexis-cli fehlt im Ziel-Bucket. Keine Deinstallation.'
|
||||
}
|
||||
foreach ($obsolete in $obsoleteApps) {
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'Für Deinstallationen muss ScoopRoot der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
& scoop uninstall $obsolete
|
||||
if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath (Join-Path $apps $obsolete))) {
|
||||
throw "'$obsolete' konnte nicht deinstalliert werden."
|
||||
}
|
||||
}
|
||||
if ($installCli) {
|
||||
Write-Host 'MyElexis CLI aus medelexis neu installieren ...'
|
||||
& scoop install 'medelexis/myelexis-cli'
|
||||
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath (Join-Path $apps 'myelexis-cli\current\myelexis-cli.exe'))) {
|
||||
throw 'MyElexis CLI konnte nicht vollständig installiert werden.'
|
||||
}
|
||||
}
|
||||
foreach ($rename in $renames) {
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'Für Paketumbenennungen muss ScoopRoot der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
$appsRoot = (Resolve-Path -LiteralPath $apps).Path
|
||||
if ((Split-Path $rename.Source -Parent) -ne $appsRoot -or
|
||||
(Split-Path $rename.Destination -Parent) -ne $appsRoot) {
|
||||
throw 'Paketpfade liegen ausserhalb des Apps-Verzeichnisses.'
|
||||
}
|
||||
if (Test-Path -LiteralPath $rename.PersistSource) {
|
||||
$persistRoot = (Resolve-Path -LiteralPath (Join-Path $ScoopRoot 'persist')).Path
|
||||
if ((Split-Path $rename.PersistSource -Parent) -ne $persistRoot -or
|
||||
(Split-Path $rename.PersistTarget -Parent) -ne $persistRoot) {
|
||||
throw 'Persistenzpfade liegen ausserhalb des Persist-Verzeichnisses.'
|
||||
}
|
||||
[IO.Directory]::Move($rename.PersistSource, $rename.PersistTarget)
|
||||
}
|
||||
$oldCurrent = Join-Path $rename.Source 'current'
|
||||
& attrib -R /L $oldCurrent
|
||||
if ($LASTEXITCODE -ne 0) { throw 'current-Verknüpfung konnte nicht freigegeben werden.' }
|
||||
# Nur die Junction entfernen, ohne rekursiv ihren Inhalt zu löschen.
|
||||
[IO.Directory]::Delete($oldCurrent)
|
||||
[IO.Directory]::Move($rename.Source, $rename.Destination)
|
||||
$newCurrent = Join-Path $rename.Destination 'current'
|
||||
$newVersion = Join-Path $rename.Destination $rename.Version
|
||||
New-Item -ItemType Junction -Path $newCurrent -Target $newVersion | Out-Null
|
||||
Write-Host "Startverknüpfungen für $($rename.NewName) erneuern ..."
|
||||
& scoop reset "$($rename.NewName)@$($rename.Version)"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Scoop reset für '$($rename.NewName)' fehlgeschlagen." }
|
||||
}
|
||||
foreach ($oldName in @('elexis', 'streichtech')) {
|
||||
$oldPath = Join-Path $ScoopRoot "buckets\$oldName"
|
||||
if (-not (Test-Path -LiteralPath $oldPath)) { continue }
|
||||
$activeRoot = $(if ($env:SCOOP) { $env:SCOOP } else { Join-Path $env:USERPROFILE 'scoop' })
|
||||
if ([IO.Path]::GetFullPath($ScoopRoot).TrimEnd('\') -ne [IO.Path]::GetFullPath($activeRoot).TrimEnd('\')) {
|
||||
throw 'Zum Entfernen alter Buckets muss ScoopRoot der aktiven Scoop-Installation entsprechen.'
|
||||
}
|
||||
Write-Host "Alten Bucket entfernen: $oldName"
|
||||
& scoop bucket rm $oldName
|
||||
if ($LASTEXITCODE -ne 0 -or (Test-Path -LiteralPath $oldPath)) {
|
||||
throw "Alter Bucket '$oldName' konnte nicht entfernt werden. Die Umstellung kann erneut ausgeführt werden."
|
||||
}
|
||||
}
|
||||
foreach ($name in @((Get-ChildItem -LiteralPath $apps -Directory).Name)) {
|
||||
$installFile = Join-Path $apps "$name\current\install.json"
|
||||
if (-not (Test-Path -LiteralPath $installFile)) { continue }
|
||||
if (([IO.File]::ReadAllText($installFile) | ConvertFrom-Json).bucket -ne $targetName) { continue }
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $target "bucket\$name.json")) -and
|
||||
-not (Test-Path -LiteralPath (Join-Path $target "$name.json"))) {
|
||||
Write-Warning "Für '$name' fehlt weiterhin ein gleichnamiges Manifest."
|
||||
}
|
||||
}
|
||||
Write-Host "Fertig: $($changes.Count) install.json-Dateien auf medelexis umgestellt."
|
||||
Write-Host "Paketnamen umgestellt: $($renames.Count)."
|
||||
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 {
|
||||
$autoUpdateAnswer = (Read-Host 'Bestehende Scoop-Auto-Update-Aufgabe reparieren (ersetzen)? [y/n]').Trim().ToLowerInvariant()
|
||||
} 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') {
|
||||
$autoUpdateDirectory = Join-Path $ScoopRoot 'scripts\medelexis'
|
||||
New-Item -ItemType Directory -Path $autoUpdateDirectory -Force | Out-Null
|
||||
$autoUpdateScript = Join-Path $autoUpdateDirectory 'auto-update.ps1'
|
||||
$downloadPath = Join-Path $autoUpdateDirectory ('auto-update-{0}.download' -f [guid]::NewGuid())
|
||||
try {
|
||||
$autoUpdateUrl = "$repository/raw/branch/master/scripts/auto-update.ps1"
|
||||
Invoke-WebRequest -Uri $autoUpdateUrl -OutFile $downloadPath -UseBasicParsing
|
||||
$parseTokens = $null
|
||||
$parseErrors = $null
|
||||
$downloadedScript = [Management.Automation.Language.Parser]::ParseFile($downloadPath, [ref] $parseTokens, [ref] $parseErrors)
|
||||
if ($parseErrors.Count -gt 0 -or -not $downloadedScript.ParamBlock -or
|
||||
'ScoopRoot' -notin @($downloadedScript.ParamBlock.Parameters.Name.VariablePath.UserPath)) {
|
||||
throw 'Das heruntergeladene Auto-Update-Skript ist kein gültiges Updateskript.'
|
||||
}
|
||||
Move-Item -LiteralPath $downloadPath -Destination $autoUpdateScript -Force
|
||||
# Nur die Aufgabe einrichten; das Updateskript wird durch die Aufgabe gestartet.
|
||||
# Bei einer Reparatur zuerst eine laufende Aufgabe anhalten,
|
||||
# damit ihr Starter ersetzt werden kann.
|
||||
foreach ($existingTask in $existingTasks) {
|
||||
if ($existingTask.State -eq 'Running') {
|
||||
Stop-ScheduledTask -TaskName $existingTask.TaskName
|
||||
}
|
||||
}
|
||||
# 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'
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Limited
|
||||
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RunOnlyIfNetworkAvailable `
|
||||
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 4)
|
||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||
-Principal $principal -Settings $settings -Force `
|
||||
-Description 'Tägliche Aktualisierung der installierten Elemente (Elexis ausgeschlossen)' | Out-Null
|
||||
# Die frühere Aufgabe dieses Benutzers entfernen, damit nur ein Update läuft.
|
||||
if (Get-ScheduledTask -TaskName $legacyTaskName -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName $legacyTaskName -Confirm:$false
|
||||
}
|
||||
Write-Host "Scoop-Auto-Update eingeschaltet: $taskName"
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $downloadPath) { Remove-Item -LiteralPath $downloadPath }
|
||||
}
|
||||
} elseif (-not $autoUpdateRemoved) {
|
||||
Write-Host 'Keine Änderung an Scoop-Auto-Update vorgenommen.'
|
||||
}
|
||||
Reference in new issue
Block a user