确定使用PowerShell对文件进行写锁定的进程

时间:2018-04-05 08:39:01

标签: powershell file-locking sysinternals

我需要编写一个PowerShell脚本来移动一个包含大量文件的文件夹(大约1k到10k),然后再尝试移动我想检查文件中是否有任何进程有写锁定,并且应该要求用户确认使用写锁定来杀死进程并移动文件。我已经尝试了

中提出的解决方案

Identify locks on dll files - 不适用于文件夹

Determine process that locks a file - 过于复杂且容易出错

Unlock files using handle.exe - 关闭只处理并关闭所有句柄。

一种选择是使用Handle.exe中的SysInternals并终止具有写锁定的进程。如果我们通过使用进程名称手动执行它很容易,我们可以识别哪个进程锁定了文件并将其终止。有没有办法确定它是否是基于句柄类型的写锁定(在下图中以红色突出显示)?句柄类型及其权限是否有任何映射?这样我就可以解析具有写锁定的handle.exe输出和提取过程。

Handle output

2 个答案:

答案 0 :(得分:1)

如果您只想使用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" }
     } 
 }

我没有找到任何简单的方法来提取有关它是什么类型的锁或涉及权限的信息。

答案 1 :(得分:0)

我无法找到完成此任务的唯一PowerShell方法。我使用了此Answer中的.net代码。这段代码的一个问题是它返回了一个过程对象列表,这对我来说是太多不必要的信息所以我对函数WhoisLocking做了一些修改,以返回进程id和进程的Dictionary名称。以下是函数

的修改代码
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;
}

此代码的另一个问题是它仅适用于文件,因此我在PoweShell中创建了一个包装器,以便为文件夹中的每个文件调用此函数。此类解析每个调用的所有句柄,因此为每个文件调用此方法效率很低。为了优化它,我只对具有写锁定的文件调用此函数。我也找了一些方法来添加进程所有者的信息,但不幸的是.net进程对象不包含所有者信息,我没有找到任何简单的方法在.net中进行。要获取所有者信息,我使用的是PowerShell Get-CimInstance Win32_ProcessInvoke-CimMethod。以下是我的PowerShell方法。

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
}

默认情况下,这会以表格形式打印结果,如果需要在脚本中使用,则调用者可以传递Raw,这会将其作为array返回。这可能不是最有效的解决方案,但可以很好地完成任务。如果您发现可以优化的内容,请随时发表评论。