War das Programm vorher installiert, ist es danach wieder installiert. Beim ersten Lauf war es das NICHT - danach stand ein deinstalliertes Programm da, und der naechste Schritt (den Updater starten lassen) lief ins Leere. Ein Pruefstand, der aufraeumt, muss auch sich selbst aufraeumen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
199 lines
7.4 KiB
Python
199 lines
7.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: installieren, deinstallieren - bleibt der Ordner sauber?
|
|
|
|
Seine Ansage vom 07.09.2026:
|
|
|
|
"du solltest die auch mal deinstallieren und neu installieren von windows
|
|
lassen, dass du auch pruefen kannst, dass der ordner dann leer ist und
|
|
sauber - will keine tools die unsauber sind"
|
|
|
|
Er hatte recht. Gemessen blieb nach dem Deinstallieren liegen:
|
|
|
|
Rechnungstool-1.7.1.exe, Rechnungstool-1.8.0.exe <- vom UPDATER geladen
|
|
Daten/config.json, Daten/rechnungstool.pid
|
|
|
|
Die EXEs kennt der Installer nicht - die hat der Updater abgelegt. Ohne eine
|
|
eigene Regel bleiben sie stehen, und mit jeder Fassung wird es eine mehr.
|
|
|
|
ZWEI DINGE MUESSEN GLEICHZEITIG STIMMEN
|
|
---------------------------------------
|
|
1. Der PROGRAMMordner ist danach restlos weg.
|
|
2. Die DATEN sind unangetastet - Rechnungen, Journal, Nummernbuch. Die sind
|
|
zehn Jahre aufbewahrungspflichtig (§ 147 AO); ein Deinstallieren darf sie
|
|
nie mitnehmen.
|
|
|
|
Beides zusammen geht nur, weil die Daten NEBEN dem Programm liegen:
|
|
|
|
%LOCALAPPDATA%/ravokk/ <- Daten, gemeinsames Nummernbuch
|
|
Rechnungstool/ <- Programm, wird geleert
|
|
Steuerrechnungstool/ <- Programm, wird geleert
|
|
|
|
Genau so hat er es beschrieben: *"es gibt ravokk als main verzeichnis, da liegt
|
|
config und sqlite drin, und dann gibt es den ordner rechnungstool und
|
|
steuertool"* - und dazu, warum es so heisst: *"ravokk ist meine firma und die
|
|
programme gehoeren zu der firma"*.
|
|
|
|
⚠ DIESER PRUEFSTAND INSTALLIERT WIRKLICH. Er laeuft deshalb nur, wenn das
|
|
Setup gebaut ist, und er fasst NUR die Programmordner an - vorhandene Daten
|
|
werden vorher gesichert und danach zurueckgelegt.
|
|
|
|
Und er hinterlaesst den Zustand, den er vorgefunden hat: war das Programm
|
|
vorher installiert, ist es danach wieder installiert. Beim ersten Lauf war es
|
|
das NICHT - danach stand ein deinstalliertes Programm da, und der naechste
|
|
Schritt lief ins Leere. Ein Pruefstand, der aufraeumt, muss auch sich selbst
|
|
aufraeumen.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
HIER = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HIER)
|
|
|
|
import version # noqa: E402
|
|
|
|
LOKAL = os.environ.get("LOCALAPPDATA", "")
|
|
RAVOKK = os.path.join(LOKAL, "ravokk")
|
|
NAME = os.path.splitext(version.EXE_NAME)[0]
|
|
PROGRAMMORDNER = os.path.join(RAVOKK, NAME)
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
kod = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(kod, "replace").decode(kod))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
def setup_datei():
|
|
ordner = os.path.join(HIER, "release")
|
|
if not os.path.isdir(ordner):
|
|
return ""
|
|
for d in sorted(os.listdir(ordner)):
|
|
if d.startswith(NAME + "-Setup") and d.endswith(".exe"):
|
|
return os.path.join(ordner, d)
|
|
return ""
|
|
|
|
|
|
def beenden():
|
|
subprocess.run(["powershell", "-NoProfile", "-Command",
|
|
"Get-Process | Where-Object { $_.ProcessName -match "
|
|
f"'{NAME}|Updater' " + "} | Stop-Process -Force "
|
|
"-ErrorAction SilentlyContinue"], capture_output=True)
|
|
time.sleep(1.5)
|
|
|
|
|
|
def inhalt(ordner):
|
|
if not os.path.isdir(ordner):
|
|
return []
|
|
gefunden = []
|
|
for wurzel, _, dateien in os.walk(ordner):
|
|
for d in dateien:
|
|
gefunden.append(os.path.relpath(os.path.join(wurzel, d), ordner))
|
|
return sorted(gefunden)
|
|
|
|
|
|
setup = setup_datei()
|
|
if not setup:
|
|
print(f"UEBERSPRUNGEN | kein Setup in release/ (vorher: python bauen.py)")
|
|
sys.exit(0)
|
|
if not LOKAL:
|
|
print("UEBERSPRUNGEN | kein LOCALAPPDATA")
|
|
sys.exit(0)
|
|
|
|
# ---- Vorhandene Daten in Sicherheit bringen -----------------------------
|
|
# Hier laeuft echte Software auf einem echten Rechner. Was schon da ist, wird
|
|
# beiseitegelegt und hinterher zurueckgestellt - ein Pruefstand darf niemandem
|
|
# seine Buchhaltung anfassen.
|
|
sicher = tempfile.mkdtemp(prefix="installpruef_")
|
|
gesichert = []
|
|
if os.path.isdir(RAVOKK):
|
|
for d in os.listdir(RAVOKK):
|
|
quelle = os.path.join(RAVOKK, d)
|
|
if os.path.isfile(quelle):
|
|
shutil.copy2(quelle, os.path.join(sicher, d))
|
|
gesichert.append(d)
|
|
print(f" ({len(gesichert)} vorhandene Datei(en) gesichert)")
|
|
|
|
beenden()
|
|
war_installiert = os.path.exists(os.path.join(PROGRAMMORDNER, "unins000.exe"))
|
|
alter_uninstaller = os.path.join(PROGRAMMORDNER, "unins000.exe")
|
|
if os.path.exists(alter_uninstaller):
|
|
subprocess.run([alter_uninstaller, "/VERYSILENT", "/SUPPRESSMSGBOXES",
|
|
"/NORESTART"], capture_output=True)
|
|
time.sleep(6)
|
|
|
|
# ---- 1. Installieren ----------------------------------------------------
|
|
e = subprocess.run([setup, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"],
|
|
capture_output=True)
|
|
time.sleep(5)
|
|
pruefe(e.returncode == 0, f"das Setup laeuft durch (Rueckgabe {e.returncode})")
|
|
pruefe(os.path.isdir(PROGRAMMORDNER),
|
|
f"das Programm landet unter ravokk ({PROGRAMMORDNER})")
|
|
dabei = inhalt(PROGRAMMORDNER)
|
|
pruefe(any(d.startswith(NAME + "-") for d in dabei),
|
|
f"die Programmdatei liegt dort ({dabei})")
|
|
pruefe("Updater.exe" in dabei, "und der Updater daneben")
|
|
|
|
# ---- 2. Eine Datei anlegen, wie der Updater es taete ---------------------
|
|
# So sieht der Ordner aus, nachdem das Programm ein paar Fassungen lang
|
|
# gelaufen ist. Genau diese Dateien blieben frueher liegen.
|
|
geerbt = os.path.join(PROGRAMMORDNER, f"{NAME}-9.9.9.exe")
|
|
with open(geerbt, "wb") as f:
|
|
f.write(b"als haette der Updater sie geladen")
|
|
with open(os.path.join(PROGRAMMORDNER, "installiert.json"), "w",
|
|
encoding="utf-8") as f:
|
|
f.write("{}")
|
|
|
|
# ---- 3. Eine Datendatei daneben, die ueberleben MUSS ---------------------
|
|
probe = os.path.join(RAVOKK, "pruefstand-beleg.txt")
|
|
with open(probe, "w", encoding="utf-8") as f:
|
|
f.write("steht fuer Rechnungen, Journal und Nummernbuch")
|
|
|
|
# ---- 4. Deinstallieren --------------------------------------------------
|
|
beenden()
|
|
u = os.path.join(PROGRAMMORDNER, "unins000.exe")
|
|
pruefe(os.path.exists(u), "es gibt einen Uninstaller")
|
|
if os.path.exists(u):
|
|
subprocess.run([u, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"],
|
|
capture_output=True)
|
|
time.sleep(7)
|
|
|
|
uebrig = inhalt(PROGRAMMORDNER)
|
|
pruefe(not os.path.isdir(PROGRAMMORDNER),
|
|
f"der Programmordner ist danach weg ({uebrig})")
|
|
pruefe(not uebrig,
|
|
f"und darin liegt nichts mehr - auch keine geladene Fassung ({uebrig})")
|
|
|
|
pruefe(os.path.exists(probe),
|
|
"die Daten daneben sind unangetastet (Rechnungen, Journal, Nummernbuch)")
|
|
pruefe(os.path.isdir(RAVOKK),
|
|
"und der Ordner der Firma steht noch")
|
|
|
|
# ---- Aufraeumen ---------------------------------------------------------
|
|
if os.path.exists(probe):
|
|
os.remove(probe)
|
|
if war_installiert:
|
|
# So hinterlassen, wie es vorgefunden wurde.
|
|
subprocess.run([setup, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"],
|
|
capture_output=True)
|
|
time.sleep(5)
|
|
print(f" (war vorher installiert - wieder eingerichtet: "
|
|
f"{os.path.isdir(PROGRAMMORDNER)})")
|
|
for d in gesichert:
|
|
ziel = os.path.join(RAVOKK, d)
|
|
if not os.path.exists(ziel):
|
|
os.makedirs(RAVOKK, exist_ok=True)
|
|
shutil.copy2(os.path.join(sicher, d), ziel)
|
|
shutil.rmtree(sicher, ignore_errors=True)
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Deinstallieren laesst den Programmordner leer - und die Daten stehen")
|