From 35056a6ff78f6a70f0098530050089a6a3010fb1 Mon Sep 17 00:00:00 2001 From: CodeDead Date: Mon, 18 Apr 2022 19:29:06 +0200 Subject: [PATCH] * Improved docs * Minor refactoring * Added ability to rethrow inner exceptions --- README.md | 71 ++++++++++++++++++++- deadlock-dotnet-sdk/DeadLock.cs | 36 +++++++++-- deadlock-dotnet-sdk/Domain/NativeMethods.cs | 51 +++++++++------ 3 files changed, 129 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8208be4..839264e 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,30 @@ ![GitHub](https://img.shields.io/badge/language-C%23-green) ![GitHub](https://img.shields.io/github/license/CodeDead/deadlock-dotnet-sdk) -deadlock-dotnet-sdk is a simple-to-use SDK for unlocking files in C# / dotnet. +deadlock-dotnet-sdk is a simple-to-use SDK for unlocking files in C# / dotnet on Windows based operating systems. ## Usage -Add deadlock-dotnet-sdk to your solution tree using NuGet: +Add deadlock-dotnet-sdk to your solution tree using [NuGet](https://www.nuget.org/packages/deadlock-dotnet-sdk/): ```shell Install-Package deadlock-dotnet-sdk ``` +You can initialize a new `DeadLock` helper object like so: +```c# +DeadLock deadLock = new DeadLock(); +``` + +In addition, if you would like to rethrow inner exceptions, you can change the `RethrowExceptions` property when declaring a new `DeadLock` object: +```c# +DeadLock deadLock = new DeadLock(`true` | `false`); +``` + +You can also change the property dynamically: +```c# +deadlock.RethrowExceptions = `true` | `false` +``` + ### Finding the processes that are locking a file To find all the `FileLocker` objects that are locking a file, you can make use of the `FindLockingProcesses` method: @@ -28,6 +43,11 @@ string path = @"C:\...\file.txt"; List lockers = await DeadLock.FindLockingProcessesAsync(path); ``` +To find the `Process` objects that are locking one or more files, you can invoke the `FindLockingProcesses` (or `FindLockingProcessesAsync`) method with multiple `path` parameters: +```c# +List fileLockers = FindLockingProcesses("a", "c", "c"); +``` + ### Unlocking a file To unlock a `FileLocker`, you can execute the `Unlock` method: @@ -40,9 +60,54 @@ You can also run the code asynchronously by running the `UnlockAsync` method: DeadLock.UnlockAsync(locker); ``` +To unlock more than one `FileLocker` object, you can invoke the `Unlock` (or `UnlockAsync`) method with multiple `FileLocker` parameters: +```c# +Unlock(fileLockerA, fileLockerB, fileLockerC); +``` + +### `FileLocker` + +The `FileLocker` object contains a `List` of `System.Diagnostics.Process` objects that are locking a file. +You can retrieve the `List` of `Process` objects by retrieving the respective property: +```c# +List processes = fileLocker.Lockers; +``` + +To retrieve the path of the file that the `Process` objects are locking, you can make use of the `Path` property: +```c# +string path = fileLocker.Path; +``` + +### Error handling + +deadlock-dotnet-sdk has three specific `Exception` types that might occur when trying to find the `Process` objects that are locking a file. + +* `RegisterResourceException` +* `RmListException` +* `StartSessionException` + +In case you want more detailed error messages, it is recommended that you call the `Marshal.GetLastWin32Error` method as soon as one of these `Exception` types occur: +https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getlastwin32error?view=net-6.0 + +#### `RegisterResourceException` + +This error occurs when the system goes out of memory, when a specific time-out occurs or if an invalid handle is detected. You can find more information about this exception here: +https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmregisterresources#return-value + +#### `RmListException` + +This error occurs when the system goes out of memory, when a specific time-out occurs or if an invalid handle is detected. You can find more information about this exception here: +https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmgetlist#return-value + +#### `StartSessionException` + +This error occurs when the system goes out of memory, when a specific time-out occurs or if an invalid handle is detected. You can find more information about this exception here: +https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmstartsession#return-value + ## Credits -Images by: [RemixIcon](https://remixicon.com/) +* [RemixIcon](https://remixicon.com/) +* [dotnet](https://dotnet.microsoft.com/en-us/) ## About diff --git a/deadlock-dotnet-sdk/DeadLock.cs b/deadlock-dotnet-sdk/DeadLock.cs index c635600..370ab8c 100644 --- a/deadlock-dotnet-sdk/DeadLock.cs +++ b/deadlock-dotnet-sdk/DeadLock.cs @@ -5,6 +5,32 @@ namespace deadlock_dotnet_sdk { public class DeadLock { + #region Properties + + /// + /// Property that specifies whether inner exceptions should be rethrown or not + /// + public bool RethrowExceptions { get; set; } + + #endregion + + /// + /// Default constructor + /// + public DeadLock() + { + // Default constructor + } + + /// + /// Initialize a new DeadLock + /// + /// True if inner exceptions should be rethrown, otherwise false + public DeadLock(bool rethrowExceptions) + { + RethrowExceptions = rethrowExceptions; + } + /// /// Retrieve the FileLocker object that contains a List of Process objects that are locking a file /// @@ -12,9 +38,7 @@ public class DeadLock /// The FileLocker object that contains a List of Process objects that are locking a file public FileLocker FindLockingProcesses(string filePath) { - IEnumerable lockers = NativeMethods.FindLockingProcesses(filePath); - FileLocker fileLocker = new(filePath, lockers.ToList()); - + FileLocker fileLocker = new(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList()); return fileLocker; } @@ -45,8 +69,7 @@ public async Task FindLockingProcessesAsync(string filePath) await Task.Run(() => { - IEnumerable lockers = NativeMethods.FindLockingProcesses(filePath); - fileLocker = new(filePath, lockers.ToList()); + fileLocker = new FileLocker(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList()); }); return fileLocker; @@ -65,8 +88,7 @@ await Task.Run(() => { foreach (string filePath in filePaths) { - IEnumerable lockers = NativeMethods.FindLockingProcesses(filePath); - fileLockers.Add(new(filePath, lockers.ToList())); + fileLockers.Add(new FileLocker(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList())); } }); diff --git a/deadlock-dotnet-sdk/Domain/NativeMethods.cs b/deadlock-dotnet-sdk/Domain/NativeMethods.cs index 9312f6f..76ac6fa 100644 --- a/deadlock-dotnet-sdk/Domain/NativeMethods.cs +++ b/deadlock-dotnet-sdk/Domain/NativeMethods.cs @@ -10,12 +10,15 @@ namespace deadlock_dotnet_sdk.Domain internal static class NativeMethods { #region Variables + private const int RmRebootReasonNone = 0; private const int CchRmMaxAppName = 255; private const int CchRmMaxSvcName = 63; + #endregion #region Enum_Struct + [StructLayout(LayoutKind.Sequential)] private struct RmUniqueProcess { @@ -27,16 +30,22 @@ private enum RmAppType { // ReSharper disable once UnusedMember.Local RmUnknownApp = 0, + // ReSharper disable once UnusedMember.Local RmMainWindow = 1, + // ReSharper disable once UnusedMember.Local RmOtherWindow = 2, + // ReSharper disable once UnusedMember.Local RmService = 3, + // ReSharper disable once UnusedMember.Local RmExplorer = 4, + // ReSharper disable once UnusedMember.Local RmConsole = 5, + // ReSharper disable once UnusedMember.Local RmCritical = 1000 } @@ -45,15 +54,17 @@ private enum RmAppType private struct RmProcessInfo { internal RmUniqueProcess Process; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxAppName + 1)] private readonly string strAppName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CchRmMaxSvcName + 1)] private readonly string strServiceShortName; + private readonly RmAppType ApplicationType; private readonly uint AppStatus; private readonly uint TSSessionId; - [MarshalAs(UnmanagedType.Bool)] - private readonly bool bRestartable; + [MarshalAs(UnmanagedType.Bool)] private readonly bool bRestartable; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] @@ -62,30 +73,28 @@ private struct ShellExecuteInfo public int cbSize; public uint fMask; private readonly IntPtr hwnd; - [MarshalAs(UnmanagedType.LPTStr)] - public string lpVerb; - [MarshalAs(UnmanagedType.LPTStr)] - public string lpFile; - [MarshalAs(UnmanagedType.LPTStr)] - private readonly string lpParameters; - [MarshalAs(UnmanagedType.LPTStr)] - private readonly string lpDirectory; + [MarshalAs(UnmanagedType.LPTStr)] public string lpVerb; + [MarshalAs(UnmanagedType.LPTStr)] public string lpFile; + [MarshalAs(UnmanagedType.LPTStr)] private readonly string lpParameters; + [MarshalAs(UnmanagedType.LPTStr)] private readonly string lpDirectory; public int nShow; private readonly IntPtr hInstApp; private readonly IntPtr lpIDList; - [MarshalAs(UnmanagedType.LPTStr)] - private readonly string lpClass; + [MarshalAs(UnmanagedType.LPTStr)] private readonly string lpClass; private readonly IntPtr hkeyClass; private readonly uint dwHotKey; private readonly IntPtr hIcon; private readonly IntPtr hProcess; } + #endregion #region DllImport + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] - private static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, uint nApplications, [In] RmUniqueProcess[] rgApplications, uint nServices, string[] rgsServiceNames); + private static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, + uint nApplications, [In] RmUniqueProcess[] rgApplications, uint nServices, string[] rgsServiceNames); [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); @@ -94,15 +103,18 @@ private struct ShellExecuteInfo private static extern int RmEndSession(uint pSessionHandle); [DllImport("rstrtmgr.dll")] - private static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RmProcessInfo[] rgAffectedApps, ref uint lpdwRebootReasons); + private static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, + [In, Out] RmProcessInfo[] rgAffectedApps, ref uint lpdwRebootReasons); + #endregion /// /// Find the processes that are locking a file /// /// Path to the file + /// True if inner exceptions should be rethrown, otherwise false /// A collection of processes that are locking a file - internal static IEnumerable FindLockingProcesses(string path) + internal static IEnumerable FindLockingProcesses(string path, bool rethrowExceptions) { string key = Guid.NewGuid().ToString(); List processes = new(); @@ -119,8 +131,8 @@ internal static IEnumerable FindLockingProcesses(string path) uint pnProcInfo = 0; uint lpdwRebootReasons = RmRebootReasonNone; - string[] resources = { path }; - res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); + string[] resources = {path}; + res = RmRegisterResources(handle, (uint) resources.Length, resources, 0, null, 0, null); if (res != 0) { @@ -137,7 +149,7 @@ internal static IEnumerable FindLockingProcesses(string path) res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); if (res == 0) { - processes = new List((int)pnProcInfo); + processes = new List((int) pnProcInfo); for (int i = 0; i < pnProcInfo; i++) { @@ -147,7 +159,7 @@ internal static IEnumerable FindLockingProcesses(string path) } catch (ArgumentException) { - + if (rethrowExceptions) throw; } } } @@ -159,6 +171,7 @@ internal static IEnumerable FindLockingProcesses(string path) { _ = RmEndSession(handle); } + return processes; } }