@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