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-keeautoexecpflegen (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:
- GitHub‑Releases prüfen und
.plgxautomatisch laden/aktualisieren (z. B. für KeePassRPC).
- 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