🔥 【开源解析】:Python打造专业级USB安全弹出工具(附完整源码)
🌈 个人主页:创客白泽 - CSDN博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
🐋 希望大家多多支持,我们一起进步!
👍 🎉如果文章对你有帮助的话,欢迎 点赞 👍🏻 评论 💬 收藏 ⭐️ 加关注+💗分享给更多人哦
📌 概述:为什么需要专业USB弹出工具?
在日常使用计算机时,我们经常会遇到"该设备正在使用中,无法安全移除"的烦人提示。传统解决方法要么是暴力拔插(可能损坏数据),要么是反复尝试弹出(效率低下)。本文将介绍如何使用Python开发一个专业级USB安全弹出工具,它能够:
- 智能检测占用USB设备的进程
- 自动终止顽固进程
- 深度解锁驱动器
- 安全弹出硬件设备
- 系统托盘快捷操作
相比Windows自带的弹出功能,我们的工具具有进程可视化、强制解锁、操作日志等高级特性,是IT技术人员和普通用户的实用利器。
🛠️ 功能全景图
功能模块 | 实现技术 | 特色亮点 |
---|---|---|
驱动器检测 | ctypes.windll.kernel32 |
实时刷新可移动设备列表 |
进程扫描 | psutil 库 |
全量扫描+精准定位 |
进程终止 | win32process |
权限提升处理 |
卷解锁 | win32file IOCTL控制 |
底层磁盘操作 |
设备弹出 | IOCTL_STORAGE_EJECT_MEDIA |
硬件级控制 |
GUI界面 | PyQt5 |
专业级交互体验 |
系统托盘 | QSystemTrayIcon |
后台常驻+快捷操作 |
🎨 效果展示
主界面截图
进程检测效果
[14:25:33] 🔍 正在获取进程列表...
[14:25:34] 📊 找到 156 个进程,正在扫描...
[14:25:37] ⚠️ 找到 2 个锁定进程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[14:25:37] 🆔 PID: 1234
[14:25:37] 📛 名称: explorer.exe
[14:25:37] 📂 路径: C:\Windows\explorer.exe
[14:25:37] 💻 命令: explorer /select,D:\test.docx
[14:25:37] 👤 用户: DESKTOP-Admin
[14:25:37] 📊 状态: running
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
系统托盘菜单
🧰 开发环境准备
必备组件
pip install pywin32 psutil PyQt5 ctypes
特别说明
本程序需要管理员权限运行,因为涉及:
- 进程终止操作
- 底层磁盘控制
- 硬件设备管理
🏗️ 核心代码解析
1. 驱动器检测机制
def get_removable_drives(self):
"""获取所有可移动驱动器"""
drives = []
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
if bitmask & 1:
drive_type = ctypes.windll.kernel32.GetDriveTypeW(f"{
letter}:\\")
if drive_type == win32con.DRIVE_REMOVABLE:
drives.append(f"{
letter}:")
bitmask >>= 1
return drives
关键技术点:
GetLogicalDrives()
获取所有逻辑驱动器位掩码GetDriveTypeW()
判断驱动器类型- 位运算遍历26个字母驱动器
2. 进程扫描引擎
def find_locking_processes(self):
# 获取进程列表(约150-200个系统进程)
processes = list(psutil.process_iter(['pid', 'name', 'exe', 'cmdline']))
# 双重检测机制
for proc in processes:
# 检测1:打开的文件句柄
for item in proc.open_files():
if item.path.lower().startswith(drive_path):
locking_processes.append(proc.info)
# 检测2:工作目录
try:
cwd = proc.cwd()
if cwd and cwd.lower().startswith(drive_path):
locking_processes.append(proc.info)
3. 底层解锁三连击
# 1. 锁定卷(禁止写入)
win32file.DeviceIoControl(
h_volume,
FSCTL_LOCK_VOLUME, # 控制码0x00090018
None, None, None
)
# 2. 卸载文件系统
win32file.DeviceIoControl(
h_volume,
FSCTL_DISMOUNT_VOLUME, # 控制码0x00090020
None, None, None
)
# 3. 物理弹出
win32file.DeviceIoControl(
h_volume,
IOCTL_STORAGE_EJECT_MEDIA, # 控制码0x2D4808
None, None, None
)
4. PyQt5多线程处理
class WorkerThread(QThread):
update_progress = pyqtSignal(str, int, int) # 进度更新信号
def run(self):
try:
if self.operation_type == 'find':
self.find_locking_processes()
elif self.operation_type == 'unlock_and_eject':
self.unlock_and_eject_drive()
except Exception as e:
self.log_message(f"线程错误: {
str(e)}")
🚀 使用教程
基本操作流程
- 启动程序(自动获取管理员权限)
- 从列表选择目标USB驱动器
- 点击"查找占用进程"分析问题
- 点击"解除占用并弹出"安全移除
高级技巧
- 托盘快捷操作:右键系统图标直接选择驱动器
- 自动刷新:每5秒自动更新驱动器列表
- 日志分析:查看完整的操作记录和错误信息
💾 完整源码下载
完整项目源码:
import ctypes
import sys
import win32api
import win32file
import win32con
import win32process
import psutil
import threading
from datetime import datetime
from time import sleep
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QListWidget, QPushButton, QLabel, QProgressBar, QTextEdit,
QSystemTrayIcon, QMenu, QMessageBox, QStyle, QFrame, QAction,
QDialog, QVBoxLayout, QHBoxLayout)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt5.QtGui import QIcon, QFont, QPalette, QColor
# Define constants
try:
from winioctlcon import FSCTL_LOCK_VOLUME, FSCTL_DISMOUNT_VOLUME, IOCTL_STORAGE_EJECT_MEDIA
except ImportError:
FSCTL_LOCK_VOLUME = 0x00090018
FSCTL_DISMOUNT_VOLUME = 0x00090020
IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808
class EjectProgressDialog(QDialog):
"""自定义进度对话框"""
def __init__(self, parent=None, drive_letter=""):
super().__init__(parent)
self.setWindowTitle("安全弹出USB驱动器")
self.setWindowIcon(QIcon.fromTheme('drive-removable-media'))
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self.setFixedSize(300, 120)
layout = QVBoxLayout()
self.setLayout(layout)
# 标题标签
self.title_label = QLabel(f"正在安全弹出 {
drive_letter}:...")
self.title_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.title_label)
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.progress_bar.setTextVisible(False)
layout.addWidget(self.progress_bar)
# 状态标签
self.status_label = QLabel("准备解除占用...")
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)
# 取消按钮
self.cancel_btn = QPushButton("取消")
self.cancel_btn.clicked.connect(self.reject)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
# 设置样式
self.setStyleSheet("""
QDialog {
background-color: #f5f5f5;
}
QLabel {
font-size: 12px;
}
QProgressBar {
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
height: 12px;
}
QProgressBar::chunk {
background-color: #4CAF50;
width: 10px;
}
""")
def update_progress(self, text, value, max_value):
"""更新进度显示"""
self.progress_bar.setMaximum(max_value)
self.progress_bar.setValue(value)
self.status_label.setText(text)
class WorkerThread(QThread):
update_progress = pyqtSignal(str, int, int)
update_process_text = pyqtSignal(str)
operation_complete = pyqtSignal()
show_message = pyqtSignal(str, str, str) # title, message, icon
def __init__(self,