KeePass‑Plugins automatisch aktuell halten – mein Setup in Kürze

Problem:

KeePass 2.x bringt keinen Mechanismus mit, um Plugins automatisch zu aktualisieren. Selbst wichtige Erweiterungen wie KeePassRPC (für die Browser‑Integration via „Kee“) melden zwar neue Versionen, doch das Einspielen muss manuell erfolgen.

Randbedingungen / Besonderheiten:

  • KeePassRPC wird zuverlässig über GitHub Releases ausgeliefert (z. B. v2.0.2 vom 12. Juni 2024).
  • KeeAutoExec ist offiziell gelistet und lässt sich unter Windows bequem über das Chocolatey‑Paket keepass-plugin-keeautoexec pflegen (verifizierte Pakettests).
  • GlobalSearch erweitert die Suche auf alle geöffneten KeePass‑Datenbanken; der Code liegt auf GitHub. [Keepass Pl…Seach.page | Loop]

Lösung: Ein Updater‑Skript + geplanter Task (ohne XML)

Ich nutze ein PowerShell‑Skript, das zwei Update‑Wege kombiniert:

  1. GitHub‑Releases prüfen und .plgx automatisch laden/aktualisieren (z. B. für KeePassRPC).
  1. Chocolatey‑Upgrade für KeeAutoExec (choco upgrade keepass-plugin-keeautoexec -y).

Dazu kommen Robustheits‑Details:

  • Sicherer Dateitausch inkl. Backup, optionales Stoppen/Neustarten von KeePass
  • Log‑Rollover und klare Fehlerlogs
  • Sorgfältige Klammerung/Interpolation, um typische PowerShell‑Parserfehler zu vermeiden

Die Automatisierung übernimmt ein geplanter Task, den ich per PowerShell‑Cmdlets (nicht per XML) registriere – das ist schema‑sicher und läuft beim Anmelden sowie täglich zur Wunschzeit mit höchsten Privilegien.


Was wird aktualisiert?

  • KeePassRPC (GitHub) – benötigt für die Browser‑Erweiterung Kee.
  • KeeAutoExec (Chocolatey) – öffnet definierte KeePass‑Datenbanken automatisch.
  • GlobalSearch (GitHub) – Suche über alle geöffneten DBs. [Keepass Pl…Seach.page | Loop]

Ergebnis

  • Zero‑Touch Updates meiner wichtigsten KeePass‑Plugins
  • Keine XML‑Frickelei: nur PowerShell
  • Nachvollziehbare Logs + Backups für schnelle Rollbacks
  • Einheitliche Pflege auf mehreren Systemen

🔧 Hinweis zur Anpassung (Vorlage erweiterbar)

Die Vorlage ist bewusst modular: Du kannst weitere Plugins hinzufügen (z. B. aus der offiziellen [KeePass‑Pluginliste]), entweder als GitHub‑Eintrag (Repo + Asset‑Filter) oder als Chocolatey‑Paket. Ebenso lassen sich Zeitpläne, Pfade (portable vs. installiert) und Benachrichtigungen (z. B. E‑Mail/Toast) unkompliziert erweitern.

hier das Updater Script


<#
KeePass Plugin Updater (bereinigt & produktiv)
Marko Fieber – 2026
#>

# ================================
# === Benutzer-Konfiguration  ====
# ================================

$KeePassExePath = "C:\Program Files\KeePass Password Safe 2\KeePass.exe"
$PluginDir      = "C:\Program Files\KeePass Password Safe 2\Plugins"

$WorkDir = "$env:ProgramData\KeePassPluginUpdater"
$LogFile = Join-Path $WorkDir "update.log"

$StopKeePassDuringUpdate = $true
$UsePreReleases = $false
$EnvGitHubToken = $env:GITHUB_TOKEN

$Plugins = @(
    @{
        Name         = "KeePassRPC"
        Repo         = "kee-org/keepassrpc"
        AssetPattern = "KeePassRPC\.plgx$"
        TargetFile   = "KeePassRPC.plgx"
    },
    @{
        Name         = "GlobalSearch"
        Repo         = "Rookiestyle/GlobalSearch"
        AssetPattern = "\.plgx$"
        TargetFile   = "GlobalSearch.plgx"
    },
    @{
        Name         = "KeeAutoExec"
        ChocoPackage = "keepass-plugin-keeautoexec"
        TargetFile   = "KeeAutoExec.plgx"
    }
)

# ================================
# === Hilfsfunktionen ============
# ================================

function Write-Log {
    param([string]$Msg, [string]$Level = "INFO")
    $ts = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    $line = "[$ts] [$Level] $Msg"
    Write-Host $line
    Add-Content $LogFile -Value $line
}

function Ensure-Dirs {
    if (-not (Test-Path $WorkDir)) { New-Item -ItemType Directory -Path $WorkDir | Out-Null }
}

function Set-Tls12 {
    try {
        [Net.ServicePointManager]::SecurityProtocol =
            [Net.SecurityProtocolType]::Tls12 -bor
            [Net.SecurityProtocolType]::Tls11 -bor
            [Net.SecurityProtocolType]::Tls
    } catch {}
}

function Get-GitHubRelease {
    param([string]$Repo, [bool]$IncludePreRelease=$false)

    $headers = @{ "User-Agent" = "KeePassPluginUpdater" }
    if ($EnvGitHubToken) { $headers["Authorization"] = "Bearer $EnvGitHubToken" }

    $url = "https://api.github.com/repos/$Repo/releases"
    $resp = Invoke-RestMethod -Uri $url -Headers $headers -ErrorAction Stop

    if ($IncludePreRelease) { return ($resp | Select-Object -First 1) }
    return ($resp | Where-Object { -not $_.prerelease } | Select-Object -First 1)
}

function Stop-KeePass {
    if (-not $StopKeePassDuringUpdate) { return }
    $p = Get-Process KeePass -ErrorAction SilentlyContinue
    if ($p) {
        Write-Log "Beende KeePass..."
        $p | Stop-Process -Force
        Start-Sleep -Seconds 2
    }
}

function Start-KeePass {
    if ($StopKeePassDuringUpdate -and (Test-Path $KeePassExePath)) {
        Write-Log "Starte KeePass neu..."
        Start-Process $KeePassExePath | Out-Null
    }
}

function Backup-File {
    param([string]$FilePath)
    if (Test-Path $FilePath) {
        $backupDir = Join-Path $WorkDir "backups"
        if (-not (Test-Path $backupDir)) { New-Item -ItemType Directory -Path $backupDir | Out-Null }
        $stamp = (Get-Date).ToString("yyyyMMdd-HHmmss")
        $dest  = Join-Path $backupDir ("$(Split-Path $FilePath -Leaf).$stamp.bak")
        Copy-Item $FilePath $dest -Force
        Write-Log "Backup erstellt: $dest"
    }
}

function Download-Asset {
    param($Release, $Pattern)
    $asset = $Release.assets | Where-Object { $_.name -match $Pattern } | Select-Object -First 1
    if (-not $asset) { throw "Kein Asset gefunden, Pattern: $Pattern" }

    $dst = Join-Path $WorkDir $asset.name
    Write-Log "Lade herunter: $($asset.browser_download_url)"
    Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $dst -UseBasicParsing
    return $dst
}

function Expand-IfArchive {
    param([string]$Path)
    if ([IO.Path]::GetExtension($Path) -eq ".zip") {
        $dir = Join-Path $WorkDir ([IO.Path]::GetFileNameWithoutExtension($Path))
        if (Test-Path $dir) { Remove-Item $dir -Recurse -Force }
        Expand-Archive -Path $Path -DestinationPath $dir
        return $dir
    }
    return $null
}

function Install-PluginFile {
    param([string]$Src, [string]$Target)

    $targetPath = Join-Path $PluginDir $Target
    Backup-File $targetPath
    Copy-Item $Src $targetPath -Force
    Write-Log "Installiert: $Target"
}

function Update-PluginViaChocolatey {
    param([string]$Pkg)

    $choco = Get-Command choco -ErrorAction SilentlyContinue
    if (-not $choco) { throw "Chocolatey nicht installiert." }

    Write-Log "Chocolatey-Update: $Pkg"
    Start-Process $choco.Source -ArgumentList "upgrade $Pkg -y --no-progress" -Wait -PassThru | Out-Null
}

# ================================
# === Ablauf =====================
# ================================

Ensure-Dirs
Set-Tls12

# Log-Rollover
if (Test-Path $LogFile) {
    $tooLarge = ((Get-Item $LogFile).Length -gt 5MB)
    if ($tooLarge) {
        Rename-Item $LogFile -NewName ("update_{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss"))
    }
}

Write-Log "==== Start ===="

Stop-KeePass

foreach ($p in $Plugins) {
    $name = $p.Name
    Write-Log "Prüfe Plugin: ${name}"

    # Chocolatey-Plugins
    if ($p.ChocoPackage) {
        try {
            Update-PluginViaChocolatey $p.ChocoPackage
            $expected = Join-Path $PluginDir $p.TargetFile
            Write-Log ("Verifiziert: " + (Test-Path $expected))
        } catch {
            Write-Log "Fehler bei ${name}: $($_.Exception.Message)" "ERROR"
        }
        continue
    }

    # GitHub-Plugins
    try {
        $rel = Get-GitHubRelease -Repo $p.Repo -IncludePreRelease:$UsePreReleases
        if (-not $rel) {
            Write-Log "Kein Release für ${name}" "WARN"
            continue
        }

        $localFile = Join-Path $PluginDir $p.TargetFile
        $localHash = if (Test-Path $localFile) { (Get-FileHash $localFile).Hash } else { $null }

        $dl = Download-Asset -Release $rel -Pattern $p.AssetPattern
        $dir = Expand-IfArchive $dl
        $src = if ($dir) {
            (Get-ChildItem $dir -Recurse -Filter $p.TargetFile | Select-Object -First 1).FullName
        } else { $dl }

        $newHash = (Get-FileHash $src).Hash
        if ($localHash -eq $newHash) {
            Write-Log "${name} ist bereits aktuell."
            continue
        }

        Install-PluginFile -Src $src -Target $p.TargetFile
        Write-Log "${name} aktualisiert auf $($rel.tag_name)"
    }
    catch {
        Write-Log "Fehler bei ${name}: $($_.Exception.Message)" "ERROR"
    }
}

Start-KeePass
Write-Log "==== Fertig ===="

und hier das Script für den Task

# ==========================================
# KeePass Plugin Updater – Task Registration
# ==========================================

$TaskName   = "KeePass Plugin Updater"
$ScriptPath = 'C:\Program Files\KeePass Password Safe 2\Update-KeePassPlugins.ps1'
$WorkDir    = 'C:\Program Files\KeePass Password Safe 2'
$DailyTime  = [datetime]"09:15"
$UserId     = "$env:UserName"

# Action
$action = New-ScheduledTaskAction `
    -Execute 'powershell.exe' `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`"" `
    -WorkingDirectory $WorkDir

# Trigger: Beim Anmelden + täglich
$trLogon = New-ScheduledTaskTrigger -AtLogOn
$trDaily = New-ScheduledTaskTrigger -Daily -At $DailyTime

# Principal: höchste Privilegien
$principal = New-ScheduledTaskPrincipal `
    -UserId $UserId `
    -LogonType Interactive `
    -RunLevel Highest

# Settings
$settings = New-ScheduledTaskSettingsSet `
    -StartWhenAvailable `
    -AllowStartIfOnBatteries `
    -DontStopIfGoingOnBatteries `
    -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
    -RestartCount 2 `
    -RestartInterval (New-TimeSpan -Minutes 5)

# Task zusammenbauen
$task = New-ScheduledTask -Action $action -Trigger @($trLogon, $trDaily) -Principal $principal -Settings $settings

# Vorher löschen, falls vorhanden
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
    Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
}

# Registrieren
Register-ScheduledTask -TaskName $TaskName -InputObject $task

Write-Host "Task erfolgreich registriert."

Hinweis/Disclaimer: Dieses Setup wurde erfolgreich auf einem Windows 11 System angewendet. Es erfolgt keine Garantie oder Gewährleistung für Funktion, Vollständigkeit oder Eignung in anderen Umgebungen.

**Stand:** 14.02.2026