吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1734|回复: 37
上一主题 下一主题
收起左侧

[Python 原创] win系统驱动一键备份与恢复工具。

  [复制链接]
跳转到指定楼层
楼主
liuyang207 发表于 2026-4-17 20:55 回帖奖励
平时没事,经常重装系统,安装驱动很是麻烦,用这个就方便多了。

驱动备份与恢复工具https://www.alipan.com/s/b7sbp9AmUKt/奉上源码,方便大家魔改。
[Python] 纯文本查看 复制代码
# -*- coding: utf-8 -*-
"""
Windows Driver Backup & Restore Tool v1.0
带图形界面的驱动备份与恢复工具
"""

import os
import sys
import subprocess
import threading
import shutil
import datetime
import platform
from pathlib import Path

try:
    import tkinter as tk
    from tkinter import ttk, messagebox, filedialog, scrolledtext
except ImportError:
    print("错误: 需要 tkinter 库, 请确认 Python 环境完整")
    sys.exit(1)


# ──────────────────────────────────────────────
# 全局常量
# ──────────────────────────────────────────────
WINDOW_TITLE = "Windows 驱动备份与恢复工具"
WINDOW_SIZE = "920x680"
def _default_export_dir():
    """优先 D:\\MyDrivers,D 盘不存在则用 C:\\MyDrivers"""
    if os.path.isdir("D:\\"):
        return r"D:\MyDrivers"
    return r"C:\MyDrivers"

DEFAULT_EXPORT_DIR = _default_export_dir()

# ── 配色方案 (商务深蓝风格) ──
THEME_BG       = "#1a2332"
THEME_SURFACE  = "#2c3e50"
THEME_CARD     = "#34495e"
THEME_PRIMARY  = "#3498db"
THEME_SECONDARY= "#2980b9"
THEME_WARNING  = "#f39c12"
THEME_ERROR    = "#e74c3c"
THEME_TEXT     = "#ecf0f1"
THEME_DIM      = "#95a5a6"
THEME_COMMENT  = "#7f8c8d"
THEME_BLUE     = "#3498db"
THEME_LINE     = "#34495e"
THEME_OVERLAY  = "#7f8c8d"

# ── 图标 (Unicode 符号) ──
ICON_BACKUP  = "\u2B07"   # ⬇
ICON_RESTORE = "\u2B06"   # ⬆
ICON_SCAN    = "\u2316"   # ⌖
ICON_FOLDER  = "\U0001F4C2"  # 📂
ICON_ADMIN   = "\u26A0"   # ⚠
ICON_OK      = "\u2713"   # ✓
ICON_FAIL    = "\u2717"   # ✗


class DriverTool:
    """驱动备份与恢复工具主类"""

    def __init__(self, root):
        self.root = root
        self.root.title(WINDOW_TITLE)
        self.root.geometry(WINDOW_SIZE)
        self.root.minsize(780, 560)
        self.root.configure(bg=THEME_BG)

        self._setup_styles()
        self._build_ui()
        self._check_admin()

    # ── 样式配置 ──────────────────────────────
    def _setup_styles(self):
        style = ttk.Style()
        style.theme_use("clam")

        # 全局默认
        style.configure(".", background=THEME_BG, foreground=THEME_TEXT,
                         fieldbackground=THEME_SURFACE, font=("Microsoft YaHei UI", 10))
        style.configure("TFrame", background=THEME_BG)
        style.configure("TLabel", background=THEME_BG, foreground=THEME_TEXT)

        # 标题
        style.configure("Title.TLabel", font=("Microsoft YaHei UI", 18, "bold"),
                         foreground=THEME_PRIMARY, background=THEME_BG)
        style.configure("Subtitle.TLabel", font=("Microsoft YaHei UI", 9),
                         foreground=THEME_DIM, background=THEME_BG)

        # 子标题 / 状态
        style.configure("Sub.TLabel", font=("Microsoft YaHei UI", 10),
                         foreground=THEME_DIM, background=THEME_BG)
        style.configure("Status.TLabel", font=("Microsoft YaHei UI", 9),
                         foreground=THEME_SECONDARY, background=THEME_BG)
        style.configure("Error.TLabel", font=("Microsoft YaHei UI", 9, "bold"),
                         foreground=THEME_ERROR, background=THEME_BG)
        style.configure("Admin.TLabel", font=("Microsoft YaHei UI", 9, "bold"),
                         foreground=THEME_WARNING, background=THEME_BG)

        # 卡片容器
        style.configure("Card.TFrame", background=THEME_CARD)
        style.configure("Card.TLabel", background=THEME_CARD, foreground=THEME_TEXT)
        style.configure("CardSub.TLabel", background=THEME_CARD, foreground=THEME_DIM,
                         font=("Microsoft YaHei UI", 9, "bold"))

        # 主操作按钮 (备份/恢复)
        style.configure("Accent.TButton", font=("Microsoft YaHei UI", 11, "bold"),
                         padding=(20, 8))
        style.map("Accent.TButton",
                   background=[("active", THEME_PRIMARY), ("!active", THEME_LINE), ("disabled", THEME_COMMENT)],
                   foreground=[("active", THEME_BG), ("!active", THEME_TEXT), ("disabled", THEME_DIM)])

        # 次要按钮 (扫描/打开)
        style.configure("Secondary.TButton", font=("Microsoft YaHei UI", 10),
                         padding=(16, 6))
        style.map("Secondary.TButton",
                   background=[("active", THEME_SECONDARY), ("!active", THEME_SURFACE), ("disabled", THEME_COMMENT)],
                   foreground=[("active", THEME_BG), ("!active", THEME_TEXT), ("disabled", THEME_DIM)])

        # 输入框
        style.configure("TEntry", fieldbackground=THEME_SURFACE,
                         foreground=THEME_TEXT, insertcolor=THEME_TEXT,
                         bordercolor=THEME_LINE, focuscolor=THEME_BLUE)

        # 小按钮 (浏览/清空)
        style.configure("Browse.TButton", padding=(10, 4))
        style.map("Browse.TButton",
                   background=[("active", THEME_LINE), ("!active", THEME_SURFACE)],
                   foreground=[("active", THEME_TEXT), ("!active", THEME_TEXT)])

        # 单选按钮
        style.configure("TRadiobutton", background=THEME_CARD, foreground=THEME_TEXT,
                         font=("Microsoft YaHei UI", 10))
        style.map("TRadiobutton",
                   background=[("active", THEME_CARD)],
                   foreground=[("active", THEME_TEXT), ("!active", THEME_TEXT)])

        # 分隔线
        style.configure("TSeparator", background=THEME_LINE)

        # 进度条
        style.configure("TProgressbar",
                         troughcolor=THEME_SURFACE,
                         background=THEME_PRIMARY,
                         bordercolor=THEME_LINE,
                         lightcolor=THEME_PRIMARY,
                         darkcolor=THEME_PRIMARY)

    # ── 界面构建 ──────────────────────────────
    def _build_ui(self):
        main = ttk.Frame(self.root, padding=(28, 18, 28, 14))
        main.pack(fill="both", expand=True)

        # ── 标题区 ──
        title_frame = ttk.Frame(main)
        title_frame.pack(fill="x", pady=(0, 4))
        ttk.Label(title_frame, text=WINDOW_TITLE, style="Title.TLabel").pack(side="left")
        ttk.Label(title_frame, text="v1.0", style="Subtitle.TLabel").pack(side="left", padx=(8, 0), pady=(8, 0))

        # 管理员状态
        self.admin_label = ttk.Label(main, text="", style="Status.TLabel")
        self.admin_label.pack(anchor="w", pady=(0, 8))

        # ── 卡片: 路径设置 ──
        path_card = ttk.Frame(main, style="Card.TFrame", padding=(14, 10))
        path_card.pack(fill="x", pady=(0, 8))

        ttk.Label(path_card, text="驱动目录", style="CardSub.TLabel").pack(anchor="w", pady=(0, 6))
        path_row = ttk.Frame(path_card, style="Card.TFrame")
        path_row.pack(fill="x")

        self.path_var = tk.StringVar(value=DEFAULT_EXPORT_DIR)
        entry = ttk.Entry(path_row, textvariable=self.path_var, width=60)
        entry.pack(side="left", padx=(0, 8), fill="x", expand=True)
        ttk.Button(path_row, text="浏览...", style="Browse.TButton",
                    command=self._browse_dir).pack(side="left")

        # ── 卡片: 安装模式 ──
        mode_card = ttk.Frame(main, style="Card.TFrame", padding=(14, 10))
        mode_card.pack(fill="x", pady=(0, 8))

        ttk.Label(mode_card, text="安装模式", style="CardSub.TLabel").pack(anchor="w", pady=(0, 6))
        mode_row = ttk.Frame(mode_card, style="Card.TFrame")
        mode_row.pack(fill="x")

        self.mode_var = tk.StringVar(value="full")
        modes = [
            ("完整安装 (添加 + 安装)", "full"),
            ("仅添加到驱动存储", "add"),
            ("仅扫描硬件变更", "scan"),
        ]
        for i, (text, val) in enumerate(modes):
            rb = ttk.Radiobutton(mode_row, text=text, variable=self.mode_var, value=val)
            rb.pack(side="left", padx=(0, 20) if i < len(modes) - 1 else (0, 0))

        # ── 按钮区 ──
        btn_frame = ttk.Frame(main)
        btn_frame.pack(fill="x", pady=(4, 8))

        self.btn_backup = ttk.Button(btn_frame, text=f" {ICON_BACKUP}  备份驱动 ",
                                      style="Accent.TButton",
                                      command=self._start_backup)
        self.btn_backup.pack(side="left", padx=(0, 12))

        self.btn_restore = ttk.Button(btn_frame, text=f" {ICON_RESTORE}  恢复驱动 ",
                                       style="Accent.TButton",
                                       command=self._start_restore)
        self.btn_restore.pack(side="left", padx=(0, 12))

        self.btn_scan = ttk.Button(btn_frame, text=f" {ICON_SCAN}  扫描硬件 ",
                                    style="Secondary.TButton",
                                    command=self._start_scan)
        self.btn_scan.pack(side="left", padx=(0, 12))

        self.btn_open = ttk.Button(btn_frame, text=f" {ICON_FOLDER}  打开目录 ",
                                    style="Secondary.TButton",
                                    command=self._open_dir)
        self.btn_open.pack(side="left")

        # ── 进度条 + 状态 ──
        progress_frame = ttk.Frame(main)
        progress_frame.pack(fill="x", pady=(0, 4))

        self.progress = ttk.Progressbar(progress_frame, mode="determinate", length=400)
        self.progress.pack(fill="x")

        stat_frame = ttk.Frame(main)
        stat_frame.pack(fill="x", pady=(2, 0))
        self.stat_label = ttk.Label(stat_frame, text="就绪", style="Status.TLabel")
        self.stat_label.pack(side="left")

        # ── 日志区 ──
        log_header = ttk.Frame(main)
        log_header.pack(fill="x", pady=(10, 4))
        ttk.Label(log_header, text="运行日志", style="Sub.TLabel").pack(side="left")
        ttk.Button(log_header, text="清空", style="Browse.TButton",
                    command=self._clear_log).pack(side="right")

        self.log = scrolledtext.ScrolledText(
            main, height=10, wrap="word", state="disabled",
            bg=THEME_SURFACE, fg=THEME_TEXT, insertbackground=THEME_TEXT,
            font=("Cascadia Code", 9), relief="flat", padx=10, pady=8,
            selectbackground=THEME_BLUE, selectforeground=THEME_BG,
            borderwidth=0, highlightthickness=1,
            highlightbackground=THEME_LINE, highlightcolor=THEME_BLUE
        )
        self.log.pack(fill="both", expand=True, pady=(0, 4))

        # 日志颜色标签
        self.log.tag_configure("ok", foreground=THEME_PRIMARY)
        self.log.tag_configure("warn", foreground=THEME_WARNING)
        self.log.tag_configure("error", foreground=THEME_ERROR)
        self.log.tag_configure("info", foreground=THEME_SECONDARY)
        self.log.tag_configure("dim", foreground=THEME_DIM)
        self.log.tag_configure("header", foreground=THEME_OVERLAY,
                                font=("Cascadia Code", 9, "bold"))

        # ── 底部统计栏 ──
        ttk.Separator(main, orient="horizontal").pack(fill="x", pady=(4, 4))
        bottom = ttk.Frame(main)
        bottom.pack(fill="x")
        self.count_label = ttk.Label(bottom, text="", style="Sub.TLabel")
        self.count_label.pack(side="left")

    # ── 辅助方法 ──────────────────────────────
    def _check_admin(self):
        """检测管理员权限"""
        try:
            result = subprocess.run(
                ["net", "session"],
                capture_output=True, timeout=5
            )
            if result.returncode == 0:
                self.admin_label.configure(
                    text=f"{ICON_OK} 管理员权限已确认", style="Status.TLabel"
                )
                self._set_buttons(True)
            else:
                self.admin_label.configure(
                    text=f"{ICON_ADMIN} 未以管理员身份运行 - 备份/恢复功能不可用",
                    style="Error.TLabel"
                )
                self._set_buttons(False)
        except Exception:
            self.admin_label.configure(
                text=f"{ICON_FAIL} 无法检测权限状态", style="Error.TLabel"
            )
            self._set_buttons(False)

    def _set_buttons(self, enabled):
        state = "normal" if enabled else "disabled"
        for btn in (self.btn_backup, self.btn_restore, self.btn_scan):
            btn.configure(state=state)

    def _browse_dir(self):
        chosen = filedialog.askdirectory(
            title="选择驱动目录",
            initialdir=self.path_var.get()
        )
        if chosen:
            self.path_var.set(chosen)

    def _open_dir(self):
        path = self.path_var.get()
        if not os.path.isdir(path):
            messagebox.showwarning("提示", f"目录不存在:\n{path}")
            return
        os.startfile(path)

    def _get_driver_dir(self):
        path = self.path_var.get().strip()
        if not path:
            return None
        if path.endswith(("\\", "/")):
            path = path.rstrip("\\/")
        return path

    def _ui(self, func, *args):
        """将 UI 操作调度到主线程执行 (线程安全)"""
        self.root.after(0, func, *args)

    def _log(self, text, tag=None, end=True):
        """写入日志. end=False 时不追加换行符"""
        self.log.configure(state="normal")
        line = text if not end else text + "\n"
        if tag:
            self.log.insert("end", line, tag)
        else:
            self.log.insert("end", line)
        self.log.see("end")
        self.log.configure(state="disabled")

    def _log_header(self, text):
        self._log(f"{'=' * 60}", "dim")
        self._log(text, "header")
        self._log(f"{'=' * 60}", "dim")

    def _clear_log(self):
        self.log.configure(state="normal")
        self.log.delete("1.0", "end")
        self.log.configure(state="disabled")

    def _set_progress(self, current, total):
        if total > 0:
            self.progress["value"] = (current / total) * 100

    def _update_stat(self, text):
        self.stat_label.configure(text=text)

    def _update_count(self, ok=0, added=0, skip=0, fail=0):
        text = (f"安装成功: {ok}  |  已添加: {added}  |  "
                f"跳过: {skip}  |  失败: {fail}")
        self.count_label.configure(text=text)

    def _toggle_buttons(self, enabled):
        state = "normal" if enabled else "disabled"
        for btn in (self.btn_backup, self.btn_restore, self.btn_scan):
            btn.configure(state=state)

    def _find_inf_files(self, directory):
        """递归查找所有 .inf 文件"""
        inf_list = []
        for root_dir, dirs, files in os.walk(directory):
            for f in files:
                if f.lower().endswith(".inf"):
                    inf_list.append(os.path.join(root_dir, f))
        return inf_list

    def _get_system_info(self):
        """收集系统信息"""
        info = {}
        
        # 基本系统信息
        info["操作系统"] = platform.platform()
        info["系统版本"] = platform.version()
        info["系统架构"] = platform.architecture()[0]
        info["计算机名称"] = platform.node()
        info["处理器"] = platform.processor()
        
        # Windows 特定信息
        if sys.platform == "win32":
            try:
                # 获取 Windows 版本详细信息
                code, stdout, _ = self._run_cmd(["systeminfo"], timeout=30)
                if code == 0:
                    for line in stdout.splitlines():
                        if "OS 名称:" in line:
                            info["OS 名称"] = line.split(":", 1)[1].strip()
                        elif "OS 版本:" in line:
                            info["OS 版本"] = line.split(":", 1)[1].strip()
                        elif "系统类型:" in line:
                            info["系统类型"] = line.split(":", 1)[1].strip()
                        elif "注册的所有人:" in line:
                            info["注册的所有人"] = line.split(":", 1)[1].strip()
            except Exception:
                pass
        
        # 内存信息
        try:
            if sys.platform == "win32":
                code, stdout, _ = self._run_cmd(["wmic", "OS", "get", "TotalVisibleMemorySize"], timeout=10)
                if code == 0:
                    lines = stdout.strip().splitlines()
                    if len(lines) > 1:
                        try:
                            memory_kb = int(lines[1].strip())
                            info["内存"] = f"{memory_kb / 1024 / 1024:.2f} GB"
                        except:
                            pass
        except Exception:
            pass
        
        return info

    def _read_backup_info(self, directory):
        """读取并解析备份信息文件"""
        info_file = os.path.join(directory, "backup_info.txt")
        if not os.path.exists(info_file):
            return None
        
        info = {}
        try:
            with open(info_file, "r", encoding="utf-8") as f:
                lines = f.readlines()
                
            current_section = None
            for line in lines:
                line = line.strip()
                if line.startswith("#"):
                    current_section = line[1:].strip()
                elif line.startswith("="):
                    continue
                elif ":" in line:
                    key, value = line.split(":", 1)
                    key = key.strip()
                    value = value.strip()
                    info[key] = value
        except Exception:
            return None
        
        return info

    # ── 运行命令 ──────────────────────────────
    def _run_cmd(self, cmd, timeout=120):
        """运行系统命令并返回结果"""
        try:
            kwargs = dict(capture_output=True, text=True, timeout=timeout)
            if sys.platform == "win32":
                kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
            result = subprocess.run(cmd, **kwargs)
            return result.returncode, result.stdout, result.stderr
        except subprocess.TimeoutExpired:
            return -1, "", "命令执行超时"
        except Exception as e:
            return -1, "", str(e)

    # ── 备份驱动 ──────────────────────────────
    def _start_backup(self):
        driver_dir = self._get_driver_dir()
        if not driver_dir:
            messagebox.showwarning("提示", "请先设置驱动导出目录")
            return

        def worker():
            self._ui(self._toggle_buttons, False)
            self._ui(self._clear_log)
            self._ui(self._log_header, f"开始备份驱动 -> {driver_dir}")
            self._ui(self._log, f"时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", "info")
            self._ui(self._log, "")

            if not os.path.isdir(driver_dir):
                os.makedirs(driver_dir, exist_ok=True)
                self._ui(self._log, f"已创建目录: {driver_dir}", "info")

            self._ui(self._update_stat, "正在备份驱动, 请稍候...")
            self._ui(self._log, "执行: dism /online /export-driver ...", "dim")
            self._ui(self._log, "")

            code, stdout, stderr = self._run_cmd([
                "dism", "/online", "/export-driver",
                f"/destination:{driver_dir}"
            ], timeout=600)

            if code == 0:
                self._ui(self._log, "[OK] 驱动备份成功!", "ok")
                if stdout:
                    for line in stdout.strip().splitlines():
                        if line.strip():
                            self._ui(self._log, f"  {line}", "dim")
                self._ui(self._update_stat, "备份完成")
                inf_files = self._find_inf_files(driver_dir)
                self._ui(self._log, f"\n目录中共有 {len(inf_files)} 个驱动文件 (.inf)", "info")
                
                # 创建备份信息文件
                info_file = os.path.join(driver_dir, "backup_info.txt")
                try:
                    with open(info_file, "w", encoding="utf-8") as f:
                        f.write("# 驱动备份信息\n")
                        f.write("=" * 60 + "\n")
                        f.write(f"备份时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                        f.write(f"备份目录: {driver_dir}\n")
                        f.write(f"驱动文件数量: {len(inf_files)}\n")
                        f.write(f"作者: 吾爱破解论坛  liuyang207\n")
                        f.write("\n# 系统信息\n")
                        f.write("=" * 60 + "\n")
                        system_info = self._get_system_info()
                        for key, value in system_info.items():
                            f.write(f"{key}: {value}\n")
                        f.write("\n# 备份命令\n")
                        f.write("=" * 60 + "\n")
                        f.write(f"dism /online /export-driver /destination:{driver_dir}\n")
                    self._ui(self._log, f"[OK] 已创建备份信息文件: {info_file}", "ok")
                except Exception as e:
                    self._ui(self._log, f"[WARN] 创建备份信息文件失败: {str(e)}", "warn")
            else:
                self._ui(self._log, f"[FAIL] 驱动备份失败, 错误码: {code}", "error")
                if stderr:
                    self._ui(self._log, f"  {stderr.strip()}", "error")
                self._ui(self._update_stat, "备份失败")
                self._ui(messagebox.showerror, "错误", f"驱动备份失败:\n{stderr or f'错误码 {code}'}")

            self._ui(self._set_progress, 100, 100)
            self._ui(self._toggle_buttons, True)

        threading.Thread(target=worker, daemon=True).start()

    # ── 恢复驱动 ──────────────────────────────
    def _start_restore(self):
        driver_dir = self._get_driver_dir()
        if not driver_dir:
            messagebox.showwarning("提示", "请先设置驱动目录")
            return
        if not os.path.isdir(driver_dir):
            messagebox.showwarning("提示", f"驱动目录不存在:\n{driver_dir}")
            return

        # 检查备份信息文件
        backup_info = self._read_backup_info(driver_dir)
        if backup_info:
            # 显示自定义风格的备份信息确认对话框
            dialog = tk.Toplevel(self.root)
            dialog.title("备份信息确认")
            dialog.geometry("500x400")
            dialog.resizable(False, False)
            dialog.transient(self.root)
            dialog.grab_set()
            dialog.attributes('-topmost', True)

            # 计算对话框位置,使其在主窗口中央
            root_x = self.root.winfo_x()
            root_y = self.root.winfo_y()
            root_width = self.root.winfo_width()
            root_height = self.root.winfo_height()

            dialog_width = 500
            dialog_height = 400

            x = root_x + (root_width - dialog_width) // 2
            y = root_y + (root_height - dialog_height) // 2

            # 确保对话框位置有效
            x = max(0, x)
            y = max(0, y)

            dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")

            # 强制对话框显示在最前面
            dialog.lift()
            self.root.update()
            dialog.focus_force()

            # 设置对话框样式
            dialog.configure(bg=THEME_BG)

            # 标题
            title_label = ttk.Label(
                dialog,
                text="备份信息确认",
                style="Title.TLabel"
            )
            title_label.pack(pady=(18, 12))

            # 信息框架
            info_frame = ttk.Frame(dialog, style="Card.TFrame", padding=(16, 12))
            info_frame.pack(fill="both", expand=True, padx=24, pady=(0, 12))

            # 备份信息
            info_text = []
            if "备份时间" in backup_info:
                info_text.append(f"备份时间: {backup_info['备份时间']}")
            if "驱动文件数量" in backup_info:
                info_text.append(f"驱动文件数量: {backup_info['驱动文件数量']}")
            if "作者" in backup_info:
                info_text.append(f"作者: {backup_info['作者']}")
            if "操作系统" in backup_info:
                info_text.append(f"操作系统: {backup_info['操作系统']}")
            if "系统架构" in backup_info:
                info_text.append(f"系统架构: {backup_info['系统架构']}")

            for line in info_text:
                ttk.Label(info_frame, text=line, style="Card.TLabel",
                          wraplength=430).pack(anchor="w", pady=3)

            # 确认提示
            confirm_label = ttk.Label(
                dialog,
                text="确认要恢复这些驱动吗?",
                style="Status.TLabel"
            )
            confirm_label.pack(pady=(0, 12))

            # 按钮框架
            btn_frame = ttk.Frame(dialog)
            btn_frame.pack(fill="x", padx=24, pady=(0, 20))

            # 确认按钮
            confirm_btn = ttk.Button(
                btn_frame,
                text="  确认恢复  ",
                style="Accent.TButton",
                command=lambda: self._confirm_restore(dialog, True)
            )
            confirm_btn.pack(side="left", padx=(0, 12), fill="x", expand=True)

            # 取消按钮
            cancel_btn = ttk.Button(
                btn_frame,
                text="  取  消  ",
                style="Secondary.TButton",
                command=lambda: self._confirm_restore(dialog, False)
            )
            cancel_btn.pack(side="left", fill="x", expand=True)
            
            # 等待用户响应
            self.restore_confirmed = None
            dialog.wait_window()
            
            if not self.restore_confirmed:
                return

        def worker():
            self._ui(self._toggle_buttons, False)
            self._ui(self._clear_log)

            mode = self.mode_var.get()
            mode_text = {"full": "完整安装", "add": "仅添加", "scan": "仅扫描"}
            self._ui(self._log_header, f"恢复驱动 - 模式: {mode_text.get(mode, mode)}")
            self._ui(self._log, f"驱动来源: {driver_dir}", "info")
            self._ui(self._log, f"时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", "info")
            self._ui(self._log, "")

            # scan 模式:仅扫描硬件变更,无需遍历 inf 文件
            if mode == "scan":
                self._ui(self._log, "正在扫描硬件变更...", "info")
                self._ui(self._update_stat, "正在扫描硬件...")
                code, stdout, stderr = self._run_cmd(
                    ["pnputil", "/scan-devices"], timeout=120
                )
                if stdout:
                    for line in stdout.strip().splitlines():
                        if line.strip():
                            self._ui(self._log, f"  {line}")
                self._ui(self._log, "")
                if code == 0:
                    self._ui(self._log, "[OK] 硬件扫描完成", "ok")
                    self._ui(self._log, "部分驱动可能需要重启后生效。", "warn")
                else:
                    self._ui(self._log, f"[FAIL] 扫描失败, 错误码: {code}", "error")
                    if stderr:
                        self._ui(self._log, f"  {stderr.strip()}", "error")

                self._ui(self._set_progress, 100, 100)
                self._ui(self._update_stat, "扫描完成")
                self._ui(self._toggle_buttons, True)
                return

            inf_files = self._find_inf_files(driver_dir)
            total = len(inf_files)

            if total == 0:
                self._ui(self._log, "[WARN] 未找到任何 .inf 驱动文件!", "warn")
                self._ui(self._update_stat, "未找到驱动文件")
                self._ui(self._toggle_buttons, True)
                return

            self._ui(self._log, f"共发现 {total} 个驱动文件\n", "info")

            ok = added = skip = fail = 0

            for i, inf_path in enumerate(inf_files, 1):
                name = os.path.basename(inf_path)
                self._ui(self._set_progress, i, total)
                self._ui(self._update_stat, f"处理中 [{i}/{total}]: {name}")
                self._ui(self._log, f"[{i}/{total}] {name} ... ", "dim", False)

                if mode == "full":
                    # 先尝试 add + install
                    code, stdout, stderr = self._run_cmd(
                        ["pnputil", "/add-driver", inf_path, "/install"],
                        timeout=60
                    )
                    if code == 0:
                        self._ui(self._log_append, "OK - installed", "ok")
                        ok += 1
                        continue
                    # 回退到仅添加
                    code2, _, _ = self._run_cmd(
                        ["pnputil", "/add-driver", inf_path],
                        timeout=60
                    )
                    if code2 == 0:
                        self._ui(self._log_append, "OK - added to store", "ok")
                        added += 1
                    elif code2 == 258:
                        self._ui(self._log_append, "SKIP - already exists", "warn")
                        skip += 1
                    else:
                        self._ui(self._log_append, f"FAIL - error {code2}", "error")
                        fail += 1

                elif mode == "add":
                    code, stdout, stderr = self._run_cmd(
                        ["pnputil", "/add-driver", inf_path],
                        timeout=60
                    )
                    if code == 0:
                        self._ui(self._log_append, "OK - added", "ok")
                        added += 1
                    elif code == 258:
                        self._ui(self._log_append, "SKIP - already exists", "warn")
                        skip += 1
                    else:
                        self._ui(self._log_append, f"FAIL - error {code}", "error")
                        fail += 1

            # 汇总
            self._ui(self._log, "")
            self._ui(self._log_header, "结果汇总")
            self._ui(self._log, f"  安装成功:   {ok}", "ok")
            self._ui(self._log, f"  已添加:     {added}", "info")
            self._ui(self._log, f"  跳过:       {skip}", "warn")
            self._ui(self._log, f"  失败:       {fail}", "error" if fail > 0 else "dim")
            self._ui(self._log, f"  合计:       {total}", "info")

            self._ui(self._update_count, ok, added, skip, fail)
            self._ui(self._set_progress, 100, 100)

            # 安装成功后扫描硬件
            if ok > 0 or added > 0:
                self._ui(self._log, "")
                self._ui(self._log, "正在扫描硬件变更...", "info")
                code, stdout, stderr = self._run_cmd(
                    ["pnputil", "/scan-devices"], timeout=120
                )
                self._ui(self._log, "硬件扫描完成。", "ok" if code == 0 else "error")
                self._ui(self._log, "")
                self._ui(self._log, "部分驱动可能需要重启后生效。", "warn")

            self._ui(self._update_stat, "恢复完成")
            self._ui(self._toggle_buttons, True)

            # 显示带有倒计时的重启确认对话框
            self._ui(self._show_restart_dialog, ok + added, skip, fail)

        threading.Thread(target=worker, daemon=True).start()

    def _start_scan(self):
        """仅扫描硬件变更"""
        def worker():
            self._ui(self._toggle_buttons, False)
            self._ui(self._clear_log)
            self._ui(self._log_header, "扫描硬件变更")
            self._ui(self._log, "")

            self._ui(self._update_stat, "正在扫描硬件...")
            code, stdout, stderr = self._run_cmd(
                ["pnputil", "/scan-devices"], timeout=120
            )

            if stdout:
                for line in stdout.strip().splitlines():
                    if line.strip():
                        self._ui(self._log, f"  {line}")

            self._ui(self._log, "")
            if code == 0:
                self._ui(self._log, "[OK] 硬件扫描完成", "ok")
            else:
                self._ui(self._log, f"[FAIL] 扫描失败, 错误码: {code}", "error")

            self._ui(self._update_stat, "扫描完成")
            self._ui(self._set_progress, 100, 100)
            self._ui(self._toggle_buttons, True)

        threading.Thread(target=worker, daemon=True).start()

    # ── 日志辅助(同一行追加) ──────────────────
    def _log_append(self, text, tag=None):
        """在当前行末尾追加内容并换行(用于显示处理结果)"""
        self.log.configure(state="normal")
        self.log.insert("end", text + "\n", tag)
        self.log.see("end")
        self.log.configure(state="disabled")

    def _show_restart_dialog(self, success_count, skip_count, fail_count):
        """显示带有倒计时的重启确认对话框"""
        dialog = tk.Toplevel(self.root)
        dialog.title("驱动恢复完成")
        dialog.geometry("500x300")
        dialog.resizable(False, False)
        dialog.transient(self.root)
        dialog.grab_set()
        dialog.attributes('-topmost', True)

        # 计算对话框位置,使其在主窗口中央
        root_x = self.root.winfo_x()
        root_y = self.root.winfo_y()
        root_width = self.root.winfo_width()
        root_height = self.root.winfo_height()

        dialog_width = 500
        dialog_height = 300

        x = root_x + (root_width - dialog_width) // 2
        y = root_y + (root_height - dialog_height) // 2

        # 确保对话框位置有效
        x = max(0, x)
        y = max(0, y)

        dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")

        # 强制对话框显示在最前面
        dialog.lift()
        self.root.update()
        dialog.focus_force()

        # 设置对话框样式
        dialog.configure(bg=THEME_BG)

        # 标题
        title_label = ttk.Label(
            dialog,
            text="驱动恢复完成",
            style="Title.TLabel"
        )
        title_label.pack(pady=(20, 10))

        # 结果信息
        result_text = f"成功: {success_count}  跳过: {skip_count}  失败: {fail_count}"
        result_label = ttk.Label(
            dialog,
            text=result_text,
            style="Sub.TLabel"
        )
        result_label.pack(pady=(0, 12))

        # 重启提示
        restart_label = ttk.Label(
            dialog,
            text="部分驱动需要重启后生效",
            style="Status.TLabel"
        )
        restart_label.pack(pady=(0, 8))

        # 倒计时标签
        self.countdown_var = tk.StringVar(value="10")
        countdown_label = ttk.Label(
            dialog,
            textvariable=self.countdown_var,
            font=("Microsoft YaHei UI", 20, "bold"),
            foreground=THEME_PRIMARY,
            background=THEME_BG
        )
        countdown_label.pack(pady=(0, 16))

        # 按钮框架
        btn_frame = ttk.Frame(dialog)
        btn_frame.pack(fill="x", padx=24, pady=(0, 20))

        # 立即重启按钮
        restart_btn = ttk.Button(
            btn_frame,
            text="  立即重启  ",
            style="Accent.TButton",
            command=lambda: self._restart_system(dialog)
        )
        restart_btn.pack(side="left", padx=(0, 12), fill="x", expand=True)

        # 取消重启按钮
        cancel_btn = ttk.Button(
            btn_frame,
            text="  取消重启  ",
            style="Secondary.TButton",
            command=lambda: self._cancel_restart(dialog)
        )
        cancel_btn.pack(side="left", fill="x", expand=True)
        
        # 开始倒计时
        self.countdown_time = 10
        self.countdown_id = None
        self._update_countdown(dialog)
        
    def _update_countdown(self, dialog):
        """更新倒计时"""
        if self.countdown_time > 0:
            # 检查对话框是否仍然存在
            if not dialog.winfo_exists():
                return
            
            self.countdown_time -= 1
            self.countdown_var.set(str(self.countdown_time))
            self.countdown_id = self.root.after(1000, self._update_countdown, dialog)
        else:
            # 倒计时结束,自动重启
            if dialog.winfo_exists():
                self._restart_system(dialog)
    
    def _cancel_restart(self, dialog):
        """取消重启"""
        # 取消倒计时
        if self.countdown_id:
            self.root.after_cancel(self.countdown_id)
        # 关闭对话框
        dialog.destroy()
    
    def _confirm_restore(self, dialog, confirmed):
        """处理恢复确认"""
        self.restore_confirmed = confirmed
        dialog.destroy()

    def _restart_system(self, dialog):
        """重启系统"""
        dialog.destroy()
        try:
            # 使用 shutdown 命令重启系统,/r 表示重启,/t 0 表示立即执行
            subprocess.run(["shutdown", "/r", "/t", "0"], capture_output=True)
        except Exception:
            pass


def main():
    root = tk.Tk()

    # 尝试设置 DPI 感知 (Windows)
    try:
        from ctypes import windll
        windll.shcore.SetProcessDpiAwareness(1)
    except Exception:
        pass

    # 尝试设置窗口图标
    try:
        root.iconbitmap(default="")
    except Exception:
        pass

    app = DriverTool(root)
    root.mainloop()


if __name__ == "__main__":
    main()

1.png (59.89 KB, 下载次数: 2)

1.png

免费评分

参与人数 10吾爱币 +17 热心值 +9 收起 理由
RETIAN + 1 + 1 我很赞同!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
施施乐 + 1 + 1 谢谢@Thanks!
wchy056 + 1 热心回复!
y107620560 + 1 + 1 谢谢@Thanks!
jakhu + 3 + 1 还没试,转个盘给大家:https://jakhu.lanzouu.com/i4N5O3njbb2f
cd1688 + 1 谢谢@Thanks!
leechjia + 1 + 1 谢谢@Thanks!
stysty0930 + 1 + 1 热心回复!
ysy2001 + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
jakhu 发表于 2026-4-18 11:16
推荐
xiaoxino 发表于 2026-4-18 16:38
Traceback (most recent call last):
  File "C:\Users\Administrator\Desktop\系统驱动备份恢复\qdbf.py", line 969, in <module>
    main()
  File "C:\Users\Administrator\Desktop\系统驱动备份恢复\qdbf.py", line 964, in main
    app = DriverTool(root)
  File "C:\Users\Administrator\Desktop\系统驱动备份恢复\qdbf.py", line 75, in __init__
    self._build_ui()
  File "C:\Users\Administrator\Desktop\系统驱动备份恢复\qdbf.py", line 222, in _build_ui
    command=self._open_dir)
  File "D:\Python36\lib\tkinter\ttk.py", line 614, in __init__
    Widget.__init__(self, master, "ttk::button", kw)
  File "D:\Python36\lib\tkinter\ttk.py", line 559, in __init__
    tkinter.Widget.__init__(self, master, widgetname, kw=kw)
  File "D:\Python36\lib\tkinter\__init__.py", line 2296, in __init__
    (widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: character U+1f4c2 is above the range (U+0000-U+FFFF) allowed by Tcl
沙发
紫蝶冰澜 发表于 2026-4-18 05:59
其实是封装了dism命令,不过py写的ui还行,学习
3#
daoye9988 发表于 2026-4-18 07:34
非常喜欢这个界面
4#
wzzhal123 发表于 2026-4-18 08:37
正在学习python,好好研究下楼主的编程
5#
sgcp 发表于 2026-4-18 08:50
不错的东西,感谢大神分享。没阿里云,坐等夸克、蓝奏转发。
6#
xujili168 发表于 2026-4-18 08:53
有这个就再也不用担心安装系统那么麻烦了.还能找到备份前的模样.
7#
52soft 发表于 2026-4-18 08:55
驱动安装就快了
8#
yuetaigroup 发表于 2026-4-18 09:11
看起来不错,没阿里云下不了。
9#
smallmouse228 发表于 2026-4-18 09:21
阿里云网盘算了,感谢分享!!
10#
VL008 发表于 2026-4-18 09:26
有打包好的吗
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-17 02:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表