Skip to content

Commit

Permalink
Merge pull request #2 from CodeDead/feature/docs
Browse files Browse the repository at this point in the history
* Improved docs
  • Loading branch information
CodeDead authored Apr 18, 2022
2 parents ec420b5 + 35056a6 commit 3589491
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 29 deletions.
71 changes: 68 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -28,6 +43,11 @@ string path = @"C:\...\file.txt";
List<FileLocker> 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<FileLocker> fileLockers = FindLockingProcesses("a", "c", "c");
```

### Unlocking a file

To unlock a `FileLocker`, you can execute the `Unlock` method:
Expand All @@ -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<Process> 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

Expand Down
36 changes: 29 additions & 7 deletions deadlock-dotnet-sdk/DeadLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,40 @@ namespace deadlock_dotnet_sdk
{
public class DeadLock
{
#region Properties

/// <summary>
/// Property that specifies whether inner exceptions should be rethrown or not
/// </summary>
public bool RethrowExceptions { get; set; }

#endregion

/// <summary>
/// Default constructor
/// </summary>
public DeadLock()
{
// Default constructor
}

/// <summary>
/// Initialize a new DeadLock
/// </summary>
/// <param name="rethrowExceptions">True if inner exceptions should be rethrown, otherwise false</param>
public DeadLock(bool rethrowExceptions)
{
RethrowExceptions = rethrowExceptions;
}

/// <summary>
/// Retrieve the FileLocker object that contains a List of Process objects that are locking a file
/// </summary>
/// <param name="filePath">The full path of a file</param>
/// <returns>The FileLocker object that contains a List of Process objects that are locking a file</returns>
public FileLocker FindLockingProcesses(string filePath)
{
IEnumerable<Process> lockers = NativeMethods.FindLockingProcesses(filePath);
FileLocker fileLocker = new(filePath, lockers.ToList());

FileLocker fileLocker = new(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList());
return fileLocker;
}

Expand Down Expand Up @@ -45,8 +69,7 @@ public async Task<FileLocker> FindLockingProcessesAsync(string filePath)

await Task.Run(() =>
{
IEnumerable<Process> lockers = NativeMethods.FindLockingProcesses(filePath);
fileLocker = new(filePath, lockers.ToList());
fileLocker = new FileLocker(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList());
});

return fileLocker;
Expand All @@ -65,8 +88,7 @@ await Task.Run(() =>
{
foreach (string filePath in filePaths)
{
IEnumerable<Process> lockers = NativeMethods.FindLockingProcesses(filePath);
fileLockers.Add(new(filePath, lockers.ToList()));
fileLockers.Add(new FileLocker(filePath, NativeMethods.FindLockingProcesses(filePath, RethrowExceptions).ToList()));
}
});

Expand Down
51 changes: 32 additions & 19 deletions deadlock-dotnet-sdk/Domain/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
}
Expand All @@ -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)]
Expand All @@ -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);
Expand All @@ -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

/// <summary>
/// Find the processes that are locking a file
/// </summary>
/// <param name="path">Path to the file</param>
/// <param name="rethrowExceptions">True if inner exceptions should be rethrown, otherwise false</param>
/// <returns>A collection of processes that are locking a file</returns>
internal static IEnumerable<Process> FindLockingProcesses(string path)
internal static IEnumerable<Process> FindLockingProcesses(string path, bool rethrowExceptions)
{
string key = Guid.NewGuid().ToString();
List<Process> processes = new();
Expand All @@ -119,8 +131,8 @@ internal static IEnumerable<Process> 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)
{
Expand All @@ -137,7 +149,7 @@ internal static IEnumerable<Process> FindLockingProcesses(string path)
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
processes = new List<Process>((int) pnProcInfo);

for (int i = 0; i < pnProcInfo; i++)
{
Expand All @@ -147,7 +159,7 @@ internal static IEnumerable<Process> FindLockingProcesses(string path)
}
catch (ArgumentException)
{

if (rethrowExceptions) throw;
}
}
}
Expand All @@ -159,6 +171,7 @@ internal static IEnumerable<Process> FindLockingProcesses(string path)
{
_ = RmEndSession(handle);
}

return processes;
}
}
Expand Down

0 comments on commit 3589491

Please # to comment.