Skip to content

Commit 0e411ae

Browse files
committed
Add GetProcessesWithCurrentDirectory()
1 parent 47722ab commit 0e411ae

File tree

2 files changed

+316
-5
lines changed

2 files changed

+316
-5
lines changed

LockCheck/Windows/NativeMethods.cs

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Runtime.InteropServices;
4+
using System.Runtime.Versioning;
45
using System.Security.Principal;
56
using System.Text;
67
using Microsoft.Win32.SafeHandles;
@@ -10,6 +11,10 @@ namespace LockCheck.Windows
1011
internal static class NativeMethods
1112
{
1213
private const string NtDll = "ntdll.dll";
14+
private const string RestartManagerDll = "rstrtmgr.dll";
15+
private const string AdvApi32Dll = "advapi32.dll";
16+
private const string KernelDll = "kernel32.dll";
17+
private const string PsApiDll = "psapi.dll";
1318

1419
[StructLayout(LayoutKind.Sequential, Pack = 0)]
1520
internal struct IO_STATUS_BLOCK
@@ -34,17 +39,31 @@ internal static extern uint NtQueryInformationFile(SafeFileHandle fileHandle, re
3439
[DllImport(NtDll)]
3540
internal static extern int RtlNtStatusToDosError(uint status);
3641

42+
[DllImport(NtDll)]
43+
internal static extern int NtQueryInformationProcess(SafeProcessHandle ProcessHandle, int ProcessInformationClass, ref PROCESS_BASIC_INFORMATION ProcessInformation, int ProcessInformationLength, IntPtr ReturnLength);
44+
45+
[DllImport(NtDll)]
46+
internal static extern int NtQueryInformationProcess(SafeProcessHandle ProcessHandle, int ProcessInformationClass, ref IntPtr ProcessInformation, int ProcessInformationLength, IntPtr ReturnLength);
47+
48+
[DllImport(NtDll)]
49+
internal static extern int NtWow64QueryInformationProcess64(SafeProcessHandle ProcessHandle, int ProcessInformationClass, ref PROCESS_BASIC_INFORMATION_WOW64 ProcessInformation, int ProcessInformationLength, IntPtr ReturnLength);
50+
51+
[DllImport(NtDll)]
52+
internal static extern int NtWow64ReadVirtualMemory64(SafeProcessHandle hProcess, long lpBaseAddress, ref long lpBuffer, long dwSize, IntPtr lpNumberOfBytesRead);
53+
54+
[DllImport(NtDll)]
55+
internal static extern int NtWow64ReadVirtualMemory64(SafeProcessHandle hProcess, long lpBaseAddress, ref UNICODE_STRING_WOW64 lpBuffer, long dwSize, IntPtr lpNumberOfBytesRead);
56+
57+
[DllImport(NtDll)]
58+
internal static extern int NtWow64ReadVirtualMemory64(SafeProcessHandle hProcess, long lpBaseAddress, [MarshalAs(UnmanagedType.LPWStr)] string lpBuffer, long dwSize, IntPtr lpNumberOfBytesRead);
59+
3760
internal const int ERROR_MR_MID_NOT_FOUND = 317;
3861

3962
internal const uint STATUS_SUCCESS = 0;
4063
internal const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
4164

4265
// ----------------------------------------------------------------------------------------------
4366

44-
private const string RestartManagerDll = "rstrtmgr.dll";
45-
private const string AdvApi32Dll = "advapi32.dll";
46-
private const string KernelDll = "kernel32.dll";
47-
4867
[DllImport(RestartManagerDll, CharSet = CharSet.Unicode)]
4968
internal static extern int RmRegisterResources(uint pSessionHandle,
5069
uint nFiles,
@@ -174,7 +193,7 @@ internal static extern bool OpenProcessToken(SafeProcessHandle processHandle,
174193
internal const int PROCESS_VM_WRITE = 0x20;
175194

176195
[DllImport(KernelDll, SetLastError = true)]
177-
private static extern SafeProcessHandle OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
196+
internal static extern SafeProcessHandle OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
178197

179198
internal static SafeProcessHandle OpenProcessLimited(int pid)
180199
{
@@ -211,6 +230,22 @@ internal static DateTime GetProcessStartTime(SafeProcessHandle handle)
211230
[DllImport(KernelDll, SetLastError = true)]
212231
private static extern bool ProcessIdToSessionId(int dwProcessId, out int sessionId);
213232

233+
[DllImport(KernelDll, SetLastError = true)]
234+
internal static extern bool ReadProcessMemory(SafeProcessHandle hProcess, IntPtr lpBaseAddress, ref IntPtr lpBuffer, IntPtr dwSize, IntPtr lpNumberOfBytesRead);
235+
236+
[DllImport(KernelDll, SetLastError = true)]
237+
internal static extern bool ReadProcessMemory(SafeProcessHandle hProcess, IntPtr lpBaseAddress, ref UNICODE_STRING lpBuffer, IntPtr dwSize, IntPtr lpNumberOfBytesRead);
238+
239+
[DllImport(KernelDll, SetLastError = true)]
240+
internal static extern bool ReadProcessMemory(SafeProcessHandle hProcess, IntPtr lpBaseAddress, ref UNICODE_STRING_32 lpBuffer, IntPtr dwSize, IntPtr lpNumberOfBytesRead);
241+
242+
[DllImport(KernelDll, SetLastError = true)]
243+
internal static extern bool ReadProcessMemory(SafeProcessHandle hProcess, IntPtr lpBaseAddress, [MarshalAs(UnmanagedType.LPWStr)] string lpBuffer, IntPtr dwSize, IntPtr lpNumberOfBytesRead);
244+
245+
[DllImport(KernelDll, SetLastError = true, CallingConvention = CallingConvention.Winapi)]
246+
[return: MarshalAs(UnmanagedType.Bool)]
247+
internal static extern bool IsWow64Process([In] SafeProcessHandle processHandle, [Out, MarshalAs(UnmanagedType.Bool)] out bool wow64Process);
248+
214249
internal static int GetProcessSessionId(int dwProcessId)
215250
{
216251
if (ProcessIdToSessionId(dwProcessId, out int sessionId))
@@ -308,5 +343,111 @@ internal static SafeFileHandle GetFileHandle(string name)
308343
(int)FileAttributes.Normal,
309344
IntPtr.Zero);
310345
}
346+
347+
[DllImport(PsApiDll, CharSet = CharSet.Auto, SetLastError = true)]
348+
[ResourceExposure(ResourceScope.Machine)]
349+
public static extern bool EnumProcesses(int[] processIds, int size, out int needed);
350+
351+
internal static int[] GetProcessIds()
352+
{
353+
int[] processIds = new int[256];
354+
int size;
355+
356+
for (; ; )
357+
{
358+
if (!EnumProcesses(processIds, processIds.Length * 4, out size))
359+
{
360+
return Array.Empty<int>();
361+
}
362+
363+
if (size == processIds.Length * 4)
364+
{
365+
processIds = new int[processIds.Length * 2];
366+
continue;
367+
}
368+
369+
break;
370+
}
371+
372+
int[] ids = new int[size / 4];
373+
Array.Copy(processIds, ids, ids.Length);
374+
375+
return ids;
376+
}
377+
378+
[StructLayout(LayoutKind.Sequential)]
379+
internal struct UNICODE_STRING_32
380+
{
381+
public short Length;
382+
public short MaximumLength;
383+
public int Buffer;
384+
}
385+
386+
[StructLayout(LayoutKind.Sequential)]
387+
internal struct UNICODE_STRING
388+
{
389+
public short Length;
390+
public short MaximumLength;
391+
public IntPtr Buffer;
392+
}
393+
394+
// for 32-bit process
395+
[StructLayout(LayoutKind.Sequential)]
396+
internal struct UNICODE_STRING_WOW64
397+
{
398+
public short Length;
399+
public short MaximumLength;
400+
public long Buffer;
401+
}
402+
403+
internal enum PROCESSINFOCLASS : int
404+
{
405+
ProcessBasicInformation = 0, // 0, q: PROCESS_BASIC_INFORMATION, PROCESS_EXTENDED_BASIC_INFORMATION
406+
ProcessWow64Information = 26, // q: ULONG_PTR
407+
}
408+
409+
[StructLayout(LayoutKind.Sequential)]
410+
internal struct PROCESS_BASIC_INFORMATION
411+
{
412+
public IntPtr Reserved1;
413+
public IntPtr PebBaseAddress;
414+
public IntPtr Reserved2_0;
415+
public IntPtr Reserved2_1;
416+
public IntPtr UniqueProcessId;
417+
public IntPtr Reserved3;
418+
}
419+
420+
// for 32-bit process in a 64-bit OS only
421+
[StructLayout(LayoutKind.Sequential)]
422+
internal struct PROCESS_BASIC_INFORMATION_WOW64
423+
{
424+
public long Reserved1;
425+
public long PebBaseAddress;
426+
public long Reserved2_0;
427+
public long Reserved2_1;
428+
public long UniqueProcessId;
429+
public long Reserved3;
430+
}
431+
432+
internal static bool GetProcessIsWow64(SafeProcessHandle hProcess)
433+
{
434+
if ((OSVersion.Major == 5 && OSVersion.Minor >= 1) || OSVersion.Major >= 6)
435+
{
436+
if (!IsWow64Process(hProcess, out bool retVal))
437+
{
438+
return false;
439+
}
440+
441+
return retVal;
442+
}
443+
else
444+
{
445+
return false;
446+
}
447+
}
448+
449+
internal static readonly Version OSVersion = Environment.OSVersion.Version;
450+
internal static readonly bool Is64BitOperatingSystem = Environment.Is64BitOperatingSystem;
451+
internal static readonly bool IsCurrentProcessWOW64 = Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess;
311452
}
312453
}

LockCheck/Windows/NtDll.cs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.ComponentModel;
44
using System.Runtime.InteropServices;
5+
using static LockCheck.Windows.NativeMethods;
56

67
namespace LockCheck.Windows
78
{
@@ -99,6 +100,175 @@ private static Exception GetException(uint status, string apiName, string messag
99100

100101
return new NtException(res, status, $"{message} ({apiName}() status {status} (0x{status:8X})");
101102
}
103+
104+
/// <summary>
105+
/// Gets the processes for which the current directory is the given directory, or a[n indirect] parent directory.
106+
/// If the resulting list is not empty, the given directory cannot be deleted, as it is locked by the processes
107+
/// returned.
108+
/// </summary>
109+
public static IEnumerable<int> GetProcessesWithCurrentDirectory(string directory)
110+
{
111+
if (string.IsNullOrWhiteSpace(directory))
112+
{
113+
return Array.Empty<int>();
114+
}
115+
116+
var list = new List<int>();
117+
118+
var processes = GetProcessIds();
119+
foreach (var id in processes)
120+
{
121+
string currentDirectory = GetCurrentDirectory(id);
122+
if (currentDirectory != null && currentDirectory.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
123+
{
124+
list.Add(id);
125+
}
126+
}
127+
128+
return list;
129+
}
130+
131+
public static string GetCurrentDirectory(int processId)
132+
{
133+
try
134+
{
135+
return GetProcessParametersString(processId);
136+
}
137+
catch
138+
{
139+
return null;
140+
}
141+
}
142+
143+
private static string GetProcessParametersString(int processId)
144+
{
145+
using var handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, bInheritHandle: false, processId);
146+
if (handle.IsInvalid)
147+
{
148+
return null;
149+
}
150+
151+
bool IsTargetWow64Process = GetProcessIsWow64(handle);
152+
bool IsTarget64BitProcess = Is64BitOperatingSystem && !IsTargetWow64Process;
153+
154+
long offset = IsTarget64BitProcess ? 0x38 : 0x24;
155+
long processParametersOffset = IsTarget64BitProcess ? 0x20 : 0x10;
156+
157+
long pebAddress = 0;
158+
if (IsTargetWow64Process) // OS: 64Bit, Current: 32 or 64, Target: 32bit
159+
{
160+
IntPtr peb32 = new IntPtr();
161+
162+
int hr = NtQueryInformationProcess(handle, (int)PROCESSINFOCLASS.ProcessWow64Information, ref peb32, IntPtr.Size, IntPtr.Zero);
163+
if (hr != 0)
164+
{
165+
return null;
166+
}
167+
168+
pebAddress = peb32.ToInt64();
169+
170+
IntPtr pp = new IntPtr();
171+
if (!ReadProcessMemory(handle, new IntPtr(pebAddress + processParametersOffset), ref pp, new IntPtr(Marshal.SizeOf(pp)), IntPtr.Zero))
172+
{
173+
return null;
174+
}
175+
176+
UNICODE_STRING_32 us = new UNICODE_STRING_32();
177+
if (!ReadProcessMemory(handle, new IntPtr(pp.ToInt64() + offset), ref us, new IntPtr(Marshal.SizeOf(us)), IntPtr.Zero))
178+
{
179+
return null;
180+
}
181+
182+
if ((us.Buffer == 0) || (us.Length == 0))
183+
{
184+
return null;
185+
}
186+
187+
string s = new string('\0', us.Length / 2);
188+
if (!ReadProcessMemory(handle, new IntPtr(us.Buffer), s, new IntPtr(us.Length), IntPtr.Zero))
189+
{
190+
return null;
191+
}
192+
193+
return s;
194+
}
195+
else if (IsCurrentProcessWOW64) // OS: 64Bit, Current: 32, Target: 64
196+
{
197+
// NtWow64QueryInformationProcess64 is an "undocumented API", see issue 1702
198+
PROCESS_BASIC_INFORMATION_WOW64 pbi = new PROCESS_BASIC_INFORMATION_WOW64();
199+
int hr = NtWow64QueryInformationProcess64(handle, (int)PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, Marshal.SizeOf(pbi), IntPtr.Zero);
200+
if (hr != 0)
201+
{
202+
return null;
203+
}
204+
205+
pebAddress = pbi.PebBaseAddress;
206+
207+
long pp = 0;
208+
hr = NtWow64ReadVirtualMemory64(handle, pebAddress + processParametersOffset, ref pp, Marshal.SizeOf(pp), IntPtr.Zero);
209+
if (hr != 0)
210+
{
211+
return null;
212+
}
213+
214+
UNICODE_STRING_WOW64 us = new UNICODE_STRING_WOW64();
215+
hr = NtWow64ReadVirtualMemory64(handle, pp + offset, ref us, Marshal.SizeOf(us), IntPtr.Zero);
216+
if (hr != 0)
217+
{
218+
return null;
219+
}
220+
221+
if ((us.Buffer == 0) || (us.Length == 0))
222+
{
223+
return null;
224+
}
225+
226+
string s = new string('\0', us.Length / 2);
227+
hr = NtWow64ReadVirtualMemory64(handle, us.Buffer, s, us.Length, IntPtr.Zero);
228+
if (hr != 0)
229+
{
230+
return null;
231+
}
232+
233+
return s;
234+
}
235+
else // OS, Current, Target: 64 or 32
236+
{
237+
PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION();
238+
int hr = NtQueryInformationProcess(handle, (int)PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, Marshal.SizeOf(pbi), IntPtr.Zero);
239+
if (hr != 0)
240+
{
241+
return null;
242+
}
243+
244+
pebAddress = pbi.PebBaseAddress.ToInt64();
245+
246+
IntPtr pp = new IntPtr();
247+
if (!ReadProcessMemory(handle, new IntPtr(pebAddress + processParametersOffset), ref pp, new IntPtr(Marshal.SizeOf(pp)), IntPtr.Zero))
248+
{
249+
return null;
250+
}
251+
252+
UNICODE_STRING us = new UNICODE_STRING();
253+
if (!ReadProcessMemory(handle, new IntPtr((long)pp + offset), ref us, new IntPtr(Marshal.SizeOf(us)), IntPtr.Zero))
254+
{
255+
return null;
256+
}
257+
258+
if (us.Buffer == IntPtr.Zero || us.Length == 0)
259+
{
260+
return null;
261+
}
262+
263+
string s = new string('\0', us.Length / 2);
264+
if (!ReadProcessMemory(handle, us.Buffer, s, new IntPtr(us.Length), IntPtr.Zero))
265+
{
266+
return null;
267+
}
268+
269+
return s;
270+
}
271+
}
102272
}
103273

104274
public class NtException : Win32Exception

0 commit comments

Comments
 (0)