用 Python 批量改 1000 台电脑的注册表?别慌,这篇手把手教你

4 阅读9分钟

手动打开 regedit,逐层找到目标键值,修改,确认,重启……一台电脑 5 分钟,1000 台就是 83 小时。用 Python?30 分钟搞定。

为什么需要自动化注册表操作?

Windows 注册表(Registry)是系统的"中央配置数据库",几乎所有系统行为都可以通过注册表控制:

  • 软件配置:安装路径、版本号、许可证信息
  • 网络设置:代理配置、DNS、防火墙规则
  • 安全策略:密码策略、账户锁定、UAC 设置
  • 启动项管理:开机自启程序、服务配置
  • 文件关联:默认打开程序、右键菜单

在 IT 运维中,我们经常需要:

  1. 批量修改代理服务器地址
  2. 统一配置防火墙策略
  3. 禁用 USB 存储设备
  4. 修改远程桌面端口
  5. 清理过期的启动项

手动一台台改?不存在的。

基础篇:用 winreg 读写本地注册表

Python 标准库的 winreg 模块提供了完整的注册表操作能力。

注册表的五大根键

HKEY_CLASSES_ROOT  (HKCR)  — 文件关联和 COM 类信息
HKEY_CURRENT_USER  (HKCU)  — 当前用户配置
HKEY_LOCAL_MACHINE (HKLM)  — 本机系统配置(最常用)
HKEY_USERS         (HKU)   — 所有用户配置
HKEY_CURRENT_CONFIG(HKCC)  — 当前硬件配置

读操作:查询注册表值

import winreg

def read_registry(key_path, value_name, hive=winreg.HKEY_LOCAL_MACHINE):
    """
    读取注册表值
    key_path: 例如 r"SOFTWARE\Microsoft\Windows\CurrentVersion"
    value_name: 值的名称,如 "ProgramFilesDir"
    """
    try:
        key = winreg.OpenKey(hive, key_path)
        value, value_type = winreg.QueryValueEx(key, value_name)
        winreg.CloseKey(key)
        return value, value_type
    except FileNotFoundError:
        print(f"键或值不存在: {key_path}\\{value_name}")
        return None, None
    except Exception as e:
        print(f"读取失败: {e}")
        return None, None

# 示例:读取 Windows 安装路径
path, vtype = read_registry(
    r"SOFTWARE\Microsoft\Windows\CurrentVersion",
    "ProgramFilesDir"
)
print(f"安装路径: {path}, 类型: {vtype}")
# 输出: 安装路径: C:\Program Files, 类型: 1 (REG_SZ)

写操作:修改注册表值

def write_registry(key_path, value_name, value, hive=winreg.HKEY_LOCAL_MACHINE):
    """
    写入注册表值(自动判断类型)
    """
    # 类型映射
    type_map = {
        str: winreg.REG_SZ,
        int: winreg.REG_DWORD,
        bytes: winreg.REG_BINARY,
        list: winreg.REG_MULTI_SZ,
    }

    reg_type = type_map.get(type(value), winreg.REG_SZ)

    try:
        # 打开键(不存在则创建)
        key = winreg.CreateKeyEx(hive, key_path, 0, winreg.KEY_WRITE)
        winreg.SetValueEx(key, value_name, 0, reg_type, value)
        winreg.CloseKey(key)
        return True
    except PermissionError:
        print("权限不足,请以管理员身份运行!")
        return False
    except Exception as e:
        print(f"写入失败: {e}")
        return False

# 示例:修改代理设置
write_registry(
    r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
    "ProxyEnable",
    1  # 启用代理
)
write_registry(
    r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
    "ProxyServer",
    "proxy.company.com:8080"
)

列举子键和值

def enumerate_registry(key_path, hive=winreg.HKEY_LOCAL_MACHINE):
    """列举某个键下的所有子键和值"""
    key = winreg.OpenKey(hive, key_path)

    print(f"=== {key_path} 的子键 ===")
    idx = 0
    while True:
        try:
            subkey_name = winreg.EnumKey(key, idx)
            print(f"  [{subkey_name}]")
            idx += 1
        except OSError:
            break

    print(f"\n=== {key_path} 的值 ===")
    idx = 0
    while True:
        try:
            name, value, vtype = winreg.EnumValue(key, idx)
            type_names = {
                winreg.REG_SZ: "REG_SZ",
                winreg.REG_DWORD: "REG_DWORD",
                winreg.REG_BINARY: "REG_BINARY",
                winreg.REG_EXPAND_SZ: "REG_EXPAND_SZ",
                winreg.REG_MULTI_SZ: "REG_MULTI_SZ",
            }
            type_name = type_names.get(vtype, f"UNKNOWN({vtype})")
            # 二进制数据截断显示
            if isinstance(value, bytes):
                display = f"<{len(value)} bytes>"
            elif isinstance(value, list):
                display = ", ".join(str(v) for v in value[:3])
                if len(value) > 3:
                    display += f" ... ({len(value)} items)"
            else:
                display = str(value)
            print(f"  {name} = {display}  ({type_name})")
            idx += 1
        except OSError:
            break

    winreg.CloseKey(key)

# 示例:查看当前运行的服务
enumerate_registry(r"SYSTEM\CurrentControlSet\Services")

进阶篇:注册表备份与恢复

修改注册表前必须备份,这是铁律。

导出注册表为 .reg 文件

import subprocess
import datetime
from pathlib import Path

def export_registry(key_path, output_dir=None, hive="HKLM"):
    """
    导出注册表分支到 .reg 文件
    使用 Windows 自带的 reg.exe 命令
    """
    if output_dir is None:
        output_dir = Path("registry_backups")
    output_dir.mkdir(exist_ok=True)

    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    safe_name = key_path.replace("\\", "_").replace(" ", "")
    filename = output_dir / f"{hive}_{safe_name}_{timestamp}.reg"

    cmd = f'reg export "{hive}\\{key_path}" "{filename}" /y'

    result = subprocess.run(
        cmd, shell=True, capture_output=True, text=True
    )

    if result.returncode == 0:
        print(f"备份成功: {filename}")
        return str(filename)
    else:
        print(f"备份失败: {result.stderr}")
        return None

# 示例:备份代理设置
export_registry(
    r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings"
)

通过 .reg 文件恢复

def import_registry(reg_file):
    """从 .reg 文件恢复注册表"""
    cmd = f'reg import "{reg_file}"'
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

    if result.returncode == 0:
        print(f"恢复成功: {reg_file}")
        return True
    else:
        print(f"恢复失败: {result.stderr}")
        return False

# 使用
import_registry("registry_backups/HKLM_SOFTWARE_Microsoft_...20260506.reg")

纯 Python 备份(递归导出)

如果你需要在 Python 里直接操作而不是调用 reg.exe

def backup_key_recursive(key_path, hive=winreg.HKEY_LOCAL_MACHINE):
    """
    递归备份注册表键,返回嵌套字典结构
    """
    try:
        key = winreg.OpenKey(hive, key_path)
    except FileNotFoundError:
        return None

    data = {"_values": {}, "_subkeys": {}}

    # 备份所有值
    idx = 0
    while True:
        try:
            name, value, vtype = winreg.EnumValue(key, idx)
            data["_values"][name] = {"value": value, "type": vtype}
            idx += 1
        except OSError:
            break

    # 递归备份子键
    idx = 0
    while True:
        try:
            subkey_name = winreg.EnumKey(key, idx)
            sub_path = f"{key_path}\\{subkey_name}"
            data["_subkeys"][subkey_name] = backup_key_recursive(sub_path, hive)
            idx += 1
        except OSError:
            break

    winreg.CloseKey(key)
    return data

# 导出为 JSON
import json

backup = backup_key_recursive(
    r"SOFTWARE\MyApp\Config"
)
with open("registry_backup.json", "w", encoding="utf-8") as f:
    json.dump(backup, f, ensure_ascii=False, indent=2, default=str)

实战篇:批量远程注册表操作

核心场景来了——一次性修改 1000 台电脑的注册表

方案一:WMI 远程操作

import wmi
from concurrent.futures import ThreadPoolExecutor, as_completed

def remote_read_registry(
    computer, key_path, value_name,
    hive="HKEY_LOCAL_MACHINE",
    username=None, password=None
):
    """
    通过 WMI 远程读取注册表
    需要目标电脑开启 WMI 服务和远程管理
    """
    try:
        if username and password:
            # 带凭据连接
            c = wmi.WMI(
                computer,
                user=f".\\{username}",
                password=password
            )
        else:
            # 当前域凭据连接
            c = wmi.WMI(computer)

        # 使用 StdRegProv 类
        registry = c.StdRegProv

        # hive 映射
        hive_map = {
            "HKEY_CLASSES_ROOT": 0x80000000,
            "HKEY_CURRENT_USER": 0x80000001,
            "HKEY_LOCAL_MACHINE": 0x80000002,
            "HKEY_USERS": 0x80000003,
        }
        hive_id = hive_map.get(hive, 0x80000002)

        # 先获取值的类型
        result, value_type = registry.GetStringValue(
            hDefKey=hive_id,
            sSubKeyName=key_path,
            sValueName=value_name
        )

        if result == 0:
            return {"computer": computer, "value": value, "status": "ok"}
        else:
            return {"computer": computer, "value": None, "status": "not_found"}

    except wmi.x_wmi as e:
        return {"computer": computer, "value": None, "status": f"WMI错误: {e}"}
    except Exception as e:
        return {"computer": computer, "value": None, "status": f"错误: {e}"}


def batch_read_registry(computers, key_path, value_name, max_workers=20):
    """并发批量读取多台电脑的注册表"""
    results = []

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(
                remote_read_registry, pc, key_path, value_name
            ): pc
            for pc in computers
        }

        for future in as_completed(futures):
            result = future.result()
            results.append(result)
            status_icon = "✓" if result["status"] == "ok" else "✗"
            print(f"  {status_icon} {result['computer']}: {result['status']}")

    return results


# 示例:检查 100 台电脑的代理设置
computers = [f"PC-{i:03d}" for i in range(1, 101)]
results = batch_read_registry(
    computers,
    key_path=r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
    value_name="ProxyServer"
)

# 统计
success = sum(1 for r in results if r["status"] == "ok")
print(f"\n成功: {success}/{len(results)}")

方案二:PsExec 远程执行

对于 WMI 不方便的场景,可以用 PsExec 远程执行命令:

import subprocess
import tempfile
from pathlib import Path

def remote_registry_via_psexec(
    computer, script_content,
    psexec_path=r"C:\Tools\PSTools\PsExec.exe",
    username=None, password=None
):
    """
    通过 PsExec 在远程电脑上执行注册表操作脚本
    """
    # 生成临时 Python 脚本
    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".py", delete=False, encoding="utf-8"
    ) as f:
        f.write(script_content)
        temp_script = f.name

    try:
        cmd = [
            psexec_path,
            f"\\\\{computer}",
            "-s",  # 以 SYSTEM 账户运行
            "-nobanner",
        ]

        if username and password:
            cmd.extend(["-u", username, "-p", password])

        cmd.extend([
            "python",
            temp_script
        ])

        result = subprocess.run(
            cmd, capture_output=True, text=True, timeout=120
        )

        return {
            "computer": computer,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode,
        }
    finally:
        Path(temp_script).unlink(missing_ok=True)

实用工具函数集合

以下是运维中最高频的注册表操作,直接拿去用。

禁用/启用 USB 存储设备

def disable_usb_storage(enable=True):
    """
    禁用或启用 USB 大容量存储设备
    enable=False 禁用, enable=True 启用
    """
    value = 4 if not enable else 3  # 4=禁用, 3=启用

    # 方法1:通过注册表
    write_registry(
        r"SYSTEM\CurrentControlSet\Services\USBSTOR",
        "Start",
        value
    )

    # 方法2:额外阻止安装新 USB 设备
    write_registry(
        r"SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices",
        "Deny_All",
        1 if not enable else 0
    )

    print(f"USB 存储 {'已启用' if enable else '已禁用'}")

修改远程桌面端口

def change_rdp_port(new_port=33890):
    """修改远程桌面端口(默认 3389)"""
    if not (1024 <= new_port <= 65535):
        print("端口范围必须在 1024-65535 之间")
        return False

    # 1. 修改注册表中的端口
    success = write_registry(
        r"SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp",
        "PortNumber",
        new_port
    )

    if success:
        # 2. 添加防火墙规则
        firewall_cmd = (
            f'netsh advfirewall firewall add rule '
            f'name="RDP {new_port}" dir=in action=allow '
            f'protocol=TCP localport={new_port}'
        )
        subprocess.run(firewall_cmd, shell=True, capture_output=True)

        print(f"RDP 端口已修改为 {new_port}")
        print("请重启电脑使更改生效")
    return success

自动清理无效的卸载残留

def cleanup_uninstall_entries():
    """
    扫描并报告注册表中的无效卸载条目
    (程序已删除但注册表残留的情况)
    """
    uninstall_key = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, uninstall_key)

    orphaned = []
    idx = 0

    while True:
        try:
            subkey_name = winreg.EnumKey(key, idx)

            subkey = winreg.OpenKey(key, subkey_name)
            try:
                display_name, _ = winreg.QueryValueEx(subkey, "DisplayName")
                install_location, _ = winreg.QueryValueEx(
                    subkey, "InstallLocation"
                )

                # 检查安装路径是否存在
                if install_location and not Path(install_location).exists():
                    orphaned.append({
                        "name": display_name,
                        "subkey": subkey_name,
                        "path": install_location,
                    })
            except FileNotFoundError:
                # 没有 DisplayName 或 InstallLocation,跳过
                pass
            finally:
                winreg.CloseKey(subkey)

            idx += 1
        except OSError:
            break

    winreg.CloseKey(key)

    if orphaned:
        print(f"发现 {len(orphaned)} 个无效卸载条目:")
        for item in orphaned:
            print(f"  - {item['name']}")
            print(f"    路径: {item['path']} (不存在)")
            print(f"    键: {item['subkey']}")
    else:
        print("未发现无效卸载条目")

    return orphaned

批量查询所有电脑的安装软件

def get_installed_software(hive=winreg.HKEY_LOCAL_MACHINE):
    """获取已安装软件列表"""
    uninstall_paths = [
        r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
        r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
    ]

    software = []

    for uninstall_path in uninstall_paths:
        try:
            key = winreg.OpenKey(hive, uninstall_path)
        except FileNotFoundError:
            continue

        idx = 0
        while True:
            try:
                subkey_name = winreg.EnumKey(key, idx)
                subkey = winreg.OpenKey(key, subkey_name)

                info = {}
                for field in ["DisplayName", "DisplayVersion",
                              "Publisher", "InstallDate", "UninstallString"]:
                    try:
                        val, _ = winreg.QueryValueEx(subkey, field)
                        info[field] = val
                    except FileNotFoundError:
                        pass

                if info.get("DisplayName"):
                    software.append(info)

                winreg.CloseKey(subkey)
                idx += 1
            except OSError:
                break

        winreg.CloseKey(key)

    # 去重 + 排序
    seen = set()
    unique = []
    for s in sorted(software, key=lambda x: x.get("DisplayName", "")):
        name = s.get("DisplayName", "")
        if name not in seen:
            seen.add(name)
            unique.append(s)

    return unique

# 使用
software = get_installed_software()
print(f"已安装 {len(software)} 个软件")
for s in software[:10]:
    print(f"  {s.get('DisplayName')} {s.get('DisplayVersion', '')}")

安全注意事项

操作注册表时,务必记住:

  1. 备份优先:修改前总是先导出相关键值
  2. 最小权限:能用 HKCU 就不用 HKLM(后者影响所有用户)
  3. 管理员权限:修改 HKLM 需要管理员权限,脚本开头加检查:
import ctypes
import sys

def require_admin():
    """检查是否以管理员身份运行"""
    if not ctypes.windll.shell32.IsUserAnAdmin():
        print("此操作需要管理员权限!")
        print("请右键选择「以管理员身份运行」")
        sys.exit(1)

require_admin()
  1. 64 位注意:在 64 位系统上,Python 默认访问 64 位视图。如需访问 32 位视图:
# 访问 32 位注册表视图
KEY_WOW64_64KEY = 0x0100
KEY_WOW64_32KEY = 0x0200

key = winreg.OpenKey(
    winreg.HKEY_LOCAL_MACHINE,
    r"SOFTWARE\MyApp",
    0,
    winreg.KEY_READ | KEY_WOW64_32KEY  # 强制 32 位视图
)
  1. 测试先行:先在测试机上验证,再批量推生产环境

完整实战:一键配置新电脑

把上面的函数组合起来,做一个新电脑初始化脚本:

def setup_new_computer():
    """新电脑初始化配置"""
    require_admin()

    print("=== 新电脑初始化配置 ===\n")

    # 1. 备份当前配置
    print("[1/6] 备份当前配置...")
    export_registry(r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings")
    export_registry(r"SYSTEM\CurrentControlSet\Services\USBSTOR")

    # 2. 配置代理
    print("[2/6] 配置代理服务器...")
    write_registry(
        r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
        "ProxyEnable", 1
    )
    write_registry(
        r"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings",
        "ProxyServer", "proxy.company.com:8080"
    )

    # 3. 修改 RDP 端口
    print("[3/6] 修改远程桌面端口...")
    change_rdp_port(33990)

    # 4. 禁用 USB 存储
    print("[4/6] 禁用 USB 存储...")
    disable_usb_storage(enable=False)

    # 5. 配置 PowerShell 执行策略
    print("[5/6] 配置 PowerShell 执行策略...")
    subprocess.run(
        ["powershell", "-Command", "Set-ExecutionPolicy RemoteSigned -Scope MachinePolicy"],
        capture_output=True
    )

    # 6. 启用 Windows 远程管理
    print("[6/6] 启用 WinRM...")
    subprocess.run(
        ["powershell", "-Command", "Enable-PSRemoting -Force"],
        capture_output=True
    )

    print("\n=== 配置完成!请重启电脑使所有更改生效 ===")

setup_new_computer()

小结

操作方法适用场景
读注册表winreg.QueryValueEx()获取配置信息
写注册表winreg.SetValueEx()修改系统配置
枚举子键winreg.EnumKey()遍历服务/软件列表
远程操作WMI StdRegProv批量管理多台电脑
备份恢复reg export/import 或 Python 递归修改前必须备份
64位视图KEY_WOW64_32KEY 标志访问 32 位软件注册表

注册表操作是 Windows 运维的基本功。掌握了这些,批量配置电脑、排查问题、安全加固都能轻松搞定。

下一篇预告:日志是排障的眼睛。我们来看看如何用 Python 自动分析 Windows 事件日志,从海量日志中秒定位问题根因。