DEINSTALLIEREN.bat
Rama main · réplica de lectura del repositorio original.
@echo off
:: ============================================================
:: PBP Deinstaller — Self-Relocation (Fix #620)
:: ============================================================
:: Wenn die .bat aus %APP_DIR% laeuft (Apps & Features-Aufruf),
:: kopieren wir uns nach %TEMP% und starten von dort neu. Sonst
:: wuerde Schritt [4/7] (rmdir APP_DIR) das laufende Skript
:: loeschen — cmd.exe liest Skripte just-in-time von Disk und
:: bricht still ab. Folge: Registry-Eintrag bleibt, Apps-Liste
:: zeigt PBP weiterhin an.
:: ============================================================
set "PBP_BASEDIR=%~dp0"
if "%PBP_BASEDIR:~-1%"=="\" set "PBP_BASEDIR=%PBP_BASEDIR:~0,-1%"
set "PBP_APP_DIR_CHECK=%LOCALAPPDATA%\BewerbungsAssistent\app"
if /i "%PBP_BASEDIR%"=="%PBP_APP_DIR_CHECK%" if not "%PBP_DEINST_RELOCATED%"=="1" goto :pbp_relocate
goto :pbp_main
:pbp_relocate
set "PBP_RELOC_BAT=%TEMP%\PBP-Deinstaller-%RANDOM%%RANDOM%.bat"
copy /Y "%~f0" "%PBP_RELOC_BAT%" >nul
set PBP_DEINST_RELOCATED=1
:: Gegenprobe 05.10.2026 (PP13): Wer den Deinstaller per Doppelklick oder ueber den Knopf im Dashboard startet, hat den
:: App-Ordner als Arbeitsordner. Diese cmd.exe wartet unten auf die verschobene Kopie und haelt damit genau den Ordner
:: fest, den Schritt [5/7] loeschen will (Meldung "konnte nicht entfernt werden", ein leerer Ordner bleibt liegen).
:: Also zuerst aus dem Ordner heraus.
cd /d "%TEMP%" 2>nul || cd /d "%SystemRoot%"
:: Gegenprobe 05.10.2026 (PP16): Aufraeumen und Beenden stehen in DERSELBEN Zeile wie der Aufruf. Beendet die verschobene
:: Kopie, hat Schritt [5/7] die Ursprungsdatei laengst geloescht; cmd liest die naechste Zeile dann von der Platte, findet
:: nichts und bricht still ab - das `del` darunter lief nie, und die Kopie (rund 15 KB) blieb in %TEMP% liegen.
:: Eine Zeile wird vor dem Aufruf vollstaendig gelesen, die Teile dahinter laufen also auch ohne die Datei.
cmd /c ""%PBP_RELOC_BAT%"" & del /Q "%PBP_RELOC_BAT%" >nul 2>&1 & exit /b 0
:pbp_main
:: v1.8.0 (#1207): ein "!" im Windows-Profil verschwindet unter der verzoegerten Expansion aus jedem Pfad, der aus
:: %LOCALAPPDATA% gebildet wird. Der Deinstaller meldete dann fuer jeden Ordner "nicht gefunden" und entfernte nichts. Hier
:: wird vor der ersten Aenderung geprueft und mit dem Handgriff abgebrochen.
setlocal DisableDelayedExpansion
:: Gedruckt wird mit `set PBP_ORT_`: kein eigenes setlocal (PP19), und ein "&" im Namen wird nicht als Befehl gelesen.
set "PBP_ORT_1=%LOCALAPPDATA%"
set "PBP_ORT_2=%TEMP%"
set "PBP_ORT_3=%USERPROFILE%"
set "PBP_PROFIL_ORTE=%LOCALAPPDATA%%TEMP%%USERPROFILE%"
if "%PBP_PROFIL_ORTE:!=%"=="%PBP_PROFIL_ORTE%" goto :pbp_profil_ok
echo.
echo ****************************************************
echo * FEHLER: Dein Windows-Profil enthaelt ein *
echo * Ausrufezeichen *
echo ****************************************************
echo.
echo Der Deinstaller kann die Ordner von PBP in diesem Profil
echo nicht sicher finden. Geprueft habe ich:
set PBP_ORT_
echo.
echo SO GEHT ES WEITER:
echo 1. Oeffne den Explorer und tippe in die Adressleiste
echo %%LOCALAPPDATA%% (mit den Prozentzeichen) ein
echo 2. Loesche dort den Ordner BewerbungsAssistent
echo 3. Entferne die Desktop-Verknuepfung von PBP
echo.
pause
exit /b 1
:pbp_profil_ok
endlocal
setlocal EnableDelayedExpansion
title PBP Bewerbungs-Assistent - Deinstallation
color 0C
set "BASEDIR=%~dp0"
if "%BASEDIR:~-1%"=="\" set "BASEDIR=%BASEDIR:~0,-1%"
set "BASE_INSTALL=%LOCALAPPDATA%\BewerbungsAssistent"
set "APP_DIR=%BASE_INSTALL%\app"
set "DATA_DIR=%BASE_INSTALL%\data"
:: Legacy-Pfade (v1.4.x Kompatibilitaet)
set "LEGACY_RUNTIME=%BASE_INSTALL%\python"
set "LEGACY_SRC=%BASE_INSTALL%\src"
set "LOCAL_RUNTIME_DIR=%BASEDIR%\python"
set "LOGFILE=%BASEDIR%\deinstall_log.txt"
if exist "%LOGFILE%" for %%F in ("%LOGFILE%") do if %%~zF GTR 1000000 del "%LOGFILE%" 2>nul
echo ================================================== >> "%LOGFILE%"
echo PBP Deinstaller v0.1.0 - %date% %time% >> "%LOGFILE%"
echo User: %USERNAME% >> "%LOGFILE%"
echo Basispfad: %BASEDIR% >> "%LOGFILE%"
echo Datenpfad: %DATA_DIR% >> "%LOGFILE%"
echo ================================================== >> "%LOGFILE%"
echo.
echo ====================================================
echo.
echo PBP - Persoenliches Bewerbungs-Portal
echo Deinstallation
echo.
echo ====================================================
echo.
echo Was entfernt wird:
echo - MCP-Eintrag "bewerbungs-assistent" in Claude Desktop
echo - PBP-Runtime aus %APP_DIR%
echo - Windows Apps ^& Features Eintrag
echo - Desktop-Verknuepfung "PBP Bewerbungs-Portal"
echo - Nachinstallierte Komponenten ^(z. B. die Texterkennung^)
echo - Auf Nachfrage: Browser-Dateien ^(Playwright^) und pip-Zwischenspeicher
echo.
echo Hinweis:
echo Deine Bewerbungsdaten bleiben standardmaessig erhalten.
echo Danach kannst du optional ALLE Daten loeschen.
echo.
set /p CONFIRM=" Deinstallation jetzt starten? (j/n): "
if /i "!CONFIRM!" neq "j" (
echo.
echo Abgebrochen - nichts wurde geaendert.
echo.
pause
exit /b 0
)
echo.
echo [1/7] Beende laufende PBP-Prozesse...
call :stop_pbp_processes
echo [OK] Laufende PBP-Prozesse beendet (falls vorhanden)
echo.
echo [2/7] Entferne Claude Desktop MCP-Eintrag...
call :remove_claude_entry
set "CLAUDE_RESULT=!errorlevel!"
if "!CLAUDE_RESULT!"=="0" echo [OK] MCP-Eintrag entfernt
if "!CLAUDE_RESULT!"=="1" echo [--] MCP-Eintrag war nicht vorhanden
if "!CLAUDE_RESULT!"=="2" echo [--] Keine mcpServers in Claude-Config gefunden
if "!CLAUDE_RESULT!"=="3" echo [^^!^^!] Claude-Config konnte nicht gelesen werden (ungueltiges JSON)
if "!CLAUDE_RESULT!"=="4" echo [--] Claude-Config nicht gefunden
if "!CLAUDE_RESULT!"=="5" echo [^^!^^!] Fehler beim Entfernen des MCP-Eintrags
echo.
echo [3/7] Entferne Desktop-Verknuepfung...
call :remove_shortcut
if "!errorlevel!"=="0" (
echo [OK] Desktop-Verknuepfung entfernt
) else (
echo [--] Desktop-Verknuepfung nicht gefunden
)
echo.
echo [4/7] Entferne Windows Apps ^& Features Eintrag...
:: #620: Registry VOR Verzeichnis-Loeschung entfernen. Falls die Self-
:: Relocation am Skript-Anfang aus irgendeinem Grund nicht greift,
:: stellt diese Reihenfolge mindestens sicher dass der Apps-Eintrag
:: weg ist (das war der prominenteste User-Sichtbare Bug).
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PBP" /f >nul 2>&1
if !errorlevel! equ 0 (
echo [OK] Registry-Eintrag entfernt
) else (
echo [--] Registry-Eintrag war nicht vorhanden
)
:: Verifikation: pruefen ob der Key wirklich weg ist (#343)
reg query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PBP" >nul 2>&1
if !errorlevel! equ 0 (
echo [^^!^^!] Registry-Eintrag konnte nicht entfernt werden - versuche erneut...
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PBP" /f >nul 2>&1
echo [WARN] Registry retry >> "%LOGFILE%"
)
echo.
echo [5/7] Entferne Runtime-Dateien...
set "REMOVE_ERRORS=0"
:: v1.5.0 Pfade (app/)
call :remove_path "%APP_DIR%" "App-Verzeichnis %APP_DIR%"
:: Legacy v1.4.x Pfade
call :remove_path "%LEGACY_RUNTIME%" "Legacy Runtime in %BASE_INSTALL%\python"
call :remove_path "%LEGACY_SRC%" "Legacy Source in %BASE_INSTALL%\src"
:: v1.8.0 (#1149 Punkt 9a): Python-Ordner, Installationsprotokoll und heruntergeladene Python-ZIPs neben dem Skript.
call :ordner_neben_dem_skript
:: PP26 (Testlauf 11.10.2026): hat PBP die Texterkennung ohne Administratorrechte "nur fuer mich" eingerichtet, liegt sie
:: ausserhalb von components\ - der Vermerk dort sagt es. Dann geht sie mit ihrem eigenen Deinstallationsprogramm.
call :texterkennung_entfernen
set "TESS_RESULT=!errorlevel!"
if "!TESS_RESULT!"=="0" echo [OK] Texterkennung ^(Tesseract^) entfernt
if "!TESS_RESULT!"=="1" echo [^^!^^!] Die Texterkennung konnte nicht vollstaendig entfernt werden
if "!TESS_RESULT!"=="1" set /a REMOVE_ERRORS+=1
if "!TESS_RESULT!"=="8" echo [--] Die Texterkennung am bekannten Ort stammt nicht von PBP und bleibt
echo.
echo [6/7] Optional: Backup deiner Bewerbungsdaten erstellen
echo.
set "BACKUP_FEHLER=0"
:: v1.8.0 #1149 Punkt 8: die Pfade kommen ueber die Umgebungsvariablen DATA_DIR und BACKUP_ZIP, nicht eingebettet. Ein Apostroph
:: im Benutzernamen wie O'Brien machte aus dem Befehl einen Syntaxfehler mit Exit 1 - und Exit 1 heisst hier "kein Datenordner":
:: es gab kein Backup, keine Warnung, und danach durfte der Mensch endgueltig loeschen.
:: #1224: HIER STEHT BEWUSST KEIN KOMMENTAR INNERHALB DES BLOCKS. cmd liest den Block beim Einlesen in einem Zug; zwei
:: Kommentarzeilen mit Klammern darin liessen den Stapel dort enden - der Deinstaller schloss sich nach der Backup-Frage.
:: #1296: das Datum fuer den Namen kommt aus PowerShell. Vorher wurde es aus Teilen der Variablen date geschnitten, das setzt
:: TT.MM.JJJJ voraus; bei englischem Format wie "Sun 11/01/2026" entstand ein Name mit Schraegstrichen und die Sicherung
:: scheiterte immer. Ohne Antwort von PowerShell heisst die Datei PBP-Backup-sicherung.zip.
set "BACKUP_DATUM="
for /f "usebackq delims=" %%D in (`powershell -NoProfile -Command "Get-Date -Format yyyy-MM-dd"`) do set "BACKUP_DATUM=%%D"
if not defined BACKUP_DATUM set "BACKUP_DATUM=sicherung"
set /p CREATE_BACKUP=" Soll ein Backup auf dem Desktop erstellt werden? (j/n): "
if /i "!CREATE_BACKUP!"=="j" (
set "BACKUP_ZIP=%USERPROFILE%\Desktop\PBP-Backup-%BACKUP_DATUM%.zip"
echo Erstelle Backup...
powershell -ExecutionPolicy Bypass -NoProfile -Command "$ErrorActionPreference = 'Stop'; if (Test-Path -LiteralPath $env:DATA_DIR) { try { Compress-Archive -Path (Join-Path $env:DATA_DIR '*') -DestinationPath $env:BACKUP_ZIP -Force; if (-not (Test-Path -LiteralPath $env:BACKUP_ZIP)) { throw 'Die ZIP-Datei fehlt.' }; Write-Host (' [OK] Backup erstellt: ' + $env:BACKUP_ZIP); exit 0 } catch { Write-Host (' [FEHLER] Backup NICHT erstellt: ' + $_.Exception.Message); exit 2 } } else { Write-Host ' [--] Kein Datenordner gefunden'; exit 1 }" 2>>"%LOGFILE%"
set "BACKUP_RC=!errorlevel!"
if !BACKUP_RC! geq 2 set "BACKUP_FEHLER=1"
if "!BACKUP_RC!"=="0" echo [INFO] Desktop-Backup erstellt >> "%LOGFILE%"
if "!BACKUP_RC!"=="1" echo [INFO] Desktop-Backup uebersprungen ^(kein Datenordner^) >> "%LOGFILE%"
if "!BACKUP_FEHLER!"=="1" echo [WARN] Desktop-Backup NICHT erstellt >> "%LOGFILE%"
)
echo.
echo [7/7] Optional: Alle Bewerbungsdaten loeschen
echo.
echo ACHTUNG: Dein Profil, alle Stellen und Bewerbungen
echo werden UNWIDERRUFLICH geloescht!
echo.
if "!BACKUP_FEHLER!"=="1" (
echo ACHTUNG: Das Backup konnte NICHT erstellt werden ^(siehe oben^).
echo Mit LOESCHEN ist alles endgueltig weg. Zum Abbrechen etwas anderes eingeben.
echo.
)
set /p DELETE_DATA=" Bist du sicher? Tippe LOESCHEN zum Bestaetigen: "
if "!DELETE_DATA!"=="LOESCHEN" (
if exist "%DATA_DIR%" (
rmdir /s /q "%DATA_DIR%" >nul 2>&1
if exist "%DATA_DIR%" (
echo [^^!^^!] Datenordner konnte nicht komplett entfernt werden
echo [WARN] Datenordner konnte nicht komplett entfernt werden >> "%LOGFILE%"
set "DATA_RESULT=failed"
) else (
echo [OK] Alle Bewerbungsdaten entfernt
echo [OK] Datenordner komplett entfernt >> "%LOGFILE%"
set "DATA_RESULT=deleted"
)
) else (
echo [--] Datenordner nicht gefunden
set "DATA_RESULT=not_found"
)
) else (
echo [OK] Bewerbungsdaten bleiben erhalten
echo [INFO] Bewerbungsdaten wurden beibehalten >> "%LOGFILE%"
set "DATA_RESULT=kept"
)
:: v1.8.0-beta.0 (#751 I10): Nachinstallierte Komponenten (z.B. Tesseract)
:: sind Runtime wie app\, KEINE User-Daten — immer mit entfernen.
:: Extern installierte Programme (Program Files, PATH) beruehrt das nicht.
if exist "%BASE_INSTALL%\components" (
rmdir /s /q "%BASE_INSTALL%\components" >nul 2>&1
if exist "%BASE_INSTALL%\components" (
echo [WARN] Komponenten-Ordner konnte nicht entfernt werden >> "%LOGFILE%"
) else (
echo [OK] Komponenten-Ordner entfernt >> "%LOGFILE%"
)
)
:: PP10 (Praxisprobe 1.8): zum Schluss die Frage nach den Zusatzdateien (Playwright-Browser, pip-Zwischenspeicher).
call :zusatzdateien
:: #620: Stamm-Ordner BASE_INSTALL entfernen wenn leer
:: rmdir ohne /s loescht NUR leere Verzeichnisse — sicher.
:: Wenn der User die Daten behalten hat, bleibt %DATA_DIR% drin und
:: damit auch %BASE_INSTALL% — kein Datenverlust.
rmdir "%BASE_INSTALL%" 2>nul
if exist "%BASE_INSTALL%" (
echo [INFO] !BASE_INSTALL! nicht entfernt ^(enthaelt noch Daten^) >> "%LOGFILE%"
) else (
echo [OK] !BASE_INSTALL! Stamm-Ordner entfernt >> "%LOGFILE%"
)
echo.
echo ====================================================
echo.
echo Deinstallation abgeschlossen
echo.
echo ====================================================
echo.
set "CLAUDE_MANUELL=0"
if "!CLAUDE_RESULT!"=="3" set "CLAUDE_MANUELL=1"
if "!CLAUDE_RESULT!"=="5" set "CLAUDE_MANUELL=1"
if "!CLAUDE_MANUELL!"=="1" (
echo WICHTIG:
echo Die Claude-Konfigurationsdatei konnte nicht automatisch
echo bearbeitet werden. Entferne den MCP-Server-Eintrag
echo "bewerbungs-assistent" manuell in:
echo !APPDATA!\Claude\claude_desktop_config.json
echo ^(bei Claude aus dem Microsoft Store: !LOCALAPPDATA!\Packages\Claude_...
echo \LocalCache\Roaming\Claude\claude_desktop_config.json^)
echo ^(neuere Store-Fassung von Claude: !LOCALAPPDATA!\Claude-Data\claude_...\claude_desktop_config.json^)
echo.
)
if "!DATA_RESULT!"=="kept" (
echo Deine Daten sind weiterhin vorhanden unter:
echo !DATA_DIR!
echo.
)
if exist "%BASE_INSTALL%" (
echo Hinweis: Falls Reste verbleiben ^(z.B. weil Dateien noch gesperrt
echo waren^), kannst du diesen Ordner gefahrlos manuell loeschen:
echo !BASE_INSTALL!
echo.
)
echo Bitte Claude Desktop einmal komplett neu starten.
echo Log-Datei: %LOGFILE%
echo.
echo Druecke eine beliebige Taste zum Schliessen...
pause >nul
exit /b 0
:stop_pbp_processes
:: #739: wmic ist auf neueren Windows-Builds nicht mehr zuverlaessig verfuegbar
:: (deprecated Feature-on-Demand) — frueher haengte der Prozess-Stopp daran.
:: Robuster Stopp via PowerShell/CIM: beendet nur python-Prozesse, deren
:: Kommandozeile eindeutig zu PBP gehoert. Ausgabe geht ins Log (Diagnose).
:: Praxisprobe 1.8 (05.10.2026): auch das Konsolenfenster des Dashboards (cmd /K "Dashboard starten.bat") wird
:: geschlossen - es blieb sonst nach der Deinstallation leer stehen. Die Deinstaller-Fassung traegt einen anderen Namen.
powershell -ExecutionPolicy Bypass -NoProfile -Command "Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { (($_.Name -eq 'python.exe' -or $_.Name -eq 'pythonw.exe') -and ($_.CommandLine -match 'bewerbungs_assistent|start_dashboard|_selftest')) -or ($_.Name -eq 'cmd.exe' -and $_.CommandLine -match 'Dashboard starten\.bat') } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }" >> "%LOGFILE%" 2>&1
:: Kurze Pause, damit gesperrte Datei-Handles (pbp.db / WAL) freigegeben werden,
:: bevor die Runtime-Dateien geloescht werden — sonst schlaegt rmdir still fehl.
ping -n 3 127.0.0.1 >nul 2>&1
exit /b 0
:: Praxisprobe 1.8 (05.10.2026): _setup_claude.py schreibt den Eintrag in ALLE Konfigurationsdateien (Standardpfad und
:: Store-Pakete). Der Deinstaller las nur den Standardpfad - bei der Store-Fassung blieb der Eintrag stehen, und Claude
:: meldete danach bei jedem Start einen Server, dessen Programm es nicht mehr gab. Rueckgabe: 0 entfernt, 1 kein Eintrag,
:: 2 keine mcpServers, 3 nicht lesbar, 4 keine Datei, 5 Schreibfehler; bei mehreren Dateien gilt der schlimmste Fall.
:remove_claude_entry
powershell -ExecutionPolicy Bypass -NoProfile -Command "$pfade = @(Join-Path $env:APPDATA 'Claude\claude_desktop_config.json'); if ($env:LOCALAPPDATA) { foreach ($muster in 'Claude_*', 'AnthropicPBC.Claude*') { foreach ($d in @(Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Packages') -Directory -Filter $muster -ErrorAction SilentlyContinue)) { $pfade += Join-Path $d.FullName 'LocalCache\Roaming\Claude\claude_desktop_config.json' } } }; if ($env:LOCALAPPDATA) { foreach ($d in @(Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Claude-Data') -Directory -Filter 'claude*' -ErrorAction SilentlyContinue)) { $pfade += Join-Path $d.FullName 'claude_desktop_config.json' } }; $ergebnisse = @(); foreach ($p in $pfade) { $r = 4; $wie = ''; if (Test-Path $p) { $cfg = $null; try { $cfg = Get-Content -Path $p -Raw -Encoding UTF8 | ConvertFrom-Json; $r = 0 } catch { $r = 3 }; if ($r -eq 0) { if (-not ($cfg.PSObject.Properties.Name -contains 'mcpServers') -or -not $cfg.mcpServers) { $r = 2 } elseif (-not ($cfg.mcpServers.PSObject.Properties.Name -contains 'bewerbungs-assistent')) { $r = 1 } else { try { Copy-Item -Path $p -Destination ($p + '.pbp-backup') -Force; $q = [char]34; $bs = [char]92; $roh = [IO.File]::ReadAllText($p); $neu = $null; $a = [regex]::Match($roh, $q + 'mcpServers' + $q + '\s*:\s*\{'); if ($a.Success) { $re = New-Object System.Text.RegularExpressions.Regex($q + 'bewerbungs-assistent' + $q + '\s*:\s*\{'); $m = $re.Match($roh, $a.Index); if ($m.Success) { $t = 0; $s = $false; $e = -1; for ($i = $m.Index + $m.Length - 1; $i -lt $roh.Length; $i++) { $z = $roh[$i]; if ($s) { if ($z -eq $bs) { $i++ } elseif ($z -eq $q) { $s = $false } } elseif ($z -eq $q) { $s = $true } elseif ($z -eq '{') { $t++ } elseif ($z -eq '}') { $t--; if ($t -eq 0) { $e = $i; break } } }; if ($e -ge 0) { $von = $m.Index; $bis = $e + 1; $k = $bis; while ($k -lt $roh.Length -and [char]::IsWhiteSpace($roh[$k])) { $k++ }; if ($k -lt $roh.Length -and $roh[$k] -eq ',') { $k++; while ($k -lt $roh.Length -and [char]::IsWhiteSpace($roh[$k])) { $k++ }; $bis = $k } else { $j = $von - 1; while ($j -ge 0 -and [char]::IsWhiteSpace($roh[$j])) { $j-- }; if ($j -ge 0 -and $roh[$j] -eq ',') { $von = $j } }; $neu = [regex]::Replace($roh.Remove($von, $bis - $von), '(' + $q + 'mcpServers' + $q + '\s*:\s*)\{\s*\}', '${1}{}') } } }; $gut = $false; if ($neu) { try { $k1 = ConvertFrom-Json -InputObject $neu; $k2 = ConvertFrom-Json -InputObject $roh; $null = $k2.mcpServers.PSObject.Properties.Remove('bewerbungs-assistent'); $gut = ((ConvertTo-Json -InputObject $k1 -Depth 15 -Compress) -eq (ConvertTo-Json -InputObject $k2 -Depth 15 -Compress)) } catch { $gut = $false } }; $wie = ' (nur der Eintrag entfernt)'; if ($gut) { [IO.File]::WriteAllText($p, $neu, (New-Object System.Text.UTF8Encoding($false))) } else { $wie = ' (neu geschrieben)'; $null = $cfg.mcpServers.PSObject.Properties.Remove('bewerbungs-assistent'); if ($cfg.mcpServers.PSObject.Properties.Count -eq 0) { $cfg.mcpServers = @{} }; [IO.File]::WriteAllText($p, ($cfg | ConvertTo-Json -Depth 15), (New-Object System.Text.UTF8Encoding($false))) }; $r = 0 } catch { $r = 5 } } } }; Write-Host ('[INFO] Claude-Config ' + $p + ' -> ' + $r + $wie); $ergebnisse += $r }; foreach ($c in 5, 3, 0, 1, 2) { if ($ergebnisse -contains $c) { exit $c } }; exit 4" >> "%LOGFILE%" 2>&1
if %errorlevel% geq 5 exit /b 5
exit /b %errorlevel%
:remove_shortcut
powershell -ExecutionPolicy Bypass -NoProfile -Command "$s = Join-Path ([Environment]::GetFolderPath('Desktop')) 'PBP Bewerbungs-Portal.lnk'; if (Test-Path $s) { Remove-Item -Path $s -Force; exit 0 } else { exit 1 }" >nul 2>&1
exit /b %errorlevel%
:remove_path
:: v1.8.0 (#1149 Punkt 8): in den Bloecken !VAR! statt %VAR% - die Beschriftung nennt den Ordner, und ein Ordner wie
:: "PBP-1.8.0 (1)" (so legt der Browser eine zweite Kopie an) trug seine Klammer in den Block und beendete ihn dort.
set "TARGET=%~1"
set "TARGET_LABEL=%~2"
if not exist "%TARGET%" (
echo [--] !TARGET_LABEL! nicht gefunden
echo [INFO] Nicht gefunden: !TARGET! >> "%LOGFILE%"
exit /b 0
)
rmdir /s /q "%TARGET%" >nul 2>&1
:: #739: ein Retry nach kurzer Pause — bei frisch beendetem Dashboard koennen
:: Datei-Handles noch eine Sekunde gehalten werden.
if exist "%TARGET%" (
ping -n 3 127.0.0.1 >nul 2>&1
rmdir /s /q "%TARGET%" >nul 2>&1
)
if exist "%TARGET%" (
echo [^^!^^!] !TARGET_LABEL! konnte nicht entfernt werden
echo [WARN] Entfernen fehlgeschlagen: !TARGET! >> "%LOGFILE%"
set /a REMOVE_ERRORS+=1
) else (
echo [OK] !TARGET_LABEL! entfernt
echo [OK] Entfernt: !TARGET! >> "%LOGFILE%"
)
exit /b 0
:: --- basedir Anfang
:ordner_neben_dem_skript
:: #1149 (Punkt 9a): Beim Aufruf ueber "Apps & Features" kopiert sich der Deinstaller nach %TEMP% (siehe oben) und laeuft
:: von dort. %~dp0 ist dann %TEMP% selbst: der Python-Ordner, das Installationsprotokoll und die heruntergeladenen
:: Python-ZIPs "neben dem Skript" waeren die, die in %TEMP% liegen - und das sind nicht PBPs. Nur ein Skript, das im
:: entpackten PBP-Ordner liegt, hat dort etwas zu entfernen.
set "BASEDIR_FREMD="
if "%PBP_DEINST_RELOCATED%"=="1" set "BASEDIR_FREMD=1"
if /i "%BASEDIR%"=="%TEMP%" set "BASEDIR_FREMD=1"
if /i "%BASEDIR%"=="%TMP%" set "BASEDIR_FREMD=1"
if defined BASEDIR_FREMD (
echo [INFO] Deinstaller laeuft aus dem Temp-Ordner: Ordner neben dem Skript bleiben unberuehrt >> "%LOGFILE%"
exit /b 0
)
call :remove_path "%LOCAL_RUNTIME_DIR%" "Lokaler Python-Ordner in %BASEDIR%\python"
if exist "%BASEDIR%\install_log.txt" (
del /q "%BASEDIR%\install_log.txt" >nul 2>&1
if exist "%BASEDIR%\install_log.txt" (
echo [^^!^^!] install_log.txt konnte nicht entfernt werden
set /a REMOVE_ERRORS+=1
) else (
echo [OK] install_log.txt entfernt
)
)
for %%F in ("%BASEDIR%\python-*-embed-amd64.zip") do (
if exist "%%~fF" del /q "%%~fF" >nul 2>&1
)
exit /b 0
:: --- basedir Ende
:: --- texterkennung Anfang
:texterkennung_entfernen
:: PP26 (Testlauf 11.10.2026): ohne Administratorrechte legt das Setup der Texterkennung sie "nur fuer mich" unter
:: %LOCALAPPDATA%\Programs\Tesseract-OCR ab und ignoriert das Ziel. PBP merkt sich das in components\tesseract\pbp-installation.json;
:: nur dann, und nur an diesem einen Ort, wird entfernt. Das Deinstallationsprogramm des Setups macht die Arbeit (Programmdateien,
:: Startmenue); es kopiert sich nach TEMP und kehrt sofort zurueck, darum wird gewartet, bis das Programm weg ist. Der Rest
:: (nachgeladene Sprachdaten) geht mit dem Ordner. Entfernt wird nur, wenn tesseract.exe noch dieselbe Datei ist wie bei der
:: Einrichtung (Groesse und Anlegezeit stehen im Vermerk): wer sie spaeter selbst neu angelegt hat, dem gehoert sie.
:: Rueckgabe: 0 entfernt, 1 nicht vollstaendig, 8 gehoert nicht mehr PBP oder ist schon weg, 9 kein Vermerk.
if not exist "%BASE_INSTALL%\components\tesseract\pbp-installation.json" exit /b 9
powershell -ExecutionPolicy Bypass -NoProfile -Command "$d = Join-Path $env:LOCALAPPDATA 'Programs\Tesseract-OCR'; if (-not (Test-Path -LiteralPath $d)) { Write-Host '[INFO] Texterkennung: Ordner nicht vorhanden'; exit 0 }; $b = Join-Path $d 'tesseract.exe'; $gleich = $false; try { $m = Get-Content -Raw -LiteralPath (Join-Path $env:BASE_INSTALL 'components\tesseract\pbp-installation.json') | ConvertFrom-Json; $f = Get-Item -LiteralPath $b -ErrorAction Stop; $gleich = ($f.Length -eq [int64]$m.binary_groesse) -and (([DateTimeOffset]$f.CreationTimeUtc).ToUnixTimeSeconds() -eq [int64]$m.binary_erstellt) } catch { $gleich = $false }; if (-not $gleich) { Write-Host '[INFO] Texterkennung: stammt nicht mehr von PBP oder ist schon entfernt, bleibt'; exit 8 }; $u = Join-Path $d 'tesseract-uninstall.exe'; $env:__COMPAT_LAYER = 'RunAsInvoker'; if (Test-Path -LiteralPath $u) { try { Start-Process -FilePath $u -ArgumentList '/S' -WindowStyle Hidden } catch { Write-Host ('[INFO] Deinstallationsprogramm nicht gestartet: ' + $_.Exception.Message) }; $max = 60; if ($env:PBP_WARTEZEIT_S -match '^[0-9]+$') { $max = [int]$env:PBP_WARTEZEIT_S }; for ($i = 0; $i -lt $max -and (Test-Path -LiteralPath $b); $i++) { Start-Sleep -Seconds 1 }; Start-Sleep -Seconds 2 }; Remove-Item -LiteralPath $d -Recurse -Force -ErrorAction SilentlyContinue; if (Test-Path -LiteralPath $d) { Write-Host ('[WARN] Texterkennung nicht vollstaendig entfernt: ' + $d); exit 1 } else { Write-Host ('[OK] Texterkennung entfernt: ' + $d); exit 0 }" >> "%LOGFILE%" 2>&1
exit /b %errorlevel%
:: --- texterkennung Ende
:: --- zusatzdateien Anfang
:zusatzdateien
:: PP10 (Praxisprobe 1.8, 05.10.2026): Der Installer laedt ausserhalb von PBP rund 830 MB: den Browser fuer Quellen, die nur im Browser
:: liefern (Playwright, %LOCALAPPDATA%\ms-playwright, rund 700 MB), und den Zwischenspeicher von pip (%LOCALAPPDATA%\pip, rund 125 MB).
:: Der Deinstaller liess beides liegen, ohne es zu erwaehnen (macOS und Linux fragen). Jetzt wird gefragt, Vorgabe BEHALTEN: andere
:: Programme koennen dieselben Ordner nutzen, und ein Deinstaller, der Fremdes mitnimmt, ist schlimmer als einer, der zu wenig entfernt.
:: Playwright: der ganze Ordner (wie unter macOS und Linux). pip: nur der Unterordner Cache, der Ordner pip nur, wenn er danach leer ist.
set "ZD_PW=%LOCALAPPDATA%\ms-playwright"
set "ZD_PIP=%LOCALAPPDATA%\pip"
set "ZD_PW_MB="
set "ZD_PIP_MB="
if exist "!ZD_PW!\" call :ordnergroesse "!ZD_PW!" ZD_PW_MB
if exist "!ZD_PIP!\" call :ordnergroesse "!ZD_PIP!" ZD_PIP_MB
if not exist "!ZD_PW!\" if not exist "!ZD_PIP!\" (
echo [INFO] Keine Zusatzdateien ^(Playwright, pip^) gefunden >> "%LOGFILE%"
exit /b 0
)
if not defined ZD_PW_MB set "ZD_PW_MB=?"
if not defined ZD_PIP_MB set "ZD_PIP_MB=?"
echo.
echo Zum Schluss: Dateien, die der Installer ausserhalb von PBP geladen hat
echo.
if exist "!ZD_PW!\" echo - Browser fuer Quellen ^(Playwright^): !ZD_PW! ^(!ZD_PW_MB! MB^)
if exist "!ZD_PIP!\" echo - pip-Zwischenspeicher: !ZD_PIP! ^(!ZD_PIP_MB! MB^)
echo.
echo Andere Programme auf diesem Rechner koennen diese Ordner ebenfalls
echo nutzen ^(sie laden die Dateien bei Bedarf neu^). Im Zweifel behalten.
echo.
set "ZD_ANTWORT=n"
set /p ZD_ANTWORT=" Diese Dateien loeschen? (j/n): "
if /i not "!ZD_ANTWORT!"=="j" (
echo [OK] Die Dateien bleiben erhalten
echo [INFO] Zusatzdateien wurden beibehalten >> "%LOGFILE%"
exit /b 0
)
if exist "!ZD_PW!\" (
rmdir /s /q "!ZD_PW!" >nul 2>&1
if exist "!ZD_PW!\" (
echo [^^!^^!] Browser-Dateien konnten nicht ganz entfernt werden ^(Datei in Benutzung?^)
echo [WARN] Playwright-Ordner nicht ganz entfernt: !ZD_PW! >> "%LOGFILE%"
) else (
echo [OK] Browser-Dateien entfernt
echo [OK] Playwright-Ordner entfernt: !ZD_PW! >> "%LOGFILE%"
)
)
if exist "!ZD_PIP!\" (
if exist "!ZD_PIP!\Cache\" rmdir /s /q "!ZD_PIP!\Cache" >nul 2>&1
rmdir "!ZD_PIP!" >nul 2>&1
if exist "!ZD_PIP!\Cache\" (
echo [^^!^^!] pip-Zwischenspeicher konnte nicht ganz entfernt werden
echo [WARN] pip-Zwischenspeicher nicht ganz entfernt: !ZD_PIP! >> "%LOGFILE%"
) else (
echo [OK] pip-Zwischenspeicher entfernt
echo [OK] pip-Zwischenspeicher entfernt: !ZD_PIP! >> "%LOGFILE%"
)
)
exit /b 0
:ordnergroesse
:: %1 = Ordner, %2 = Name der Variablen fuer die Groesse in MB. Der Pfad geht ueber die Umgebung an PowerShell, damit Leerzeichen und
:: Hochkommas im Benutzernamen nichts zerlegen. Bleibt die Variable leer, schreibt der Aufrufer ein Fragezeichen.
set "ZD_PFAD=%~1"
for /f "usebackq delims=" %%S in (`powershell -NoProfile -Command "[int]((Get-ChildItem -LiteralPath $env:ZD_PFAD -Recurse -Force -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum / 1MB)"`) do set "%~2=%%S"
exit /b 0
:: --- zusatzdateien Ende