@echo off
rem ===========================================================================
rem  Track of the day - Avalonia-Beta einrichten
rem
rem  Doppelklick genuegt. Verlangt Windows Administratorrechte, dann nur fuer
rem  den Eintrag des Zertifikats - beim naechsten Mal entfaellt das. Wer die
rem  WPF-Beta schon hat, wird gar nicht erst gefragt: Es ist dasselbe
rem  Zertifikat.
rem
rem  Vor der Installation wird eine Sicherung der eigenen Daten angelegt
rem  (Dokumente\Track of the day\Backups). Das ist Absicht: Die Avalonia-
rem  Fassung arbeitet auf denselben Daten wie die Fassung aus dem Store.
rem
rem  Am Ende steht auf dem Desktop ein Protokoll.
rem
rem  Nur pruefen, nichts aendern:   TDMac-Beta-installieren.cmd pruefen
rem  Ohne Datensicherung:          TDMac-Beta-installieren.cmd ohnesicherung
rem ---------------------------------------------------------------------------
rem  Diese Datei ist nur die Startrampe. Der eigentliche Ablauf ist das
rem  PowerShell-Skript weiter unten (TDMac-Beta-einrichten.ps1); es wird von
rem  hier herausgeschnitten und gestartet. Die Batch-Zeilen enden bei
rem  "exit /b" - alles danach sieht der Befehlsprozessor nie.
rem ===========================================================================

rem ★★★ SCHLUSS-BACKSLASH WEG, BEVOR der Pfad weitergereicht wird.
rem  %~dp0 endet immer mit einem Backslash. Enthaelt der Ordner Leerzeichen -
rem  etwa "C:\Workspaces\Track of the Day\..." -, setzt PowerShell beim Aufruf
rem  des naechsten Prozesses Anfuehrungszeichen darum, und der Backslash maskiert
rem  dann das schliessende Anfuehrungszeichen: Ordner und Modus verschmelzen zu
rem  EINEM Wert, der Pruefmodus ging verloren und Test-Path warf "Illegales
rem  Zeichen im Pfad". Gemessen am 08.09.2026.
set "TDORDNER=%~dp0"
if "%TDORDNER:~-1%"=="\" set "TDORDNER=%TDORDNER:~0,-1%"

powershell -NoProfile -ExecutionPolicy Bypass -Command "$c = Get-Content -Raw -LiteralPath '%~f0' -Encoding UTF8; $m = '#--- POWERSHELL' + ' AB HIER ---'; $i = $c.LastIndexOf($m); if ($i -lt 0) { Write-Host 'Die Datei ist unvollstaendig - bitte neu herunterladen.' -ForegroundColor Red; pause; exit 1 }; $f = Join-Path $env:TEMP 'TDMac-Beta-einrichten.ps1'; Set-Content -LiteralPath $f -Value $c.Substring($i) -Encoding UTF8; & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $f '%TDORDNER%' '%1'"

exit /b
#--- POWERSHELL AB HIER ---
<#
============================================================================
 Track of the day - Avalonia-Beta einrichten
 ---------------------------------------------------------------------------
 Traegt bei Bedarf das Entwicklerzertifikat an der RICHTIGEN Stelle ein,
 legt eine Sicherung der eigenen Daten an und installiert das MSIX-Paket.

 Aufruf:
   Doppelklick auf TDMac-Beta-installieren.cmd
   oder:  .\TDMac-Beta-einrichten.ps1
   nur pruefen, nichts aendern:   .\TDMac-Beta-einrichten.ps1 -Modus pruefen
   ohne Datensicherung:           .\TDMac-Beta-einrichten.ps1 -Modus ohnesicherung

 ★★★ WAS DIESE BETA IST: die neue, plattformuebergreifende Fassung von
 "Track of the day" (Avalonia statt WPF). Sie laeuft NEBEN der Fassung aus
 dem Microsoft Store - beide bleiben installiert, beide sind im Startmenue
 zu finden. Gedacht ist sie zum Vergleichen: dieselbe Aufgabe einmal hier,
 einmal dort, und dann Rueckmeldung, wo es abweicht.

 ★★★ WARUM ES DIESES SKRIPT GIBT: Der Zertifikats-Assistent von Windows steht
 standardmaessig auf "Aktueller Benutzer". Genau dort nuetzt das Zertifikat fuer
 ein MSIX-Paket aber NICHTS - Windows sucht es unter "Lokaler Computer" im
 Speicher "Vertrauenswuerdige Personen". Wer den Assistenten mit den Vorgaben
 durchklickt, bekommt deshalb zwangslaeufig "Das Zertifikat wird nicht erkannt".

 ★ Wer die TD8-Beta schon hat, hat das Zertifikat bereits: Es ist dasselbe.
   Dann entfaellt die Rueckfrage von Windows, und es bleibt ein Doppelklick.

 ★ Sicherheitsnetz: Eingetragen wird NUR ein Zertifikat mit genau dem unten
   festgeschriebenen Fingerabdruck, und installiert wird das Paket nur, wenn
   seine Signatur gueltig ist UND von genau diesem Zertifikat stammt. Ein
   Skript, das blind Zertifikate in den Vertrauensspeicher legt, waere genau
   das, wovor man Nutzer warnt.
============================================================================
#>
param(
    # Ordner, in dem .cer/.msix bereits liegen koennten (die .cmd reicht ihn durch,
    # weil das Skript von dort nach %TEMP% ausgepackt wird).
    [string]$Ordner,
    # "pruefen" = nur berichten, nichts veraendern.
    # "ohnesicherung" = installieren, aber keine Datensicherung anlegen.
    [string]$Modus
)

# ★ BEIDES ist noetig, sonst steht dort "Vertrauenswürdige" als "VertrauenswÃ¼rdige":
#   die Zeichentabelle des Fensters auf UTF-8 stellen UND .NET UTF-8 schreiben lassen.
& chcp.com 65001 | Out-Null
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$ErrorActionPreference = 'Stop'

$NurPruefen     = ($Modus -eq 'pruefen')
$OhneSicherung  = ($Modus -eq 'ohnesicherung')
$Fingerabdruck  = '677866FB5CA8AA468CF69943C9030630F95DA09A'
$Basis          = 'https://tdvalhalla.blob.core.windows.net/apps/tdmac-beta'
$PaketName      = 'TrackOfTheDay-Mac-Beta-x64.msix'
$CerName        = 'TrackOfTheDay-Beta.cer'
# Muster fuer Get-AppxPackage. Die Store-Fassung heisst "…Trackoftheday" OHNE Zusatz,
# die WPF-Beta "…Trackoftheday.Beta" - beide duerfen hier NICHT mit angesprochen werden.
$PaketMuster    = '*Trackoftheday.MacBeta*'

# ★★★ %~dp0 endet IMMER mit einem Backslash. Ein Backslash unmittelbar vor dem
#   schliessenden Anfuehrungszeichen maskiert dieses beim Weiterreichen an einen neuen
#   Prozess - im erhoehten Lauf kam der Ordner dann MIT Anfuehrungszeichen an, und
#   Test-Path warf "Illegales Zeichen im Pfad" (Nutzerbefund 08.09.2026, DELL XPS17).
#   Deshalb: Anfuehrungszeichen und Schluss-Backslashes weg, bevor der Wert benutzt
#   ODER weitergereicht wird.
if ($Ordner) { $Ordner = $Ordner.Trim().Trim('"').TrimEnd('\') }
if (-not $Ordner) { $Ordner = $PSScriptRoot }

$istAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
            ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

# ── Selbst erhöhen ──────────────────────────────────────────────────────────
# ★ Ohne Administratorrechte lässt sich nichts in den Speicher des lokalen
#   Rechners eintragen. Statt den Nutzer zu belehren, holt sich das Skript die
#   Rechte selbst - ein Klick auf "Ja" genügt.
# ★★★ NUR wenn es wirklich noetig ist, naemlich zum Eintragen des Zertifikats.
#   Add-AppxPackage braucht KEINE Administratorrechte. Wer das Zertifikat schon hat -
#   also jeder Tester der TD8-Beta und jeder ab dem zweiten Mal - soll fuer ein
#   Update nicht wieder eine Rueckfrage von Windows wegklicken muessen.
function ErhoeheUndBeende {
    $eigenerPfad = if ($PSCommandPath) { $PSCommandPath } else { $MyInvocation.MyCommand.Path }
    Write-Host ""
    Write-Host "Für den Eintrag des Zertifikats werden Administratorrechte gebraucht." -ForegroundColor Yellow
    Write-Host "Windows fragt gleich nach - bitte mit 'Ja' bestätigen." -ForegroundColor Yellow
    # ★ $Ordner ist oben schon von Anfuehrungszeichen und Schluss-Backslash befreit -
    #   sonst maskiert der Backslash hier das schliessende Anfuehrungszeichen.
    $argumente = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-NoExit', '-File', ('"' + $eigenerPfad + '"'), ('"' + $Ordner + '"'))
    if ($Modus) { $argumente += ('"' + $Modus + '"') }
    try {
        Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList $argumente
    } catch {
        Write-Host "Abgebrochen - ohne Administratorrechte lässt sich das Zertifikat nicht eintragen." -ForegroundColor Red
        Read-Host "Eingabetaste zum Schließen"
    }
}

$desktop = [Environment]::GetFolderPath('Desktop')
if (-not (Test-Path $desktop)) { $desktop = $env:TEMP }
$Protokoll = Join-Path $desktop 'TD-Avalonia-Beta-Einrichtung.txt'
"Track of the day - Avalonia-Beta einrichten, $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" |
    Set-Content -LiteralPath $Protokoll -Encoding UTF8

function Sag($text, $farbe = 'Gray') {
    Write-Host $text -ForegroundColor $farbe
    Add-Content -LiteralPath $Protokoll -Value $text -Encoding UTF8
}
function Titel($text) { Sag ""; Sag $text 'Cyan' }

Titel "Track of the day - Avalonia-Beta einrichten"
Sag   "Protokoll: $Protokoll"
if ($NurPruefen)    { Sag "PRÜFMODUS - es wird nichts verändert." 'Yellow' }
if ($OhneSicherung) { Sag "Es wird KEINE Datensicherung angelegt (so gewünscht)." 'Yellow' }

$fehler  = @()
$hinweis = @()

# ── 1. Voraussetzungen ──────────────────────────────────────────────────────
Titel "1) Voraussetzungen"
$os = Get-CimInstance Win32_OperatingSystem
Sag "   Windows .................. $($os.Caption) $($os.Version)"
Sag "   Angemeldet als ........... $env:USERNAME"
Sag "   Administratorrechte ...... $(if ($istAdmin) { 'ja' } else { 'nein' })"

$appInst = Get-AppxPackage -Name Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue
Sag "   App-Installer ............ $(if ($appInst) { $appInst.Version } else { 'FEHLT' })"
if (-not $appInst) {
    $fehler += "Der 'App Installer' von Microsoft fehlt. Er kommt aus dem Microsoft Store (dort nach 'App Installer' suchen)."
}

# ★ Welche Fassungen liegen sonst noch da? Das gehoert ins Protokoll, weil genau der
#   Vergleich der Zweck dieser Beta ist - und weil der Tester in seiner Rueckmeldung
#   sonst nicht sagen kann, GEGEN WAS er verglichen hat.
$store   = Get-AppxPackage -Name '*Trackoftheday' -ErrorAction SilentlyContinue |
           Where-Object { $_.Name -notmatch '\.(Mac)?Beta$' } | Select-Object -First 1
$wpfBeta = Get-AppxPackage -Name '*Trackoftheday.Beta' -ErrorAction SilentlyContinue | Select-Object -First 1
Sag "   Store-Fassung (WPF) ...... $(if ($store)   { $store.Version }   else { 'nicht installiert' })"
Sag "   WPF-Beta ................. $(if ($wpfBeta) { $wpfBeta.Version } else { 'nicht installiert' })"

# ── 2. Zertifikat ───────────────────────────────────────────────────────────
Titel "2) Zertifikat"

# ★★★ NICHT ueber das Cert:-Laufwerk pruefen. Gemessen am 08.09.2026: Wird
#   PowerShell aus cmd.exe heraus gestartet - also genau ueber die Startrampe -,
#   liefert der Cert:-Anbieter fuer JEDEN Speicher 0 Treffer, obwohl die
#   Zertifikate da sind (Root meldete 0 statt 76). Die Store-Schnittstelle
#   liefert im selben Lauf korrekt 7. Eine Pruefung, die im Ernstfall luegt,
#   ist schlimmer als gar keine - deshalb hier ausschliesslich die Store-API.
# ★★★ SIGNATUR OHNE Get-AuthenticodeSignature. Das Cmdlet steckt im Modul
#   Microsoft.PowerShell.Security, und genau dieses Modul laesst sich auf manchen
#   Rechnern in diesem Startweg NICHT laden (gemessen am 08.09.2026). Dasselbe
#   Modul stellt auch das Cert:-Laufwerk. Der Signierer wird darum direkt aus
#   AppxSignature.p7x im Paket gelesen: 4 Byte Kennung "PKCX", dahinter PKCS#7.
# ★ Was hier NICHT geprueft wird, ist die Vertrauenskette - das macht Windows beim
#   Installieren selbst und lehnt sonst mit 0x800B0109 ab. Hier geht es nur um die
#   Frage "stammt das Paket von UNSEREM Zertifikat".
function SignaturFingerabdruck($msixPfad) {
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    Add-Type -AssemblyName System.Security
    $zip = [System.IO.Compression.ZipFile]::OpenRead($msixPfad)
    try {
        $eintrag = $zip.Entries | Where-Object { $_.FullName -eq 'AppxSignature.p7x' }
        if (-not $eintrag) { throw "Das Paket enthält keine Signatur (AppxSignature.p7x fehlt)." }
        $ms = New-Object System.IO.MemoryStream
        $strom = $eintrag.Open(); $strom.CopyTo($ms); $strom.Close()
        $roh = $ms.ToArray()
        if ($roh.Length -lt 5) { throw "Die Signatur im Paket ist unbrauchbar." }
        $cms = New-Object System.Security.Cryptography.Pkcs.SignedCms
        $cms.Decode($roh[4..($roh.Length - 1)])
        return $cms.SignerInfos[0].Certificate.Thumbprint
    } finally {
        $zip.Dispose()
    }
}

# ★ Ein "liegt daneben"-Pfad darf nie werfen: Der Ordner kommt von aussen.
function LiegtDatei($ordner, $name) {
    try {
        if (-not $ordner) { return $null }
        $pfad = Join-Path $ordner $name
        if (Test-Path -LiteralPath $pfad) { return $pfad }
    } catch { }
    return $null
}

function Liegt($speicher, $ort) {
    try {
        $s = New-Object System.Security.Cryptography.X509Certificates.X509Store($speicher, $ort)
        $s.Open('ReadOnly')
        $treffer = [bool]($s.Certificates | Where-Object { $_.Thumbprint -eq $Fingerabdruck })
        $s.Close()
        return $treffer
    } catch {
        return $false
    }
}

$imRechner  = (Liegt 'TrustedPeople' 'LocalMachine')
$imStamm    = (Liegt 'Root'          'LocalMachine')
$richtig    = $imRechner -or $imStamm
$falsch     = (Liegt 'TrustedPeople' 'CurrentUser') -or (Liegt 'Root' 'CurrentUser')

Sag "   Lokaler Computer / Vertrauenswürdige Personen ... $(if ($imRechner) { 'ja' } else { 'nein' })"
Sag "   Lokaler Computer / Vertrauenswürdige Stämme ..... $(if ($imStamm) { 'ja' } else { 'nein' })"
Sag "   Aktueller Benutzer (hilft NICHT) ................ $(if ($falsch) { 'ja' } else { 'nein' })"

if ($richtig) {
    Sag "   -> Das Zertifikat liegt richtig." 'Green'
    if ($wpfBeta) { Sag "      (Es ist dasselbe wie bei der WPF-Beta - deshalb war hier nichts zu tun.)" }
} elseif ($NurPruefen) {
    Sag "   -> Das Zertifikat fehlt im richtigen Speicher." 'Yellow'
    if ($falsch) { Sag "      (Es liegt beim aktuellen Benutzer - genau die Falle des Assistenten.)" 'Yellow' }
} elseif (-not $istAdmin) {
    Sag "   -> Das Zertifikat fehlt im richtigen Speicher; dafür braucht es Administratorrechte." 'Yellow'
    ErhoeheUndBeende
    return
} else {
    try {
        $cer = Join-Path $env:TEMP $CerName
        $daneben = LiegtDatei $Ordner $CerName
        if ($daneben) {
            Copy-Item -LiteralPath $daneben -Destination $cer -Force
            Sag "   Zertifikat liegt neben dem Skript - wird von dort genommen."
        } else {
            Sag "   Zertifikat wird geladen ..."
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            (New-Object Net.WebClient).DownloadFile("$Basis/$CerName", $cer)
        }

        # ★ Erst prüfen, dann eintragen.
        $geladen = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $cer
        Sag "   Fingerabdruck ............ $($geladen.Thumbprint)"
        if ($geladen.Thumbprint -ne $Fingerabdruck) {
            throw "Der Fingerabdruck stimmt NICHT mit dem erwarteten überein. Es wird nichts eingetragen."
        }

        # ★ Auch das Eintragen ueber die Store-API - Import-Certificate haengt am
        #   Cert:-Anbieter und wuerde sich denselben Ausfall einfangen.
        $zielSpeicher = New-Object System.Security.Cryptography.X509Certificates.X509Store('TrustedPeople', 'LocalMachine')
        $zielSpeicher.Open('ReadWrite')
        $zielSpeicher.Add($geladen)
        $zielSpeicher.Close()
        Sag "   -> Eingetragen unter: Lokaler Computer / Vertrauenswürdige Personen" 'Green'
        $richtig = $true
    } catch {
        Sag "   -> Fehlgeschlagen: $($_.Exception.Message)" 'Red'
        $fehler += "Das Zertifikat konnte nicht eingetragen werden: $($_.Exception.Message)"
    }
}

# ── 3. Datensicherung ───────────────────────────────────────────────────────
# ★★★ WARUM DIESER SCHRITT NEU IST (die TD8-Beta hatte ihn nicht):
#   Die Avalonia-Fassung arbeitet auf GENAU DENSELBEN Daten wie die Store-Fassung -
#   dieselbe Datenbank, dieselbe Garage, dieselben Routing-Profile unter
#   "Dokumente\Track of the day". Das ist gewollt, denn nur so laesst sich 1:1
#   vergleichen. Es heisst aber auch: Ein Fehler in der Testfassung trifft die
#   echten Daten, und zwar auch die der Store-Fassung.
#   Eine Kopie VOR der ersten Installation kostet ein paar Minuten und ist das
#   einzige, was im Ernstfall hilft.
Titel "3) Sicherung der eigenen Daten"
$datenOrdner = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'Track of the day'

# Nur das Unersetzliche. Karten, Kacheln und Zwischenspeicher laedt das Programm
# jederzeit neu - sie wuerden die Sicherung auf zig Gigabyte aufblaehen.
# ★ Die beiden SQLite-Begleitdateien .db-wal und .db-shm gehoeren DAZU: Ohne sie fehlen
#   der zurueckgesicherten Datenbank die zuletzt geschriebenen Aenderungen.
$sichern = @(
    'TrackOfTheDay.db', 'TrackOfTheDay.db-wal', 'TrackOfTheDay.db-shm',
    'DatabaseFiles', 'Garage', 'RoutingProfiles',
    'Settings', 'Icons', 'Archive', 'Workspace', 'Regions', 'AiPrompts'
)

# ★★★ Compress-Archive scheitert an JEDER Datei, die gerade offen ist - und die
#   Datenbank ist offen, sobald "Track of the day" laeuft. Genau das ist beim ersten
#   echten Lauf passiert (21.09.2026): "Der Prozess kann nicht auf die Datei
#   TrackOfTheDay.db zugreifen". Ein Tester hat das Programm aber fast immer offen.
#   Deshalb wird nicht direkt gepackt, sondern zuerst mit FileShare.ReadWrite
#   weggelesen - das gelingt auch bei gesperrten Dateien - und dann das Abbild gepackt.
function KopiereAuchWennOffen($quelle, $ziel) {
    $q = [System.IO.File]::Open($quelle, [System.IO.FileMode]::Open,
             [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
    try {
        $z = [System.IO.File]::Create($ziel)
        try { $q.CopyTo($z) } finally { $z.Dispose() }
    } finally { $q.Dispose() }
}

if (-not (Test-Path -LiteralPath $datenOrdner)) {
    Sag "   Kein Datenordner gefunden - es gibt nichts zu sichern."
    Sag "   ($datenOrdner)"
} else {
    $vorhanden = @()
    $bytes = 0L
    foreach ($n in $sichern) {
        $p = Join-Path $datenOrdner $n
        if (Test-Path -LiteralPath $p) {
            $vorhanden += $p
            $bytes += (Get-ChildItem -LiteralPath $p -Recurse -File -ErrorAction SilentlyContinue |
                       Measure-Object Length -Sum).Sum
        }
    }
    Sag ("   Zu sichern ............... {0} Bereiche, rund {1:N0} MB" -f $vorhanden.Count, ($bytes / 1MB))

    if ($NurPruefen -or $OhneSicherung) {
        Sag "   (Übersprungen.)" 'Yellow'
    } elseif ($vorhanden.Count -eq 0) {
        Sag "   Nichts Sicherungswürdiges gefunden."
    } else {
        # ★ Ein laufendes Programm schreibt womoeglich WAEHREND der Sicherung weiter.
        #   Das Wegkopieren gelingt trotzdem, das Abbild kann dann aber aus zwei
        #   Augenblicken stammen. Also darauf hinweisen - und nicht so tun, als sei
        #   eine Sicherung im laufenden Betrieb genauso gut.
        $laufen = @(Get-Process -ErrorAction SilentlyContinue |
                    Where-Object { $_.ProcessName -like 'TrackOfTheDay*' })
        if ($laufen.Count) {
            Sag "   ⚠ Es läuft gerade: $(($laufen | ForEach-Object ProcessName | Sort-Object -Unique) -join ', ')" 'Yellow'
            Sag "     Die Sicherung gelingt trotzdem, wird aber verlässlicher, wenn vorher" 'Yellow'
            Sag "     alle Fenster von 'Track of the day' geschlossen sind." 'Yellow'
        }
        $abbild = $null
        try {
            $ziel = Join-Path $datenOrdner 'Backups'
            New-Item -ItemType Directory -Force -Path $ziel | Out-Null
            $datei = Join-Path $ziel ("Vor-Avalonia-Beta-{0:yyyy-MM-dd-HHmm}.zip" -f (Get-Date))
            Sag "   Wird geschrieben nach:"
            Sag "   $datei"
            Sag "   (Das dauert je nach Datenmenge ein bis zwei Minuten.)"

            # Schritt 1: Abbild in einen Temp-Ordner - auch aus gesperrten Dateien.
            $abbild = Join-Path $env:TEMP ("TD-Sicherung-" + [guid]::NewGuid().ToString('N'))
            New-Item -ItemType Directory -Force -Path $abbild | Out-Null
            $uebersprungen = 0
            foreach ($p in $vorhanden) {
                $name = Split-Path $p -Leaf
                if (Test-Path -LiteralPath $p -PathType Container) {
                    foreach ($f in (Get-ChildItem -LiteralPath $p -Recurse -File -ErrorAction SilentlyContinue)) {
                        $rel  = $f.FullName.Substring($datenOrdner.Length).TrimStart('\')
                        $dest = Join-Path $abbild $rel
                        New-Item -ItemType Directory -Force -Path (Split-Path $dest -Parent) | Out-Null
                        try { KopiereAuchWennOffen $f.FullName $dest } catch { $uebersprungen++ }
                    }
                } else {
                    try { KopiereAuchWennOffen $p (Join-Path $abbild $name) } catch { $uebersprungen++ }
                }
            }
            if ($uebersprungen) {
                Sag "   $uebersprungen Datei(en) ließen sich nicht lesen und fehlen in der Sicherung." 'Yellow'
            }

            # Schritt 2: Das Abbild packen. Hier ist nichts mehr gesperrt.
            # ★ -Force waere hier falsch: Es gibt keinen Grund, eine fremde Datei zu
            #   ueberschreiben. Der Zeitstempel im Namen macht Kollisionen ohnehin
            #   unwahrscheinlich; passiert es doch, soll es auffallen.
            Compress-Archive -Path (Join-Path $abbild '*') -DestinationPath $datei -CompressionLevel Optimal
            $mb = (Get-Item -LiteralPath $datei).Length / 1MB
            Sag ("   -> Sicherung angelegt ({0:N0} MB)." -f $mb) 'Green'
            Sag "      Das ist eine gewöhnliche ZIP-Datei: Im Notfall die Programme schließen"
            Sag "      und den Inhalt zurück nach '$datenOrdner' entpacken."
        } catch {
            Sag "   -> Sicherung fehlgeschlagen: $($_.Exception.Message)" 'Red'
            # ★ KEIN Abbruch, aber auch kein Verschweigen: Der Tester soll selbst
            #   entscheiden, ob er ohne Sicherung weitermacht.
            $hinweis += ("Die Datensicherung ist fehlgeschlagen. Vor dem ersten Start bitte in der " +
                         "Store-Fassung unter Einstellungen -> Datensicherung eine .tdb-Sicherung anlegen.")
        } finally {
            if ($abbild -and (Test-Path -LiteralPath $abbild)) {
                Remove-Item -LiteralPath $abbild -Recurse -Force -ErrorAction SilentlyContinue
            }
        }
    }
}

# ── 4. Paket ────────────────────────────────────────────────────────────────
Titel "4) Programmpaket"
$vorher = Get-AppxPackage -Name $PaketMuster -ErrorAction SilentlyContinue
Sag "   Bereits installiert ...... $(if ($vorher) { $vorher.Version } else { 'nein' })"

if ($NurPruefen) {
    Sag "   (Prüfmodus - es wird nichts installiert.)" 'Yellow'
} elseif (-not $richtig) {
    Sag "   Übersprungen - ohne eingetragenes Zertifikat lehnt Windows die Installation ab." 'Yellow'
} else {
    try {
        $msix = Join-Path $env:TEMP $PaketName
        $daneben = LiegtDatei $Ordner $PaketName
        if ($daneben) {
            $msix = $daneben
            Sag "   Paket liegt neben dem Skript - wird von dort genommen."
        } else {
            Sag "   Paket wird geladen (einige hundert MB, das dauert je nach Leitung ein paar Minuten) ..."
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
            (New-Object Net.WebClient).DownloadFile("$Basis/$PaketName", $msix)
        }
        Sag "   Größe .................... $('{0:N0}' -f (Get-Item $msix).Length) Bytes"

        # ★ Signatur prüfen, BEVOR installiert wird - gegen genau das Zertifikat,
        #   das oben geprüft bzw. eingetragen wurde.
        $sigDaumen = SignaturFingerabdruck $msix
        Sag "   Signiert von ............. $sigDaumen"
        if ($sigDaumen -ne $Fingerabdruck) {
            throw "Das Paket ist nicht mit dem erwarteten Zertifikat signiert."
        }

        Sag "   Wird installiert ..."
        Add-AppxPackage -Path $msix -ForceUpdateFromAnyVersion
        Sag "   -> Installiert." 'Green'
    } catch {
        $meldung = $_.Exception.Message
        Sag "   -> Fehlgeschlagen: $meldung" 'Red'
        if ($meldung -match '0x800B0109') {
            $fehler += "Windows vertraut dem Zertifikat nicht (0x800B0109). Rechner neu starten und das Skript noch einmal ausführen."
        } elseif ($meldung -match '0x80073CFF') {
            $fehler += "Windows lehnt das Paket ab (0x80073CFF) - meist fehlt das Zertifikat im Speicher 'Lokaler Computer'."
        } else {
            $fehler += "Die Installation ist fehlgeschlagen: $meldung"
        }
    }
}

# ── 5. Ergebnis ─────────────────────────────────────────────────────────────
Titel "5) Ergebnis"
$nachher = Get-AppxPackage -Name $PaketMuster -ErrorAction SilentlyContinue
$unveraendert = $nachher -and $vorher -and ($nachher.Version -eq $vorher.Version)
if ($nachher -and (-not $unveraendert -or $NurPruefen)) {
    Sag "   Track of the day - Avalonia-Beta $($nachher.Version) ist installiert." 'Green'
    Sag "   Zu finden im Startmenü unter 'Track of the day - Avalonia-Beta'."
} elseif ($nachher) {
    # ★ Nicht gruen faerben: Es liegt noch die BISHERIGE Fassung da, die Installation
    #   hat nichts bewirkt. Sonst liest sich ein Fehlschlag wie ein Erfolg.
    Sag "   Unverändert - es liegt weiterhin die bisherige Fassung $($nachher.Version) vor." 'Yellow'
} else {
    Sag "   Es ist nichts installiert." 'Yellow'
}

if ($fehler.Count) {
    Titel "Es hat nicht geklappt - das ist zu tun:"
    foreach ($f in $fehler) { Sag "   * $f" 'Yellow' }
    Sag ""
    Sag "   Bitte die Datei '$Protokoll' an den Support schicken." 'Yellow'
} elseif (-not $NurPruefen -and $nachher) {
    Titel "Bitte vor dem ersten Start lesen"
    # ★★★ Der wichtigste Satz des ganzen Skripts. Zwei Programme, die gleichzeitig in
    #   dieselbe SQLite-Datenbank schreiben, sind ein Weg, Daten zu verlieren.
    Sag "   * Die Avalonia-Beta und die Store-Fassung arbeiten auf DENSELBEN Daten." 'Yellow'
    Sag "     Bitte immer nur EINE von beiden gleichzeitig laufen lassen." 'Yellow'
    Sag "   * Zum Vergleichen also: die eine schließen, die andere öffnen."
    foreach ($h in $hinweis) { Sag "   * $h" 'Yellow' }
    Sag ""
    Sag "   Rückmeldungen sind ausdrücklich erwünscht - gerade dort, wo etwas" 'Green'
    Sag "   anders aussieht oder sich anders anfühlt als in der Store-Fassung." 'Green'
}

Sag ""
Sag "Protokoll liegt hier: $Protokoll"
Write-Host ""
Write-Host "Dieses Fenster kann geschlossen werden." -ForegroundColor DarkGray
