|
1 | 1 | using System; |
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.IO; |
| 4 | +using System.Linq; |
4 | 5 | using System.Text; |
5 | | -using System.Threading.Tasks; |
6 | | -using System.Web.Script.Serialization; |
7 | 6 | using CliWrap; |
8 | 7 | using CliWrap.Buffered; |
| 8 | +using ShowWhatProcessLocksFile.LockFinding.Utils; |
9 | 9 |
|
10 | 10 | namespace ShowWhatProcessLocksFile.LockFinding; |
11 | 11 |
|
12 | | -public class LockingProcess |
13 | | -{ |
14 | | - public int pid; |
15 | | - public string process_full_name; |
16 | | - public string user; |
17 | | - public string domain; |
18 | | - public List<string> locked_paths; |
19 | | -} |
| 12 | +// More information on the meaning of the fields that are printed by Handle.exe: |
| 13 | +// https://stackoverflow.com/questions/52701911/output-of-sysinternals-handle-exe |
| 14 | +public readonly record struct HandleInfo ( |
| 15 | + string ProcessName, |
| 16 | + int Pid, |
| 17 | + string HandleType, |
| 18 | + string UserAndDomainName, |
| 19 | + long HandleAddress, |
| 20 | + string LockedPath); |
20 | 21 |
|
21 | 22 | public static class HandleExe |
22 | 23 | { |
23 | | - private static readonly string HandleExeFullName = Path.Combine(AppContext.BaseDirectory, "Handle2.exe"); |
| 24 | + private static readonly string HandleExeFullName = Path.Combine(AppContext.BaseDirectory, "handle64_v5.0_Unicode.exe"); |
| 25 | + |
| 26 | + public static IEnumerable<HandleInfo> Execute(string path) |
| 27 | + { |
| 28 | + var rawOutput = Launch(path); |
| 29 | + return ParseRawOutput(rawOutput); |
| 30 | + } |
24 | 31 |
|
25 | | - public static async Task<IEnumerable<LockingProcess>> GetProcessesLockingPath(string path) |
| 32 | + private static string Launch(string path) |
26 | 33 | { |
27 | | - var res = await Cli |
| 34 | + // Handle.exe doesn't work if a path contains "\" character at the end |
| 35 | + path = PathUtils.RemoveTrailingPathSeparator(path); |
| 36 | + |
| 37 | + var res = Cli |
28 | 38 | .Wrap(HandleExeFullName) |
29 | 39 | .WithValidation(CommandResultValidation.None) |
30 | | - .WithArguments(new[] { "--json", path }) |
31 | | - .ExecuteBufferedAsync(Encoding.UTF8); |
| 40 | + .WithArguments(new[] { "-u", "-nobanner", "-accepteula", "-v", path }) |
| 41 | + .ExecuteBufferedAsync(Encoding.UTF8) |
| 42 | + .GetAwaiter() |
| 43 | + .GetResult(); |
32 | 44 | if (res.ExitCode != 0) |
33 | 45 | { |
| 46 | + if (res.StandardOutput == "No matching handles found.\r\n") |
| 47 | + { |
| 48 | + return ""; |
| 49 | + } |
34 | 50 | throw new ApplicationException( |
35 | | - $"{HandleExeFullName} failed for '{path}'. ExitCode={res.ExitCode}. Error message:\n{res.StandardError}"); |
| 51 | + $"'{HandleExeFullName}' failed for '{path}'.\nExitCode={res.ExitCode}\nStdError:\n{res.StandardError}\nStdOut:\n{res.StandardOutput}"); |
36 | 52 | } |
37 | 53 |
|
38 | | - return new JavaScriptSerializer().Deserialize<List<LockingProcess>>(res.StandardOutput); |
| 54 | + return res.StandardOutput; |
| 55 | + } |
| 56 | + |
| 57 | + // The console output of Handle.exe looks like this: |
| 58 | + // Process,PID,User,Handle,Type,Share Flags,Name,Access |
| 59 | + // ipf_helper.exe,3456,File,Domain\UserName,0x00000050,C:\Windows\System32\DriverStore\FileRepository\ipf_cpu.inf_amd64_e6050705c26c770f |
| 60 | + // sihost.exe,21260,File,Domain\UserName,0x00000044,C:\Windows\System32 |
| 61 | + // sihost.exe,21260,File,Domain\UserName,0x000005E0,C:\Windows\System32\en-US\windows.storage.dll.mui |
| 62 | + // Notes: |
| 63 | + // * The header doesn't match the actual data that is being printed. Therefore we have to ignore. |
| 64 | + // * There is a trailing whitespace at the end of every line. |
| 65 | + private static IEnumerable<HandleInfo> ParseRawOutput(string rawOutput) |
| 66 | + { |
| 67 | + return CsvParser.Parse(rawOutput).Skip(1).Select(line => new HandleInfo |
| 68 | + { |
| 69 | + ProcessName = line[0], |
| 70 | + Pid = int.Parse(line[1]), |
| 71 | + HandleType = line[2], |
| 72 | + UserAndDomainName = line[3], |
| 73 | + HandleAddress = Convert.ToInt64(line[4], 16), |
| 74 | + LockedPath = line[5] |
| 75 | + }); |
39 | 76 | } |
40 | 77 | } |
0 commit comments