Ctrl K
ComunidadGuías…

DEINSTALLIEREN.bat

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
@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