Files
aza/AzA march 2026/_test_aza_shutdown_residual.py
2026-06-10 22:55:03 +02:00

132 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Phase-5-Hilfe: AzA-Fenster und eigene AzA-Prozesse nach Shutdown pruefen."""
from __future__ import annotations
import argparse
import ctypes
import subprocess
import sys
from ctypes import wintypes
AZA_WINDOW_PREFIXES = (
"AzA Office",
"AzA-Empfang",
"AzA Empfang Chat",
"AzA MiniChat",
"AzA Kontakte",
"AzA Add-on Beta",
"Diktat",
"AZA von Arzt zu Arzt",
"AzA von Arzt zu Arzt",
)
AZA_PROCESS_NAMES = (
"aza_desktop.exe",
"aza_start_panel.exe",
"AZA_EmpfangShell.exe",
"AZA_KontaktPanel.exe",
"aza_updater.exe",
)
def _matches(title: str, prefixes: tuple[str, ...]) -> bool:
return any(title == p or title.startswith(p) for p in prefixes if p)
def count_aza_windows() -> list[tuple[int, int, str]]:
if sys.platform != "win32":
return []
user32 = ctypes.windll.user32
found: list[tuple[int, int, str]] = []
@ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
def _cb(hwnd, _lp):
visible = bool(user32.IsWindowVisible(hwnd) or user32.IsIconic(hwnd))
if not visible:
return True
buf = ctypes.create_unicode_buffer(320)
user32.GetWindowTextW(hwnd, buf, 320)
t = buf.value or ""
if not _matches(t, AZA_WINDOW_PREFIXES):
return True
p = wintypes.DWORD()
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(p))
found.append((int(hwnd), int(p.value), t))
return True
user32.EnumWindows(_cb, 0)
return found
def count_aza_processes() -> list[tuple[int, str]]:
if sys.platform != "win32":
return []
out: list[tuple[int, str]] = []
try:
r = subprocess.run(
[
"powershell",
"-NoProfile",
"-Command",
"Get-Process | Where-Object { $_.ProcessName -match "
"'^(aza_desktop|aza_start_panel|AZA_EmpfangShell|AZA_KontaktPanel|aza_updater)$' } "
"| Select-Object Id,ProcessName | ConvertTo-Json -Compress",
],
capture_output=True,
text=True,
timeout=15,
)
raw = (r.stdout or "").strip()
if not raw:
return out
import json
data = json.loads(raw)
if isinstance(data, dict):
data = [data]
for item in data or []:
pid = int(item.get("Id") or 0)
name = str(item.get("ProcessName") or "")
if pid and name:
out.append((pid, name.lower() + ".exe"))
except Exception:
pass
return out
def main() -> int:
ap = argparse.ArgumentParser(description="AzA Shutdown Restpruefung Phase 5")
ap.add_argument(
"--check",
action="store_true",
help="Exit 1 wenn noch AzA-Fenster oder eigene AzA-EXEs laufen",
)
args = ap.parse_args()
if sys.platform != "win32":
print("ERROR: nur Windows")
return 2
wins = count_aza_windows()
procs = count_aza_processes()
print(f"aza_window_count={len(wins)}")
print(f"aza_process_count={len(procs)}")
for hwnd, pid, title in wins:
print(f" window: HWND={hwnd} PID={pid} title={title!r}")
for pid, name in procs:
if name in AZA_PROCESS_NAMES:
print(f" process: PID={pid} name={name}")
if args.check:
bad = bool(wins) or bool(procs)
if bad:
print("SHUTDOWN_RESIDUAL_FAIL")
return 1
print("SHUTDOWN_RESIDUAL_OK")
return 0
return 0
if __name__ == "__main__":
raise SystemExit(main())