使用PowerShell确定文件上具有写锁的进程
我需要编写一个PowerShell脚本来移动包含大量文件(约1k到10k)的文件夹,然后再尝试移动。我想检查文件中是否有任何进程具有写锁,并应要求用户确认使用写锁终止进程并移动文件。我已经尝试了在中提出的解决方案 --不适用于文件夹 --太复杂,容易出错 --仅关闭句柄并关闭所有句柄 一个选项是使用使用PowerShell确定文件上具有写锁的进程,powershell,file-locking,sysinternals,Powershell,File Locking,Sysinternals,我需要编写一个PowerShell脚本来移动包含大量文件(约1k到10k)的文件夹,然后再尝试移动。我想检查文件中是否有任何进程具有写锁,并应要求用户确认使用写锁终止进程并移动文件。我已经尝试了在中提出的解决方案 --不适用于文件夹 --太复杂,容易出错 --仅关闭句柄并关闭所有句柄 一个选项是使用SysInternals中的Handle.exe,并终止具有写锁的进程。如果我们手动执行,就很容易了,因为通过使用进程名,我们可以识别哪个进程对文件有锁并将其杀死。有没有办法根据句柄类型(下图中以红色
SysInternals
中的Handle.exe
,并终止具有写锁的进程。如果我们手动执行,就很容易了,因为通过使用进程名,我们可以识别哪个进程对文件有锁并将其杀死。有没有办法根据句柄类型(下图中以红色突出显示)确定它是否为写锁?句柄类型及其权限是否存在映射?这样我就可以解析带有写锁的handle.exe
输出和提取进程
如果您只想使用
handle.exe
的输出请求用户确认终止进程,您可以将其包装成一个简短的脚本:
$path = "C:\Workspace"
$files = (C:\SysInternals\handle.exe -nobanner $path) | Out-String
$filesArray = $files.Split([Environment]::NewLine,[System.StringSplitOptions]::RemoveEmptyEntries)
foreach($file in $filesArray) {
$parts = $file.split(" ",[System.StringSplitOptions]::RemoveEmptyEntries)
$proc = $parts[0]
$procId = $parts[2]
$type = $parts[4]
$handle = $parts[5].split(":")[0]
$path = $parts[6]
$Readhost = Read-Host "Terminate $($proc)? ( y / n ) "
Switch ($ReadHost)
{
Y {(.\handle -c $handle)}
N { Write-Host "Skipping $proc" }
}
}
我没有找到任何简单的方法来提取有关它是什么类型的锁或涉及的权限的信息。我找不到唯一的PowerShell方法来完成此任务。我已经使用了此文件中的.net代码。这段代码的一个问题是它返回了一个进程对象列表,这对我来说是太多不必要的信息,所以我对函数
WhoisLocking
做了一些修改,以返回进程id和进程名称的字典。下面是该函数的修改代码
static public Dictionary<int, String> WhoIsLocking(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();
Dictionary<int, String> processDict = new Dictionary<int, String>();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the
// list to be returned
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
// catch the error -- in case the process is no longer running
catch (ArgumentException) { }
}
}
else throw new Exception("Could not list processes locking resource.");
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
//Returning a dictionary to keep it simple
foreach(Process p in processes){
processDict.Add(p.Id, p.ProcessName);
}
return processDict;
}
默认情况下,这将以表格形式打印结果,如果需要在脚本中使用,调用方可以传递Raw
,它将以数组的形式返回结果。这可能不是最有效的解决方案,但可以很好地完成任务。如果您发现可以优化的内容,请随时发表评论。谢谢您的脚本。我写了一个几乎相似的脚本,但问题是它关闭了太多的进程。如果文件夹在登录后打开,即使现在还没有打开,它也将始终具有某些或其他句柄。大多数情况下,它关闭了太多需要的进程。
function Get-LockingProcess {
[CmdletBinding(SupportsShouldProcess = $true)]
param(
# Specifies a path to the file to be modified
[Parameter(Mandatory = $true)]
$path ,
[switch]$Raw
)
#adding using reflection to avoid file being locked
$bytes = [System.IO.File]::ReadAllBytes($($PSScriptRoot + "\lib\LockedProcess.dll"))
[System.Reflection.Assembly]::Load($bytes) | Out-Null
$process = @{}
$path = Get-Item -Path $path
if ($path.PSIsContainer) {
Get-ChildItem -Path $path -Recurse | % {
$TPath = $_
try { [IO.File]::OpenWrite($TPath.FullName).close()}
catch {
if (! $TPath.PSIsContainer) {
$resp = $([FileUtil]::WhoIsLocking($TPath.FullName))
}
foreach ($k in $resp.Keys) {
if (! $process.ContainsKey($k)) {
$process.$k = $resp.$k
}
}
}
}
}
else {
$process = $([FileUtil]::WhoIsLocking($path.FullName))
}
#adding additional details to the hash
$processList=@()
foreach($id in $process.Keys){
$temp=@{}
$temp.Name=$process.$id
$proc=Get-CimInstance Win32_Process -Filter $("ProcessId="+$id)
$proc=Invoke-CimMethod -InputObject $proc -MethodName GetOwner
if($proc.Domain -ne ""){
$temp.Owner=$proc.Domain+"\"+$proc.User
}else{
$temp.Owner=$proc.User
}
$temp.PID=$id
$processList+=$temp
}
if($Raw){
$processList
}else{
$processList.ForEach({[PSCustomObject]$_}) | Format-Table -AutoSize
}