文件句柄占用解锁.psm1
|
# 文件句柄占用解锁.psm1 # PowerShell 模块:封装 文件句柄占用解锁.exe,提供列出占用进程、关闭占用句柄、抢夺串口等功能。 # 本模块仅负责解析参数并转发调用 exe,不重复实现其内部逻辑。 # 注意:exe 需管理员权限(加载 PROCEXP152 内核驱动、启用 SeDebug/SeLoadDriver 特权); # 本模块在非管理员下会唤起 UAC 提权运行 exe 并把输出取回。 # 模块内共享:解析并缓存 exe 路径 $script:Exe路径 = $null function Test-管理员权限 { <# .SYNOPSIS 判断当前 PowerShell 进程是否以管理员(提升的)权限运行。 .OUTPUTS System.Boolean 是管理员返回 $true,否则返回 $false。 .EXAMPLE if (-not (Test-管理员权限)) { Write-Warning '需要管理员权限' } #> $身份 = [System.Security.Principal.WindowsIdentity]::GetCurrent() $主体 = [System.Security.Principal.WindowsPrincipal]::new($身份) return $主体.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Resolve-Exe路径 { <# .SYNOPSIS 查找与本模块同目录的 文件句柄占用解锁.exe 的实际路径。 .DESCRIPTION exe 经 vcxproj 配置输出到解决方案的 PowerShell 目录,与本模块同目录。 找到后缓存到模块变量,后续调用直接复用。 .OUTPUTS System.String exe 的完整路径;未找到时抛出异常。 #> if ($script:Exe路径 -and (Test-Path $script:Exe路径)) { return $script:Exe路径 } # exe 经 vcxproj 输出到 命令行\PowerShell\(与本模块所在 PowerShell\ 平级) $模块根 = Split-Path $PSScriptRoot -Parent $候选列表 = @( (Join-Path $模块根 '命令行\PowerShell\文件句柄占用解锁.exe'), (Join-Path $PSScriptRoot '文件句柄占用解锁.exe') ) foreach ($候选 in $候选列表) { if (Test-Path $候选) { $script:Exe路径 = (Resolve-Path $候选).Path return $script:Exe路径 } } throw "找不到 文件句柄占用解锁.exe,请先编译 命令行 项目。已尝试:$($候选列表 -join ';')" } function Invoke-底层工具 { <# .SYNOPSIS 调用 文件句柄占用解锁.exe 并透传参数,返回其原始输出与退出码。 .DESCRIPTION 供模块内各公开函数统一调用的内部转发器。把全部参数原样传给 exe。 若当前不是管理员,则唤起 UAC 以管理员身份运行 exe(隐藏窗口),输出经临时文件读回。 返回包含 输出、退出码 的对象。 .PARAMETER 参数 要透传给 exe 的参数数组。 #> param([Parameter(ValueFromRemainingArguments = $true)][string[]]$参数) $exe = Resolve-Exe路径 if (Test-管理员权限) { # 已是管理员:直接调用并捕获输出 $输出 = & $exe @参数 2>&1 | Out-String return [pscustomobject]@{ 输出 = $输出; 退出码 = $LASTEXITCODE } } # 非管理员:唤起 UAC 提权运行 exe 本身。RunAs 无法直接重定向,故经 cmd /c 把输出写入临时文件再读回(用完即删)。 $临时输出 = Join-Path $env:TEMP ('文件句柄占用解锁_' + [guid]::NewGuid().ToString('N') + '.log') $参数拼接 = ($参数 | ForEach-Object { '"' + $_ + '"' }) -join ' ' $命令行 = '/c ""' + $exe + '" ' + $参数拼接 + ' > "' + $临时输出 + '" 2>&1"' try { $进程 = Start-Process -FilePath 'cmd.exe' -ArgumentList $命令行 -Verb RunAs -Wait -WindowStyle Hidden -PassThru $输出 = if (Test-Path $临时输出) { Get-Content $临时输出 -Raw -Encoding UTF8 } else { '' } return [pscustomobject]@{ 输出 = $输出; 退出码 = $进程.ExitCode } } finally { Remove-Item $临时输出 -Force -ErrorAction SilentlyContinue } } function Find-文件占用 { <# .SYNOPSIS 列出占用指定文件、目录或串口的所有进程及其句柄。 .DESCRIPTION 转发调用 exe 的 list 命令。枚举系统句柄表,借助 PROCEXP152 内核驱动匹配目标路径, 输出占用该目标的进程 PID、句柄值与可执行文件路径。 结果解析为对象数组返回,便于进一步筛选或管道传给 Close-占用句柄。 .PARAMETER 路径 要检查的文件路径、目录路径,或串口号(如 COM3)。 .PARAMETER 原始输出 若指定此开关,则不解析为对象,直接返回 exe 的原始文本输出。 .OUTPUTS PSCustomObject[] 含 进程ID、句柄值、进程路径 属性;未找到占用时返回空数组。 .EXAMPLE Find-文件占用 -路径 'C:\test.txt' 列出占用 C:\test.txt 的所有进程。 .EXAMPLE Find-文件占用 -路径 'C:\test.txt' | Close-占用句柄 查出占用 C:\test.txt 的进程句柄并全部关闭,解除占用。 .EXAMPLE Find-文件占用 -路径 'COM3' 列出占用串口 COM3 的进程。 #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string]$路径, [switch]$原始输出 ) $结果 = Invoke-底层工具 'list' $路径 if ($原始输出) { return $结果.输出 } $占用列表 = @() foreach ($匹配 in [regex]::Matches($结果.输出, 'PID=(\d+)\s+句柄=0x([0-9A-Fa-f]+)\s+(.+?)[\r\n]')) { $占用列表 += [pscustomobject]@{ 进程ID = [int]$匹配.Groups[1].Value 句柄值 = '0x' + $匹配.Groups[2].Value 进程路径 = $匹配.Groups[3].Value.Trim() } } return $占用列表 } function Close-占用句柄 { <# .SYNOPSIS 关闭指定进程的指定句柄,解除其对文件或串口的占用。 .DESCRIPTION 转发调用 exe 的 close 命令。可一次传入多组 进程ID+句柄值。 句柄值接受十六进制字符串(可省略 0x 前缀)或整数。 支持从 Find-文件占用 经管道输入对象。 .PARAMETER 进程ID 占用进程的数字 PID。可传多个,与 句柄值 一一对应。 .PARAMETER 句柄值 占用句柄的十六进制字符串(如 0x1A8 或 1A8)。可传多个,与 进程ID 一一对应。 .PARAMETER 输入对象 从 Find-文件占用 管道传入的对象(含 进程ID 与 句柄值 属性)。 .OUTPUTS System.String exe 的原始文本输出(每个句柄的关闭结果与汇总)。 .EXAMPLE Close-占用句柄 -进程ID 1234 -句柄值 '0x1A8' 关闭 PID 为 1234 的进程的句柄 0x1A8。 .EXAMPLE Close-占用句柄 -进程ID 1234,5678 -句柄值 '0x1A8','0x2B4' 一次关闭两组 进程/句柄。 .EXAMPLE Find-文件占用 -路径 'C:\test.txt' | Close-占用句柄 关闭占用 C:\test.txt 的全部句柄。 #> [CmdletBinding(DefaultParameterSetName = '参数')] param( [Parameter(Mandatory = $true, ParameterSetName = '参数', Position = 0)] [int[]]$进程ID, [Parameter(Mandatory = $true, ParameterSetName = '参数', Position = 1)] [string[]]$句柄值, [Parameter(Mandatory = $true, ParameterSetName = '管道', ValueFromPipeline = $true)] [psobject[]]$输入对象 ) begin { $参数列表 = @() } process { if ($PSCmdlet.ParameterSetName -eq '管道') { foreach ($项 in $输入对象) { $参数列表 += [string]$项.进程ID $参数列表 += [string]$项.句柄值 } } } end { if ($PSCmdlet.ParameterSetName -eq '参数') { if ($进程ID.Count -ne $句柄值.Count) { throw "进程ID 数量($($进程ID.Count))与 句柄值 数量($($句柄值.Count))不一致。" } for ($i = 0; $i -lt $进程ID.Count; $i++) { $参数列表 += [string]$进程ID[$i] $参数列表 += [string]$句柄值[$i] } } if ($参数列表.Count -eq 0) { throw "未提供任何要关闭的 进程ID/句柄值。" } $结果 = Invoke-底层工具 'close' @参数列表 return $结果.输出 } } function Clear-串口占用 { <# .SYNOPSIS 强行关闭占用指定串口的其它进程的句柄,抢夺该串口。 .DESCRIPTION 转发调用 exe 的 snatch 命令。查询串口对应的内核设备路径, 找到占用它的其它进程并关闭其句柄,使本进程可打开该串口。 若串口正被本进程自己占用,将报错而不会抢夺。 .PARAMETER 串口号 要抢夺的串口号(如 COM3)。 .OUTPUTS System.String exe 的原始文本输出(每个被关闭句柄的结果与汇总)。 .EXAMPLE Clear-串口占用 -串口号 'COM3' 关闭占用 COM3 的其它进程的句柄。 #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string]$串口号 ) $结果 = Invoke-底层工具 'snatch' $串口号 return $结果.输出 } Export-ModuleMember -Function Find-文件占用, Close-占用句柄, Clear-串口占用, Test-管理员权限 |