Загрузка данных
"""
ZabHelper v4.5 — Центр восстановления Windows.
UI: синий glassmorphism (сайдбар + карточки).
Раздел "Ограничения": сканирование + ручное редактирование значений реестра
+ подмена utilman.exe / sethc.exe на саму программу.
"""
from __future__ import annotations
import base64
import csv
import ctypes
import datetime
import glob
import json
import os
import re
import shutil
import subprocess
import sys
import threading
import tkinter as tk
from tkinter import filedialog, ttk, scrolledtext
from typing import Any, Callable, Optional
try:
import winreg # type: ignore
except ImportError:
winreg = None # type: ignore
# ====================================================================== #
# ПАЛИТРА: СИНИЙ GLASSMORPHISM
# ====================================================================== #
PALETTE = {
"bg_deep": "#070b1c",
"bg_mid": "#0e1630",
"bg_glass": "#101c3a",
"bg_card": "#0f1c40",
"bg_card_hi": "#152a5c",
"bg_sidebar": "#0a1228",
"bg_input": "#0c1836",
"border": "#1f3a75",
"border_hi": "#2c5bb8",
"fg": "#eaf1ff",
"fg_dim": "#8aa2d6",
"fg_mute": "#5f74a8",
"accent": "#4c8dff",
"accent_hi": "#7aa8ff",
"accent_deep": "#1b47a8",
"glow": "#6ea8ff",
"success": "#4ade80",
"warning": "#fbbf24",
"error": "#f87171",
"sys": "#a78bfa",
"hidden": "#6b7ba8",
}
FONT_UI = ("Segoe UI", 10)
FONT_UI_B = ("Segoe UI", 10, "bold")
FONT_H1 = ("Segoe UI Semibold", 16, "bold")
FONT_H2 = ("Segoe UI Semibold", 12, "bold")
FONT_MONO = ("Consolas", 9)
FONT_SMALL = ("Segoe UI", 9)
# ====================================================================== #
# ЗАПУСК КОМАНД
# ====================================================================== #
class ConsoleRunner:
UTF8_BOM = b"\xef\xbb\xbf"
UTF16_LE_BOM = b"\xff\xfe"
UTF16_BE_BOM = b"\xfe\xff"
CREATE_NO_WINDOW = 0x08000000 if sys.platform == "win32" else 0
@staticmethod
def _decode(raw: bytes) -> str:
if not raw:
return ""
if raw.startswith(ConsoleRunner.UTF8_BOM):
return raw[3:].decode("utf-8", errors="replace")
if raw.startswith(ConsoleRunner.UTF16_LE_BOM):
return raw[2:].decode("utf-16-le", errors="replace")
if raw.startswith(ConsoleRunner.UTF16_BE_BOM):
return raw[2:].decode("utf-16-be", errors="replace")
if len(raw) >= 4 and raw[1::2].count(0) > len(raw) // 4:
try:
return raw.decode("utf-16-le")
except UnicodeDecodeError:
pass
for enc in ("utf-8", "cp866", "cp1251"):
try:
return raw.decode(enc)
except UnicodeDecodeError:
continue
return raw.decode("utf-8", errors="replace")
@staticmethod
def run(command: str, timeout: int = 120, force_utf8: bool = True) -> str:
full = f'chcp 65001 >nul && {command}' if force_utf8 else command
try:
result = subprocess.run(
full, shell=True, capture_output=True, timeout=timeout,
creationflags=ConsoleRunner.CREATE_NO_WINDOW,
)
out = ConsoleRunner._decode(result.stdout or b"")
err = ConsoleRunner._decode(result.stderr or b"")
if err.strip():
out += "\n" + err
return out
except subprocess.TimeoutExpired:
return "[таймаут]"
except Exception as e:
return f"[ошибка: {e}]"
@staticmethod
def run_ps(script: str, timeout: int = 120) -> str:
try:
encoded = base64.b64encode(script.encode("utf-16-le")).decode("ascii")
except Exception:
return "[ошибка кодирования PowerShell]"
full = (
'chcp 65001 >nul && powershell -NoProfile -ExecutionPolicy Bypass '
f'-EncodedCommand {encoded}'
)
try:
result = subprocess.run(
full, shell=True, capture_output=True, timeout=timeout,
creationflags=ConsoleRunner.CREATE_NO_WINDOW,
)
return ConsoleRunner._decode(result.stdout or b"")
except subprocess.TimeoutExpired:
return "[таймаут PowerShell]"
except Exception as e:
return f"[ошибка PowerShell: {e}]"
# ====================================================================== #
# ГЛАВНОЕ ПРИЛОЖЕНИЕ
# ====================================================================== #
class ZabHelperApp:
SECTIONS: list[tuple[str, str, str]] = [
("monitor", "◉", "Мониторинг"),
("running", "▷", "Запущенные"),
("services", "⚙", "Службы"),
("startup", "▲", "Автозагрузка"),
("tweaks", "◇", "Твики"),
("repair", "✚", "Восстановление"),
("network", "⌬", "Сеть"),
("security", "⛨", "Безопасность"),
("restrict", "⚿", "Ограничения"),
("open", "↗", "Открыть"),
("backups", "▣", "Бэкапы"),
("diag", "◎", "Диагностика"),
]
def __init__(self, root: tk.Tk) -> None:
self.root = root
self.root.title("ZabHelper v4.5 — Центр восстановления Windows")
self.root.geometry("1320x880")
self.root.minsize(1120, 720)
self.root.configure(bg=PALETTE["bg_mid"])
self.is_admin: bool = self._check_admin_rights()
self._proc_cache: list[dict[str, Any]] = []
self._running_cache: list[dict[str, Any]] = []
self._startup_cache: list[dict[str, Any]] = []
self._restrict_cache: list[dict[str, Any]] = []
self._proc_sort_col: Optional[str] = None
self._proc_sort_rev: bool = False
self._current_section: str = "monitor"
self._section_buttons: dict[str, tk.Frame] = {}
self._section_frames: dict[str, tk.Frame] = {}
self._confirm_var = tk.IntVar(value=0)
self._confirm_result: Optional[bool] = None
self._toast_after: Optional[str] = None
self._auto_refresh: bool = False
self._auto_refresh_after: Optional[str] = None
self._setup_styles()
self._build_layout()
self._bind_hotkeys()
self.log("ZabHelper v4.5 запущен.", "ok")
self.log(f"Права администратора: {'ДА' if self.is_admin else 'НЕТ'}",
"ok" if self.is_admin else "warn")
if not self.is_admin:
self.toast("Запущено без прав администратора.", "warn", 5000)
self.show_section("monitor")
# ------------------------------------------------------------------ #
# ПРАВА
# ------------------------------------------------------------------ #
@staticmethod
def _check_admin_rights() -> bool:
try:
return bool(ctypes.windll.shell32.IsUserAnAdmin())
except Exception:
return False
def _restart_as_admin(self) -> None:
try:
params = " ".join(f'"{a}"' for a in sys.argv)
ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, params, None, 1)
self.root.destroy()
except Exception as e:
self.log(f"Не удалось перезапустить: {e}", "err")
# ------------------------------------------------------------------ #
# СТИЛИ TTK
# ------------------------------------------------------------------ #
def _setup_styles(self) -> None:
style = ttk.Style()
try:
style.theme_use("clam")
except tk.TclError:
pass
style.configure(".", background=PALETTE["bg_mid"], foreground=PALETTE["fg"],
fieldbackground=PALETTE["bg_input"], borderwidth=0,
font=FONT_UI)
style.configure("TFrame", background=PALETTE["bg_mid"])
style.configure("Card.TFrame", background=PALETTE["bg_card"])
style.configure("TLabel", background=PALETTE["bg_mid"], foreground=PALETTE["fg"])
style.configure("TButton",
background=PALETTE["bg_card"], foreground=PALETTE["fg"],
borderwidth=1, focusthickness=0, padding=(10, 8),
font=FONT_UI)
style.map("TButton",
background=[("active", PALETTE["bg_card_hi"]),
("pressed", PALETTE["accent_deep"])],
foreground=[("active", PALETTE["fg"])])
style.configure("Accent.TButton",
background=PALETTE["accent_deep"], foreground="#ffffff",
font=FONT_UI_B, borderwidth=0)
style.map("Accent.TButton",
background=[("active", PALETTE["accent"]),
("pressed", PALETTE["accent_hi"])])
style.configure("Danger.TButton",
background="#3b0d1a", foreground=PALETTE["error"],
font=FONT_UI_B, borderwidth=0)
style.map("Danger.TButton", background=[("active", "#5a1325")])
style.configure("Success.TButton",
background="#0c2a1a", foreground=PALETTE["success"],
font=FONT_UI_B, borderwidth=0)
style.map("Success.TButton", background=[("active", "#124a2b")])
style.configure("Ghost.TButton",
background=PALETTE["bg_mid"], foreground=PALETTE["fg_dim"],
borderwidth=0, padding=(8, 6))
style.map("Ghost.TButton",
background=[("active", PALETTE["bg_card"])],
foreground=[("active", PALETTE["fg"])])
style.configure("Treeview",
background=PALETTE["bg_card"], foreground=PALETTE["fg"],
fieldbackground=PALETTE["bg_card"], borderwidth=0,
font=FONT_UI, rowheight=26)
style.configure("Treeview.Heading",
background=PALETTE["bg_glass"], foreground=PALETTE["accent_hi"],
font=FONT_UI_B, relief="flat", padding=(6, 6))
style.map("Treeview",
background=[("selected", PALETTE["accent_deep"])],
foreground=[("selected", "#ffffff")])
style.map("Treeview.Heading",
background=[("active", PALETTE["bg_card_hi"])])
style.configure("TProgressbar",
background=PALETTE["accent"], troughcolor=PALETTE["bg_card"],
borderwidth=0, thickness=6)
style.configure("TEntry",
fieldbackground=PALETTE["bg_input"],
foreground=PALETTE["fg"], insertcolor=PALETTE["accent"],
borderwidth=0, padding=6)
style.configure("TCheckbutton",
background=PALETTE["bg_card"], foreground=PALETTE["fg"],
focuscolor=PALETTE["accent"])
style.map("TCheckbutton",
background=[("active", PALETTE["bg_card"])],
foreground=[("active", PALETTE["accent_hi"])])
# ------------------------------------------------------------------ #
# КАРКАС
# ------------------------------------------------------------------ #
def _build_layout(self) -> None:
self._build_sidebar()
self._build_main_area()
self._build_statusbar()
self._build_confirm_bar()
self._build_toast()
def _build_sidebar(self) -> None:
bar = tk.Frame(self.root, bg=PALETTE["bg_sidebar"], width=240)
bar.pack(side=tk.LEFT, fill=tk.Y)
bar.pack_propagate(False)
head = tk.Frame(bar, bg=PALETTE["bg_sidebar"])
head.pack(fill=tk.X, padx=18, pady=(20, 14))
logo = tk.Canvas(head, width=42, height=42, bg=PALETTE["bg_sidebar"],
highlightthickness=0)
logo.pack(side=tk.LEFT)
logo.create_oval(2, 2, 40, 40, fill=PALETTE["accent_deep"], outline="")
logo.create_text(21, 21, text="⚡", fill="#ffffff",
font=("Segoe UI", 16, "bold"))
title_box = tk.Frame(head, bg=PALETTE["bg_sidebar"])
title_box.pack(side=tk.LEFT, padx=(10, 0))
tk.Label(title_box, text="ZabHelper", bg=PALETTE["bg_sidebar"],
fg=PALETTE["fg"], font=FONT_H2).pack(anchor="w")
tk.Label(title_box, text="v4.5 · Windows Care", bg=PALETTE["bg_sidebar"],
fg=PALETTE["fg_mute"], font=FONT_SMALL).pack(anchor="w")
tk.Frame(bar, bg=PALETTE["border"], height=1).pack(fill=tk.X, padx=18, pady=(4, 14))
nav = tk.Frame(bar, bg=PALETTE["bg_sidebar"])
nav.pack(fill=tk.BOTH, expand=True, padx=10)
for key, icon, title in self.SECTIONS:
item = tk.Frame(nav, bg=PALETTE["bg_sidebar"], cursor="hand2")
item.pack(fill=tk.X, pady=2)
icon_lbl = tk.Label(item, text=icon, bg=PALETTE["bg_sidebar"],
fg=PALETTE["fg_dim"], font=("Segoe UI", 12), width=3)
icon_lbl.pack(side=tk.LEFT, padx=(8, 4), pady=8)
text_lbl = tk.Label(item, text=title, bg=PALETTE["bg_sidebar"],
fg=PALETTE["fg_dim"], font=FONT_UI, anchor="w")
text_lbl.pack(side=tk.LEFT, fill=tk.X, expand=True, pady=8)
for w in (item, icon_lbl, text_lbl):
w.bind("<Button-1>", lambda e, k=key: self.show_section(k))
w.bind("<Enter>", lambda e, it=item, il=icon_lbl, tl=text_lbl:
self._nav_hover(it, il, tl, True))
w.bind("<Leave>", lambda e, it=item, il=icon_lbl, tl=text_lbl:
self._nav_hover(it, il, tl, False))
self._section_buttons[key] = item
bottom = tk.Frame(bar, bg=PALETTE["bg_sidebar"])
bottom.pack(fill=tk.X, padx=14, pady=14)
status_color = PALETTE["success"] if self.is_admin else PALETTE["error"]
status_text = "● Администратор" if self.is_admin else "● Без прав"
tk.Label(bottom, text=status_text, bg=PALETTE["bg_sidebar"],
fg=status_color, font=FONT_UI_B).pack(anchor="w")
tk.Label(bottom, text="ZabHelper · 2025", bg=PALETTE["bg_sidebar"],
fg=PALETTE["fg_mute"], font=FONT_SMALL).pack(anchor="w", pady=(6, 0))
if not self.is_admin:
ttk.Button(bottom, text="Запустить от админа",
style="Accent.TButton",
command=self._restart_as_admin).pack(fill=tk.X, pady=(10, 0))
def _nav_hover(self, item: tk.Frame, icon_lbl: tk.Label,
text_lbl: tk.Label, on: bool) -> None:
key = next((k for k, v in self._section_buttons.items() if v is item), None)
if key == self._current_section:
return
bg = PALETTE["bg_card"] if on else PALETTE["bg_sidebar"]
fg = PALETTE["fg"] if on else PALETTE["fg_dim"]
for w in (item, icon_lbl, text_lbl):
w.configure(bg=bg)
icon_lbl.configure(fg=PALETTE["accent"] if on else PALETTE["fg_dim"])
text_lbl.configure(fg=fg)
def _build_main_area(self) -> None:
self.main_area = tk.Frame(self.root, bg=PALETTE["bg_mid"])
self.main_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
topbar = tk.Frame(self.main_area, bg=PALETTE["bg_mid"])
topbar.pack(fill=tk.X, padx=22, pady=(18, 6))
self.section_title = tk.Label(topbar, text="Мониторинг",
bg=PALETTE["bg_mid"], fg=PALETTE["fg"],
font=FONT_H1)
self.section_title.pack(side=tk.LEFT)
search_wrap = tk.Frame(topbar, bg=PALETTE["bg_input"],
highlightthickness=1,
highlightbackground=PALETTE["border"])
search_wrap.pack(side=tk.RIGHT)
tk.Label(search_wrap, text="⌕", bg=PALETTE["bg_input"],
fg=PALETTE["fg_dim"], font=("Segoe UI", 12)).pack(side=tk.LEFT, padx=(10, 0))
self.search_var = tk.StringVar()
search_entry = tk.Entry(search_wrap, textvariable=self.search_var,
bg=PALETTE["bg_input"], fg=PALETTE["fg"],
insertbackground=PALETTE["accent"],
relief="flat", font=FONT_UI, width=32)
search_entry.pack(side=tk.LEFT, ipady=6, padx=(6, 10))
search_entry.bind("<Return>", lambda e: self._quick_search())
self.section_subtitle = tk.Label(self.main_area, text="",
bg=PALETTE["bg_mid"], fg=PALETTE["fg_dim"],
font=FONT_SMALL)
self.section_subtitle.pack(anchor="w", padx=22, pady=(0, 8))
self.content = tk.Frame(self.main_area, bg=PALETTE["bg_mid"])
self.content.pack(fill=tk.BOTH, expand=True, padx=22, pady=(0, 10))
builders: list[tuple[str, Callable[[tk.Frame], tk.Frame]]] = [
("monitor", self._build_section_monitor),
("running", self._build_section_running),
("services", self._build_section_services),
("startup", self._build_section_startup),
("tweaks", self._build_section_tweaks),
("repair", self._build_section_repair),
("network", self._build_section_network),
("security", self._build_section_security),
("restrict", self._build_section_restrictions),
("open", self._build_section_open),
("backups", self._build_section_backups),
("diag", self._build_section_diagnostics),
]
for key, builder in builders:
try:
self._section_frames[key] = builder(self.content)
except Exception as e:
fb = tk.Frame(self.content, bg=PALETTE["bg_mid"])
tk.Label(fb, text=f"Раздел недоступен: {key}\n{e}",
bg=PALETTE["bg_mid"], fg=PALETTE["error"],
font=FONT_UI).pack(padx=20, pady=20)
self._section_frames[key] = fb
self._build_log_panel(self.main_area)
def _build_statusbar(self) -> None:
bar = tk.Frame(self.root, bg=PALETTE["bg_glass"], height=26)
bar.pack(side=tk.BOTTOM, fill=tk.X)
color = PALETTE["success"] if self.is_admin else PALETTE["error"]
tk.Label(bar, text=("● Администратор" if self.is_admin else "● Без прав"),
bg=PALETTE["bg_glass"], fg=color,
font=FONT_UI_B).pack(side=tk.LEFT, padx=12)
tk.Label(bar,
text="F5 — обновить · Ctrl+S — сохранить лог · Ctrl+L — очистить · Ctrl+Q — выход",
bg=PALETTE["bg_glass"], fg=PALETTE["fg_mute"],
font=FONT_SMALL).pack(side=tk.LEFT, padx=20)
self.clock = tk.Label(bar, text="", bg=PALETTE["bg_glass"],
fg=PALETTE["fg_dim"], font=FONT_MONO)
self.clock.pack(side=tk.RIGHT, padx=12)
self._tick_clock()
def _tick_clock(self) -> None:
self.clock.config(text=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
self.root.after(1000, self._tick_clock)
def _build_confirm_bar(self) -> None:
self.confirm_frame = tk.Frame(self.root, bg=PALETTE["bg_glass"],
highlightthickness=1,
highlightbackground=PALETTE["border_hi"])
self.confirm_label = tk.Label(self.confirm_frame, text="",
bg=PALETTE["bg_glass"], fg=PALETTE["warning"],
font=FONT_UI_B, wraplength=900, justify="left")
self.confirm_label.pack(side=tk.LEFT, padx=14, pady=10)
ttk.Button(self.confirm_frame, text="Да", style="Success.TButton",
command=lambda: self._resolve_confirm(True)
).pack(side=tk.RIGHT, padx=6, pady=8)
ttk.Button(self.confirm_frame, text="Нет", style="Danger.TButton",
command=lambda: self._resolve_confirm(False)
).pack(side=tk.RIGHT, padx=6, pady=8)
def ask_confirm(self, text: str) -> bool:
self.confirm_label.config(text=text)
self.confirm_frame.pack(side=tk.TOP, fill=tk.X, padx=0, pady=0)
self.confirm_frame.lift()
self._confirm_var.set(0)
self._confirm_result = None
self.root.wait_variable(self._confirm_var)
return bool(self._confirm_result)
def _resolve_confirm(self, value: bool) -> None:
self._confirm_result = value
self.confirm_frame.pack_forget()
self._confirm_var.set(1)
def _build_toast(self) -> None:
self.toast_label = tk.Label(self.root, text="", bg=PALETTE["accent_deep"],
fg="#ffffff", font=FONT_UI_B,
padx=16, pady=10)
def toast(self, text: str, level: str = "info", ms: int = 3000) -> None:
bg = {"ok": "#124a2b", "warn": "#5a3a0c",
"err": "#5a1325", "info": PALETTE["accent_deep"]}.get(
level, PALETTE["accent_deep"])
self.toast_label.config(text=text, bg=bg)
self.toast_label.place(relx=0.5, rely=0.06, anchor="n")
if self._toast_after:
self.root.after_cancel(self._toast_after)
self._toast_after = self.root.after(ms, self.toast_label.place_forget)
def _build_log_panel(self, parent: tk.Frame) -> None:
outer = tk.Frame(parent, bg=PALETTE["bg_mid"])
outer.pack(fill=tk.X, padx=22, pady=(0, 12))
header = tk.Frame(outer, bg=PALETTE["bg_mid"])
header.pack(fill=tk.X)
tk.Label(header, text="Журнал", bg=PALETTE["bg_mid"],
fg=PALETTE["fg_dim"], font=FONT_UI_B).pack(side=tk.LEFT)
ttk.Button(header, text="Сохранить", style="Ghost.TButton",
command=self.save_log).pack(side=tk.RIGHT, padx=3)
ttk.Button(header, text="Очистить", style="Ghost.TButton",
command=self.clear_log).pack(side=tk.RIGHT, padx=3)
self.txt_log = scrolledtext.ScrolledText(
outer, height=8, state="disabled", font=FONT_MONO,
bg=PALETTE["bg_glass"], fg=PALETTE["fg"],
insertbackground=PALETTE["accent"],
relief="flat", borderwidth=0, wrap="word")
self.txt_log.pack(fill=tk.X, pady=(6, 0))
self.txt_log.tag_config("ok", foreground=PALETTE["success"])
self.txt_log.tag_config("warn", foreground=PALETTE["warning"])
self.txt_log.tag_config("err", foreground=PALETTE["error"])
self.txt_log.tag_config("info", foreground=PALETTE["fg"])
self.log_menu = tk.Menu(self.root, tearoff=0,
bg=PALETTE["bg_card"], fg=PALETTE["fg"],
activebackground=PALETTE["accent_deep"],
activeforeground="#ffffff")
self.log_menu.add_command(label="Копировать всё", command=self._copy_log)
self.log_menu.add_command(label="Сохранить как…", command=self.save_log)
self.log_menu.add_separator()
self.log_menu.add_command(label="Очистить", command=self.clear_log)
self.txt_log.bind("<Button-3>", self._show_log_menu)
def _show_log_menu(self, event: tk.Event) -> None:
try:
self.log_menu.tk_popup(event.x_root, event.y_root)
finally:
self.log_menu.grab_release()
def _copy_log(self) -> None:
self.root.clipboard_clear()
self.root.clipboard_append(self.txt_log.get("1.0", tk.END))
self.toast("Лог скопирован", "ok")
def log(self, message: str, level: str = "info") -> None:
try:
ts = datetime.datetime.now().strftime("%H:%M:%S")
prefix = {"info": "·", "ok": "+", "warn": "!", "err": "×"}.get(level, "·")
self.txt_log.configure(state="normal")
self.txt_log.insert(tk.END, f"[{ts}] {prefix} {message}\n", level)
self.txt_log.see(tk.END)
self.txt_log.configure(state="disabled")
except Exception:
pass
def clear_log(self) -> None:
self.txt_log.configure(state="normal")
self.txt_log.delete("1.0", tk.END)
self.txt_log.configure(state="disabled")
self.log("Журнал очищен.")
def save_log(self) -> None:
path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")],
initialfile=f"zabhelper_log_{datetime.datetime.now():%Y%m%d_%H%M%S}.txt")
if not path:
return
try:
with open(path, "w", encoding="utf-8") as f:
f.write(self.txt_log.get("1.0", tk.END))
self.toast("Лог сохранён", "ok")
except Exception as e:
self.log(f"Не удалось сохранить лог: {e}", "err")
# ------------------------------------------------------------------ #
# НАВИГАЦИЯ
# ------------------------------------------------------------------ #
def show_section(self, key: str) -> None:
if key not in self._section_frames:
return
self._current_section = key
for k, frame in self._section_frames.items():
if k == key:
frame.pack(fill=tk.BOTH, expand=True)
else:
frame.pack_forget()
meta = next((s for s in self.SECTIONS if s[0] == key), None)
if meta:
self.section_title.config(text=meta[2])
subtitles = {
"monitor": "Процессы, включая скрытые и системные.",
"running": "Программы с окнами и фоновые процессы.",
"services": "Службы и драйверы.",
"startup": "Записи автозагрузки.",
"tweaks": "Очистка и твики.",
"repair": "Восстановление компонентов системы.",
"network": "Диагностика и сброс сети.",
"security": "Defender, UAC, брандмауэр.",
"restrict": "Сканирование, редактирование значений, подмена Utilman/Sethc.",
"open": "Быстрый доступ к оснасткам.",
"backups": "Бэкапы и точки восстановления.",
"diag": "Диагностический отчёт.",
}
self.section_subtitle.config(text=subtitles.get(key, ""))
for k, item in self._section_buttons.items():
active = (k == key)
item.configure(bg=PALETTE["bg_card"] if active else PALETTE["bg_sidebar"])
for child in item.winfo_children():
child.configure(bg=PALETTE["bg_card"] if active else PALETTE["bg_sidebar"])
if isinstance(child, tk.Label):
is_icon = child.cget("text") in [s[1] for s in self.SECTIONS]
if active:
child.configure(fg=PALETTE["accent_hi"] if is_icon else PALETTE["fg"])
else:
child.configure(fg=PALETTE["fg_dim"])
if key == "monitor" and not self._proc_cache:
self.refresh_processes()
elif key == "running" and not self._running_cache:
self.refresh_running(only_gui=True)
elif key == "startup" and not self._startup_cache:
self.refresh_startup()
elif key == "restrict" and not self._restrict_cache:
self.scan_restrictions()
def _quick_search(self) -> None:
q = self.search_var.get().strip().lower()
if not q:
return
self.log(f"Поиск: '{q}'", "info")
found = 0
for sec_key, _, sec_title in self.SECTIONS:
frame = self._section_frames.get(sec_key)
if frame is None:
continue
for sub in self._iter_widgets(frame):
if isinstance(sub, ttk.Button):
try:
if q in str(sub.cget("text")).lower():
self.log(f" [{sec_title}] {sub.cget('text')}", "ok")
found += 1
except Exception:
pass
if not found:
self.log("Ничего не найдено.", "warn")
def _iter_widgets(self, w: tk.Misc):
yield w
for c in w.winfo_children():
yield from self._iter_widgets(c)
# ------------------------------------------------------------------ #
# ХЕЛПЕРЫ UI
# ------------------------------------------------------------------ #
def _make_card(self, parent: tk.Misc, title: str, subtitle: str = "") -> tk.Frame:
card = tk.Frame(parent, bg=PALETTE["bg_card"], highlightthickness=1,
highlightbackground=PALETTE["border"])
card.pack(fill=tk.X, pady=6)
head = tk.Frame(card, bg=PALETTE["bg_card"])
head.pack(fill=tk.X, padx=16, pady=(12, 4))
tk.Label(head, text=title, bg=PALETTE["bg_card"], fg=PALETTE["fg"],
font=FONT_UI_B).pack(anchor="w")
if subtitle:
tk.Label(head, text=subtitle, bg=PALETTE["bg_card"],
fg=PALETTE["fg_dim"], font=FONT_SMALL,
wraplength=900, justify="left").pack(anchor="w", pady=(2, 0))
body = tk.Frame(card, bg=PALETTE["bg_card"])
body.pack(fill=tk.X, padx=16, pady=(4, 12))
return body
def _grid_buttons(self, parent: tk.Frame, funcs: list[Any], cols: int = 3) -> None:
for i, item in enumerate(funcs):
label, cmd, *rest = item
style = rest[0] if rest else "TButton"
r, c = divmod(i, cols)
ttk.Button(parent, text=label, command=cmd, style=style
).grid(row=r, column=c, padx=4, pady=4, sticky="ew")
for c in range(cols):
parent.columnconfigure(c, weight=1)
# ================================================================== #
# РАЗДЕЛ: МОНИТОРИНГ
# ================================================================== #
def _build_section_monitor(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
toolbar = tk.Frame(frame, bg=PALETTE["bg_mid"])
toolbar.pack(fill=tk.X, pady=(0, 6))
ttk.Button(toolbar, text="Обновить", style="Accent.TButton",
command=self.refresh_processes).pack(side=tk.LEFT)
ttk.Button(toolbar, text="Автообновление",
command=self.toggle_auto_refresh).pack(side=tk.LEFT, padx=4)
ttk.Button(toolbar, text="Дерево процессов",
command=self.show_process_tree).pack(side=tk.LEFT, padx=4)
ttk.Button(toolbar, text="CSV", command=self.export_processes_csv
).pack(side=tk.LEFT, padx=4)
ttk.Button(toolbar, text="Завершить", style="Danger.TButton",
command=self.kill_selected_process).pack(side=tk.RIGHT, padx=4)
ttk.Button(toolbar, text="Свойства",
command=self.show_selected_process_props).pack(side=tk.RIGHT, padx=4)
ttk.Button(toolbar, text="Папка",
command=self.open_selected_process_folder).pack(side=tk.RIGHT, padx=4)
flt = tk.Frame(frame, bg=PALETTE["bg_card"], highlightthickness=1,
highlightbackground=PALETTE["border"])
flt.pack(fill=tk.X, pady=(0, 6))
inner = tk.Frame(flt, bg=PALETTE["bg_card"])
inner.pack(fill=tk.X, padx=14, pady=10)
self.show_hidden_var = tk.BooleanVar(value=True)
self.show_system_var = tk.BooleanVar(value=True)
self.show_svchost_var = tk.BooleanVar(value=True)
for text, var in [("Скрытые", self.show_hidden_var),
("Системные", self.show_system_var),
("svchost", self.show_svchost_var)]:
ttk.Checkbutton(inner, text=text, variable=var,
command=self._apply_process_filter).pack(side=tk.LEFT, padx=(0, 10))
tk.Label(inner, text="Фильтр:", bg=PALETTE["bg_card"],
fg=PALETTE["fg_dim"], font=FONT_SMALL).pack(side=tk.LEFT, padx=(10, 6))
self.proc_filter = tk.StringVar()
tk.Entry(inner, textvariable=self.proc_filter,
bg=PALETTE["bg_input"], fg=PALETTE["fg"],
insertbackground=PALETTE["accent"],
relief="flat", font=FONT_UI).pack(side=tk.LEFT, fill=tk.X,
expand=True, ipady=4, padx=4)
self.proc_filter.trace_add("write", lambda *a: self._apply_process_filter())
self.proc_stats = tk.Label(frame, text="", bg=PALETTE["bg_mid"],
fg=PALETTE["fg_dim"], font=FONT_SMALL)
self.proc_stats.pack(anchor="w", pady=(0, 4))
cols = ("PID", "PPID", "Имя", "Пользователь", "CPU, с",
"Память, МБ", "Потоки", "Статус", "Путь")
self.proc_tree = ttk.Treeview(frame, columns=cols, show="headings")
widths = {"PID": 70, "PPID": 70, "Имя": 220, "Пользователь": 170,
"CPU, с": 80, "Память, МБ": 100, "Потоки": 70,
"Статус": 130, "Путь": 520}
for c in cols:
self.proc_tree.heading(c, text=c,
command=lambda col=c: self._sort_processes(col))
self.proc_tree.column(c, width=widths[c],
anchor=("e" if "МБ" in c or "CPU" in c else "w"))
self.proc_tree.pack(fill=tk.BOTH, expand=True)
self.proc_tree.tag_configure("system", foreground=PALETTE["sys"])
self.proc_tree.tag_configure("hidden", foreground=PALETTE["hidden"])
self.proc_tree.tag_configure("user", foreground=PALETTE["fg"])
self.proc_tree.bind("<Button-3>", self._show_proc_menu)
self.proc_tree.bind("<Double-1>", lambda e: self.show_selected_process_props())
self.proc_menu = tk.Menu(self.root, tearoff=0,
bg=PALETTE["bg_card"], fg=PALETTE["fg"],
activebackground=PALETTE["accent_deep"],
activeforeground="#ffffff")
self.proc_menu.add_command(label="Завершить", command=self.kill_selected_process)
self.proc_menu.add_command(label="Дерево", command=self.show_process_tree)
self.proc_menu.add_separator()
self.proc_menu.add_command(label="Открыть папку",
command=self.open_selected_process_folder)
self.proc_menu.add_command(label="Свойства",
command=self.show_selected_process_props)
self.proc_menu.add_command(label="Поиск в интернете",
command=self.search_selected_process)
return frame
def _show_proc_menu(self, event: tk.Event) -> None:
row = self.proc_tree.identify_row(event.y)
if row:
self.proc_tree.selection_set(row)
try:
self.proc_menu.tk_popup(event.x_root, event.y_root)
finally:
self.proc_menu.grab_release()
def toggle_auto_refresh(self) -> None:
self._auto_refresh = not self._auto_refresh
if self._auto_refresh:
self.toast("Автообновление включено (3 с)", "ok")
self._auto_refresh_tick()
else:
self.toast("Автообновление выключено", "warn")
if self._auto_refresh_after:
self.root.after_cancel(self._auto_refresh_after)
self._auto_refresh_after = None
def _auto_refresh_tick(self) -> None:
if not self._auto_refresh:
return
if self._current_section == "monitor":
self.refresh_processes()
self._auto_refresh_after = self.root.after(3000, self._auto_refresh_tick)
def refresh_processes(self) -> None:
self.log("Чтение процессов (WMI/CIM)…", "info")
ps = r"""
$result = @()
$allProcs = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue
foreach ($p in $allProcs) {
$cpu = 0.0; $mem = 0.0; $threads = 0; $user = ""; $path = ""
try {
$wp = Get-Process -Id $p.ProcessId -ErrorAction SilentlyContinue
if ($wp) {
if ($wp.CPU) { $cpu = [math]::Round($wp.CPU, 2) }
$mem = [math]::Round($wp.WorkingSet64 / 1MB, 2)
$threads = $wp.Threads.Count
$path = $wp.Path
}
} catch {}
try {
$owner = Invoke-CimMethod -InputObject $p -MethodName GetOwner -ErrorAction SilentlyContinue
if ($owner -and $owner.User) { $user = "$($owner.Domain)\$($owner.User)" }
} catch {}
$result += [PSCustomObject]@{
PID = $p.ProcessId; PPID = $p.ParentProcessId; Name = $p.Name
User = $user; CPU = $cpu; MB = $mem; Threads = $threads
Path = if ($path) { $path } else { $p.ExecutablePath }
CommandLine = $p.CommandLine
}
}
$result | ConvertTo-Json -Depth 3 -Compress
"""
out = ConsoleRunner.run_ps(ps, timeout=90)
data: list[dict[str, Any]] = []
if out.strip().startswith(("[", "{")):
try:
parsed = json.loads(out)
data = parsed if isinstance(parsed, list) else [parsed]
except Exception as e:
self.log(f"Ошибка JSON: {e}", "err")
if not data:
self.log("WMI недоступен — tasklist", "warn")
out = ConsoleRunner.run("tasklist /fo csv /nh /v")
tmp: list[dict[str, Any]] = []
for line in out.splitlines():
parts = [p.strip('"') for p in line.split('","')]
if len(parts) >= 8:
tmp.append({
"pid": parts[1], "ppid": "?", "name": parts[0],
"user": parts[6], "cpu": 0,
"mb": parts[4].replace(" K", "").replace(",", "").strip(),
"threads": 0, "path": "", "cmd": "",
"status": self._process_status(0, parts[0], parts[6]),
})
self._proc_cache = tmp
else:
cache: list[dict[str, Any]] = []
for p in data:
pid = str(p.get("PID", ""))
ppid = str(p.get("PPID", ""))
name = p.get("Name", "") or "?"
user = p.get("User", "") or ""
cpu = float(p.get("CPU", 0) or 0)
mb = float(p.get("MB", 0) or 0)
threads = int(p.get("Threads", 0) or 0)
path = p.get("Path", "") or ""
cmd = p.get("CommandLine", "") or ""
status = self._process_status(int(pid) if pid.isdigit() else 0, name, user)
cache.append({"pid": pid, "ppid": ppid, "name": name, "user": user,
"cpu": cpu, "mb": mb, "threads": threads,
"path": path, "cmd": cmd, "status": status})
self._proc_cache = cache
self._apply_process_filter()
self.log(f"Всего процессов: {len(self._proc_cache)}.", "ok")
@staticmethod
def _process_status(pid: int, name: str, user: str) -> str:
if pid in (0, 4):
return "system"
n = name.lower()
if n in ("system", "registry", "memory compression", "secure system",
"smss.exe", "csrss.exe", "wininit.exe", "winlogon.exe",
"services.exe", "lsass.exe", "fontdrvhost.exe", "dwm.exe"):
return "system"
if user.lower().startswith(("nt authority\\", "nt service\\",
"font driver host\\")):
return "system"
if not user:
return "hidden"
return "user"
def _apply_process_filter(self) -> None:
if not hasattr(self, "proc_tree"):
return
q = self.proc_filter.get().strip().lower() if hasattr(self, "proc_filter") else ""
for row in self.proc_tree.get_children():
self.proc_tree.delete(row)
shown = sys_cnt = hid_cnt = usr_cnt = 0
for p in self._proc_cache:
st = p.get("status", "user")
if st == "system":
sys_cnt += 1
if not self.show_system_var.get():
continue
elif st == "hidden":
hid_cnt += 1
if not self.show_hidden_var.get():
continue
else:
usr_cnt += 1
if p["name"].lower() == "svchost.exe" and not self.show_svchost_var.get():
continue
if q and (q not in p["name"].lower()
and q not in str(p["path"]).lower()
and q not in str(p["user"]).lower()
and q not in str(p["pid"])):
continue
self.proc_tree.insert("", "end",
values=(p["pid"], p["ppid"], p["name"], p["user"], p["cpu"],
p["mb"], p["threads"],
{"system": "СИСТЕМНЫЙ", "hidden": "СКРЫТЫЙ",
"user": "пользователь"}.get(st, st),
p["path"]),
tags=(st,))
shown += 1
self.proc_stats.config(
text=f"Показано: {shown} · всего: {len(self._proc_cache)} · "
f"пользовательских: {usr_cnt} · системных: {sys_cnt} · "
f"скрытых: {hid_cnt} · "
f"права: {'админ' if self.is_admin else 'без админа'}")
def _sort_processes(self, col: str) -> None:
if self._proc_sort_col == col:
self._proc_sort_rev = not self._proc_sort_rev
else:
self._proc_sort_col = col
self._proc_sort_rev = False
def to_int(v: Any) -> int:
try:
return int(v)
except (TypeError, ValueError):
return 0
def to_float(v: Any) -> float:
try:
return float(v)
except (TypeError, ValueError):
return 0.0
key_map: dict[str, Callable[[dict[str, Any]], Any]] = {
"PID": lambda p: to_int(p["pid"]),
"PPID": lambda p: to_int(p["ppid"]),
"Имя": lambda p: str(p["name"]).lower(),
"Пользователь": lambda p: str(p["user"]).lower(),
"CPU, с": lambda p: to_float(p["cpu"]),
"Память, МБ": lambda p: to_float(p["mb"]),
"Потоки": lambda p: to_int(p["threads"]),
"Статус": lambda p: p["status"],
"Путь": lambda p: str(p["path"]).lower(),
}
if col in key_map:
self._proc_cache.sort(key=key_map[col], reverse=self._proc_sort_rev)
self._apply_process_filter()
def show_process_tree(self) -> None:
win = tk.Toplevel(self.root)
win.title("Дерево процессов")
win.geometry("1000x640")
win.configure(bg=PALETTE["bg_mid"])
tk.Label(win, text="Дерево процессов", bg=PALETTE["bg_mid"],
fg=PALETTE["accent_hi"], font=FONT_H2).pack(pady=10)
tree = ttk.Treeview(win, columns=("PID", "MB", "Path"), show="tree headings")
tree.heading("#0", text="Процесс")
tree.heading("PID", text="PID")
tree.heading("MB", text="МБ")
tree.heading("Path", text="Путь")
tree.column("#0", width=380)
tree.column("PID", width=70, anchor="center")
tree.column("MB", width=90, anchor="e")
tree.column("Path", width=420)
tree.pack(fill=tk.BOTH, expand=True, padx=12, pady=10)
by_pid = {p["pid"]: p for p in self._proc_cache}
children: dict[str, list[dict[str, Any]]] = {}
for p in self._proc_cache:
children.setdefault(p.get("ppid", "0"), []).append(p)
def add_node(parent_node: str, proc: dict[str, Any]) -> None:
node = tree.insert(parent_node, "end",
text=f"{proc['name']} ({proc['user'] or '—'})",
values=(proc["pid"], proc["mb"], proc["path"]),
tags=(proc.get("status", "user"),))
for child in children.get(proc["pid"], []):
add_node(node, child)
for pid, procs in children.items():
if pid not in by_pid and pid != "0":
for proc in procs:
add_node("", proc)
if not tree.get_children():
for p in self._proc_cache:
add_node("", p)
tree.tag_configure("system", foreground=PALETTE["sys"])
tree.tag_configure("hidden", foreground=PALETTE["hidden"])
ttk.Button(win, text="Закрыть", command=win.destroy).pack(pady=6)
def _get_selected_process(self) -> Optional[dict[str, Any]]:
sel = self.proc_tree.selection()
if not sel:
self.toast("Процесс не выбран", "warn")
return None
values = self.proc_tree.item(sel[0], "values")
pid, name = values[0], values[2]
for p in self._proc_cache:
if p["pid"] == pid and p["name"] == name:
return p
return None
def kill_selected_process(self) -> None:
p = self._get_selected_process()
if not p:
return
if p["pid"] in ("0", "4"):
self.toast("Системный процесс PID=0/4 нельзя завершить", "err")
return
pid = str(p["pid"])
try:
result = subprocess.run(
["taskkill", "/f", "/t", "/pid", pid],
capture_output=True, timeout=15,
creationflags=ConsoleRunner.CREATE_NO_WINDOW,
)
out = (ConsoleRunner._decode(result.stdout or b"")
+ ConsoleRunner._decode(result.stderr or b""))
rc = result.returncode
except Exception as e:
self.log(f"taskkill PID={pid}: {e}", "err")
self.toast(f"Не удалось завершить PID {pid}", "err")
return
if rc == 0:
self.log(f"Процесс {p['name']} (PID {pid}) завершён.", "ok")
self.toast(f"Завершён: {p['name']}", "ok")
else:
self.log(f"taskkill PID={pid} rc={rc}: {out.strip()[:300]}", "err")
self.toast(f"Не удалось завершить {p['name']}", "err")
self.refresh_processes()
def open_selected_process_folder(self) -> None:
p = self._get_selected_process()
if not p:
return
path = p.get("path", "")
if path and os.path.exists(path):
subprocess.Popen(f'explorer /select,"{path}"', shell=True)
self.toast("Папка открыта", "ok")
else:
self.toast("Путь неизвестен", "warn")
def show_selected_process_props(self) -> None:
p = self._get_selected_process()
if not p:
return
path = p.get("path", "")
if path and os.path.exists(path):
try:
ret = ctypes.windll.shell32.ShellExecuteW(
None, "properties", path, None, None, 1
)
if int(ret) > 32:
self.log(f"Свойства: {path}", "ok")
self.toast("Свойства открыты", "ok")
else:
self.log(f"ShellExecuteW code={ret} для {path}", "err")
self.toast("Не удалось открыть свойства", "err")
except Exception as e:
self.log(f"Ошибка открытия свойств: {e}", "err")
self.toast("Не удалось открыть свойства", "err")
else:
self.log(f"PID={p['pid']}, имя={p['name']}, "
f"пользователь={p['user'] or '—'}, "
f"память={p['mb']} МБ, статус={p['status']}", "info")
self.toast(f"{p['name']} (PID {p['pid']})", "info")
def search_selected_process(self) -> None:
p = self._get_selected_process()
if not p:
return
q = p["name"] or p["path"]
subprocess.Popen(
f'start "" "https://www.google.com/search?q={q}+Windows+process"',
shell=True)
self.toast(f"Поиск: {q}", "ok")
def export_processes_csv(self) -> None:
path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV", "*.csv"), ("Все", "*.*")],
initialfile=f"processes_{datetime.datetime.now():%Y%m%d_%H%M%S}.csv")
if not path:
return
try:
with open(path, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f, delimiter=";")
w.writerow(["PID", "PPID", "Имя", "Пользователь", "CPU",
"МБ", "Потоки", "Статус", "Путь", "Командная строка"])
for p in self._proc_cache:
w.writerow([p["pid"], p["ppid"], p["name"], p["user"],
p["cpu"], p["mb"], p["threads"], p["status"],
p["path"], p.get("cmd", "")])
self.toast("CSV сохранён", "ok")
except Exception as e:
self.log(f"Ошибка экспорта: {e}", "err")
# ================================================================== #
# РАЗДЕЛ: ЗАПУЩЕННЫЕ
# ================================================================== #
def _build_section_running(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
tb = tk.Frame(frame, bg=PALETTE["bg_mid"]); tb.pack(fill=tk.X, pady=(0, 6))
ttk.Button(tb, text="Обновить", style="Accent.TButton",
command=self.refresh_running).pack(side=tk.LEFT)
ttk.Button(tb, text="С окнами",
command=lambda: self.refresh_running(only_gui=True)).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Все",
command=lambda: self.refresh_running(only_gui=False)).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Завершить", style="Danger.TButton",
command=self.kill_selected_running).pack(side=tk.RIGHT, padx=4)
ttk.Button(tb, text="Папка",
command=self.open_selected_running_folder).pack(side=tk.RIGHT, padx=4)
ttk.Button(tb, text="Свойства",
command=self.show_selected_running_props).pack(side=tk.RIGHT, padx=4)
filt = tk.Frame(frame, bg=PALETTE["bg_card"], highlightthickness=1,
highlightbackground=PALETTE["border"])
filt.pack(fill=tk.X, pady=(0, 6))
inner = tk.Frame(filt, bg=PALETTE["bg_card"]); inner.pack(fill=tk.X, padx=14, pady=10)
tk.Label(inner, text="Фильтр:", bg=PALETTE["bg_card"],
fg=PALETTE["fg_dim"], font=FONT_SMALL).pack(side=tk.LEFT, padx=(0, 6))
self.running_filter = tk.StringVar()
tk.Entry(inner, textvariable=self.running_filter, bg=PALETTE["bg_input"],
fg=PALETTE["fg"], insertbackground=PALETTE["accent"],
relief="flat", font=FONT_UI).pack(side=tk.LEFT, fill=tk.X,
expand=True, ipady=4)
self.running_filter.trace_add("write", lambda *a: self._apply_running_filter())
cols = ("PID", "Имя", "Заголовок окна", "Память, МБ", "Путь")
self.running_tree = ttk.Treeview(frame, columns=cols, show="headings")
for c in cols:
self.running_tree.heading(c, text=c)
self.running_tree.column("PID", width=70, anchor="center")
self.running_tree.column("Имя", width=200)
self.running_tree.column("Заголовок окна", width=320)
self.running_tree.column("Память, МБ", width=100, anchor="e")
self.running_tree.column("Путь", width=520)
self.running_tree.pack(fill=tk.BOTH, expand=True)
self.running_tree.bind("<Double-1>", lambda e: self.show_selected_running_props())
return frame
def refresh_running(self, only_gui: bool = True) -> None:
if not hasattr(self, "running_tree"):
return
for row in self.running_tree.get_children():
self.running_tree.delete(row)
self._running_cache = []
self.log("Чтение запущенных программ…", "info")
if only_gui:
ps = ("Get-Process | Where-Object {$_.MainWindowTitle -ne ''} "
"| Select-Object Id,ProcessName,MainWindowTitle,"
"@{n='MB';e={[math]::Round($_.WS/1MB,1)}},Path "
"| ConvertTo-Json -Depth 2")
else:
ps = ("Get-Process | Select-Object Id,ProcessName,MainWindowTitle,"
"@{n='MB';e={[math]::Round($_.WS/1MB,1)}},Path "
"| ConvertTo-Json -Depth 2")
out = ConsoleRunner.run_ps(ps, timeout=60)
data: list[dict[str, Any]] = []
if out.strip().startswith(("[", "{")):
try:
parsed = json.loads(out)
data = parsed if isinstance(parsed, list) else [parsed]
except Exception:
data = []
if not data:
self.log("PowerShell недоступен — tasklist", "warn")
out = ConsoleRunner.run("tasklist /fo csv /nh")
for line in out.splitlines():
parts = [p.strip('"') for p in line.split('","')]
if len(parts) >= 5:
self._running_cache.append({
"pid": parts[1], "name": parts[0],
"title": parts[4] if parts[4] != "N/A" else "",
"mb": parts[3].replace(" K", "").replace(",", "").strip() or "?",
"path": "",
})
else:
for p in data:
self._running_cache.append({
"pid": str(p.get("Id", "")),
"name": (p.get("ProcessName", "") + ".exe")
if p.get("ProcessName") else "",
"title": p.get("MainWindowTitle", "") or "",
"mb": str(p.get("MB", "")),
"path": p.get("Path", "") or "",
})
self._apply_running_filter()
self.log(f"Найдено: {len(self._running_cache)}.", "ok")
def _apply_running_filter(self) -> None:
if not hasattr(self, "running_tree"):
return
q = self.running_filter.get().strip().lower() if hasattr(self, "running_filter") else ""
for row in self.running_tree.get_children():
self.running_tree.delete(row)
for it in self._running_cache:
if q and (q not in it["name"].lower()
and q not in it["title"].lower()
and q not in it["path"].lower()):
continue
self.running_tree.insert("", "end",
values=(it["pid"], it["name"], it["title"], it["mb"], it["path"]))
def _get_selected_running(self) -> Optional[dict[str, Any]]:
sel = self.running_tree.selection()
if not sel:
self.toast("Не выбрано", "warn")
return None
values = self.running_tree.item(sel[0], "values")
pid, name = values[0], values[1]
for it in self._running_cache:
if it["pid"] == pid and it["name"] == name:
return it
return None
def kill_selected_running(self) -> None:
it = self._get_selected_running()
if not it:
return
pid = str(it["pid"])
try:
result = subprocess.run(
["taskkill", "/f", "/t", "/pid", pid],
capture_output=True, timeout=15,
creationflags=ConsoleRunner.CREATE_NO_WINDOW,
)
out = (ConsoleRunner._decode(result.stdout or b"")
+ ConsoleRunner._decode(result.stderr or b""))
rc = result.returncode
except Exception as e:
self.log(f"taskkill PID={pid}: {e}", "err")
self.toast(f"Не удалось завершить PID {pid}", "err")
return
if rc == 0:
self.log(f"Программа {it['name']} (PID {pid}) завершена.", "ok")
self.toast(f"Завершено: {it['name']}", "ok")
else:
self.log(f"taskkill PID={pid} rc={rc}: {out.strip()[:300]}", "err")
self.toast(f"Не удалось завершить {it['name']}", "err")
self.refresh_running()
def open_selected_running_folder(self) -> None:
it = self._get_selected_running()
if not it:
return
if it["path"] and os.path.exists(it["path"]):
subprocess.Popen(f'explorer /select,"{it["path"]}"', shell=True)
self.toast("Папка открыта", "ok")
else:
self.toast("Путь неизвестен", "warn")
def show_selected_running_props(self) -> None:
it = self._get_selected_running()
if not it:
return
path = it.get("path", "")
if path and os.path.exists(path):
try:
ret = ctypes.windll.shell32.ShellExecuteW(
None, "properties", path, None, None, 1
)
if int(ret) > 32:
self.log(f"Свойства: {path}", "ok")
self.toast("Свойства открыты", "ok")
else:
self.log(f"ShellExecuteW code={ret} для {path}", "err")
self.toast("Не удалось открыть свойства", "err")
except Exception as e:
self.log(f"Ошибка открытия свойств: {e}", "err")
else:
self.toast(f"{it['name']} (PID {it['pid']})", "info")
# ================================================================== #
# РАЗДЕЛ: СЛУЖБЫ
# ================================================================== #
def _build_section_services(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
body = self._make_card(frame, "Службы и драйверы",
"Просмотр, запуск, остановка, конфигурирование.")
funcs = [
("Список служб", self.list_services),
("Список драйверов", self.list_drivers),
("Разблокировать Windows Update", self.unlock_wuauserv),
("Восстановить Defender", self.restore_defender),
("Включить VSS", lambda: self.set_service_start("VSS", "demand")),
("Включить BITS", lambda: self.set_service_start("BITS", "delayed-auto")),
("Включить wuauserv", lambda: self.set_service_start("wuauserv", "demand")),
("Очистить IFEO", self.clear_ifeo),
("Снять запрет .msi",
lambda: self.reg_reset(r"Software\Policies\Microsoft\Windows\Installer",
"DisableMSI")),
("Открыть taskmgr", lambda: self.open_exe("taskmgr.exe")),
("Открыть Process Explorer", self.open_in_procexp),
("Открыть services.msc", lambda: self.open_exe("services.msc")),
]
self._grid_buttons(body, funcs, cols=3)
return frame
def open_in_procexp(self) -> None:
candidates = [
r"C:\Program Files\SysinternalsSuite\procexp64.exe",
r"C:\Program Files\SysinternalsSuite\procexp.exe",
r"C:\Program Files (x86)\SysinternalsSuite\procexp64.exe",
r"C:\Program Files (x86)\SysinternalsSuite\procexp.exe",
r"C:\SysinternalsSuite\procexp64.exe",
r"C:\SysinternalsSuite\procexp.exe",
os.path.join(os.path.expanduser("~"),
r"Downloads\SysinternalsSuite\procexp64.exe"),
]
for c in candidates:
if os.path.exists(c):
try:
subprocess.Popen(f'"{c}"', shell=True)
self.log(f"Запущен Process Explorer: {c}", "ok")
self.toast("Process Explorer запущен", "ok")
return
except Exception as e:
self.log(f"Не удалось запустить {c}: {e}", "err")
try:
subprocess.Popen(
'start "" "https://learn.microsoft.com/sysinternals/downloads/process-explorer"',
shell=True)
self.log("Process Explorer не найден, открыта страница загрузки.", "warn")
self.toast("Process Explorer не найден", "warn")
except Exception as e:
self.log(f"Не удалось открыть страницу: {e}", "err")
# ================================================================== #
# РАЗДЕЛ: АВТОЗАГРУЗКА
# ================================================================== #
def _build_section_startup(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
tb = tk.Frame(frame, bg=PALETTE["bg_mid"]); tb.pack(fill=tk.X, pady=(0, 6))
ttk.Button(tb, text="Обновить", style="Accent.TButton",
command=self.refresh_startup).pack(side=tk.LEFT)
ttk.Button(tb, text="Отключить",
command=self.disable_selected_startup).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Удалить", style="Danger.TButton",
command=self.delete_selected_startup).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Удалить сторонние", style="Danger.TButton",
command=self.delete_all_third_party).pack(side=tk.LEFT, padx=4)
filt = tk.Frame(frame, bg=PALETTE["bg_card"], highlightthickness=1,
highlightbackground=PALETTE["border"])
filt.pack(fill=tk.X, pady=(0, 6))
inner = tk.Frame(filt, bg=PALETTE["bg_card"]); inner.pack(fill=tk.X, padx=14, pady=10)
tk.Label(inner, text="Фильтр:", bg=PALETTE["bg_card"],
fg=PALETTE["fg_dim"], font=FONT_SMALL).pack(side=tk.LEFT, padx=(0, 6))
self.startup_filter = tk.StringVar()
tk.Entry(inner, textvariable=self.startup_filter, bg=PALETTE["bg_input"],
fg=PALETTE["fg"], insertbackground=PALETTE["accent"],
relief="flat", font=FONT_UI).pack(side=tk.LEFT, fill=tk.X,
expand=True, ipady=4)
self.startup_filter.trace_add("write", lambda *a: self._apply_startup_filter())
cols = ("Источник", "Имя", "Команда", "Статус")
self.startup_tree = ttk.Treeview(frame, columns=cols, show="headings")
for c in cols:
self.startup_tree.heading(c, text=c)
self.startup_tree.column("Источник", width=200)
self.startup_tree.column("Имя", width=260)
self.startup_tree.column("Команда", width=560)
self.startup_tree.column("Статус", width=110, anchor="center")
self.startup_tree.pack(fill=tk.BOTH, expand=True)
self.startup_tree.bind("<Double-1>",
lambda e: self.open_selected_startup_location())
return frame
def refresh_startup(self) -> None:
if winreg is None:
self.log("winreg недоступен (не Windows?)", "err")
return
if not hasattr(self, "startup_tree"):
return
for row in self.startup_tree.get_children():
self.startup_tree.delete(row)
self._startup_cache = []
locations = [
(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", "HKCU\\Run"),
(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\RunOnce", "HKCU\\RunOnce"),
(winreg.HKEY_LOCAL_MACHINE, r"Software\Microsoft\Windows\CurrentVersion\Run", "HKLM\\Run"),
(winreg.HKEY_LOCAL_MACHINE, r"Software\Microsoft\Windows\CurrentVersion\RunOnce", "HKLM\\RunOnce"),
(winreg.HKEY_LOCAL_MACHINE,
r"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run", "HKLM\\Wow\\Run"),
(winreg.HKEY_LOCAL_MACHINE,
r"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce", "HKLM\\Wow\\RunOnce"),
]
for hive, path, label in locations:
try:
key = winreg.OpenKey(hive, path)
i = 0
while True:
try:
name, val, _ = winreg.EnumValue(key, i)
self._startup_cache.append({
"source": label, "name": name, "cmd": val,
"type": "registry", "hive": hive, "path": path,
"status": "enabled",
})
i += 1
except OSError:
break
winreg.CloseKey(key)
except FileNotFoundError:
continue
except Exception as e:
self.log(f"Ошибка чтения {label}: {e}", "warn")
for folder, label in [
(os.path.join(os.environ.get("APPDATA", ""),
r"Microsoft\Windows\Start Menu\Programs\Startup"),
"Startup (User)"),
(os.path.join(os.environ.get("ProgramData", ""),
r"Microsoft\Windows\Start Menu\Programs\Startup"),
"Startup (Common)"),
]:
if os.path.isdir(folder):
for f in os.listdir(folder):
full = os.path.join(folder, f)
self._startup_cache.append({
"source": label, "name": f, "cmd": full,
"type": "folder", "path": full, "status": "enabled",
})
self._apply_startup_filter()
self.log(f"Автозагрузка: {len(self._startup_cache)} записей.", "ok")
def _apply_startup_filter(self) -> None:
if not hasattr(self, "startup_tree"):
return
q = self.startup_filter.get().strip().lower() if hasattr(self, "startup_filter") else ""
for row in self.startup_tree.get_children():
self.startup_tree.delete(row)
for it in self._startup_cache:
if q and (q not in it["name"].lower()
and q not in str(it["cmd"]).lower()
and q not in it["source"].lower()):
continue
self.startup_tree.insert("", "end",
values=(it["source"], it["name"],
str(it["cmd"])[:200], it.get("status", "enabled")))
def _get_selected_startup(self) -> Optional[dict[str, Any]]:
sel = self.startup_tree.selection()
if not sel:
self.toast("Не выбрано", "warn")
return None
values = self.startup_tree.item(sel[0], "values")
source, name = values[0], values[1]
for it in self._startup_cache:
if it["source"] == source and it["name"] == name:
return it
return None
def disable_selected_startup(self) -> None:
it = self._get_selected_startup()
if not it or winreg is None:
return
try:
if it["type"] == "registry":
key = winreg.OpenKey(it["hive"], it["path"], 0, winreg.KEY_SET_VALUE)
winreg.DeleteValue(key, it["name"])
winreg.CloseKey(key)
elif it["type"] == "folder":
if not it["path"].endswith(".disabled"):
os.rename(it["path"], it["path"] + ".disabled")
self.log(f"Отключено: {it['name']}", "ok")
self.toast(f"Отключено: {it['name']}", "ok")
self.refresh_startup()
except Exception as e:
self.log(f"Ошибка: {e}", "err")
def delete_selected_startup(self) -> None:
it = self._get_selected_startup()
if not it or winreg is None:
return
try:
if it["type"] == "registry":
key = winreg.OpenKey(it["hive"], it["path"], 0, winreg.KEY_SET_VALUE)
winreg.DeleteValue(key, it["name"])
winreg.CloseKey(key)
elif it["type"] == "folder":
if os.path.isfile(it["path"]):
os.remove(it["path"])
else:
shutil.rmtree(it["path"], ignore_errors=True)
self.log(f"Удалено: {it['name']}", "ok")
self.toast(f"Удалено: {it['name']}", "ok")
self.refresh_startup()
except Exception as e:
self.log(f"Ошибка: {e}", "err")
def delete_all_third_party(self) -> None:
if winreg is None:
return
wl = {"SecurityHealth", "WindowsDefender", "OneDrive",
"MicrosoftEdgeAutoLaunch", "RTHDVCPL"}
cand = [it for it in self._startup_cache
if it["type"] in ("registry", "folder")
and it["name"] not in wl
and not it["name"].lower().startswith(("windows", "microsoft"))]
if not cand:
self.toast("Сторонних записей нет", "warn")
return
removed = 0
for it in cand:
try:
if it["type"] == "registry":
key = winreg.OpenKey(it["hive"], it["path"], 0, winreg.KEY_SET_VALUE)
winreg.DeleteValue(key, it["name"])
winreg.CloseKey(key)
elif it["type"] == "folder":
if os.path.isfile(it["path"]):
os.remove(it["path"])
else:
shutil.rmtree(it["path"], ignore_errors=True)
removed += 1
except Exception as e:
self.log(f"Ошибка {it['name']}: {e}", "err")
self.log(f"Удалено сторонних записей: {removed}.", "ok")
self.toast(f"Удалено: {removed}", "ok")
self.refresh_startup()
def open_selected_startup_location(self) -> None:
it = self._get_selected_startup()
if not it:
return
if it["type"] == "folder":
subprocess.Popen(f'explorer /select,"{it["path"]}"', shell=True)
elif it["type"] == "registry":
self.open_exe("regedit.exe")
else:
self.open_exe("taskschd.msc")
# ================================================================== #
# РАЗДЕЛ: ТВИКИ
# ================================================================== #
def _build_section_tweaks(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
body = self._make_card(frame, "Твики, очистка и вызовы",
"Операции с подтверждением.")
funcs = [
("Сбросить ассоциации .exe", self.reset_exe_assoc),
("Отключить залипание клавиш", self.disable_sticky_keys),
("Показать скрытые файлы", self.show_hidden_files),
("Показать расширения", self.show_extensions),
("Сбросить настройки проводника", self.reset_explorer_view),
("Сбросить hosts", self.reset_hosts),
("Очистить Temp", self.clean_user_temp),
("Очистить кэш обновлений", self.clean_wu_cache),
("Очистить Prefetch", self.clean_prefetch),
("Очистить корзину", self.clean_recycle_bin),
("Очистить DNS-кэш", self.flush_dns),
("Перезапустить explorer.exe", self.restart_explorer),
("sfc /scannow", lambda: self.run_cmd_async("sfc /scannow")),
("DISM /RestoreHealth",
lambda: self.run_cmd_async("DISM /Online /Cleanup-Image /RestoreHealth")),
("chkdsk", lambda: self.run_cmd_async("chkdsk")),
("CMD на Рабочем столе", self.open_cmd_desktop),
("PowerShell от админа", lambda: self.open_exe("powershell.exe")),
("Перезагрузка через 60 сек", self.confirm_reboot, "Danger.TButton"),
("Отмена перезагрузки", lambda: self.run_quiet("shutdown /a")),
("Включить редактор реестра",
lambda: self.reg_reset(
r"Software\Microsoft\Windows\CurrentVersion\Policies\System",
"DisableRegistryTools")),
("Включить диспетчер задач",
lambda: self.reg_reset(
r"Software\Microsoft\Windows\CurrentVersion\Policies\System",
"DisableTaskMgr")),
("Включить командную строку",
lambda: self.reg_reset(
r"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"DisableCMD")),
("Разрешить панель управления",
lambda: self.reg_reset(
r"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoControlPanel")),
("Восстановить msconfig", self.restore_msconfig),
("Восстановить PowerShell", self.restore_powershell),
("Разблокировать gpedit", self.restore_gpedit),
]
self._grid_buttons(body, funcs, cols=3)
return frame
# ================================================================== #
# РАЗДЕЛ: ВОССТАНОВЛЕНИЕ
# ================================================================== #
def _build_section_repair(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
body = self._make_card(frame, "Ремонт и восстановление",
"Компоненты Windows, сеть, WMI, брандмауэр.")
funcs = [
("sfc /scannow", lambda: self.run_cmd_async("sfc /scannow")),
("DISM RestoreHealth",
lambda: self.run_cmd_async("DISM /Online /Cleanup-Image /RestoreHealth")),
("Сброс Winsock и TCP/IP", self.repair_network),
("Сброс Windows Update", self.repair_windows_update),
("Сброс кэша Store", self.repair_store),
("Восстановление шрифтов", self.repair_fonts),
("Перезапуск планировщика", self.repair_scheduler),
("Пересборка WMI", self.repair_wmi),
("Сброс брандмауэра", self.repair_firewall),
("Включение .NET 3.5",
lambda: self.run_cmd_async(
"dism /online /enable-feature /featurename:NetFx3 /all")),
("rstrui", lambda: self.open_exe("rstrui.exe")),
("systemreset", lambda: self.open_exe("systemreset.exe")),
]
self._grid_buttons(body, funcs, cols=3)
return frame
# ================================================================== #
# РАЗДЕЛ: СЕТЬ
# ================================================================== #
def _build_section_network(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
top_body = self._make_card(frame, "Диагностика сети",
"ping / tracert / nslookup")
top = tk.Frame(top_body, bg=PALETTE["bg_card"]); top.pack(fill=tk.X)
tk.Label(top, text="Хост:", bg=PALETTE["bg_card"],
fg=PALETTE["fg_dim"], font=FONT_SMALL).pack(side=tk.LEFT)
self.net_host = tk.Entry(top, bg=PALETTE["bg_input"], fg=PALETTE["fg"],
insertbackground=PALETTE["accent"], relief="flat",
font=("Consolas", 10), width=30)
self.net_host.pack(side=tk.LEFT, padx=6, ipady=4)
self.net_host.insert(0, "8.8.8.8")
ttk.Button(top, text="Ping", command=self.do_ping).pack(side=tk.LEFT, padx=3)
ttk.Button(top, text="Tracert", command=self.do_tracert).pack(side=tk.LEFT, padx=3)
ttk.Button(top, text="nslookup", command=self.do_nslookup).pack(side=tk.LEFT, padx=3)
body = self._make_card(frame, "Сетевые операции", "")
funcs = [
("IP-конфигурация", lambda: self._show_output("ipconfig /all")),
("Маршруты", lambda: self._show_output("route print")),
("ARP", lambda: self._show_output("arp -a")),
("DNS-кэш", lambda: self._show_output("ipconfig /displaydns")),
("Соединения", lambda: self._show_output("netstat -ano")),
("Сброс Winsock", self.reset_winsock),
("Сброс TCP/IP", self.reset_tcpip),
("Сброс DNS", self.flush_dns),
("Сброс прокси", self.reset_proxy),
("Wi-Fi профили", lambda: self._show_output("netsh wlan show profiles")),
("Сетевые подключения", lambda: self.open_exe("ncpa.cpl")),
("Брандмауэр", lambda: self.open_exe("wf.msc")),
]
self._grid_buttons(body, funcs, cols=3)
return frame
def _show_output(self, command: str) -> None:
out = ConsoleRunner.run(command)
self.log(f"--- {command} ---", "info")
for line in out.splitlines()[:30]:
if line.strip():
self.log(line)
self.log(f"(всего строк: {len(out.splitlines())})", "info")
def do_ping(self) -> None:
host = self.net_host.get().strip() or "8.8.8.8"
self.log(f"Ping {host}…", "info")
def run() -> None:
out = ConsoleRunner.run(f"ping -n 4 {host}")
for line in out.splitlines():
self.log(line)
threading.Thread(target=run, daemon=True).start()
def do_tracert(self) -> None:
host = self.net_host.get().strip() or "8.8.8.8"
self.log(f"Tracert {host}…", "info")
def run() -> None:
out = ConsoleRunner.run(f"tracert -h 15 -w 500 {host}", timeout=180)
for line in out.splitlines():
self.log(line)
threading.Thread(target=run, daemon=True).start()
def do_nslookup(self) -> None:
host = self.net_host.get().strip() or "google.com"
out = ConsoleRunner.run(f"nslookup {host}")
for line in out.splitlines():
self.log(line)
# ================================================================== #
# РАЗДЕЛ: БЕЗОПАСНОСТЬ
# ================================================================== #
def _build_section_security(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
body = self._make_card(frame, "Безопасность",
"Defender, UAC, брандмауэр, аудит.")
funcs = [
("Статус Defender", self.check_defender_status),
("Статус брандмауэра", self.check_firewall_status),
("Статус UAC", self.check_uac_status),
("Последние угрозы", self.check_defender_threats),
("Быстрая проверка", self.run_defender_quick, "Accent.TButton"),
("Полная проверка", self.run_defender_full, "Accent.TButton"),
("Обновить базы", self.update_defender),
("Восстановить Defender", self.restore_defender),
("Включить брандмауэр", self.enable_firewall),
("Включить UAC", self.enable_uac),
("Администраторы", self.list_admins),
("Сессии", lambda: self._show_output("query user")),
]
self._grid_buttons(body, funcs, cols=3)
return frame
# ================================================================== #
# РАЗДЕЛ: ОГРАНИЧЕНИЯ
# ================================================================== #
def _build_section_restrictions(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
# Тулбар 1 — сканирование и правка значений
tb = tk.Frame(frame, bg=PALETTE["bg_mid"]); tb.pack(fill=tk.X, pady=(0, 6))
ttk.Button(tb, text="Сканировать", style="Accent.TButton",
command=self.scan_restrictions).pack(side=tk.LEFT)
ttk.Button(tb, text="Изменить значение", style="Success.TButton",
command=self.edit_selected_restriction).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Открыть ветку в regedit",
command=self.open_selected_restriction_in_regedit
).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Папка бэкапов",
command=self.open_restrictions_backup_folder
).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="CSV", command=self.export_restrictions_csv
).pack(side=tk.RIGHT, padx=4)
ttk.Button(tb, text="Копировать отчёт",
command=self.copy_restrictions_report
).pack(side=tk.RIGHT, padx=4)
# Тулбар 2 — подмена Utilman / Sethc
tb2 = tk.Frame(frame, bg=PALETTE["bg_mid"]); tb2.pack(fill=tk.X, pady=(0, 6))
ttk.Button(tb2, text="Utilman → ZabHelper",
command=self.replace_utilman).pack(side=tk.LEFT)
ttk.Button(tb2, text="Sethc → ZabHelper",
command=self.replace_sethc).pack(side=tk.LEFT, padx=4)
ttk.Button(tb2, text="Восстановить Utilman", style="Ghost.TButton",
command=self.restore_utilman).pack(side=tk.LEFT, padx=(16, 4))
ttk.Button(tb2, text="Восстановить Sethc", style="Ghost.TButton",
command=self.restore_sethc).pack(side=tk.LEFT, padx=4)
info = tk.Frame(frame, bg=PALETTE["bg_card"], highlightthickness=1,
highlightbackground=PALETTE["border"])
info.pack(fill=tk.X, pady=(0, 6))
self.restrict_stats = tk.Label(info, text="Нажмите «Сканировать»…",
bg=PALETTE["bg_card"], fg=PALETTE["fg_dim"],
font=FONT_SMALL, anchor="w", justify="left")
self.restrict_stats.pack(fill=tk.X, padx=14, pady=10)
cols = ("Категория", "Расположение", "Параметр", "Значение", "Статус")
self.restrict_tree = ttk.Treeview(frame, columns=cols, show="headings",
selectmode="extended")
headers = {
"Категория": (200, "w"),
"Расположение":(320, "w"),
"Параметр": (240, "w"),
"Значение": (160, "center"),
"Статус": (200, "center"),
}
for c in cols:
w, a = headers[c]
self.restrict_tree.heading(c, text=c)
self.restrict_tree.column(c, width=w, anchor=a)
self.restrict_tree.pack(fill=tk.BOTH, expand=True)
self.restrict_tree.tag_configure("found", foreground=PALETTE["warning"])
self.restrict_tree.tag_configure("ok", foreground=PALETTE["success"])
self.restrict_tree.tag_configure("locked", foreground=PALETTE["error"])
self.restrict_tree.tag_configure("info", foreground=PALETTE["fg_dim"])
self.restrict_tree.bind("<Double-1>",
lambda e: self.edit_selected_restriction())
self.restrict_tree.bind("<Button-3>", self._show_restrict_menu)
self.restrict_menu = tk.Menu(self.root, tearoff=0,
bg=PALETTE["bg_card"], fg=PALETTE["fg"],
activebackground=PALETTE["accent_deep"],
activeforeground="#ffffff")
self.restrict_menu.add_command(label="Изменить значение…",
command=self.edit_selected_restriction)
self.restrict_menu.add_command(label="Открыть ветку в regedit",
command=self.open_selected_restriction_in_regedit)
self.restrict_menu.add_separator()
self.restrict_menu.add_command(label="Копировать строку",
command=self.copy_selected_restriction_row)
return frame
def _show_restrict_menu(self, event: tk.Event) -> None:
row = self.restrict_tree.identify_row(event.y)
if row:
if row not in self.restrict_tree.selection():
self.restrict_tree.selection_set(row)
try:
self.restrict_menu.tk_popup(event.x_root, event.y_root)
finally:
self.restrict_menu.grab_release()
def _restriction_catalog(self) -> list[tuple[Any, ...]]:
if winreg is None:
return []
HKLM = winreg.HKEY_LOCAL_MACHINE
HKCU = winreg.HKEY_CURRENT_USER
return [
("Редактор реестра", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
"DisableRegistryTools", 1),
("Редактор реестра", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
"DisableRegistryTools", 1),
("Диспетчер задач", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
"DisableTaskMgr", 1),
("Диспетчер задач", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
"DisableTaskMgr", 1),
("Командная строка", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"DisableCMD", 1),
("Командная строка", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"DisableCMD", 2),
("Контекстное меню", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoViewContextMenu", 1),
("Свойства папки", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoFolderOptions", 1),
("Win+R", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoRun", 1),
("Панель управления", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoControlPanel", 1),
("Панель управления", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoControlPanel", 1),
("Значки рабочего стола", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoDesktop", 1),
("Смена обоев", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\ActiveDesktop",
"NoChangingWallPaper", 1),
("Экран блокировки", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows\Personalization",
"NoLockScreen", 1),
("Меню Пуск", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoStartMenuPinnedList", 1),
("Defender", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows Defender",
"DisableAntiSpyware", 1),
("Defender", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows Defender",
"DisableAntiVirus", 1),
("Defender: real-time", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection",
"DisableRealtimeMonitoring", 1),
("Defender: cloud", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows Defender\Spynet",
"SpynetReporting", 0),
("Windows Update", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU",
"NoAutoUpdate", 1),
("Windows Update", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate",
"DisableWindowsUpdateAccess", 1),
("Windows Update", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU",
"AUOptions", 1),
("UAC", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
"EnableLUA", 0),
("UAC", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
"ConsentPromptBehaviorAdmin", 0),
("Установщик MSI", HKLM,
r"SOFTWARE\Policies\Microsoft\Windows\Installer",
"DisableMSI", 1),
("Запуск только разрешённых", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"RestrictRun", 1),
("Запуск только разрешённых", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"RestrictRun", 1),
("Windows Script Host", HKLM,
r"SOFTWARE\Microsoft\Windows Script Host\Settings",
"Enabled", 0),
("Autorun USB", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"NoDriveTypeAutoRun", 0xFF),
("Прокси (HKCU)", HKCU,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
"ProxyEnable", 1),
("Прокси (HKLM)", HKLM,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
"ProxyEnable", 1),
]
def scan_restrictions(self) -> None:
if winreg is None:
self.log("winreg недоступен — раздел не работает вне Windows.", "err")
return
if not hasattr(self, "restrict_tree"):
return
for row in self.restrict_tree.get_children():
self.restrict_tree.delete(row)
self._restrict_cache = []
catalog = self._restriction_catalog()
found = clean = missing = 0
for cat, hive, subkey, value_name, bad_value in catalog:
entry: dict[str, Any] = {
"category": cat,
"location": subkey,
"value": value_name,
"hive": hive,
"hive_name": self._hive_name(hive),
"bad_value": bad_value,
"data": None,
"vtype": None,
"tag": "info",
"status": "",
}
try:
with winreg.OpenKey(hive, subkey, 0, winreg.KEY_READ) as key:
try:
val, vtype = winreg.QueryValueEx(key, value_name)
entry["data"] = val
entry["vtype"] = vtype
except FileNotFoundError:
entry["status"] = "нет значения"
entry["tag"] = "info"
missing += 1
self._restrict_cache.append(entry)
continue
is_bad = False
try:
if isinstance(val, int) and isinstance(bad_value, int):
is_bad = (int(val) == int(bad_value))
else:
is_bad = (str(val).strip().lower()
== str(bad_value).strip().lower())
except Exception:
is_bad = (val == bad_value)
if is_bad:
entry["status"] = "ограничение"
entry["tag"] = "found"
found += 1
else:
entry["status"] = "не критично"
entry["tag"] = "ok"
clean += 1
self._restrict_cache.append(entry)
except FileNotFoundError:
missing += 1
except PermissionError:
entry["data"] = None
entry["status"] = "нет доступа"
entry["tag"] = "locked"
self._restrict_cache.append(entry)
except Exception as e:
self.log(f"Ошибка чтения {subkey}\\{value_name}: {e}", "warn")
self._render_restrictions()
self.restrict_stats.config(
text=(f"ограничение: {found} · не критично: {clean} · "
f"нет значения: {missing} · всего: {len(catalog)}"))
self.log(f"Сканирование: ограничение={found}, не критично={clean}.", "ok")
self.toast("Ограничений не найдено" if found == 0 else f"Найдено: {found}",
"ok" if found == 0 else "warn")
def _render_restrictions(self) -> None:
if not hasattr(self, "restrict_tree"):
return
for row in self.restrict_tree.get_children():
self.restrict_tree.delete(row)
for it in self._restrict_cache:
data = it.get("data")
if data is None:
data_str = "—"
elif isinstance(data, int):
data_str = f"0x{data:08X}" if data > 9 else str(data)
else:
data_str = str(data)
self.restrict_tree.insert(
"", "end",
values=(it["category"], it["location"], it["value"],
data_str, it["status"]),
tags=(it["tag"],),
)
def _get_selected_restriction(self) -> Optional[dict[str, Any]]:
sel = self.restrict_tree.selection()
if not sel:
return None
values = self.restrict_tree.item(sel[0], "values")
loc, val = values[1], values[2]
for it in self._restrict_cache:
if it["location"] == loc and it["value"] == val:
return it
return None
def _restrictions_backup_dir(self) -> str:
d = os.path.join(self._backup_dir(), "restrictions")
os.makedirs(d, exist_ok=True)
return d
def open_restrictions_backup_folder(self) -> None:
self.open_path(self._restrictions_backup_dir())
@staticmethod
def _hive_name(hive: Any) -> str:
if winreg is None:
return "HKLM"
return {winreg.HKEY_LOCAL_MACHINE: "HKLM",
winreg.HKEY_CURRENT_USER: "HKCU",
winreg.HKEY_CLASSES_ROOT: "HKCR",
winreg.HKEY_USERS: "HKU",
winreg.HKEY_CURRENT_CONFIG: "HKCC"}.get(hive, "HKLM")
def _backup_restriction_key(self, item: dict[str, Any]) -> Optional[str]:
hive_name = item["hive_name"]
subkey = item["location"]
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
safe = re.sub(r"[\\/:*?\"<>|]+", "_", subkey)[-60:]
out_path = os.path.join(self._restrictions_backup_dir(),
f"{hive_name}_{safe}_{ts}.reg")
try:
result = subprocess.run(
["reg", "export", f"{hive_name}\\{subkey}", out_path, "/y"],
capture_output=True, timeout=20,
creationflags=ConsoleRunner.CREATE_NO_WINDOW,
)
if result.returncode == 0 and os.path.exists(out_path):
return out_path
self.log(f"reg export rc={result.returncode} для {hive_name}\\{subkey}",
"warn")
except Exception as e:
self.log(f"reg export ошибка: {e}", "warn")
return None
def _write_restriction_value(self, item: dict[str, Any], new_value_str: str) -> bool:
if winreg is None:
return False
vtype = item.get("vtype") or winreg.REG_DWORD
try:
if vtype == winreg.REG_DWORD:
s = new_value_str.strip()
val_int = int(s, 16) if s.lower().startswith("0x") else int(s, 10)
if val_int < 0 or val_int > 0xFFFFFFFF:
raise ValueError("значение вне диапазона DWORD")
value = val_int
rtype = winreg.REG_DWORD
elif vtype == winreg.REG_SZ:
value = new_value_str
rtype = winreg.REG_SZ
elif vtype == winreg.REG_EXPAND_SZ:
value = new_value_str
rtype = winreg.REG_EXPAND_SZ
else:
s = new_value_str.strip()
val_int = int(s, 16) if s.lower().startswith("0x") else int(s, 10)
value = val_int
rtype = winreg.REG_DWORD
with winreg.OpenKey(item["hive"], item["location"], 0,
winreg.KEY_SET_VALUE) as key:
winreg.SetValueEx(key, item["value"], 0, rtype, value)
return True
except Exception as e:
self.log(f"Ошибка записи {item['location']}\\{item['value']}: {e}", "err")
return False
def edit_selected_restriction(self) -> None:
item = self._get_selected_restriction()
if not item:
self.toast("Строка не выбрана", "warn")
return
self._open_edit_value_dialog(item)
def _open_edit_value_dialog(self, item: dict[str, Any]) -> None:
win = tk.Toplevel(self.root)
win.title("Изменение значения")
win.geometry("560x360")
win.configure(bg=PALETTE["bg_mid"])
win.transient(self.root)
win.grab_set()
head = tk.Frame(win, bg=PALETTE["bg_card"], highlightthickness=1,
highlightbackground=PALETTE["border_hi"])
head.pack(fill=tk.X, padx=16, pady=(16, 8))
tk.Label(head, text=item["category"], bg=PALETTE["bg_card"],
fg=PALETTE["accent_hi"], font=FONT_H2
).pack(anchor="w", padx=12, pady=(10, 2))
tk.Label(head, text=f"{item['hive_name']}\\{item['location']}",
bg=PALETTE["bg_card"], fg=PALETTE["fg_dim"],
font=FONT_SMALL, wraplength=520, justify="left"
).pack(anchor="w", padx=12)
tk.Label(head, text=f"Параметр: {item['value']}",
bg=PALETTE["bg_card"], fg=PALETTE["fg"], font=FONT_UI_B
).pack(anchor="w", padx=12, pady=(4, 10))
form = tk.Frame(win, bg=PALETTE["bg_mid"])
form.pack(fill=tk.X, padx=16, pady=(4, 6))
cur = item.get("data")
if cur is None:
cur_str = ""
elif isinstance(cur, int):
cur_str = str(cur)
else:
cur_str = str(cur)
tk.Label(form, text="Текущее значение:",
bg=PALETTE["bg_mid"], fg=PALETTE["fg_dim"], font=FONT_SMALL
).pack(anchor="w")
tk.Label(form, text=cur_str if cur_str else "—",
bg=PALETTE["bg_mid"], fg=PALETTE["fg"], font=FONT_MONO
).pack(anchor="w", pady=(2, 10))
tk.Label(form, text="Новое значение:",
bg=PALETTE["bg_mid"], fg=PALETTE["fg"], font=FONT_UI_B
).pack(anchor="w")
var = tk.StringVar(value=cur_str)
entry = tk.Entry(form, textvariable=var, bg=PALETTE["bg_input"],
fg=PALETTE["fg"], insertbackground=PALETTE["accent"],
relief="flat", font=FONT_MONO)
entry.pack(fill=tk.X, ipady=6, pady=(2, 4))
entry.focus_set()
entry.select_range(0, tk.END)
tk.Label(form,
text="Для DWORD — целое число (например 0, 1, 2) или hex (0x1).",
bg=PALETTE["bg_mid"], fg=PALETTE["fg_mute"], font=FONT_SMALL
).pack(anchor="w", pady=(0, 10))
btns = tk.Frame(win, bg=PALETTE["bg_mid"])
btns.pack(fill=tk.X, padx=16, pady=(0, 12))
def apply_and_close() -> None:
new_val = var.get().strip()
if new_val == "":
self.toast("Пустое значение", "warn")
return
backup = self._backup_restriction_key(item)
if backup:
self.log(f"Бэкап: {backup}", "ok")
else:
self.log("Не удалось создать бэкап — действие отменено.", "err")
self.toast("Бэкап не создан, отмена", "err")
return
if self._write_restriction_value(item, new_val):
self.log(f"Изменено: {item['location']}\\{item['value']} = {new_val}",
"ok")
self.toast(f"Изменено: {item['value']} = {new_val}", "ok")
win.destroy()
self.scan_restrictions()
else:
self.toast("Ошибка записи", "err")
def cancel() -> None:
win.destroy()
ttk.Button(btns, text="Применить", style="Success.TButton",
command=apply_and_close).pack(side=tk.RIGHT, padx=4)
ttk.Button(btns, text="Отмена", style="Danger.TButton",
command=cancel).pack(side=tk.RIGHT, padx=4)
ttk.Button(btns, text="Открыть ветку в regedit", style="Ghost.TButton",
command=lambda: self._open_regedit_at(item)
).pack(side=tk.LEFT, padx=4)
win.bind("<Return>", lambda e: apply_and_close())
win.bind("<Escape>", lambda e: cancel())
def open_selected_restriction_in_regedit(self) -> None:
item = self._get_selected_restriction()
if not item:
self.toast("Строка не выбрана", "warn")
return
self._open_regedit_at(item)
def _open_regedit_at(self, item: dict[str, Any]) -> None:
hive = item["hive_name"]
sub = item["location"]
try:
if winreg is not None:
try:
with winreg.CreateKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Applets\Regedit"
):
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Applets\Regedit",
0, winreg.KEY_SET_VALUE
) as k:
winreg.SetValueEx(k, "LastKey", 0, winreg.REG_SZ,
f"Компьютер\\{hive}\\{sub}")
except Exception:
pass
subprocess.Popen(["regedit.exe"], shell=False)
self.log(f"regedit: {hive}\\{sub}", "ok")
except Exception as e:
self.log(f"Не удалось открыть regedit: {e}", "err")
def copy_selected_restriction_row(self) -> None:
item = self._get_selected_restriction()
if not item:
return
data = item.get("data")
line = (f"{item['category']} | {item['hive_name']}\\{item['location']} "
f"| {item['value']} = {data}")
self.root.clipboard_clear()
self.root.clipboard_append(line)
self.toast("Строка скопирована", "ok")
def export_restrictions_csv(self) -> None:
if not self._restrict_cache:
self.toast("Сначала выполните сканирование", "warn")
return
path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV", "*.csv"), ("Все", "*.*")],
initialfile=f"restrictions_{datetime.datetime.now():%Y%m%d_%H%M%S}.csv")
if not path:
return
try:
with open(path, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f, delimiter=";")
w.writerow(["Категория", "Расположение", "Параметр", "Значение", "Статус"])
for it in self._restrict_cache:
w.writerow([it["category"], it["location"], it["value"],
it.get("data"), it["status"]])
self.toast("CSV сохранён", "ok")
except Exception as e:
self.log(f"Ошибка экспорта: {e}", "err")
def copy_restrictions_report(self) -> None:
if not self._restrict_cache:
self.toast("Сначала выполните сканирование", "warn")
return
lines = ["ZabHelper v4.5 — отчёт по ограничениям",
f"Время: {datetime.datetime.now():%Y-%m-%d %H:%M:%S}",
"=" * 70]
for it in self._restrict_cache:
lines.append(f"[{it['status']}] {it['category']}")
lines.append(f" Расположение: {it['location']}")
lines.append(f" Параметр: {it['value']} = {it.get('data')}")
lines.append("")
self.root.clipboard_clear()
self.root.clipboard_append("\n".join(lines))
self.toast("Отчёт скопирован в буфер", "ok")
# ------------------------------------------------------------------ #
# ПОДМЕНА UTILMAN / SETHC
# ------------------------------------------------------------------ #
def _self_path(self) -> Optional[str]:
if getattr(sys, "frozen", False):
exe = sys.executable
if exe and os.path.isfile(exe):
return exe
if sys.argv and sys.argv[0].lower().endswith(".exe"):
p = os.path.abspath(sys.argv[0])
if os.path.isfile(p):
return p
return None
def _system32_path(self, name: str) -> str:
root = os.environ.get("SystemRoot", r"C:\Windows")
return os.path.join(root, "System32", name)
def _replace_system_binary(self, target_name: str) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err")
return
src = self._self_path()
if not src:
self.toast(
"Подмена возможна только для .exe. Собери .exe "
"(pyinstaller --onefile --noconsole --name ZabHelper zabhelperv4.py) "
"и запусти его.",
"err", 6000)
self.log("Подмена отменена: ZabHelper запущен не как .exe.", "err")
return
dst = self._system32_path(target_name)
bak = dst + ".bak"
if not self.ask_confirm(
f"Заменить {target_name} на ZabHelper?\n\n"
f"Источник: {src}\n"
f"Цель: {dst}\n"
f"Бэкап: {bak}\n\n"
f"После подмены вызов {target_name} на экране входа откроет ZabHelper.\n"
f"Откат — кнопкой «Восстановить …» в этом же разделе."
):
return
try:
if os.path.exists(dst) and not os.path.exists(bak):
shutil.copy2(dst, bak)
self.log(f"Бэкап: {bak}", "ok")
elif os.path.exists(bak):
self.log(f"Бэкап уже существует: {bak}", "info")
else:
self.log(f"Оригинал {dst} не найден — пропускаю бэкап.", "warn")
except Exception as e:
self.log(f"Ошибка бэкапа {dst}: {e}", "err")
self.toast("Не удалось создать бэкап — отмена", "err")
return
try:
shutil.copy2(src, dst)
self.log(f"Подменено: {dst} ← {src}", "ok")
self.toast(f"{target_name} заменён на ZabHelper", "ok")
except PermissionError as e:
self.log(f"Отказано в доступе к {dst}: {e}", "err")
self.log(
"Решение: возьмите владение через "
f'takeown /f "{dst}" /a и icacls "{dst}" /grant *S-1-5-32-544:F',
"warn")
self.toast("Отказано в доступе. См. лог.", "err", 6000)
except Exception as e:
self.log(f"Ошибка копирования {dst}: {e}", "err")
self.toast("Ошибка подмены", "err")
def _restore_system_binary(self, target_name: str) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err")
return
dst = self._system32_path(target_name)
bak = dst + ".bak"
if not os.path.exists(bak):
self.toast(f"Бэкап {bak} не найден", "warn")
return
if not self.ask_confirm(f"Восстановить {target_name} из {bak}?"):
return
try:
shutil.copy2(bak, dst)
self.log(f"Восстановлено: {dst} ← {bak}", "ok")
self.toast(f"{target_name} восстановлен", "ok")
except PermissionError as e:
self.log(f"Отказано в доступе к {dst}: {e}", "err")
self.toast("Отказано в доступе. См. лог.", "err", 6000)
except Exception as e:
self.log(f"Ошибка восстановления {dst}: {e}", "err")
self.toast("Ошибка восстановления", "err")
def replace_utilman(self) -> None:
self._replace_system_binary("utilman.exe")
def replace_sethc(self) -> None:
self._replace_system_binary("sethc.exe")
def restore_utilman(self) -> None:
self._restore_system_binary("utilman.exe")
def restore_sethc(self) -> None:
self._restore_system_binary("sethc.exe")
# ================================================================== #
# РАЗДЕЛ: ОТКРЫТЬ
# ================================================================== #
def _build_section_open(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
body = self._make_card(frame, "Быстрый доступ", "")
funcs: list[tuple[str, Callable[[], Any]]] = [
("Панель управления", lambda: self.open_exe("control")),
("Параметры Windows", lambda: self.open_exe("ms-settings:")),
("Диспетчер задач", lambda: self.open_exe("taskmgr.exe")),
("Проводник", lambda: self.open_exe("explorer.exe")),
("Редактор реестра", lambda: self.open_exe("regedit.exe")),
("Службы", lambda: self.open_exe("services.msc")),
("Планировщик", lambda: self.open_exe("taskschd.msc")),
("Принтеры", lambda: self.open_exe("control printers")),
("Диспетчер устройств", lambda: self.open_exe("devmgmt.msc")),
("Управление дисками", lambda: self.open_exe("diskmgmt.msc")),
("Программы", lambda: self.open_exe("appwiz.cpl")),
("Учётные записи", lambda: self.open_exe("netplwiz.exe")),
("Дата и время", lambda: self.open_exe("timedate.cpl")),
("Язык и регион", lambda: self.open_exe("intl.cpl")),
("Экран", lambda: self.open_exe("desk.cpl")),
("Звук", lambda: self.open_exe("mmsys.cpl")),
("Питание", lambda: self.open_exe("powercfg.cpl")),
("Сеть", lambda: self.open_exe("ncpa.cpl")),
("Брандмауэр", lambda: self.open_exe("wf.msc")),
("Defender", lambda: self.open_exe("windowsdefender:")),
("Windows Update", lambda: self.open_exe("ms-settings:windowsupdate")),
("Журнал событий", lambda: self.open_exe("eventvwr.msc")),
("Локальные пользователи", lambda: self.open_exe("lusrmgr.msc")),
("Компоненты Windows", lambda: self.open_exe("optionalfeatures.exe")),
("Монитор ресурсов", lambda: self.open_exe("resmon.exe")),
("Сведения о системе", lambda: self.open_exe("msinfo32.exe")),
("Очистка диска", lambda: self.open_exe("cleanmgr.exe")),
("Дефрагментация", lambda: self.open_exe("dfrgui.exe")),
("DirectX", lambda: self.open_exe("dxdiag.exe")),
("gpedit", lambda: self.open_exe("gpedit.msc")),
("msconfig", lambda: self.open_exe("msconfig.exe")),
("rstrui", lambda: self.open_exe("rstrui.exe")),
("Хранилище", lambda: self.open_exe("ms-settings:storagesense")),
("Общие папки", lambda: self.open_exe("fsmgmt.msc")),
("Таблица символов", lambda: self.open_exe("charmap.exe")),
]
self._grid_buttons(body, funcs, cols=4)
return frame
# ================================================================== #
# РАЗДЕЛ: БЭКАПЫ
# ================================================================== #
def _build_section_backups(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
body = self._make_card(frame, "Бэкапы",
"Точки восстановления, экспорт реестра, hosts, автозагрузка.")
funcs = [
("Точка восстановления", self.create_restore_point, "Accent.TButton"),
("rstrui", lambda: self.open_exe("rstrui.exe")),
("Экспорт HKCU\\Software", self.export_hkcu_software),
("Экспорт HKLM\\Software", self.export_hklm_software),
("Импорт .reg", self.import_reg_file),
("Бэкап hosts", self.backup_hosts),
("Бэкап автозагрузки", self.backup_startup),
("Бэкап программ", self.backup_installed),
("Открыть папку бэкапов", self.open_backup_folder),
]
self._grid_buttons(body, funcs, cols=3)
return frame
# ================================================================== #
# РАЗДЕЛ: ДИАГНОСТИКА
# ================================================================== #
def _build_section_diagnostics(self, parent: tk.Frame) -> tk.Frame:
frame = tk.Frame(parent, bg=PALETTE["bg_mid"])
tb = tk.Frame(frame, bg=PALETTE["bg_mid"]); tb.pack(fill=tk.X, pady=(0, 6))
ttk.Button(tb, text="Собрать", style="Accent.TButton",
command=self.collect_diagnostics).pack(side=tk.LEFT)
ttk.Button(tb, text="Сохранить", command=self.save_diagnostics
).pack(side=tk.LEFT, padx=4)
ttk.Button(tb, text="Копировать", command=self.copy_diagnostics
).pack(side=tk.LEFT, padx=4)
self.diag_text = scrolledtext.ScrolledText(
frame, height=24, font=FONT_MONO,
bg=PALETTE["bg_glass"], fg=PALETTE["fg"], relief="flat")
self.diag_text.pack(fill=tk.BOTH, expand=True)
self.progress = ttk.Progressbar(frame, mode="indeterminate")
self.progress.pack(fill=tk.X, pady=(8, 0))
return frame
def collect_diagnostics(self) -> None:
self.diag_text.delete("1.0", tk.END)
def run() -> None:
self.progress.start(10)
try:
self._append_diag("=== ZabHelper v4.5 · диагностический отчёт ===")
self._append_diag(f"Время: {datetime.datetime.now()}")
self._append_diag(f"Пользователь: {os.environ.get('USERNAME', '?')}")
self._append_diag(f"Права администратора: {self.is_admin}")
self._append_diag(f"Компьютер: {os.environ.get('COMPUTERNAME', '?')}")
self._append_diag(f"Python: {sys.version}")
self._append_diag("\n--- IP-конфигурация ---")
self._append_diag(ConsoleRunner.run("ipconfig /all"))
self._append_diag("\n--- Маршруты ---")
self._append_diag(ConsoleRunner.run("route print"))
self._append_diag("\n--- Defender ---")
self._append_diag(ConsoleRunner.run_ps("Get-MpComputerStatus | Format-List"))
self._append_diag("\n--- Диски ---")
for drive in ("C:\\", "D:\\", "E:\\", "F:\\"):
if os.path.exists(drive):
try:
total, used, free = shutil.disk_usage(drive)
self._append_diag(f"{drive} всего: {total/1e9:.1f} GB, "
f"занято: {used/1e9:.1f} GB, "
f"свободно: {free/1e9:.1f} GB")
except Exception:
pass
self._append_diag("\n=== Отчёт завершён ===")
self.log("Диагностика собрана.", "ok")
finally:
self.progress.stop()
threading.Thread(target=run, daemon=True).start()
def _append_diag(self, text: Any) -> None:
try:
self.diag_text.insert(tk.END, str(text) + "\n")
self.diag_text.see(tk.END)
except Exception:
pass
def save_diagnostics(self) -> None:
path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Текстовые файлы", "*.txt"), ("Все", "*.*")],
initialfile=f"zabhelper_diag_{datetime.datetime.now():%Y%m%d_%H%M%S}.txt")
if not path:
return
try:
with open(path, "w", encoding="utf-8") as f:
f.write(self.diag_text.get("1.0", tk.END))
self.toast("Отчёт сохранён", "ok")
except Exception as e:
self.log(f"Ошибка сохранения: {e}", "err")
def copy_diagnostics(self) -> None:
self.root.clipboard_clear()
self.root.clipboard_append(self.diag_text.get("1.0", tk.END))
self.toast("Отчёт скопирован", "ok")
# ================================================================== #
# ОБЩИЕ ФУНКЦИИ
# ================================================================== #
def reg_reset(self, subkey: str, value_name: str) -> None:
if winreg is None:
return
if not self.is_admin:
self.log(f"{value_name}: могут потребоваться права администратора.", "warn")
success = False
for hk, hk_name in [(winreg.HKEY_CURRENT_USER, "HKCU"),
(winreg.HKEY_LOCAL_MACHINE, "HKLM")]:
try:
key = winreg.OpenKey(hk, subkey, 0, winreg.KEY_SET_VALUE)
winreg.SetValueEx(key, value_name, 0, winreg.REG_DWORD, 0)
winreg.CloseKey(key)
success = True
self.log(f"Изменено '{value_name}' в {hk_name} -> 0.", "ok")
except FileNotFoundError:
continue
except PermissionError:
self.log(f"Нет прав на {value_name} в {hk_name}.", "err")
except Exception as e:
self.log(f"Ошибка {value_name}: {e}", "err")
if not success:
self.log(f"'{value_name}' не найден.", "info")
else:
self.toast(f"Изменено: {value_name} -> 0", "ok")
def check_file(self, filename: str) -> None:
path = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
"System32", filename)
if os.path.exists(path):
self.log(f"{filename}: {os.path.getsize(path)} байт.", "ok")
else:
self.log(f"{filename} отсутствует!", "err")
def restore_msconfig(self) -> None:
self.reg_reset(r"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"DisableMSConfig")
self.reg_reset(
r"Software\Policies\Microsoft\MMC\{8FC0B734-A0E1-11D1-A7D3-0000F87571E3}",
"Restrict_Run")
def restore_powershell(self) -> None:
out = ConsoleRunner.run_ps("Get-ExecutionPolicy")
self.log(f"Политика PowerShell: {out.strip()}", "info")
self.run_quiet('powershell -NoProfile -Command "Set-ExecutionPolicy RemoteSigned '
'-Scope LocalMachine -Force"')
self.log("Политика = RemoteSigned.", "ok")
def restore_gpedit(self) -> None:
self.reg_reset(r"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer",
"RestrictRun")
def show_hidden_files(self) -> None:
self._set_advanced("Hidden", 1, "Скрытые файлы включены.")
def show_extensions(self) -> None:
self._set_advanced("HideFileExt", 0, "Расширения включены.")
def _set_advanced(self, name: str, value: int, ok_msg: str) -> None:
if winreg is None:
return
try:
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
0, winreg.KEY_SET_VALUE)
winreg.SetValueEx(key, name, 0, winreg.REG_DWORD, value)
winreg.CloseKey(key)
self.log(ok_msg, "ok")
self.toast(ok_msg, "ok")
except Exception as e:
self.log(f"Ошибка {name}: {e}", "err")
def reset_explorer_view(self) -> None:
if winreg is None:
return
try:
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced",
0, winreg.KEY_SET_VALUE)
for k, v in {"Hidden": 2, "HideFileExt": 1, "ShowSuperHidden": 0,
"ShowSyncProviderNotifications": 1, "LaunchTo": 1}.items():
winreg.SetValueEx(key, k, 0, winreg.REG_DWORD, v)
winreg.CloseKey(key)
self.log("Настройки Проводника сброшены.", "ok")
except Exception as e:
self.log(f"Ошибка: {e}", "err")
def reset_hosts(self) -> None:
hosts = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
r"System32\drivers\etc\hosts")
if not self.ask_confirm("Сбросить hosts? Будет создан бэкап."):
return
try:
if os.path.exists(hosts):
shutil.copy2(hosts, hosts + f".bak_{datetime.datetime.now():%Y%m%d_%H%M%S}")
with open(hosts, "w", encoding="utf-8") as f:
f.write("# localhost name resolution is handled within DNS itself.\n"
"#\t127.0.0.1 localhost\n"
"#\t::1 localhost\n")
self.log("hosts сброшен.", "ok")
except Exception as e:
self.log(f"Ошибка hosts: {e}", "err")
def flush_dns(self) -> None:
self.run_quiet("ipconfig /flushdns")
self.log("DNS-кэш очищен.", "ok")
self.toast("DNS-кэш очищен", "ok")
def reset_winsock(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("netsh winsock reset")
self.log("Winsock сброшен.", "ok")
self.toast("Winsock сброшен", "ok")
def reset_tcpip(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("netsh int ip reset")
self.log("TCP/IP сброшен.", "ok")
self.toast("TCP/IP сброшен", "ok")
def reset_proxy(self) -> None:
if winreg is None:
return
try:
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
0, winreg.KEY_SET_VALUE)
winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 0)
try:
winreg.DeleteValue(key, "ProxyServer")
except FileNotFoundError:
pass
winreg.CloseKey(key)
self.log("Прокси отключён.", "ok")
self.toast("Прокси отключён", "ok")
except Exception as e:
self.log(f"Ошибка: {e}", "err")
def restart_explorer(self) -> None:
if not self.ask_confirm("Перезапустить проводник?"):
return
self.run_quiet("taskkill /f /im explorer.exe")
subprocess.Popen("start explorer.exe", shell=True)
self.log("Проводник перезапущен.", "ok")
def unlock_wuauserv(self) -> None:
self.set_service_start("wuauserv", "demand")
self.set_service_start("UsoSvc", "auto")
self.set_service_start("BITS", "delayed-auto")
self.reg_reset(r"Software\Policies\Microsoft\Windows\WindowsUpdate\AU", "NoAutoUpdate")
self.reg_reset(r"Software\Policies\Microsoft\Windows\WindowsUpdate",
"DisableWindowsUpdateAccess")
def set_service_start(self, service: str, mode: str) -> None:
if not self.is_admin:
self.toast(f"{service}: нужны права администратора", "err"); return
subprocess.run(f"sc config {service} start= {mode}",
shell=True, capture_output=True)
subprocess.run(f"net start {service}",
shell=True, capture_output=True)
self.log(f"Служба {service} -> {mode}", "ok")
def restore_defender(self) -> None:
if winreg is None:
return
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Policies\Microsoft\Windows Defender",
0, winreg.KEY_SET_VALUE)
for v in ("DisableAntiSpyware", "DisableAntiVirus"):
try:
winreg.DeleteValue(key, v)
self.log(f"Удалено: {v}", "ok")
except FileNotFoundError:
pass
winreg.CloseKey(key)
self.log("Политики Defender очищены.", "ok")
except FileNotFoundError:
self.log("Defender не заблокирован.", "info")
except Exception as e:
self.log(f"Ошибка: {e}", "err")
def clear_ifeo(self) -> None:
if winreg is None:
return
base = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, base) as key:
i, subs = 0, []
while True:
try:
subs.append(winreg.EnumKey(key, i))
i += 1
except OSError:
break
count = 0
for sub in subs:
try:
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
f"{base}\\{sub}", 0, winreg.KEY_ALL_ACCESS) as sk:
try:
winreg.QueryValueEx(sk, "Debugger")
winreg.DeleteValue(sk, "Debugger")
count += 1
self.log(f"Удалён отладчик: {sub}", "ok")
except FileNotFoundError:
pass
except Exception:
continue
self.log(f"IFEO: удалено {count}.", "ok" if count else "info")
except Exception as e:
self.log(f"Ошибка IFEO: {e}", "err")
def list_services(self) -> None:
self.log("--- Службы ---", "info")
out = ConsoleRunner.run("sc query type= service state= all")
for line in out.splitlines()[:60]:
if line.strip():
self.log(line)
def list_drivers(self) -> None:
self.log("--- Драйверы ---", "info")
out = ConsoleRunner.run("sc query type= driver state= all")
for line in out.splitlines()[:50]:
if line.strip():
self.log(line)
def reset_exe_assoc(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
subprocess.run(r'cmd /c assoc .exe=exefile && ftype exefile="%1" %*',
shell=True, capture_output=True)
self.log("Ассоциации .exe восстановлены.", "ok")
def disable_sticky_keys(self) -> None:
if winreg is None:
return
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r"Control Panel\Accessibility\StickyKeys",
0, winreg.KEY_SET_VALUE)
winreg.SetValueEx(key, "Flags", 0, winreg.REG_SZ, "506")
winreg.CloseKey(key)
self.log("Залипание клавиш отключено.", "ok")
except Exception as e:
self.log(f"Ошибка: {e}", "err")
def clean_user_temp(self) -> None:
temp = os.environ.get("TEMP", "")
if not temp or not os.path.isdir(temp):
self.toast("TEMP не найден", "warn"); return
if not self.ask_confirm(f"Очистить Temp?\n{temp}"):
return
removed = failed = 0
for entry in glob.glob(os.path.join(temp, "*")):
try:
if os.path.isfile(entry) or os.path.islink(entry):
os.remove(entry)
else:
shutil.rmtree(entry, ignore_errors=True)
removed += 1
except Exception:
failed += 1
self.log(f"Temp: удалено {removed}, ошибок {failed}.", "ok")
def clean_wu_cache(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
path = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
"SoftwareDistribution", "Download")
if not os.path.isdir(path):
self.toast("Кэш не найден", "warn"); return
if not self.ask_confirm(f"Очистить {path}?"):
return
self.run_quiet("net stop wuauserv")
removed = 0
for entry in glob.glob(os.path.join(path, "*")):
try:
if os.path.isdir(entry):
shutil.rmtree(entry, ignore_errors=True)
else:
os.remove(entry)
removed += 1
except Exception:
pass
self.run_quiet("net start wuauserv")
self.log(f"Кэш: удалено {removed}.", "ok")
def clean_prefetch(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
path = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "Prefetch")
removed = 0
for entry in glob.glob(os.path.join(path, "*.pf")):
try:
os.remove(entry); removed += 1
except Exception:
pass
self.log(f"Prefetch: удалено {removed}.", "ok")
def clean_recycle_bin(self) -> None:
if not self.ask_confirm("Очистить корзину?"):
return
ConsoleRunner.run_ps("Clear-RecycleBin -Force", timeout=60)
self.log("Корзина очищена.", "ok")
def open_cmd_desktop(self) -> None:
desktop = os.path.join(os.path.expanduser("~"), "Desktop")
subprocess.Popen(f'start cmd.exe /k "cd /d {desktop}"', shell=True)
self.log("CMD открыт.", "ok")
def confirm_reboot(self) -> None:
if self.ask_confirm("Перезагрузить через 60 секунд?"):
self.run_quiet("shutdown /r /t 60")
self.log("Перезагрузка запланирована.", "warn")
def run_cmd_async(self, command: str) -> None:
if not self.is_admin:
self.toast(f"'{command}' требует админа", "err"); return
subprocess.Popen(f'start cmd.exe /k "chcp 65001 >nul && {command}"', shell=True)
self.log(f"Запущено: {command}", "ok")
def run_quiet(self, command: str) -> None:
try:
subprocess.run(f'chcp 65001 >nul && {command}',
shell=True, capture_output=True, timeout=180)
except Exception as e:
self.log(f"Ошибка '{command}': {e}", "err")
def open_exe(self, exe: str) -> None:
try:
subprocess.Popen(exe, shell=True)
self.log(f"Запущено: {exe}", "ok")
except Exception as e:
self.log(f"Не удалось: {exe}: {e}", "err")
def open_path(self, path: str) -> None:
try:
os.startfile(path)
self.log(f"Открыто: {path}", "ok")
except Exception as e:
self.log(f"Не удалось открыть {path}: {e}", "err")
# ---- бэкапы ---- #
def _backup_dir(self) -> str:
d = os.path.join(os.path.expanduser("~"), "ZabHelper_Backups")
os.makedirs(d, exist_ok=True)
return d
def open_backup_folder(self) -> None:
self.open_path(self._backup_dir())
def create_restore_point(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
name = f"ZabHelper_{datetime.datetime.now():%Y%m%d_%H%M}"
subprocess.run(
f'powershell -NoProfile -Command "'
f'Checkpoint-Computer -Description \'{name}\' -RestorePointType MODIFY_SETTINGS"',
shell=True, capture_output=True, timeout=300)
self.log(f"Точка восстановления: {name}", "ok")
def export_hkcu_software(self) -> None:
path = os.path.join(self._backup_dir(),
f"HKCU_Software_{datetime.datetime.now():%Y%m%d_%H%M%S}.reg")
subprocess.run(f'reg export HKCU\\Software "{path}" /y',
shell=True, capture_output=True)
self.log(f"Экспорт: {path}", "ok")
def export_hklm_software(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
path = os.path.join(self._backup_dir(),
f"HKLM_Software_{datetime.datetime.now():%Y%m%d_%H%M%S}.reg")
subprocess.run(f'reg export HKLM\\Software "{path}" /y',
shell=True, capture_output=True)
self.log(f"Экспорт: {path}", "ok")
def import_reg_file(self) -> None:
path = filedialog.askopenfilename(filetypes=[("Reg", "*.reg"), ("Все", "*.*")])
if not path:
return
if not self.ask_confirm(f"Импортировать реестр?\n{path}"):
return
subprocess.run(f'reg import "{path}"', shell=True, capture_output=True)
self.log(f"Импорт: {path}", "ok")
def backup_hosts(self) -> None:
hosts = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
r"System32\drivers\etc\hosts")
if not os.path.exists(hosts):
self.toast("hosts не найден", "warn"); return
dst = os.path.join(self._backup_dir(),
f"hosts_{datetime.datetime.now():%Y%m%d_%H%M%S}.bak")
shutil.copy2(hosts, dst)
self.log(f"Бэкап hosts: {dst}", "ok")
def backup_startup(self) -> None:
self.refresh_startup()
dst = os.path.join(self._backup_dir(),
f"startup_{datetime.datetime.now():%Y%m%d_%H%M%S}.json")
with open(dst, "w", encoding="utf-8") as f:
json.dump(self._startup_cache, f, ensure_ascii=False, indent=2, default=str)
self.log(f"Бэкап автозагрузки: {dst}", "ok")
def backup_installed(self) -> None:
out = ConsoleRunner.run_ps(
"Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* "
"| Select-Object DisplayName,DisplayVersion,Publisher | Format-Table -AutoSize")
dst = os.path.join(self._backup_dir(),
f"installed_{datetime.datetime.now():%Y%m%d_%H%M%S}.txt")
with open(dst, "w", encoding="utf-8") as f:
f.write(out)
self.log(f"Программы сохранены: {dst}", "ok")
# ---- безопасность ---- #
def check_defender_status(self) -> None:
out = ConsoleRunner.run_ps("Get-MpComputerStatus | Format-List")
self.log("--- Defender ---", "info")
for line in out.splitlines():
if line.strip():
self.log(line)
def check_firewall_status(self) -> None:
out = ConsoleRunner.run("netsh advfirewall show allprofiles state")
self.log("--- Брандмауэр ---", "info")
for line in out.splitlines():
if line.strip():
self.log(line)
def check_uac_status(self) -> None:
if winreg is None:
return
try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
) as key:
val, _ = winreg.QueryValueEx(key, "EnableLUA")
level, _ = winreg.QueryValueEx(key, "ConsentPromptBehaviorAdmin")
self.log(f"UAC EnableLUA = {val}", "ok" if val == 1 else "warn")
self.log(f"Уровень: {level}", "info")
except Exception as e:
self.log(f"Ошибка UAC: {e}", "err")
def check_defender_threats(self) -> None:
out = ConsoleRunner.run_ps(
"Get-MpThreatDetection | Select-Object -First 10 "
"InitialDetectionTime,ThreatID,ActionSuccess | Format-Table -AutoSize")
self.log("--- Угрозы ---", "info")
for line in out.splitlines():
if line.strip():
self.log(line)
def run_defender_quick(self) -> None:
self.run_cmd_async('powershell -NoProfile -Command "Start-MpScan -ScanType QuickScan"')
def run_defender_full(self) -> None:
if self.ask_confirm("Полная проверка Defender может занять часы. Продолжить?"):
self.run_cmd_async('powershell -NoProfile -Command "Start-MpScan -ScanType FullScan"')
def update_defender(self) -> None:
self.run_cmd_async('powershell -NoProfile -Command "Update-MpSignature"')
def enable_firewall(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("netsh advfirewall set allprofiles state on")
self.log("Брандмауэр включён.", "ok")
def enable_uac(self) -> None:
if winreg is None:
return
try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System",
0, winreg.KEY_SET_VALUE
) as key:
winreg.SetValueEx(key, "EnableLUA", 0, winreg.REG_DWORD, 1)
winreg.SetValueEx(key, "ConsentPromptBehaviorAdmin", 0, winreg.REG_DWORD, 5)
self.log("UAC включён (перезагрузка).", "ok")
except Exception as e:
self.log(f"Ошибка UAC: {e}", "err")
def list_admins(self) -> None:
out = ConsoleRunner.run('net localgroup Administrators')
self.log("--- Администраторы ---", "info")
for line in out.splitlines():
if line.strip():
self.log(line)
# ---- восстановление ---- #
def repair_network(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
for c in ("netsh winsock reset", "netsh int ip reset",
"ipconfig /flushdns", "ipconfig /release", "ipconfig /renew"):
self.run_quiet(c)
self.log("Сетевой стек сброшен.", "ok")
def repair_windows_update(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
for svc in ("wuauserv", "cryptSvc", "bits", "msiserver"):
self.run_quiet(f"net stop {svc}")
for folder in ("SoftwareDistribution", "catroot2"):
p = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), folder)
if os.path.isdir(p):
shutil.rmtree(p, ignore_errors=True)
self.log(f"Удалено: {p}", "ok")
for svc in ("wuauserv", "cryptSvc", "bits", "msiserver"):
self.run_quiet(f"net start {svc}")
self.log("Windows Update сброшен.", "ok")
def repair_store(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_cmd_async("wsreset.exe")
def repair_fonts(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("net stop FontCache")
cache = os.path.join(os.environ.get("SystemRoot", r"C:\Windows"),
"ServiceProfiles", "LocalService", "AppData",
"Local", "FontCache")
if os.path.isdir(cache):
for f in glob.glob(os.path.join(cache, "*.dat")):
try:
os.remove(f)
except Exception:
pass
self.run_quiet("net start FontCache")
self.log("Кэш шрифтов пересобран.", "ok")
def repair_scheduler(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("net stop Schedule")
self.run_quiet("net start Schedule")
self.log("Планировщик перезапущен.", "ok")
def repair_wmi(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("net stop winmgmt /y")
self.run_quiet("winmgmt /resetrepository")
self.run_quiet("net start winmgmt")
self.log("WMI пересобран.", "ok")
def repair_firewall(self) -> None:
if not self.is_admin:
self.toast("Нужны права администратора", "err"); return
self.run_quiet("netsh advfirewall reset")
self.run_quiet("netsh advfirewall set allprofiles state on")
self.log("Брандмауэр сброшен.", "ok")
# ---- горячие клавиши ---- #
def _bind_hotkeys(self) -> None:
self.root.bind("<F5>", lambda e: self._refresh_current_section())
self.root.bind("<Control-s>", lambda e: self.save_log())
self.root.bind("<Control-l>", lambda e: self.clear_log())
self.root.bind("<Control-q>", lambda e: self.root.destroy())
def _refresh_current_section(self) -> None:
key = self._current_section
if key == "monitor":
self.refresh_processes()
elif key == "running":
self.refresh_running()
elif key == "startup":
self.refresh_startup()
elif key == "restrict":
self.scan_restrictions()
self.toast("Обновлено (F5)", "info")
# ====================================================================== #
# ТОЧКА ВХОДА
# ====================================================================== #
def main() -> int:
if sys.platform != "win32":
print("ZabHelper рассчитан на Windows.", file=sys.stderr)
return 1
root = tk.Tk()
app = ZabHelperApp(root)
root.update_idletasks()
w, h = root.winfo_width(), root.winfo_height()
x = (root.winfo_screenwidth() - w) // 2
y = (root.winfo_screenheight() - h) // 2
root.geometry(f"+{x}+{y}")
try:
root.mainloop()
finally:
app.log("ZabHelper завершён.", "info")
return 0
if __name__ == "__main__":
sys.exit(main())