@@ -343,5 +343,132 @@ public static IEnumerable<HandleInfo> GetFileHandles()
343343 yield return hi ;
344344 }
345345 }
346+
347+ /// <summary>
348+ /// Gets handles that match the specified directory path.
349+ /// This method is used for finding processes that have a handle to a directory.
350+ /// </summary>
351+ /// <param name="directoryPath">The full path to the directory to search for.</param>
352+ /// <returns>A list of HandleInfo objects for handles matching the directory.</returns>
353+ public static IEnumerable < HandleInfo > GetDirectoryHandles ( string directoryPath )
354+ {
355+ // Normalize the path for comparison
356+ directoryPath = directoryPath . TrimEnd ( System . IO . Path . DirectorySeparatorChar , System . IO . Path . AltDirectorySeparatorChar ) ;
357+
358+ // Convert to device path format for comparison
359+ // Windows handles use paths like \Device\HarddiskVolume3\path
360+ // We need to match against the end portion of the path
361+ string normalizedPath = directoryPath . Replace ( System . IO . Path . AltDirectorySeparatorChar , System . IO . Path . DirectorySeparatorChar ) ;
362+
363+ foreach ( HandleInfo hi in GetFileHandles ( ) )
364+ {
365+ if ( hi . Name != null )
366+ {
367+ // The handle name is in device path format (e.g., \Device\HarddiskVolume3\Users\test)
368+ // We need to check if it ends with our directory path or starts with it (for files within)
369+ string handlePath = hi . Name ;
370+
371+ // Try to convert the device path to a DOS path for comparison
372+ string dosPath = ConvertDevicePathToDosPath ( handlePath ) ;
373+ if ( dosPath != null )
374+ {
375+ dosPath = dosPath . TrimEnd ( System . IO . Path . DirectorySeparatorChar ) ;
376+ if ( string . Equals ( dosPath , normalizedPath , StringComparison . OrdinalIgnoreCase ) ||
377+ dosPath . StartsWith ( normalizedPath + System . IO . Path . DirectorySeparatorChar , StringComparison . OrdinalIgnoreCase ) )
378+ {
379+ yield return hi ;
380+ }
381+ }
382+ }
383+ }
384+ }
385+
386+ private static readonly object _deviceMapLock = new object ( ) ;
387+ private static Dictionary < string , string > _deviceToDriveMap ;
388+
389+ /// <summary>
390+ /// Converts a device path (e.g., \Device\HarddiskVolume3\path) to a DOS path (e.g., C:\path).
391+ /// </summary>
392+ private static string ConvertDevicePathToDosPath ( string devicePath )
393+ {
394+ if ( string . IsNullOrEmpty ( devicePath ) )
395+ return null ;
396+
397+ // Thread-safe lazy initialization of device to drive map
398+ if ( _deviceToDriveMap == null )
399+ {
400+ lock ( _deviceMapLock )
401+ {
402+ if ( _deviceToDriveMap == null )
403+ {
404+ var map = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
405+ foreach ( string drive in System . IO . Directory . GetLogicalDrives ( ) )
406+ {
407+ string driveLetter = drive . TrimEnd ( '\\ ' ) ;
408+ string deviceName = QueryDosDevice ( driveLetter ) ;
409+ if ( deviceName != null )
410+ {
411+ map [ deviceName ] = driveLetter ;
412+ }
413+ }
414+ _deviceToDriveMap = map ;
415+ }
416+ }
417+ }
418+
419+ // Try to find a matching device prefix
420+ foreach ( var kvp in _deviceToDriveMap )
421+ {
422+ if ( devicePath . StartsWith ( kvp . Key , StringComparison . OrdinalIgnoreCase ) )
423+ {
424+ return kvp . Value + devicePath . Substring ( kvp . Key . Length ) ;
425+ }
426+ }
427+
428+ return null ;
429+ }
430+
431+ [ DllImport ( "kernel32.dll" , SetLastError = true , CharSet = CharSet . Auto ) ]
432+ private static extern uint QueryDosDevice ( string lpDeviceName , System . Text . StringBuilder lpTargetPath , uint ucchMax ) ;
433+
434+ private static string QueryDosDevice ( string driveLetter )
435+ {
436+ var buffer = new System . Text . StringBuilder ( 260 ) ;
437+ if ( QueryDosDevice ( driveLetter , buffer , ( uint ) buffer . Capacity ) != 0 )
438+ {
439+ return buffer . ToString ( ) ;
440+ }
441+ return null ;
442+ }
443+
444+ /// <summary>
445+ /// Gets processes that have a handle to the specified directory or files within it.
446+ /// </summary>
447+ /// <param name="directoryPath">The full path to the directory.</param>
448+ /// <returns>A list of unique Process objects.</returns>
449+ public static List < System . Diagnostics . Process > GetProcessesLockingDirectory ( string directoryPath )
450+ {
451+ var processIds = new HashSet < int > ( ) ;
452+ var processes = new List < System . Diagnostics . Process > ( ) ;
453+
454+ foreach ( HandleInfo hi in GetDirectoryHandles ( directoryPath ) )
455+ {
456+ if ( ! processIds . Contains ( hi . ProcessId ) )
457+ {
458+ processIds . Add ( hi . ProcessId ) ;
459+ try
460+ {
461+ processes . Add ( System . Diagnostics . Process . GetProcessById ( hi . ProcessId ) ) ;
462+ }
463+ catch ( ArgumentException )
464+ {
465+ // Process no longer exists - this is expected as processes can terminate
466+ // between the time we enumerate handles and try to get the process
467+ }
468+ }
469+ }
470+
471+ return processes ;
472+ }
346473 }
347474}
0 commit comments