经常遇到要删除文件或文件夹时, Windows系统弹出类似下面的窗口:
但并不会告诉你是什么程序在占用, 所以微软专门开发了一个命令行程序Handle来检查, 以下是官方简介:
有没有想过哪个程序打开了特定的文件或目录? 现在可以了解了。Handle 是一个实用工具,用于显示有关系统中任何进程的打开句柄的信息。 可以使用它查看打开了文件的程序,或查看程序的所有句柄的对象类型和名称。
Handle 有很多参数, 平时一般都用不到, 所以我用AutoHotkey制作了一个简单的GUI, 并借助Handle来简单的查询并关闭占用进程
下面是完整代码, 90%的代码为AI编写
#NoTrayIcon
#SingleInstance Force
#NoEnv
SplitPath, A_ScriptFullPath,, scriptDir
if (scriptDir != A_WorkingDir)
SetWorkingDir %ScriptDir%
; 初始化变量
global handlePath := ""
global lastOutput := ""
; 创建GUI
Gui, Font, s10, Segoe UI
Gui, Add, Text, w410 h20, 请输入被占用的文件或文件夹完整路径:(支持拖拽)
Gui, Add, Edit, y+5 w325 h25 vFilePath,
Gui, Add, Button, x+5 w80 h25 vCheckHandle gCheckHandle, 检查占用
Gui, Add, Text, x10 y+5 w410 h20, 占用进程列表:
Gui, Add, ListView, y+5 w410 h100 vProcessList gSelectProcess, 进程名称|PID
Gui, Add, Button, y+5 w80 h25 vKillProcess gKillProcess, 结束进程
Gui, Add, Text, x+5 yp+4 w600 h20 vStatusText, 就绪
GUI +Resize +MinSize430x200
GuiDropFiles(GuiHwnd, FileArray, CtrlHwnd, X, Y) {
FirstFile := FileArray[1] ; 只取第一个文件
GuiControl,, FilePath, %FirstFile%
}
Gui, Show, w430 h225, 文件被进程占用检测工具
; 检查handle.exe/handle64.exe是否存在
CheckHandleExe()
return
GuiSize:
StatusTextw := 600
if (A_GuiWidth>690) {
StatusTextw = %A_GuiWidth%-90
}
GuiControl, Move, FilePath, % "w" (A_GuiWidth-105)
GuiControl, Move, CheckHandle, % "x" (A_GuiWidth-90)
GuiControl, Move, ProcessList, % "h" (A_GuiHeight-130) " w" (A_GuiWidth-20)
GuiControl, Move, KillProcess, % "y" (A_GuiHeight-35)
GuiControl, Move, StatusText, % "w" (StatusTextw) " y" (A_GuiHeight-31)
return
CheckHandleExe() {
; 检查系统目录
if (FileExist(A_WinDir "\System32\handle64.exe")) {
global handlePath := A_WinDir "\System32\handle64.exe"
return true
}
if (FileExist(A_WinDir "\System32\handle.exe")) {
global handlePath := A_WinDir "\System32\handle.exe"
return true
}
; 检查脚本目录
if (FileExist(A_ScriptDir "\handle64.exe")) {
global handlePath := A_ScriptDir "\handle64.exe"
return true
}
if (FileExist(A_ScriptDir "\handle.exe")) {
global handlePath := A_ScriptDir "\handle.exe"
return true
}
; 都不存在,提示下载
GuiControl,, StatusText, 缺失handle,请下载后重试
Gui, MyPopup:New
Gui, MyPopup:Font, s12, 微软雅黑
Gui, MyPopup:Add, Text, w420, 本脚本依赖handle,请从微软官网下载: `nhttps://learn.microsoft.com/zh-cn/sysinternals/downloads/handle`n解压里面的handle.exe或handle64.exe到脚本所在目录即可`n按确定可尝试自动下载该文件的64位版本并继续脚本`n如果失败则会打开上面的网址,请手动下载并解压后重试
Gui, MyPopup:Add, Button, w60 Default, 确定 ;按钮
Gui, MyPopup:Show, Center, 警告 ;标题栏
return false
pause
}
; 检查文件占用
CheckHandle:
Gui, Submit, NoHide
filePath := Trim(FilePath)
if (filePath = "") {
GuiControl,, StatusText, 请输入文件路径
return
}
if (!FileExist(filePath)) {
GuiControl,, StatusText, 文件不存在: %filePath%
return
}
if (handlePath = "") {
if (!CheckHandleExe())
return
}
GuiControl,, StatusText, 正在检查 请稍后...
; 执行handle命令
RunWait, %ComSpec% /c ""%handlePath%" "%filePath%" > "%A_Temp%\handle_output.tmp"",,Hide
FileRead, output, %A_Temp%\handle_output.tmp
FileDelete, %A_Temp%\handle_output.tmp
global lastOutput := output
; 解析输出
LV_Delete()
; 提取进程信息
count := 0
Loop, Parse, output, `n, `r
{
line := Trim(A_LoopField)
if (RegExMatch(line, "i)^(\S+)\s+pid:\s*(\d+)", match)) {
LV_Add("", match1, match2)
count++
}
}
if (count = 0) {
GuiControl,, StatusText, 未找到占用该文件的进程
} else {
GuiControl,, StatusText, 找到 %count% 个占用进程
}
Loop % LV_GetCount("Col")
LV_ModifyCol(A_Index, "AutoHdr")
return
; 选择进程
SelectProcess:
if (A_GuiEvent = "DoubleClick") {
row := A_EventInfo ; 获取双击的行号
LV_GetText(pid, row, 2) ; 获取该行第2列(PID)的值
KillProcessByPID(pid)
}
return
; 结束进程
KillProcess:
; 获取当前选中的行号
row := LV_GetNext()
if (row = 0) {
GuiControl,, StatusText, 请先选择要结束的进程
return
}
; 获取选中行的PID
LV_GetText(pid, row, 2)
KillProcessByPID(pid)
return
; 根据PID结束进程
KillProcessByPID(pid) {
message := "确定要结束PID为 " pid " 的进程吗?`n这可能会导致数据丢失或程序异常。"
MsgBox, 36, 确认, %message%
; 获取用户选择
IfMsgBox, Yes
{
GuiControl,, StatusText, 正在结束进程 %pid%...
RunWait, %ComSpec% /c "taskkill /F /PID %pid% > nul 2>&1",, Hide
if (ErrorLevel = 1) {
GuiControl,, StatusText, 结束进程失败
} else GuiControl,, StatusText, 成功结束进程
}
}
MyPopupButton确定:
Gui, MyPopup:Destroy
SetTimer, UpdateStatus_Downloading, 100 ; 100毫秒后执行更新
UrlDownloadToFile https://download.sysinternals.com/files/Handle.zip, Handle.zip
SetTimer, UpdateStatus_Extracting, 100
FileCopyDir, Handle.zip,Handle ,1 ;要求AutoHotkey 版本v1.1.34+
FileMove, %A_ScriptDir%\Handle\handle64.exe, %A_ScriptDir%\handle64.exe, 1
FileRemoveDir,%A_ScriptDir%\Handle,1
FileDelete,%A_ScriptDir%\Handle.zip
; 检查是否成功
if (FileExist("handle64.exe") || FileExist("handle.exe")) {
SetTimer, UpdateStatus_Success, 100
CheckHandleExe() ; 重新检查handle路径
} else {
SetTimer, UpdateStatus_Failed, 100
}
pause off
return
UpdateStatus_Downloading:
SetTimer, UpdateStatus_Downloading, Off
GuiControl,, StatusText, 正在下载handle 请稍候...
return
UpdateStatus_Extracting:
SetTimer, UpdateStatus_Extracting, Off
GuiControl,, StatusText, 正在解压handle文件...
return
UpdateStatus_Success:
SetTimer, UpdateStatus_Success, Off
GuiControl,, StatusText, 已成功下载handle 可以继续了
return
UpdateStatus_Failed:
SetTimer, UpdateStatus_Failed, Off
GuiControl,, StatusText, 下载或解压handle失败,请在打开的页面手动下载并解压
run https://learn.microsoft.com/zh-cn/sysinternals/downloads/handle
return
GuiClose:
ExitApp
没有添加版本检查代码, 请确保AutoHotkey版本为v1.1.34+, 该GUI会自动尝试下载Handle, 首次使用会弹出Handle的许可协议(见下图), 请点击Agree继续!