├── global.json
├── CHANGELOG.md
├── findopenfiles.csproj
├── .vscode
└── launch.json
├── FindOpenFile.psd1
├── LICENSE
├── findopenfiles.sln
├── .github
└── workflows
│ └── ci-cd.yml
├── FindOpenFileCommand.cs
├── README.md
├── WalkmanLib.RestartManager.cs
├── .gitignore
├── Tests
└── FindOpenFile.Tests.ps1
├── GetAllHandles.cs
├── RestartManager.cs
├── WalkmanLib.GetFileLocks.GetAllHandles.cs
├── WalkmanLib.GetFileLocks.GetProcessHandles.cs
├── GetProcessHandles.cs
└── WalkmanLib.SystemHandles.cs
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "10.0.100",
4 | "rollForward": "latestMinor"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.0.1 - 5/31/2020
2 |
3 | ## Changed
4 |
5 | - Added a check for Windows
6 | - Fixed the manifest project\license path.
--------------------------------------------------------------------------------
/findopenfiles.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | PreserveNewest
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Attach",
9 | "type": "coreclr",
10 | "request": "attach",
11 | "processId": "${command:pickProcess}"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/FindOpenFile.psd1:
--------------------------------------------------------------------------------
1 | @{
2 | RootModule = 'findopenfiles.dll'
3 | ModuleVersion = '1.1.0'
4 | GUID = 'ed3d6408-fe70-40e3-adae-c43dfe1b88ae'
5 | Author = 'Adam Driscoll'
6 | CompanyName = 'Ironman Software'
7 | Copyright = 'Ironman Software 2025'
8 | Description = 'Find open files on Windows with PowerShell'
9 | CmdletsToExport = 'Find-OpenFile'
10 | PrivateData = @{
11 | PSData = @{
12 | Tags = @('windows')
13 | LicenseUri = 'https://github.com/ironmansoftware/findopenfiles/blob/master/LICENSE.md'
14 | ProjectUri = 'https://github.com/ironmansoftware/findopenfiles'
15 | ReleaseNotes = 'https://github.com/ironmansoftware/findopenfiles/blob/master/CHANGELOG.md'
16 | }
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Ironman Software, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/findopenfiles.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.5.2.0
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "findopenfiles", "findopenfiles.csproj", "{CB185BB9-65B9-30BA-F5C6-130F6F5F61F2}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {CB185BB9-65B9-30BA-F5C6-130F6F5F61F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {CB185BB9-65B9-30BA-F5C6-130F6F5F61F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {CB185BB9-65B9-30BA-F5C6-130F6F5F61F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {CB185BB9-65B9-30BA-F5C6-130F6F5F61F2}.Release|Any CPU.Build.0 = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(SolutionProperties) = preSolution
19 | HideSolutionNode = FALSE
20 | EndGlobalSection
21 | GlobalSection(ExtensibilityGlobals) = postSolution
22 | SolutionGuid = {88913DD2-0BEF-46B3-A938-F8DB80EE1F4A}
23 | EndGlobalSection
24 | EndGlobal
25 |
--------------------------------------------------------------------------------
/.github/workflows/ci-cd.yml:
--------------------------------------------------------------------------------
1 | name: CI/CD
2 |
3 | on:
4 | push:
5 | branches: [ master, main ]
6 | pull_request:
7 | branches: [ master, main ]
8 | release:
9 | types: [ published ]
10 |
11 | jobs:
12 | build:
13 | runs-on: windows-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 |
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@v4
20 | with:
21 | global-json-file: global.json
22 |
23 |
24 | - name: Restore dependencies
25 | run: dotnet restore
26 |
27 | - name: Build
28 | run: dotnet build --configuration Release --no-restore
29 |
30 | - name: Run Pester tests
31 | shell: pwsh
32 | run: |
33 | Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser
34 | Invoke-Pester -Path .\Tests\FindOpenFile.Tests.ps1 -Output Detailed
35 | env:
36 | BUILD_CONFIGURATION: Release
37 |
38 | - name: Upload build artifacts
39 | uses: actions/upload-artifact@v4
40 | with:
41 | name: module
42 | path: bin/Release/netstandard2.0/
43 |
44 | publish:
45 | needs: build
46 | runs-on: windows-latest
47 | if: github.event_name == 'release'
48 |
49 | steps:
50 | - uses: actions/checkout@v4
51 |
52 | - name: Setup .NET
53 | uses: actions/setup-dotnet@v4
54 | with:
55 | dotnet-version: '10.0.x'
56 |
57 | - name: Build Release
58 | run: dotnet build --configuration Release
59 |
60 | - name: Publish to PowerShell Gallery
61 | shell: pwsh
62 | env:
63 | PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
64 | run: |
65 | $modulePath = ".\bin\Release\netstandard2.0"
66 | Rename-Item ".\bin\Release\netstandard2.0" -NewName "FindOpenFile"
67 | $modulePath = ".\FindOpenFile"
68 | Publish-Module -Path $modulePath -NuGetApiKey $env:PSGALLERY_API_KEY -Verbose
69 |
--------------------------------------------------------------------------------
/FindOpenFileCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Management.Automation;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace FindOpenFiles
9 | {
10 | [Cmdlet(VerbsCommon.Find, "OpenFile", DefaultParameterSetName = AllParameterSet)]
11 | public class FindOpenFileCommand : PSCmdlet
12 | {
13 | private const string AllParameterSet = "All";
14 | private const string FileParameterSet = "File";
15 | private const string ProcessParameterSet = "Process";
16 |
17 | [Parameter(ParameterSetName = AllParameterSet)]
18 | public SwitchParameter System { get; set; }
19 | [Parameter(ParameterSetName = FileParameterSet, Mandatory = true, ValueFromPipeline = true)]
20 | public string FilePath { get; set; }
21 | [Parameter(ParameterSetName = ProcessParameterSet, Mandatory = true, ValueFromPipeline = true)]
22 | public Process Process { get; set; }
23 |
24 | protected override void ProcessRecord()
25 | {
26 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
27 | {
28 | throw new Exception("This cmdlet is only supported on Windows.");
29 | }
30 |
31 | if (ParameterSetName == AllParameterSet)
32 | {
33 | if (System)
34 | {
35 | WriteObject(WalkmanLib.GetFileLocks.GetAllHandles.GetSystemHandles(), true);
36 | }
37 | else
38 | {
39 | WriteObject(WalkmanLib.GetFileLocks.GetAllHandles.GetFileHandles(), true);
40 | }
41 | }
42 | else if (ParameterSetName == FileParameterSet)
43 | {
44 | FilePath = GetUnresolvedProviderPathFromPSPath(FilePath);
45 |
46 | // Check if the path is a directory
47 | if (Directory.Exists(FilePath))
48 | {
49 | // For directories, use the GetAllHandles approach
50 | WriteObject(WalkmanLib.GetFileLocks.GetAllHandles.GetProcessesLockingDirectory(FilePath), true);
51 | }
52 | else
53 | {
54 | // For files, use the RestartManager approach
55 | WriteObject(WalkmanLib.RestartManager.GetLockingProcesses(FilePath), true);
56 | }
57 | }
58 | else if (ParameterSetName == ProcessParameterSet)
59 | {
60 | WriteObject(WalkmanLib.GetFileLocks.GetAllHandles.GetFileHandles().Where(m => m.ProcessId == Process.Id), true);
61 | }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Find-OpenFile by [Ironman Software](https://ironmansoftware.com)
2 |
3 | A PowerShell module for finding open files.
4 |
5 | ----
6 |
7 | ## Installation
8 |
9 | ```
10 | Install-Module FindOpenFile
11 | ```
12 |
13 | ## Usage
14 |
15 | Find all files in use.
16 |
17 | ```
18 | PS C:\Users\adamr> Find-OpenFile
19 |
20 | ProcessId : 7936
21 | Handle : 64
22 | GrantedAccess : 1048608
23 | RawType : 37
24 | Flags : 0
25 | Name : \Device\HarddiskVolume3\Windows\System32\DriverStore\FileRepository\FN8DAD~1.INF\driver
26 | TypeString : File
27 | Type : File
28 |
29 | ProcessId : 7936
30 | Handle : 112
31 | GrantedAccess : 1048608
32 | RawType : 37
33 | Flags : 0
34 | Name : \Device\HarddiskVolume3\Windows\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.190
35 | 41.1_none_b555e41d4684ddec
36 | TypeString : File
37 | Type : File
38 |
39 | ```
40 |
41 | Find open files based on process
42 |
43 | ```
44 | PS C:\Users\adamr> Get-Process pwsh | Find-OpenFile
45 |
46 | ProcessId : 22160
47 | Handle : 64
48 | GrantedAccess : 1048608
49 | RawType : 37
50 | Flags : 0
51 | Name : \Device\HarddiskVolume3\Windows\SysWOW64
52 | TypeString : File
53 | Type : File
54 |
55 | ProcessId : 22160
56 | Handle : 964
57 | GrantedAccess : 1048577
58 | RawType : 37
59 | Flags : 0
60 | Name : \Device\HarddiskVolume3\Windows\System32\en-US\winnlsres.dll.mui
61 | TypeString : File
62 | Type : File
63 | ```
64 |
65 | Find process locking a file
66 |
67 | ```
68 | PS C:\Users\adamr> Find-OpenFile -FilePath C:\Windows\System32\en-US\KernelBase.dll.mui
69 |
70 | NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
71 | ------ ----- ----- ------ -- -- -----------
72 | 115 226.44 176.13 0.00 1940 1 dwm
73 | 11 2.63 10.35 0.00 7792 1 DAX3API
74 | 26 11.29 37.09 35.92 5568 1 sihost
75 | 154 168.61 203.50 652.52 8340 1 explorer
76 | 27 12.43 8.03 66.62 9952 1 SettingSyncHost
77 | 36 40.49 90.16 19.86 9480 1 StartMenuExperienceHost
78 | 18 6.96 27.73 10.08 9712 1 RuntimeBroker
79 | 140 157.37 97.46 93.78 10356 1 SearchApp
80 | 36 44.05 50.38 33.58 10520 1 RuntimeBroker
81 | 103 212.27 52.13 79.25 11236 1 SkypeApp
82 | 29 10.79 6.42 35.92 8748 1 PowerToys
83 | 27 14.69 48.04 3.91 11624 1 LockApp
84 | ```
85 |
86 | Find processes accessing a folder or files within it
87 |
88 | ```
89 | PS C:\Users\adamr> Find-OpenFile -FilePath C:\Test
90 |
91 | NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
92 | ------ ----- ----- ------ -- -- -----------
93 | 56 50.62 74.23 10.42 15136 1 notepad
94 | ```
95 |
96 | ## Source
97 |
98 | - C# Code Forked from [this repository](https://github.com/Walkman100/FileLocks)
99 |
--------------------------------------------------------------------------------
/WalkmanLib.RestartManager.cs:
--------------------------------------------------------------------------------
1 | // RestartManager method
2 | //https://stackoverflow.com/a/3504251/2999220
3 | //https://stackoverflow.com/a/20623311/2999220
4 | //https://stackoverflow.com/a/20623302/2999220
5 | //https://gist.github.com/mlaily/9423f1855bb176d52a327f5874915a97
6 | //https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/april/net-matters-restart-manager-and-generic-method-compilation
7 | //https://devblogs.microsoft.com/oldnewthing/?p=8283
8 |
9 |
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Runtime.InteropServices;
13 | using System.Diagnostics;
14 | using System.ComponentModel;
15 |
16 | namespace WalkmanLib
17 | {
18 | public static class RestartManager
19 | {
20 | const int CCH_RM_MAX_APP_NAME = 255;
21 | const int CCH_RM_MAX_SVC_NAME = 63;
22 | const int ERROR_MORE_DATA = 234;
23 |
24 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/ns-restartmanager-rm_process_info
25 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
26 | public struct ProcessInfo
27 | {
28 | public UniqueProcess Process;
29 |
30 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
31 | public string AppName;
32 |
33 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
34 | public string ServiceShortName;
35 |
36 | public AppType ApplicationType;
37 | public uint AppStatus;
38 | public uint TSSessionId;
39 | [MarshalAs(UnmanagedType.Bool)]
40 | public bool Restartable;
41 | }
42 |
43 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/ns-restartmanager-rm_unique_process
44 | [StructLayout(LayoutKind.Sequential)]
45 | public struct UniqueProcess
46 | {
47 | public uint ProcessID;
48 | System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
49 | }
50 |
51 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/ne-restartmanager-rm_app_type
52 | // values: https://github.com/microsoft/msbuild/blob/2791d9d93e88325011eb6907579d6fdac0b1b62e/src/Tasks/LockCheck.cs#L101
53 | public enum AppType
54 | {
55 | RmUnknownApp = 0,
56 | RmMainWindow = 1,
57 | RmOtherWindow = 2,
58 | RmService = 3,
59 | RmExplorer = 4,
60 | RmConsole = 5,
61 | RmCritical = 1000
62 | }
63 |
64 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmregisterresources
65 | [DllImport("rstrtmgr.dll", SetLastError = true, CharSet = CharSet.Unicode)]
66 | static extern int RmRegisterResources(uint pSessionHandle,
67 | uint nFiles,
68 | string[] rgsFilenames,
69 | uint nApplications,
70 | [In] UniqueProcess[] rgApplications,
71 | uint nServices,
72 | string[] rgsServiceNames);
73 |
74 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmstartsession
75 | [DllImport("rstrtmgr.dll", SetLastError = true, CharSet = CharSet.Auto)]
76 | static extern int RmStartSession(out uint pSessionHandle,
77 | int dwSessionFlags,
78 | string strSessionKey);
79 |
80 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmendsession
81 | [DllImport("rstrtmgr.dll", SetLastError = true)]
82 | static extern int RmEndSession(uint pSessionHandle);
83 |
84 | //https://docs.microsoft.com/en-us/windows/win32/api/restartmanager/nf-restartmanager-rmgetlist
85 | [DllImport("rstrtmgr.dll", SetLastError = true)]
86 | static extern int RmGetList(uint dwSessionHandle,
87 | out uint pnProcInfoNeeded,
88 | ref uint pnProcInfo,
89 | [In, Out] ProcessInfo[] rgAffectedApps,
90 | ref uint lpdwRebootReasons);
91 |
92 | public static ProcessInfo[] GetLockingProcessInfos(string path)
93 | {
94 | uint handle;
95 | if (RmStartSession(out handle, 0, Guid.NewGuid().ToString()) != 0)
96 | throw new Exception("Could not begin session. Unable to determine file lockers.", new Win32Exception());
97 |
98 | try
99 | {
100 | uint ArrayLengthNeeded = 0,
101 | ArrayLength = 0,
102 | lpdwRebootReasons = 0; //RmRebootReasonNone;
103 |
104 | string[] resources = { path }; // Just checking on one resource.
105 |
106 | if (RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null) != 0)
107 | throw new Exception("Could not register resource.", new Win32Exception());
108 |
109 | switch (RmGetList(handle, out ArrayLengthNeeded, ref ArrayLength, null, ref lpdwRebootReasons))
110 | {
111 | case ERROR_MORE_DATA:
112 | ProcessInfo[] processInfos = new ProcessInfo[ArrayLengthNeeded];
113 | ArrayLength = ArrayLengthNeeded;
114 |
115 | if (RmGetList(handle, out ArrayLengthNeeded, ref ArrayLength, processInfos, ref lpdwRebootReasons) != 0)
116 | throw new Exception("Could not list processes locking resource.", new Win32Exception());
117 |
118 | return processInfos;
119 | case 0:
120 | return new ProcessInfo[0];
121 | default:
122 | throw new Exception("Could not list processes locking resource. Failed to get size of result.", new Win32Exception());
123 | }
124 | }
125 | finally
126 | {
127 | RmEndSession(handle);
128 | }
129 | }
130 |
131 | public static List GetLockingProcesses(string path)
132 | {
133 | List processes = new List();
134 | foreach (ProcessInfo pI in GetLockingProcessInfos(path))
135 | {
136 | try
137 | {
138 | Process process = Process.GetProcessById((int)pI.Process.ProcessID);
139 | processes.Add(process);
140 | }
141 | catch (ArgumentException) { }
142 | }
143 | return processes;
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/Tests/FindOpenFile.Tests.ps1:
--------------------------------------------------------------------------------
1 | BeforeAll {
2 | # Get configuration from environment variable, default to 'Debug' if not set
3 | $Configuration = $env:BUILD_CONFIGURATION
4 | if ([string]::IsNullOrWhiteSpace($Configuration)) {
5 | $Configuration = 'Debug'
6 | }
7 |
8 | # Validate configuration value
9 | if ($Configuration -notin @('Debug', 'Release')) {
10 | throw "Invalid BUILD_CONFIGURATION value: '$Configuration'. Must be 'Debug' or 'Release'."
11 | }
12 |
13 | # Import the module from the specified build configuration directory
14 | $modulePath = Join-Path $PSScriptRoot "..\bin\$Configuration\netstandard2.0\FindOpenFile.psd1"
15 |
16 | if (-not (Test-Path $modulePath)) {
17 | throw "Module not found at $modulePath. Please build the project in $Configuration configuration first."
18 | }
19 |
20 | Import-Module $modulePath -Force
21 | }
22 |
23 | Describe 'Find-OpenFile Module' {
24 | Context 'Module Import' {
25 | It 'Should import the module successfully' {
26 | $module = Get-Module -Name FindOpenFile
27 | $module | Should -Not -BeNull
28 | }
29 |
30 | It 'Should export the Find-OpenFile cmdlet' {
31 | $command = Get-Command -Name Find-OpenFile -ErrorAction SilentlyContinue
32 | $command | Should -Not -BeNull
33 | }
34 |
35 | It 'Should have correct module version' {
36 | $module = Get-Module -Name FindOpenFile
37 | $module.Version.ToString() | Should -Match '^\d+\.\d+\.\d+$'
38 | }
39 | }
40 |
41 | Context 'Find-OpenFile Cmdlet Structure' {
42 | It 'Should have correct parameter sets' {
43 | $command = Get-Command -Name Find-OpenFile
44 | $parameterSets = $command.ParameterSets.Name
45 | $parameterSets | Should -Contain 'All'
46 | $parameterSets | Should -Contain 'File'
47 | $parameterSets | Should -Contain 'Process'
48 | }
49 |
50 | It 'Should have FilePath parameter' {
51 | $command = Get-Command -Name Find-OpenFile
52 | $command.Parameters.ContainsKey('FilePath') | Should -Be $true
53 | }
54 |
55 | It 'Should have Process parameter' {
56 | $command = Get-Command -Name Find-OpenFile
57 | $command.Parameters.ContainsKey('Process') | Should -Be $true
58 | }
59 |
60 | It 'Should have System parameter' {
61 | $command = Get-Command -Name Find-OpenFile
62 | $command.Parameters.ContainsKey('System') | Should -Be $true
63 | }
64 | }
65 |
66 | Context 'Find-OpenFile - All Parameter Set' {
67 | It 'Should return file handles when called without parameters' {
68 | $result = Find-OpenFile
69 | $result | Should -Not -BeNull
70 | $result.Count | Should -BeGreaterThan 0
71 | }
72 |
73 | It 'Should return objects with expected properties' {
74 | $result = Find-OpenFile | Select-Object -First 1
75 | $result.PSObject.Properties.Name | Should -Contain 'ProcessId'
76 | $result.PSObject.Properties.Name | Should -Contain 'Handle'
77 | $result.PSObject.Properties.Name | Should -Contain 'Name'
78 | $result.PSObject.Properties.Name | Should -Contain 'Type'
79 | }
80 |
81 | It 'Should return system handles when -System switch is used' {
82 | $result = Find-OpenFile -System
83 | $result | Should -Not -BeNull
84 | $result.Count | Should -BeGreaterThan 0
85 | }
86 |
87 | It 'Should return more handles with -System than without' {
88 | $normalHandles = @(Find-OpenFile)
89 | $systemHandles = @(Find-OpenFile -System)
90 | $systemHandles.Count | Should -BeGreaterOrEqual $normalHandles.Count
91 | }
92 | }
93 |
94 | Context 'Find-OpenFile - Process Parameter Set' {
95 | It 'Should accept Process object from pipeline' {
96 | $currentProcess = Get-Process -Id $PID
97 | $result = $currentProcess | Find-OpenFile
98 | $result | Should -Not -BeNull
99 | }
100 |
101 | It 'Should return handles for the specified process' {
102 | $currentProcess = Get-Process -Id $PID
103 | $result = Find-OpenFile -Process $currentProcess
104 | $result | Should -Not -BeNull
105 |
106 | # All results should be from the specified process
107 | $result | ForEach-Object {
108 | $_.ProcessId | Should -Be $currentProcess.Id
109 | }
110 | }
111 |
112 | It 'Should work with Get-Process pipeline' {
113 | $result = Get-Process -Id $PID | Find-OpenFile
114 | $result | Should -Not -BeNull
115 | }
116 | }
117 |
118 | Context 'Find-OpenFile - FilePath Parameter Set' {
119 | BeforeAll {
120 | # Create a temporary file that we can lock
121 | $script:testFile = Join-Path $TestDrive 'testfile.txt'
122 | 'Test content' | Out-File -FilePath $script:testFile -Force
123 | }
124 |
125 | It 'Should accept FilePath parameter' {
126 | # Lock the file by opening it
127 | $fileStream = [System.IO.File]::Open($script:testFile, 'Open', 'Read', 'None')
128 |
129 | try {
130 | $result = Find-OpenFile -FilePath $script:testFile
131 | $result | Should -Not -BeNull
132 | $result.Count | Should -BeGreaterThan 0
133 | }
134 | finally {
135 | $fileStream.Close()
136 | $fileStream.Dispose()
137 | }
138 | }
139 |
140 | It 'Should accept FilePath from pipeline' {
141 | # Lock the file by opening it
142 | $fileStream = [System.IO.File]::Open($script:testFile, 'Open', 'Read', 'None')
143 |
144 | try {
145 | $result = $script:testFile | Find-OpenFile
146 | $result | Should -Not -BeNull
147 | }
148 | finally {
149 | $fileStream.Close()
150 | $fileStream.Dispose()
151 | }
152 | }
153 |
154 | It 'Should return Process objects for locked files' {
155 | # Lock the file by opening it
156 | $fileStream = [System.IO.File]::Open($script:testFile, 'Open', 'Read', 'None')
157 |
158 | try {
159 | $result = Find-OpenFile -FilePath $script:testFile
160 | $result | Should -Not -BeNull
161 |
162 | # Result should contain process information
163 | $result | ForEach-Object {
164 | $_ | Should -BeOfType [System.Diagnostics.Process]
165 | }
166 |
167 | # One of the processes should be the current process
168 | $result.Id | Should -Contain $PID
169 | }
170 | finally {
171 | $fileStream.Close()
172 | $fileStream.Dispose()
173 | }
174 | }
175 |
176 | It 'Should handle non-existent file paths gracefully' {
177 | $nonExistentFile = Join-Path $TestDrive 'nonexistent.txt'
178 | { Find-OpenFile -FilePath $nonExistentFile } | Should -Not -Throw
179 | }
180 | }
181 |
182 | Context 'Find-OpenFile - Directory Support' {
183 | BeforeAll {
184 | # Create a temporary directory with a file
185 | $script:testDir = Join-Path $TestDrive 'testfolder'
186 | New-Item -Path $script:testDir -ItemType Directory -Force | Out-Null
187 | $script:testDirFile = Join-Path $script:testDir 'testfile.txt'
188 | 'Test content' | Out-File -FilePath $script:testDirFile -Force
189 | }
190 |
191 | It 'Should not throw error when checking a directory' {
192 | # This is the main fix - previously this would throw:
193 | # "Could not list processes locking resource. Failed to get size of result."
194 | { Find-OpenFile -FilePath $script:testDir } | Should -Not -Throw
195 | }
196 |
197 | It 'Should return Process objects when checking a directory with open handles' {
198 | # Lock a file inside the directory
199 | $fileStream = [System.IO.File]::Open($script:testDirFile, 'Open', 'Read', 'None')
200 |
201 | try {
202 | $result = Find-OpenFile -FilePath $script:testDir
203 | # The result may or may not include the process depending on how the OS reports handles
204 | # The key test is that it doesn't throw an error
205 | if ($result) {
206 | $result | ForEach-Object {
207 | $_ | Should -BeOfType [System.Diagnostics.Process]
208 | }
209 | }
210 | }
211 | finally {
212 | $fileStream.Close()
213 | $fileStream.Dispose()
214 | }
215 | }
216 |
217 | It 'Should accept directory path from pipeline' {
218 | { $script:testDir | Find-OpenFile } | Should -Not -Throw
219 | }
220 |
221 | It 'Should handle non-existent directory paths gracefully' {
222 | $nonExistentDir = Join-Path $TestDrive 'nonexistentfolder'
223 | # For non-existent paths, it falls back to the RestartManager approach
224 | # which may or may not throw depending on Windows version
225 | # The key is that it shouldn't throw the "Failed to get size of result" error for directories
226 | { Find-OpenFile -FilePath $nonExistentDir } | Should -Not -Throw
227 | }
228 | }
229 |
230 | Context 'Platform Support' {
231 | It 'Should only work on Windows' {
232 | if ($IsLinux -or $IsMacOS) {
233 | { Find-OpenFile } | Should -Throw "*only supported on Windows*"
234 | }
235 | else {
236 | { Find-OpenFile } | Should -Not -Throw
237 | }
238 | }
239 | }
240 |
241 | Context 'Output Validation' {
242 | It 'Should return enumerable results' {
243 | $result = Find-OpenFile
244 | $result | Should -Not -BeNull
245 | $result.GetType().IsArray | Should -Be $true
246 | }
247 |
248 | It 'Should have valid ProcessId values' {
249 | $result = Find-OpenFile | Select-Object -First 5
250 | $result | ForEach-Object {
251 | $_.ProcessId | Should -BeOfType [System.Int32]
252 | $_.ProcessId | Should -BeGreaterThan 0
253 | }
254 | }
255 |
256 | It 'Should have valid Handle values' {
257 | $result = Find-OpenFile | Select-Object -First 5
258 | $result | ForEach-Object {
259 | $_.Handle | Should -Not -BeNullOrEmpty
260 | }
261 | }
262 | }
263 |
264 | Context 'Real-world Scenarios' {
265 | It 'Should find PowerShell process files' {
266 | $pwshProcesses = Get-Process pwsh -ErrorAction SilentlyContinue
267 | if ($pwshProcesses) {
268 | $result = $pwshProcesses | Select-Object -First 1 | Find-OpenFile
269 | $result | Should -Not -BeNull
270 | $result.Count | Should -BeGreaterThan 0
271 | }
272 | else {
273 | Set-ItResult -Skipped -Because "No PowerShell processes found"
274 | }
275 | }
276 |
277 | It 'Should handle system files without throwing errors or return valid results' {
278 | # Create a test file in a location we control
279 | $testSystemFile = Join-Path $TestDrive 'systemtest.log'
280 | 'Test log content' | Out-File -FilePath $testSystemFile -Force
281 |
282 | # Lock the file and verify we can find the lock
283 | $fileStream = [System.IO.File]::Open($testSystemFile, 'Open', 'ReadWrite', 'None')
284 |
285 | try {
286 | $result = Find-OpenFile -FilePath $testSystemFile
287 | # Should either succeed with results or complete without error
288 | $result | Should -Not -BeNull
289 | $result.Count | Should -BeGreaterThan 0
290 | $result.Id | Should -Contain $PID
291 | }
292 | finally {
293 | $fileStream.Close()
294 | $fileStream.Dispose()
295 | }
296 | }
297 | }
298 | }
299 |
300 | AfterAll {
301 | # Clean up - remove the module
302 | Remove-Module FindOpenFile -Force -ErrorAction SilentlyContinue
303 | }
304 |
--------------------------------------------------------------------------------
/GetAllHandles.cs:
--------------------------------------------------------------------------------
1 | // Get all system open handles method - uses NTQuerySystemInformation and NTQueryObject
2 | //https://gist.github.com/i-e-b/2290426
3 | //https://stackoverflow.com/a/13735033/2999220
4 |
5 |
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Runtime.ConstrainedExecution;
9 | using System.Runtime.InteropServices;
10 |
11 | namespace GetAllHandles
12 | {
13 | public static class HandleUtil
14 | {
15 | public enum HandleType
16 | {
17 | Unknown,
18 | Other,
19 | File, Directory, SymbolicLink, Key,
20 | Process, Thread, Job, Session, WindowStation,
21 | Timer, Desktop, Semaphore, Token,
22 | Mutant, Section, Event, KeyedEvent, IoCompletion, IoCompletionReserve,
23 | TpWorkerFactory, AlpcPort, WmiGuid, UserApcReserve,
24 | }
25 |
26 | public enum NT_STATUS
27 | {
28 | STATUS_SUCCESS = 0x00000000,
29 | STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
30 | STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
31 | }
32 |
33 | public enum SYSTEM_INFORMATION_CLASS
34 | {
35 | SystemBasicInformation = 0,
36 | SystemPerformanceInformation = 2,
37 | SystemTimeOfDayInformation = 3,
38 | SystemProcessInformation = 5,
39 | SystemProcessorPerformanceInformation = 8,
40 | SystemHandleInformation = 16,
41 | SystemInterruptInformation = 23,
42 | SystemExceptionInformation = 33,
43 | SystemRegistryQuotaInformation = 37,
44 | SystemLookasideInformation = 45
45 | }
46 |
47 | public enum OBJECT_INFORMATION_CLASS
48 | {
49 | ObjectBasicInformation = 0,
50 | ObjectNameInformation = 1,
51 | ObjectTypeInformation = 2,
52 | ObjectAllTypesInformation = 3,
53 | ObjectHandleInformation = 4
54 | }
55 |
56 | [StructLayout(LayoutKind.Sequential)]
57 | private struct SystemHandleEntry
58 | {
59 | public int OwnerProcessId;
60 | public byte ObjectTypeNumber;
61 | public byte Flags;
62 | public ushort Handle;
63 | public IntPtr Object;
64 | public int GrantedAccess;
65 | }
66 |
67 | [DllImport("ntdll.dll")]
68 | internal static extern NT_STATUS NtQuerySystemInformation(
69 | [In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
70 | [In] IntPtr SystemInformation,
71 | [In] int SystemInformationLength,
72 | [Out] out int ReturnLength);
73 |
74 | [DllImport("ntdll.dll")]
75 | internal static extern NT_STATUS NtQueryObject(
76 | [In] IntPtr Handle,
77 | [In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
78 | [In] IntPtr ObjectInformation,
79 | [In] int ObjectInformationLength,
80 | [Out] out int ReturnLength);
81 |
82 | [DllImport("kernel32.dll")]
83 | internal static extern IntPtr GetCurrentProcess();
84 |
85 | [DllImport("kernel32.dll", SetLastError = true)]
86 | public static extern IntPtr OpenProcess(
87 | [In] int dwDesiredAccess,
88 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
89 | [In] int dwProcessId);
90 |
91 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
92 | [DllImport("kernel32.dll", SetLastError = true)]
93 | [return: MarshalAs(UnmanagedType.Bool)]
94 | internal static extern bool CloseHandle(
95 | [In] IntPtr hObject);
96 |
97 | [DllImport("kernel32.dll", SetLastError = true)]
98 | [return: MarshalAs(UnmanagedType.Bool)]
99 | public static extern bool DuplicateHandle(
100 | [In] IntPtr hSourceProcessHandle,
101 | [In] IntPtr hSourceHandle,
102 | [In] IntPtr hTargetProcessHandle,
103 | [Out] out IntPtr lpTargetHandle,
104 | [In] int dwDesiredAccess,
105 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
106 | [In] int dwOptions);
107 |
108 | public class HandleInfo
109 | {
110 | public int ProcessId { get; private set; }
111 | public ushort Handle { get; private set; }
112 | public int GrantedAccess { get; private set; }
113 | public byte RawType { get; private set; }
114 |
115 | public HandleInfo(int processId, ushort handle, int grantedAccess, byte rawType)
116 | {
117 | ProcessId = processId;
118 | Handle = handle;
119 | GrantedAccess = grantedAccess;
120 | RawType = rawType;
121 | }
122 |
123 | private static Dictionary _rawTypeMap = new Dictionary();
124 |
125 | private string _name, _typeStr;
126 | private HandleType _type;
127 |
128 | public string Name { get { if (_name == null) initTypeAndName(); return _name; } }
129 | public string TypeString { get { if (_typeStr == null) initType(); return _typeStr; } }
130 | public HandleType Type { get { if (_typeStr == null) initType(); return _type; } }
131 |
132 | private void initType()
133 | {
134 | if (_rawTypeMap.ContainsKey(RawType))
135 | {
136 | _typeStr = _rawTypeMap[RawType];
137 | _type = HandleTypeFromString(_typeStr);
138 | }
139 | else
140 | initTypeAndName();
141 | }
142 |
143 | bool _typeAndNameAttempted = false;
144 |
145 | private void initTypeAndName()
146 | {
147 | if (_typeAndNameAttempted)
148 | return;
149 | _typeAndNameAttempted = true;
150 |
151 | IntPtr sourceProcessHandle = IntPtr.Zero;
152 | IntPtr handleDuplicate = IntPtr.Zero;
153 | try
154 | {
155 | sourceProcessHandle = OpenProcess(0x40 /* dup_handle */, true, ProcessId);
156 |
157 | // To read info about a handle owned by another process we must duplicate it into ours
158 | // For simplicity, current process handles will also get duplicated; remember that process handles cannot be compared for equality
159 | if (!DuplicateHandle(sourceProcessHandle, (IntPtr)Handle, GetCurrentProcess(), out handleDuplicate, 0, false, 2 /* same_access */))
160 | return;
161 |
162 | // Query the object type
163 | if (_rawTypeMap.ContainsKey(RawType))
164 | _typeStr = _rawTypeMap[RawType];
165 | else
166 | {
167 | int length;
168 | NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
169 | IntPtr ptr = IntPtr.Zero;
170 | try
171 | {
172 | ptr = Marshal.AllocHGlobal(length);
173 | if (NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
174 | return;
175 | _typeStr = Marshal.PtrToStringUni((IntPtr)((int)ptr + 0x58 + 2 * IntPtr.Size));
176 | _rawTypeMap[RawType] = _typeStr;
177 | }
178 | finally
179 | {
180 | Marshal.FreeHGlobal(ptr);
181 | }
182 | }
183 | _type = HandleTypeFromString(_typeStr);
184 |
185 | // Query the object name
186 | if (_typeStr != null && GrantedAccess != 0x0012019f && GrantedAccess != 0x00120189 && GrantedAccess != 0x120089) // don't query some objects that could get stuck
187 | {
188 | int length;
189 | NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, IntPtr.Zero, 0, out length);
190 | IntPtr ptr = IntPtr.Zero;
191 | try
192 | {
193 | ptr = Marshal.AllocHGlobal(length);
194 | if (NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
195 | return;
196 | _name = Marshal.PtrToStringUni((IntPtr)((int)ptr + 2 * IntPtr.Size));
197 | }
198 | finally
199 | {
200 | Marshal.FreeHGlobal(ptr);
201 | }
202 | }
203 | }
204 | finally
205 | {
206 | CloseHandle(sourceProcessHandle);
207 | if (handleDuplicate != IntPtr.Zero)
208 | CloseHandle(handleDuplicate);
209 | }
210 | }
211 |
212 | public static HandleType HandleTypeFromString(string typeStr)
213 | {
214 | switch (typeStr)
215 | {
216 | case null:
217 | return HandleType.Unknown;
218 | case "File":
219 | return HandleType.File;
220 | case "IoCompletion":
221 | return HandleType.IoCompletion;
222 | case "TpWorkerFactory":
223 | return HandleType.TpWorkerFactory;
224 | case "ALPC Port":
225 | return HandleType.AlpcPort;
226 | case "Event":
227 | return HandleType.Event;
228 | case "Section":
229 | return HandleType.Section;
230 | case "Directory":
231 | return HandleType.Directory;
232 | case "KeyedEvent":
233 | return HandleType.KeyedEvent;
234 | case "Process":
235 | return HandleType.Process;
236 | case "Key":
237 | return HandleType.Key;
238 | case "SymbolicLink":
239 | return HandleType.SymbolicLink;
240 | case "Thread":
241 | return HandleType.Thread;
242 | case "Mutant":
243 | return HandleType.Mutant;
244 | case "WindowStation":
245 | return HandleType.WindowStation;
246 | case "Timer":
247 | return HandleType.Timer;
248 | case "Semaphore":
249 | return HandleType.Semaphore;
250 | case "Desktop":
251 | return HandleType.Desktop;
252 | case "Token":
253 | return HandleType.Token;
254 | case "Job":
255 | return HandleType.Job;
256 | case "Session":
257 | return HandleType.Session;
258 | case "IoCompletionReserve":
259 | return HandleType.IoCompletionReserve;
260 | case "WmiGuid":
261 | return HandleType.WmiGuid;
262 | case "UserApcReserve":
263 | return HandleType.UserApcReserve;
264 | default:
265 | return HandleType.Other;
266 | }
267 | }
268 | }
269 |
270 | public static IEnumerable GetHandles()
271 | {
272 | // Attempt to retrieve the handle information
273 | int length = 0x10000;
274 | IntPtr ptr = IntPtr.Zero;
275 | try
276 | {
277 | while (true)
278 | {
279 | ptr = Marshal.AllocHGlobal(length);
280 | int wantedLength;
281 | var result = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out wantedLength);
282 | if (result == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
283 | {
284 | length = Math.Max(length, wantedLength);
285 | Marshal.FreeHGlobal(ptr);
286 | ptr = IntPtr.Zero;
287 | }
288 | else if (result == NT_STATUS.STATUS_SUCCESS)
289 | break;
290 | else
291 | throw new Exception("Failed to retrieve system handle information.");
292 | }
293 |
294 | int handleCount = IntPtr.Size == 4 ? Marshal.ReadInt32(ptr) : (int)Marshal.ReadInt64(ptr);
295 | int offset = IntPtr.Size;
296 | int size = Marshal.SizeOf(typeof(SystemHandleEntry));
297 | for (int i = 0; i < handleCount; i++)
298 | {
299 | var struc = (SystemHandleEntry)Marshal.PtrToStructure((IntPtr)((int)ptr + offset), typeof(SystemHandleEntry));
300 |
301 | // see https://gist.github.com/i-e-b/2290426#gistcomment-3234676
302 | if (!(struc.GrantedAccess == 0x001a019f && struc.Flags == 2))
303 | yield return new HandleInfo(struc.OwnerProcessId, struc.Handle, struc.GrantedAccess, struc.ObjectTypeNumber);
304 | offset += size;
305 | }
306 | }
307 | finally
308 | {
309 | if (ptr != IntPtr.Zero)
310 | Marshal.FreeHGlobal(ptr);
311 | }
312 | }
313 | }
314 | }
--------------------------------------------------------------------------------
/RestartManager.cs:
--------------------------------------------------------------------------------
1 | // RestartManager method
2 | //https://stackoverflow.com/a/3504251/2999220
3 | //https://stackoverflow.com/a/20623311/2999220
4 | //https://stackoverflow.com/a/20623302/2999220
5 | //https://gist.github.com/mlaily/9423f1855bb176d52a327f5874915a97
6 | //https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/april/net-matters-restart-manager-and-generic-method-compilation
7 | //https://devblogs.microsoft.com/oldnewthing/?p=8283
8 |
9 |
10 | using System;
11 | using System.Collections.Generic;
12 | using System.Runtime.InteropServices;
13 | using System.Diagnostics;
14 |
15 | namespace RestartManager
16 | {
17 | public static class FileUtil
18 | {
19 | ///
20 | /// Uniquely identifies a process by its PID and the time the process began.
21 | /// An array of RM_UNIQUE_PROCESS structures can be passed to the RmRegisterResources function.
22 | ///
23 | [StructLayout(LayoutKind.Sequential)]
24 | struct RM_UNIQUE_PROCESS
25 | {
26 | public int dwProcessId;
27 | public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
28 | }
29 |
30 | enum RM_APP_TYPE
31 | {
32 | ///
33 | /// The application cannot be classified as any other type.
34 | /// An application of this type can only be shut down by a forced shutdown.
35 | ///
36 | RmUnknownApp = 0,
37 | ///
38 | /// A Windows application run as a stand-alone process that displays a top-level window.
39 | ///
40 | RmMainWindow = 1,
41 | ///
42 | /// A Windows application that does not run as a stand-alone process and does not display a top-level window.
43 | ///
44 | RmOtherWindow = 2,
45 | ///
46 | /// The application is a Windows service.
47 | ///
48 | RmService = 3,
49 | ///
50 | /// The application is Windows Explorer.
51 | ///
52 | RmExplorer = 4,
53 | ///
54 | /// The application is a stand-alone console application.
55 | ///
56 | RmConsole = 5,
57 | ///
58 | /// A system restart is required to complete the installation because a process cannot be shut down.
59 | /// The process cannot be shut down because of the following reasons.
60 | /// The process may be a critical process.
61 | /// The current user may not have permission to shut down the process.
62 | /// The process may belong to the primary installer that started the Restart Manager.
63 | ///
64 | RmCritical = 1000
65 | }
66 |
67 | [Flags]
68 | public enum RM_APP_STATUS
69 | {
70 | ///
71 | /// The application is in a state that is not described by any other enumerated state.
72 | ///
73 | RmStatusUnknown = 0x0,
74 | ///
75 | /// The application is currently running.
76 | ///
77 | RmStatusRunning = 0x1,
78 | ///
79 | /// The Restart Manager has stopped the application.
80 | ///
81 | RmStatusStopped = 0x2,
82 | ///
83 | /// An action outside the Restart Manager has stopped the application.
84 | ///
85 | RmStatusStoppedOther = 0x4,
86 | ///
87 | /// The Restart Manager has restarted the application.
88 | ///
89 | RmStatusRestarted = 0x8,
90 | ///
91 | /// The Restart Manager encountered an error when stopping the application.
92 | ///
93 | RmStatusErrorOnStop = 0x10,
94 | ///
95 | /// The Restart Manager encountered an error when restarting the application.
96 | ///
97 | RmStatusErrorOnRestart = 0x20,
98 | ///
99 | /// Shutdown is masked by a filter.
100 | ///
101 | RmStatusShutdownMasked = 0x40,
102 | ///
103 | /// Restart is masked by a filter.
104 | ///
105 | RmStatusRestartMasked = 0x80,
106 | }
107 |
108 | [Flags]
109 | public enum RM_REBOOT_REASON
110 | {
111 | ///
112 | /// A system restart is not required.
113 | ///
114 | RmRebootReasonNone = 0x0,
115 | ///
116 | /// The current user does not have sufficient privileges to shut down one or more processes.
117 | ///
118 | RmRebootReasonPermissionDenied = 0x1,
119 | ///
120 | /// One or more processes are running in another Terminal Services session.
121 | ///
122 | RmRebootReasonSessionMismatch = 0x2,
123 | ///
124 | /// A system restart is needed because one or more processes to be shut down are critical processes.
125 | ///
126 | RmRebootReasonCriticalProcess = 0x4,
127 | ///
128 | /// A system restart is needed because one or more services to be shut down are critical services.
129 | ///
130 | RmRebootReasonCriticalService = 0x8,
131 | ///
132 | /// A system restart is needed because the current process must be shut down.
133 | ///
134 | RmRebootReasonDetectedSelf = 0x10,
135 | }
136 |
137 | public enum RmResult
138 | {
139 | ///
140 | /// The resources specified have been registered.
141 | ///
142 | ERROR_SUCCESS = 0,
143 | ///
144 | /// A Restart Manager function could not obtain a Registry write mutex in the allotted time.
145 | /// A system restart is recommended because further use of the Restart Manager is likely to fail.
146 | ///
147 | ERROR_SEM_TIMEOUT = 121,
148 | ///
149 | /// One or more arguments are not correct.
150 | /// This error value is returned by Restart Manager function if a NULL pointer or 0 is passed in a parameter that requires a non-null and non-zero value.
151 | ///
152 | ERROR_BAD_ARGUMENTS = 160,
153 | ///
154 | /// An operation was unable to read or write to the registry.
155 | ///
156 | ERROR_WRITE_FAULT = 29,
157 | ///
158 | /// A Restart Manager operation could not complete because not enough memory was available.
159 | ///
160 | ERROR_OUTOFMEMORY = 14,
161 | ///
162 | /// An invalid handle was passed to the function.
163 | /// No Restart Manager session exists for the handle supplied.
164 | ///
165 | ERROR_INVALID_HANDLE = 6,
166 | ///
167 | /// The maximum number of sessions has been reached.
168 | ///
169 | ERROR_MAX_SESSIONS_REACHED = 353,
170 | ///
171 | /// This error value is returned by the RmGetList function if the rgAffectedApps buffer is too small to hold all application information in the list.
172 | ///
173 | ERROR_MORE_DATA = 234,
174 | ///
175 | /// The current operation is canceled by user.
176 | ///
177 | ERROR_CANCELLED = 1223
178 | }
179 |
180 | ///
181 | /// Describes an application that is to be registered with the Restart Manager.
182 | ///
183 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
184 | struct RM_PROCESS_INFO
185 | {
186 | public const int CCH_RM_MAX_APP_NAME = 255;
187 | public const int CCH_RM_MAX_SVC_NAME = 63;
188 | public const int RM_INVALID_SESSION = -1;
189 |
190 | ///
191 | /// Contains an RM_UNIQUE_PROCESS structure that uniquely identifies the application by its PID and the time the process began.
192 | ///
193 | public RM_UNIQUE_PROCESS Process;
194 |
195 | ///
196 | /// If the process is a service, this parameter returns the long name for the service.
197 | /// If the process is not a service, this parameter returns the user-friendly name for the application.
198 | /// If the process is a critical process, and the installer is run with elevated privileges,
199 | /// this parameter returns the name of the executable file of the critical process.
200 | /// If the process is a critical process, and the installer is run as a service, this parameter returns the long name of the critical process.
201 | ///
202 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
203 | public string strAppName;
204 |
205 | ///
206 | /// If the process is a service, this is the short name for the service.
207 | /// This member is not used if the process is not a service.
208 | ///
209 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
210 | public string strServiceShortName;
211 |
212 | ///
213 | /// Contains an RM_APP_TYPE enumeration value that specifies the type of application as RmUnknownApp, RmMainWindow, RmOtherWindow, RmService, RmExplorer or RmCritical.
214 | ///
215 | public RM_APP_TYPE ApplicationType;
216 |
217 | ///
218 | /// Contains a bit mask that describes the current status of the application.
219 | /// See the RM_APP_STATUS enumeration.
220 | ///
221 | public RM_APP_STATUS AppStatus;
222 |
223 | ///
224 | /// Contains the Terminal Services session ID of the process.
225 | /// If the terminal session of the process cannot be determined, the value of this member is set to RM_INVALID_SESSION (-1).
226 | /// This member is not used if the process is a service or a system critical process.
227 | ///
228 | public int TSSessionId;
229 |
230 | ///
231 | /// TRUE if the application can be restarted by the Restart Manager; otherwise, FALSE.
232 | /// This member is always TRUE if the process is a service.
233 | /// This member is always FALSE if the process is a critical system process.
234 | ///
235 | [MarshalAs(UnmanagedType.Bool)]
236 | public bool bRestartable;
237 | }
238 |
239 | ///
240 | /// Registers resources to a Restart Manager session.
241 | /// The Restart Manager uses the list of resources registered with the session to determine which applications
242 | /// and services must be shut down and restarted. Resources can be identified by filenames, service short names,
243 | /// or RM_UNIQUE_PROCESS structures that describe running applications.
244 | /// The RmRegisterResources function can be used by a primary or secondary installer.
245 | ///
246 | /// A handle to an existing Restart Manager session.
247 | /// The number of files being registered.
248 | /// An array of null-terminated strings of full filename paths. This parameter can be NULL if nFiles is 0.
249 | /// The number of processes being registered.
250 | /// An array of RM_UNIQUE_PROCESS structures. This parameter can be NULL if nApplications is 0.
251 | /// The number of services to be registered.
252 | /// An array of null-terminated strings of service short names. This parameter can be NULL if nServices is 0.
253 | /// This is the most recent error received. The function can return one of the system error codes that are defined in Winerror.h.
254 | [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
255 | static extern RmResult RmRegisterResources(int dwSessionHandle,
256 | uint nFiles,
257 | string[] rgsFilenames,
258 | uint nApplications,
259 | [In] RM_UNIQUE_PROCESS[] rgApplications,
260 | uint nServices,
261 | string[] rgsServiceNames);
262 |
263 | ///
264 | /// Starts a new Restart Manager session.
265 | /// A maximum of 64 Restart Manager sessions per user session can be open on the system at the same time.
266 | /// When this function starts a session, it returns a session handle and session key that can be used in subsequent calls to the Restart Manager API.
267 | ///
268 | ///
269 | /// A pointer to the handle of a Restart Manager session.
270 | /// The session handle can be passed in subsequent calls to the Restart Manager API.
271 | ///
272 | /// Reserved. This parameter should be 0.
273 | ///
274 | /// A null-terminated string that contains the session key to the new session.
275 | /// The string must be allocated before calling the RmStartSession function.
276 | ///
277 | /// This is the most recent error received. The function can return one of the system error codes that are defined in Winerror.h.
278 | [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
279 | static extern RmResult RmStartSession(out int pSessionHandle,
280 | int dwSessionFlags,
281 | string strSessionKey);
282 |
283 | ///
284 | /// Ends the Restart Manager session.
285 | /// This function should be called by the primary installer that has previously started the session by calling the RmStartSession function.
286 | /// The RmEndSession function can be called by a secondary installer that is joined to the session once no more resources need to be registered
287 | /// by the secondary installer.
288 | ///
289 | /// A handle to an existing Restart Manager session.
290 | /// This is the most recent error received. The function can return one of the system error codes that are defined in Winerror.h.
291 | [DllImport("rstrtmgr.dll")]
292 | static extern RmResult RmEndSession(int dwSessionHandle);
293 |
294 | ///
295 | /// Gets a list of all applications and services that are currently using resources that have been registered with the Restart Manager session.
296 | ///
297 | /// A handle to an existing Restart Manager session.
298 | ///
299 | /// A pointer to an array size necessary to receive RM_PROCESS_INFO structures required to return information for all affected applications and services.
300 | ///
301 | /// A pointer to the total number of RM_PROCESS_INFO structures in an array and number of structures filled.
302 | ///
303 | /// An array of RM_PROCESS_INFO structures that list the applications and services using resources that have been registered with the session.
304 | ///
305 | ///
306 | /// Pointer to location that receives a value of the RM_REBOOT_REASON enumeration that describes the reason a system restart is needed.
307 | ///
308 | /// This is the most recent error received. The function can return one of the system error codes that are defined in Winerror.h.
309 | [DllImport("rstrtmgr.dll")]
310 | static extern RmResult RmGetList(int dwSessionHandle,
311 | out uint pnProcInfoNeeded,
312 | ref uint pnProcInfo,
313 | [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
314 | out RM_REBOOT_REASON lpdwRebootReasons);
315 |
316 | public static List GetProcessesLockingFile(string path)
317 | {
318 | int handle;
319 | if (RmStartSession(out handle, 0, Guid.NewGuid().ToString()) != RmResult.ERROR_SUCCESS)
320 | throw new Exception("Could not begin session. Unable to determine file lockers.");
321 |
322 | try
323 | {
324 | string[] resources = { path }; // Just checking on one resource.
325 | if (RmRegisterResources(handle, (uint)resources.LongLength, resources, 0, null, 0, null) != RmResult.ERROR_SUCCESS)
326 | throw new Exception("Could not register resource.");
327 |
328 | // The first try is done expecting at most ten processes to lock the file.
329 | uint arraySize = 10;
330 | RmResult result;
331 | do
332 | {
333 | RM_PROCESS_INFO[] array = new RM_PROCESS_INFO[arraySize];
334 | uint requiredArraySize;
335 | RM_REBOOT_REASON lpdwRebootReasons;
336 | result = RmGetList(handle, out requiredArraySize, ref arraySize, array, out lpdwRebootReasons);
337 |
338 | if (result == RmResult.ERROR_SUCCESS)
339 | {
340 | // Adjust the array length to fit the actual count.
341 | Array.Resize(ref array, (int)requiredArraySize);
342 |
343 | List processes = new List((int)arraySize);
344 | // Enumerate all of the results and add them to the
345 | // list to be returned
346 | for (int i = 0; i < arraySize; i++)
347 | {
348 | try
349 | {
350 | processes.Add(Process.GetProcessById(array[i].Process.dwProcessId));
351 | }
352 | // catch the error -- in case the process is no longer running
353 | catch (ArgumentException) { }
354 | }
355 | return processes;
356 | }
357 | else if (result == RmResult.ERROR_MORE_DATA)
358 | {
359 | // We need to initialize a bigger array. We only set the size, and do another iteration.
360 | // (the out parameter requiredArraySize contains the expected value for the next try)
361 | arraySize = requiredArraySize;
362 | }
363 | else
364 | throw new Exception("Could not list processes locking resource. Failed to get size of result.");
365 | } while (result != RmResult.ERROR_SUCCESS);
366 | }
367 | finally
368 | {
369 | RmEndSession(handle);
370 | }
371 |
372 | return new List();
373 | }
374 | }
375 | }
376 |
--------------------------------------------------------------------------------
/WalkmanLib.GetFileLocks.GetAllHandles.cs:
--------------------------------------------------------------------------------
1 | // Get all system open handles method - uses NTQuerySystemInformation and NTQueryObject
2 | //https://gist.github.com/i-e-b/2290426
3 | //https://stackoverflow.com/a/13735033/2999220
4 |
5 |
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Runtime.ConstrainedExecution;
9 | using System.Runtime.InteropServices;
10 |
11 | namespace WalkmanLib.GetFileLocks
12 | {
13 | public static class GetAllHandles
14 | {
15 | public enum HandleType
16 | {
17 | Unknown,
18 | Other,
19 | File, Directory, SymbolicLink, Key,
20 | Process, Thread, Job, Session, WindowStation,
21 | Timer, Desktop, Semaphore, Token,
22 | Mutant, Section, Event, KeyedEvent, IoCompletion, IoCompletionReserve,
23 | TpWorkerFactory, AlpcPort, WmiGuid, UserApcReserve,
24 | }
25 |
26 | public enum NT_STATUS
27 | {
28 | STATUS_SUCCESS = 0x00000000,
29 | STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
30 | STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
31 | }
32 |
33 | public enum SYSTEM_INFORMATION_CLASS
34 | {
35 | SystemBasicInformation = 0,
36 | SystemPerformanceInformation = 2,
37 | SystemTimeOfDayInformation = 3,
38 | SystemProcessInformation = 5,
39 | SystemProcessorPerformanceInformation = 8,
40 | SystemHandleInformation = 16,
41 | SystemInterruptInformation = 23,
42 | SystemExceptionInformation = 33,
43 | SystemRegistryQuotaInformation = 37,
44 | SystemLookasideInformation = 45
45 | }
46 |
47 | public enum OBJECT_INFORMATION_CLASS
48 | {
49 | ObjectBasicInformation = 0,
50 | ObjectNameInformation = 1,
51 | ObjectTypeInformation = 2,
52 | ObjectAllTypesInformation = 3,
53 | ObjectHandleInformation = 4
54 | }
55 |
56 | [StructLayout(LayoutKind.Sequential)]
57 | public struct SystemHandleEntry
58 | {
59 | public int OwnerProcessId;
60 | public byte ObjectTypeNumber;
61 | public byte Flags;
62 | public ushort Handle;
63 | public IntPtr Object;
64 | public int GrantedAccess;
65 | }
66 |
67 | [DllImport("kernel32.dll")]
68 | internal static extern int GetFileType(IntPtr handle);
69 |
70 | [DllImport("ntdll.dll")]
71 | internal static extern NT_STATUS NtQuerySystemInformation(
72 | [In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
73 | [In] IntPtr SystemInformation,
74 | [In] int SystemInformationLength,
75 | [Out] out int ReturnLength);
76 |
77 | [DllImport("ntdll.dll")]
78 | internal static extern NT_STATUS NtQueryObject(
79 | [In] IntPtr Handle,
80 | [In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
81 | [In] IntPtr ObjectInformation,
82 | [In] int ObjectInformationLength,
83 | [Out] out int ReturnLength);
84 |
85 | [DllImport("kernel32.dll")]
86 | internal static extern IntPtr GetCurrentProcess();
87 |
88 | [DllImport("kernel32.dll", SetLastError = true)]
89 | public static extern IntPtr OpenProcess(
90 | [In] int dwDesiredAccess,
91 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
92 | [In] int dwProcessId);
93 |
94 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
95 | [DllImport("kernel32.dll", SetLastError = true)]
96 | [return: MarshalAs(UnmanagedType.Bool)]
97 | internal static extern bool CloseHandle(
98 | [In] IntPtr hObject);
99 |
100 | [DllImport("kernel32.dll", SetLastError = true)]
101 | [return: MarshalAs(UnmanagedType.Bool)]
102 | public static extern bool DuplicateHandle(
103 | [In] IntPtr hSourceProcessHandle,
104 | [In] IntPtr hSourceHandle,
105 | [In] IntPtr hTargetProcessHandle,
106 | [Out] out IntPtr lpTargetHandle,
107 | [In] int dwDesiredAccess,
108 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
109 | [In] int dwOptions);
110 |
111 | public class HandleInfo
112 | {
113 | public int ProcessId { get; private set; }
114 | public ushort Handle { get; private set; }
115 | public int GrantedAccess { get; private set; }
116 | public byte RawType { get; private set; }
117 | public byte Flags { get; private set; }
118 |
119 | public HandleInfo(int processId, ushort handle, int grantedAccess, byte rawType, byte flags)
120 | {
121 | ProcessId = processId;
122 | Handle = handle;
123 | GrantedAccess = grantedAccess;
124 | RawType = rawType;
125 | Flags = flags;
126 | }
127 |
128 | private static Dictionary _rawTypeMap = new Dictionary();
129 |
130 | private string _name, _typeStr;
131 | private HandleType _type;
132 |
133 | public string Name { get { if (_name == null) initTypeAndName(); return _name; } }
134 | public string TypeString { get { if (_typeStr == null) initType(); return _typeStr; } }
135 | public HandleType Type { get { if (_typeStr == null) initType(); return _type; } }
136 |
137 | private void initType()
138 | {
139 | if (_rawTypeMap.ContainsKey(RawType))
140 | {
141 | _typeStr = _rawTypeMap[RawType];
142 | _type = HandleTypeFromString(_typeStr);
143 | }
144 | else
145 | initTypeAndName();
146 | }
147 |
148 | private bool _typeAndNameAttempted = false;
149 |
150 | private void initTypeAndName()
151 | {
152 | if (_typeAndNameAttempted)
153 | return;
154 | _typeAndNameAttempted = true;
155 |
156 | IntPtr sourceProcessHandle = IntPtr.Zero;
157 | IntPtr handleDuplicate = IntPtr.Zero;
158 | try
159 | {
160 | sourceProcessHandle = OpenProcess(0x40 /* dup_handle */, true, ProcessId);
161 |
162 | // To read info about a handle owned by another process we must duplicate it into ours
163 | // For simplicity, current process handles will also get duplicated; remember that process handles cannot be compared for equality
164 | if (!DuplicateHandle(sourceProcessHandle, (IntPtr)Handle, GetCurrentProcess(), out handleDuplicate, 0, false, 2 /* same_access */))
165 | return;
166 |
167 | if (GetFileType(handleDuplicate) != 0x0001)
168 | return;
169 |
170 | // Query the object type
171 | if (_rawTypeMap.ContainsKey(RawType))
172 | _typeStr = _rawTypeMap[RawType];
173 | else
174 | {
175 | int length;
176 | NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
177 | IntPtr ptr = IntPtr.Zero;
178 | try
179 | {
180 | ptr = Marshal.AllocHGlobal(length);
181 | if (NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
182 | return;
183 | var lPtr = (long)ptr + 0x58 + (2 * IntPtr.Size);
184 | var pStr = (IntPtr)lPtr;
185 | _typeStr = Marshal.PtrToStringUni(pStr);
186 | _rawTypeMap[RawType] = _typeStr;
187 | }
188 | finally
189 | {
190 | Marshal.FreeHGlobal(ptr);
191 | }
192 | }
193 | _type = HandleTypeFromString(_typeStr);
194 |
195 | // Query the object name
196 | if (_typeStr != null &&
197 | !(GrantedAccess == 0x0012019f && Flags == 0) &&
198 | !(GrantedAccess == 0x0012019f && Flags == 2) &&
199 | !(GrantedAccess == 0x00120189 && Flags == 2) &&
200 | !(GrantedAccess == 0x00120189 && Flags == 0) &&
201 | !(GrantedAccess == 0x001a019f && Flags == 2) &&
202 | !(GrantedAccess == 0x00120089 && Flags == 2) &&
203 | !(GrantedAccess == 0x00120089 && Flags == 0) &&
204 | !(GrantedAccess == 0x001A0089 && Flags == 0)
205 | )// don't query some objects that could get stuck
206 | {
207 | int length;
208 | NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, IntPtr.Zero, 0, out length);
209 | IntPtr ptr = IntPtr.Zero;
210 | try
211 | {
212 | ptr = Marshal.AllocHGlobal(length);
213 | if (NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length) != NT_STATUS.STATUS_SUCCESS)
214 | return;
215 | _name = Marshal.PtrToStringUni((IntPtr)((long)ptr + (2 * IntPtr.Size)));
216 | }
217 | finally
218 | {
219 | Marshal.FreeHGlobal(ptr);
220 | }
221 | }
222 | }
223 | finally
224 | {
225 | CloseHandle(sourceProcessHandle);
226 | if (handleDuplicate != IntPtr.Zero)
227 | CloseHandle(handleDuplicate);
228 | }
229 | }
230 |
231 | public static HandleType HandleTypeFromString(string typeStr)
232 | {
233 | switch (typeStr)
234 | {
235 | case null:
236 | return HandleType.Unknown;
237 | case "File":
238 | return HandleType.File;
239 | case "IoCompletion":
240 | return HandleType.IoCompletion;
241 | case "TpWorkerFactory":
242 | return HandleType.TpWorkerFactory;
243 | case "ALPC Port":
244 | return HandleType.AlpcPort;
245 | case "Event":
246 | return HandleType.Event;
247 | case "Section":
248 | return HandleType.Section;
249 | case "Directory":
250 | return HandleType.Directory;
251 | case "KeyedEvent":
252 | return HandleType.KeyedEvent;
253 | case "Process":
254 | return HandleType.Process;
255 | case "Key":
256 | return HandleType.Key;
257 | case "SymbolicLink":
258 | return HandleType.SymbolicLink;
259 | case "Thread":
260 | return HandleType.Thread;
261 | case "Mutant":
262 | return HandleType.Mutant;
263 | case "WindowStation":
264 | return HandleType.WindowStation;
265 | case "Timer":
266 | return HandleType.Timer;
267 | case "Semaphore":
268 | return HandleType.Semaphore;
269 | case "Desktop":
270 | return HandleType.Desktop;
271 | case "Token":
272 | return HandleType.Token;
273 | case "Job":
274 | return HandleType.Job;
275 | case "Session":
276 | return HandleType.Session;
277 | case "IoCompletionReserve":
278 | return HandleType.IoCompletionReserve;
279 | case "WmiGuid":
280 | return HandleType.WmiGuid;
281 | case "UserApcReserve":
282 | return HandleType.UserApcReserve;
283 | default:
284 | return HandleType.Other;
285 | }
286 | }
287 | }
288 |
289 | public static SystemHandleEntry[] GetSystemHandles()
290 | {
291 | // Attempt to retrieve the handle information
292 | int length = 0x10000;
293 | IntPtr ptr = IntPtr.Zero;
294 | try
295 | {
296 | while (true)
297 | {
298 | ptr = Marshal.AllocHGlobal(length);
299 | int wantedLength;
300 | NT_STATUS result = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out wantedLength);
301 | if (result == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
302 | {
303 | length = Math.Max(length, wantedLength);
304 | Marshal.FreeHGlobal(ptr);
305 | ptr = IntPtr.Zero;
306 | }
307 | else if (result == NT_STATUS.STATUS_SUCCESS)
308 | break;
309 | else
310 | throw new Exception("Failed to retrieve system handle information.", new System.ComponentModel.Win32Exception());
311 | }
312 |
313 | int handleCount = IntPtr.Size == 4 ? Marshal.ReadInt32(ptr) : (int)Marshal.ReadInt64(ptr);
314 | int offset = IntPtr.Size;
315 | int size = Marshal.SizeOf(typeof(SystemHandleEntry));
316 |
317 | SystemHandleEntry[] systemHandleEntries = new SystemHandleEntry[handleCount];
318 | for (int i = 0; i < handleCount; i++)
319 | {
320 | SystemHandleEntry struc = (SystemHandleEntry)Marshal.PtrToStructure((IntPtr)((long)ptr + offset), typeof(SystemHandleEntry));
321 | systemHandleEntries[i] = struc;
322 |
323 | offset += size;
324 | }
325 |
326 | return systemHandleEntries;
327 | }
328 | finally
329 | {
330 | if (ptr != IntPtr.Zero)
331 | Marshal.FreeHGlobal(ptr);
332 | }
333 | }
334 |
335 | public static IEnumerable GetFileHandles()
336 | {
337 | SystemHandleEntry[] systemHandleEntries = GetSystemHandles();
338 |
339 | foreach (SystemHandleEntry struc in systemHandleEntries)
340 | {
341 | HandleInfo hi = new HandleInfo(struc.OwnerProcessId, struc.Handle, struc.GrantedAccess, struc.ObjectTypeNumber, struc.Flags);
342 | if (hi.Type == HandleType.File && hi.Name != null)
343 | yield return hi;
344 | }
345 | }
346 |
347 | ///
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 | ///
351 | /// The full path to the directory to search for.
352 | /// A list of HandleInfo objects for handles matching the directory.
353 | public static IEnumerable 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 _deviceToDriveMap;
388 |
389 | ///
390 | /// Converts a device path (e.g., \Device\HarddiskVolume3\path) to a DOS path (e.g., C:\path).
391 | ///
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(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 | ///
445 | /// Gets processes that have a handle to the specified directory or files within it.
446 | ///
447 | /// The full path to the directory.
448 | /// A list of unique Process objects.
449 | public static List GetProcessesLockingDirectory(string directoryPath)
450 | {
451 | var processIds = new HashSet();
452 | var processes = new List();
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 | }
473 | }
474 | }
--------------------------------------------------------------------------------
/WalkmanLib.GetFileLocks.GetProcessHandles.cs:
--------------------------------------------------------------------------------
1 | // Get a process's open handles method - uses NTQuerySystemInformation and NTQueryObject
2 | //https://stackoverflow.com/a/6351168/2999220
3 |
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Runtime.CompilerServices;
9 | using System.Runtime.ConstrainedExecution;
10 | using System.Runtime.InteropServices;
11 | using System.Security.Permissions;
12 | using System.Text;
13 | using System.Threading;
14 | using Microsoft.Win32.SafeHandles;
15 | using System.Diagnostics;
16 | using System.Linq;
17 |
18 | namespace WalkmanLib.GetFileLocks
19 | {
20 | public static class GetProcessHandles
21 | {
22 | private static Dictionary deviceMap;
23 | private const string networkDevicePrefix = "\\Device\\LanmanRedirector\\";
24 | private const int MAX_PATH = 260;
25 | private const int handleTypeTokenCount = 27;
26 | private static readonly string[] handleTypeTokens = new string[] {
27 | "", "", "Directory", "SymbolicLink", "Token",
28 | "Process", "Thread", "Unknown7", "Event", "EventPair", "Mutant",
29 | "Unknown11", "Semaphore", "Timer", "Profile", "WindowStation",
30 | "Desktop", "Section", "Key", "Port", "WaitablePort",
31 | "Unknown21", "Unknown22", "Unknown23", "Unknown24",
32 | "IoCompletion", "File"
33 | };
34 |
35 | internal enum NT_STATUS
36 | {
37 | STATUS_SUCCESS = 0x00000000,
38 | STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
39 | STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
40 | }
41 |
42 | internal enum SYSTEM_INFORMATION_CLASS
43 | {
44 | SystemBasicInformation = 0,
45 | SystemPerformanceInformation = 2,
46 | SystemTimeOfDayInformation = 3,
47 | SystemProcessInformation = 5,
48 | SystemProcessorPerformanceInformation = 8,
49 | SystemHandleInformation = 16,
50 | SystemInterruptInformation = 23,
51 | SystemExceptionInformation = 33,
52 | SystemRegistryQuotaInformation = 37,
53 | SystemLookasideInformation = 45
54 | }
55 |
56 | internal enum OBJECT_INFORMATION_CLASS
57 | {
58 | ObjectBasicInformation = 0,
59 | ObjectNameInformation = 1,
60 | ObjectTypeInformation = 2,
61 | ObjectAllTypesInformation = 3,
62 | ObjectHandleInformation = 4
63 | }
64 |
65 | [Flags]
66 | internal enum ProcessAccessRights
67 | {
68 | PROCESS_DUP_HANDLE = 0x00000040
69 | }
70 |
71 | [Flags]
72 | internal enum DuplicateHandleOptions
73 | {
74 | DUPLICATE_CLOSE_SOURCE = 0x1,
75 | DUPLICATE_SAME_ACCESS = 0x2
76 | }
77 |
78 | private enum SystemHandleType
79 | {
80 | OB_TYPE_UNKNOWN = 0,
81 | OB_TYPE_TYPE = 1,
82 | OB_TYPE_DIRECTORY,
83 | OB_TYPE_SYMBOLIC_LINK,
84 | OB_TYPE_TOKEN,
85 | OB_TYPE_PROCESS,
86 | OB_TYPE_THREAD,
87 | OB_TYPE_UNKNOWN_7,
88 | OB_TYPE_EVENT,
89 | OB_TYPE_EVENT_PAIR,
90 | OB_TYPE_MUTANT,
91 | OB_TYPE_UNKNOWN_11,
92 | OB_TYPE_SEMAPHORE,
93 | OB_TYPE_TIMER,
94 | OB_TYPE_PROFILE,
95 | OB_TYPE_WINDOW_STATION,
96 | OB_TYPE_DESKTOP,
97 | OB_TYPE_SECTION,
98 | OB_TYPE_KEY,
99 | OB_TYPE_PORT,
100 | OB_TYPE_WAITABLE_PORT,
101 | OB_TYPE_UNKNOWN_21,
102 | OB_TYPE_UNKNOWN_22,
103 | OB_TYPE_UNKNOWN_23,
104 | OB_TYPE_UNKNOWN_24,
105 | //OB_TYPE_CONTROLLER,
106 | //OB_TYPE_DEVICE,
107 | //OB_TYPE_DRIVER,
108 | OB_TYPE_IO_COMPLETION,
109 | OB_TYPE_FILE
110 | };
111 |
112 | [StructLayout(LayoutKind.Sequential)]
113 | private struct SYSTEM_HANDLE_ENTRY
114 | {
115 | public int OwnerPid;
116 | public byte ObjectType;
117 | public byte HandleFlags;
118 | public short HandleValue;
119 | public int ObjectPointer;
120 | public int AccessMask;
121 | }
122 |
123 | [DllImport("ntdll.dll")]
124 | internal static extern NT_STATUS NtQuerySystemInformation(
125 | [In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
126 | [In] IntPtr SystemInformation,
127 | [In] int SystemInformationLength,
128 | [Out] out int ReturnLength);
129 |
130 | [DllImport("ntdll.dll")]
131 | internal static extern NT_STATUS NtQueryObject(
132 | [In] IntPtr Handle,
133 | [In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
134 | [In] IntPtr ObjectInformation,
135 | [In] int ObjectInformationLength,
136 | [Out] out int ReturnLength);
137 |
138 | [DllImport("kernel32.dll", SetLastError = true)]
139 | internal static extern SafeProcessHandle OpenProcess(
140 | [In] ProcessAccessRights dwDesiredAccess,
141 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
142 | [In] int dwProcessId);
143 |
144 | [DllImport("kernel32.dll", SetLastError = true)]
145 | [return: MarshalAs(UnmanagedType.Bool)]
146 | internal static extern bool DuplicateHandle(
147 | [In] IntPtr hSourceProcessHandle,
148 | [In] IntPtr hSourceHandle,
149 | [In] IntPtr hTargetProcessHandle,
150 | [Out] out SafeObjectHandle lpTargetHandle,
151 | [In] int dwDesiredAccess,
152 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
153 | [In] DuplicateHandleOptions dwOptions);
154 |
155 | [DllImport("kernel32.dll")]
156 | internal static extern IntPtr GetCurrentProcess();
157 |
158 | [DllImport("kernel32.dll", SetLastError = true)]
159 | internal static extern int GetProcessId(
160 | [In] IntPtr Process);
161 |
162 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
163 | [DllImport("kernel32.dll", SetLastError = true)]
164 | [return: MarshalAs(UnmanagedType.Bool)]
165 | internal static extern bool CloseHandle(
166 | [In] IntPtr hObject);
167 |
168 | [DllImport("kernel32.dll", SetLastError = true)]
169 | internal static extern int QueryDosDevice(
170 | [In] string lpDeviceName,
171 | [Out] StringBuilder lpTargetPath,
172 | [In] int ucchMax);
173 |
174 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
175 | internal sealed class SafeObjectHandle : SafeHandleZeroOrMinusOneIsInvalid
176 | {
177 | private SafeObjectHandle()
178 | : base(true)
179 | { }
180 |
181 | internal SafeObjectHandle(IntPtr preexistingHandle, bool ownsHandle)
182 | : base(ownsHandle)
183 | {
184 | base.SetHandle(preexistingHandle);
185 | }
186 |
187 | protected override bool ReleaseHandle()
188 | {
189 | return CloseHandle(base.handle);
190 | }
191 | }
192 |
193 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
194 | internal sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
195 | {
196 | private SafeProcessHandle()
197 | : base(true)
198 | { }
199 |
200 | internal SafeProcessHandle(IntPtr preexistingHandle, bool ownsHandle)
201 | : base(ownsHandle)
202 | {
203 | base.SetHandle(preexistingHandle);
204 | }
205 |
206 | protected override bool ReleaseHandle()
207 | {
208 | return CloseHandle(base.handle);
209 | }
210 | }
211 |
212 | private sealed class OpenFiles : IEnumerable
213 | {
214 | private readonly int processId;
215 |
216 | internal OpenFiles(int processId)
217 | {
218 | this.processId = processId;
219 | }
220 |
221 | public IEnumerator GetEnumerator()
222 | {
223 | NT_STATUS ret;
224 | int length = 0x10000;
225 | // Loop, probing for required memory.
226 |
227 | do
228 | {
229 | IntPtr ptr = IntPtr.Zero;
230 | RuntimeHelpers.PrepareConstrainedRegions();
231 | try
232 | {
233 | RuntimeHelpers.PrepareConstrainedRegions();
234 | try
235 | { }
236 | finally
237 | {
238 | // CER guarantees that the address of the allocated
239 | // memory is actually assigned to ptr if an
240 | // asynchronous exception occurs.
241 | ptr = Marshal.AllocHGlobal(length);
242 | }
243 | int returnLength;
244 | ret = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out returnLength);
245 | if (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
246 | {
247 | // Round required memory up to the nearest 64KB boundary.
248 | length = (returnLength + 0xffff) & ~0xffff;
249 | }
250 | else if (ret == NT_STATUS.STATUS_SUCCESS)
251 | {
252 | int handleCount = Marshal.ReadInt32(ptr);
253 | int offset = sizeof(int);
254 | int size = Marshal.SizeOf(typeof(SYSTEM_HANDLE_ENTRY));
255 | for (int i = 0; i < handleCount; i++)
256 | {
257 | SYSTEM_HANDLE_ENTRY handleEntry = (SYSTEM_HANDLE_ENTRY)Marshal.PtrToStructure((IntPtr)((long)ptr + offset), typeof(SYSTEM_HANDLE_ENTRY));
258 | if (handleEntry.OwnerPid == processId)
259 | {
260 | IntPtr handle = (IntPtr)handleEntry.HandleValue;
261 | SystemHandleType handleType;
262 |
263 | if (GetHandleType(handle, handleEntry.OwnerPid, out handleType) && handleType == SystemHandleType.OB_TYPE_FILE)
264 | {
265 | string devicePath;
266 | if (GetFileNameFromHandle(handle, handleEntry.OwnerPid, out devicePath))
267 | {
268 | string dosPath;
269 | if (ConvertDevicePathToDosPath(devicePath, out dosPath))
270 | {
271 | if (File.Exists(dosPath))
272 | yield return dosPath; // return new FileInfo(dosPath);
273 | else if (Directory.Exists(dosPath))
274 | yield return dosPath; // new DirectoryInfo(dosPath);
275 | }
276 | }
277 | }
278 | }
279 | offset += size;
280 | }
281 | }
282 | }
283 | finally
284 | {
285 | // CER guarantees that the allocated memory is freed,
286 | // if an asynchronous exception occurs.
287 | Marshal.FreeHGlobal(ptr);
288 | //sw.Flush();
289 | //sw.Close();
290 | }
291 | }
292 | while (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH);
293 | }
294 |
295 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
296 | {
297 | return GetEnumerator();
298 | }
299 | }
300 |
301 | private class FileNameFromHandleState : IDisposable
302 | {
303 | private ManualResetEvent _mr;
304 | public IntPtr Handle { get; }
305 | public string FileName { get; set; }
306 | public bool RetValue { get; set; }
307 |
308 | public FileNameFromHandleState(IntPtr handle)
309 | {
310 | _mr = new ManualResetEvent(false);
311 | this.Handle = handle;
312 | }
313 |
314 | public bool WaitOne(int wait)
315 | {
316 | return _mr.WaitOne(wait, false);
317 | }
318 |
319 | public void Set()
320 | {
321 | try
322 | {
323 | _mr.Set();
324 | }
325 | catch { }
326 | }
327 |
328 | public void Dispose()
329 | {
330 | if (_mr != null)
331 | _mr.Close();
332 | }
333 | }
334 |
335 | private static bool GetFileNameFromHandle(IntPtr handle, out string fileName)
336 | {
337 | IntPtr ptr = IntPtr.Zero;
338 | RuntimeHelpers.PrepareConstrainedRegions();
339 | try
340 | {
341 | int length = 0x200; // 512 bytes
342 | RuntimeHelpers.PrepareConstrainedRegions();
343 | try
344 | { }
345 | finally
346 | {
347 | // CER guarantees the assignment of the allocated
348 | // memory address to ptr, if an ansynchronous exception
349 | // occurs.
350 | ptr = Marshal.AllocHGlobal(length);
351 | }
352 | NT_STATUS ret = NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
353 | if (ret == NT_STATUS.STATUS_BUFFER_OVERFLOW)
354 | {
355 | RuntimeHelpers.PrepareConstrainedRegions();
356 | try
357 | { }
358 | finally
359 | {
360 | // CER guarantees that the previous allocation is freed,
361 | // and that the newly allocated memory address is
362 | // assigned to ptr if an asynchronous exception occurs.
363 | Marshal.FreeHGlobal(ptr);
364 | ptr = Marshal.AllocHGlobal(length);
365 | }
366 | ret = NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
367 | }
368 | if (ret == NT_STATUS.STATUS_SUCCESS)
369 | {
370 | fileName = Marshal.PtrToStringUni((IntPtr)((int)ptr + 8), (length - 9) / 2);
371 | return fileName.Length != 0;
372 | }
373 | }
374 | finally
375 | {
376 | // CER guarantees that the allocated memory is freed,
377 | // if an asynchronous exception occurs.
378 | Marshal.FreeHGlobal(ptr);
379 | }
380 |
381 | fileName = string.Empty;
382 | return false;
383 | }
384 | private static void GetFileNameFromHandle(object state)
385 | {
386 | FileNameFromHandleState s = (FileNameFromHandleState)state;
387 | string fileName;
388 | s.RetValue = GetFileNameFromHandle(s.Handle, out fileName);
389 | s.FileName = fileName;
390 | s.Set();
391 | }
392 | private static bool GetFileNameFromHandle(IntPtr handle, out string fileName, int wait)
393 | {
394 | using (FileNameFromHandleState f = new FileNameFromHandleState(handle))
395 | {
396 | ThreadPool.QueueUserWorkItem(new WaitCallback(GetFileNameFromHandle), f);
397 | if (f.WaitOne(wait))
398 | {
399 | fileName = f.FileName;
400 | return f.RetValue;
401 | }
402 | else
403 | {
404 | fileName = string.Empty;
405 | return false;
406 | }
407 | }
408 | }
409 | private static bool GetFileNameFromHandle(IntPtr handle, int processId, out string fileName)
410 | {
411 | IntPtr currentProcess = GetCurrentProcess();
412 | bool remote = processId != GetProcessId(currentProcess);
413 | SafeProcessHandle processHandle = null;
414 | SafeObjectHandle objectHandle = null;
415 | try
416 | {
417 | if (remote)
418 | {
419 | processHandle = OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
420 | if (DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
421 | handle = objectHandle.DangerousGetHandle();
422 | }
423 | return GetFileNameFromHandle(handle, out fileName, 200);
424 | }
425 | finally
426 | {
427 | if (remote)
428 | {
429 | if (processHandle != null)
430 | processHandle.Close();
431 | if (objectHandle != null)
432 | objectHandle.Close();
433 | }
434 | }
435 | }
436 |
437 | private static string GetHandleTypeToken(IntPtr handle)
438 | {
439 | int length;
440 | NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
441 | IntPtr ptr = IntPtr.Zero;
442 | RuntimeHelpers.PrepareConstrainedRegions();
443 | try
444 | {
445 | RuntimeHelpers.PrepareConstrainedRegions();
446 | try
447 | { }
448 | finally
449 | {
450 | if (length >= 0)
451 | ptr = Marshal.AllocHGlobal(length);
452 | }
453 | if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) == NT_STATUS.STATUS_SUCCESS)
454 | {
455 | return Marshal.PtrToStringUni((IntPtr)((int)ptr + 0x60));
456 | }
457 | }
458 | finally
459 | {
460 | Marshal.FreeHGlobal(ptr);
461 | }
462 | return string.Empty;
463 | }
464 | private static string GetHandleTypeToken(IntPtr handle, int processId)
465 | {
466 | IntPtr currentProcess = GetCurrentProcess();
467 | bool remote = processId != GetProcessId(currentProcess);
468 | SafeProcessHandle processHandle = null;
469 | SafeObjectHandle objectHandle = null;
470 | try
471 | {
472 | if (remote)
473 | {
474 | processHandle = OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
475 | if (DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
476 | {
477 | handle = objectHandle.DangerousGetHandle();
478 | }
479 | }
480 | return GetHandleTypeToken(handle);
481 | }
482 | finally
483 | {
484 | if (remote)
485 | {
486 | if (processHandle != null)
487 | {
488 | processHandle.Close();
489 | }
490 | if (objectHandle != null)
491 | {
492 | objectHandle.Close();
493 | }
494 | }
495 | }
496 | }
497 | private static bool GetHandleTypeFromToken(string token, out SystemHandleType handleType)
498 | {
499 | for (int i = 1; i < handleTypeTokenCount; i++)
500 | {
501 | if (handleTypeTokens[i] == token)
502 | {
503 | handleType = (SystemHandleType)i;
504 | return true;
505 | }
506 | }
507 | handleType = SystemHandleType.OB_TYPE_UNKNOWN;
508 | return false;
509 | }
510 | private static bool GetHandleType(IntPtr handle, out SystemHandleType handleType)
511 | {
512 | string token = GetHandleTypeToken(handle);
513 | return GetHandleTypeFromToken(token, out handleType);
514 | }
515 | private static bool GetHandleType(IntPtr handle, int processId, out SystemHandleType handleType)
516 | {
517 | string token = GetHandleTypeToken(handle, processId);
518 | return GetHandleTypeFromToken(token, out handleType);
519 | }
520 |
521 | private static bool ConvertDevicePathToDosPath(string devicePath, out string dosPath)
522 | {
523 | EnsureDeviceMap();
524 | int i = devicePath.Length;
525 | while (i > 0 && (i = devicePath.LastIndexOf('\\', i - 1)) != -1)
526 | {
527 | string drive;
528 | if (deviceMap.TryGetValue(devicePath.Substring(0, i), out drive))
529 | {
530 | dosPath = string.Concat(drive, devicePath.Substring(i));
531 | return dosPath.Length != 0;
532 | }
533 | }
534 | dosPath = string.Empty;
535 | return false;
536 | }
537 |
538 | private static void EnsureDeviceMap()
539 | {
540 | if (deviceMap == null)
541 | {
542 | Dictionary localDeviceMap = BuildDeviceMap();
543 | Interlocked.CompareExchange(ref deviceMap, localDeviceMap, null);
544 | }
545 | }
546 |
547 | private static Dictionary BuildDeviceMap()
548 | {
549 | string[] logicalDrives = Environment.GetLogicalDrives();
550 | Dictionary localDeviceMap = new Dictionary(logicalDrives.Length);
551 | StringBuilder lpTargetPath = new StringBuilder(MAX_PATH);
552 | foreach (string drive in logicalDrives)
553 | {
554 | string lpDeviceName = drive.Substring(0, 2);
555 | QueryDosDevice(lpDeviceName, lpTargetPath, MAX_PATH);
556 | localDeviceMap.Add(NormalizeDeviceName(lpTargetPath.ToString()), lpDeviceName);
557 | }
558 | localDeviceMap.Add(networkDevicePrefix.Substring(0, networkDevicePrefix.Length - 1), "\\");
559 | return localDeviceMap;
560 | }
561 |
562 | private static string NormalizeDeviceName(string deviceName)
563 | {
564 | if (string.Compare(deviceName, 0, networkDevicePrefix, 0, networkDevicePrefix.Length, StringComparison.InvariantCulture) == 0)
565 | {
566 | string shareName = deviceName.Substring(deviceName.IndexOf('\\', networkDevicePrefix.Length) + 1);
567 | return string.Concat(networkDevicePrefix, shareName);
568 | }
569 | return deviceName;
570 | }
571 |
572 | ///
573 | /// Gets the open files enumerator.
574 | ///
575 | /// The process id.
576 | ///
577 | public static IEnumerable GetProcessOpenFiles(int processId)
578 | {
579 | return new OpenFiles(processId);
580 | }
581 |
582 | public static List GetLockingProcesses(string path)
583 | {
584 | List processes = new List();
585 | foreach (Process p in Process.GetProcesses())
586 | {
587 | try
588 | {
589 | if (GetProcessOpenFiles(p.Id).Contains(path))
590 | processes.Add(p);
591 | }
592 | catch { }//some processes will fail
593 | }
594 | return processes;
595 | }
596 | }
597 | }
598 |
--------------------------------------------------------------------------------
/GetProcessHandles.cs:
--------------------------------------------------------------------------------
1 | // Get a process's open handles method - uses NTQuerySystemInformation and NTQueryObject
2 | //https://stackoverflow.com/a/6351168/2999220
3 |
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Runtime.CompilerServices;
9 | using System.Runtime.ConstrainedExecution;
10 | using System.Runtime.InteropServices;
11 | using System.Security.Permissions;
12 | using System.Text;
13 | using System.Threading;
14 | using Microsoft.Win32.SafeHandles;
15 | using System.Diagnostics;
16 | using System.Linq;
17 |
18 | namespace GetProcessHandles
19 | {
20 | public static class DetectOpenFiles
21 | {
22 | private static Dictionary deviceMap;
23 | private const string networkDevicePrefix = "\\Device\\LanmanRedirector\\";
24 | private const int MAX_PATH = 260;
25 | private const int handleTypeTokenCount = 27;
26 | private static readonly string[] handleTypeTokens = new string[] {
27 | "", "", "Directory", "SymbolicLink", "Token",
28 | "Process", "Thread", "Unknown7", "Event", "EventPair", "Mutant",
29 | "Unknown11", "Semaphore", "Timer", "Profile", "WindowStation",
30 | "Desktop", "Section", "Key", "Port", "WaitablePort",
31 | "Unknown21", "Unknown22", "Unknown23", "Unknown24",
32 | "IoCompletion", "File"
33 | };
34 |
35 | internal enum NT_STATUS
36 | {
37 | STATUS_SUCCESS = 0x00000000,
38 | STATUS_BUFFER_OVERFLOW = unchecked((int)0x80000005L),
39 | STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004L)
40 | }
41 |
42 | internal enum SYSTEM_INFORMATION_CLASS
43 | {
44 | SystemBasicInformation = 0,
45 | SystemPerformanceInformation = 2,
46 | SystemTimeOfDayInformation = 3,
47 | SystemProcessInformation = 5,
48 | SystemProcessorPerformanceInformation = 8,
49 | SystemHandleInformation = 16,
50 | SystemInterruptInformation = 23,
51 | SystemExceptionInformation = 33,
52 | SystemRegistryQuotaInformation = 37,
53 | SystemLookasideInformation = 45
54 | }
55 |
56 | internal enum OBJECT_INFORMATION_CLASS
57 | {
58 | ObjectBasicInformation = 0,
59 | ObjectNameInformation = 1,
60 | ObjectTypeInformation = 2,
61 | ObjectAllTypesInformation = 3,
62 | ObjectHandleInformation = 4
63 | }
64 |
65 | [Flags]
66 | internal enum ProcessAccessRights
67 | {
68 | PROCESS_DUP_HANDLE = 0x00000040
69 | }
70 |
71 | [Flags]
72 | internal enum DuplicateHandleOptions
73 | {
74 | DUPLICATE_CLOSE_SOURCE = 0x1,
75 | DUPLICATE_SAME_ACCESS = 0x2
76 | }
77 |
78 | private enum SystemHandleType
79 | {
80 | OB_TYPE_UNKNOWN = 0,
81 | OB_TYPE_TYPE = 1,
82 | OB_TYPE_DIRECTORY,
83 | OB_TYPE_SYMBOLIC_LINK,
84 | OB_TYPE_TOKEN,
85 | OB_TYPE_PROCESS,
86 | OB_TYPE_THREAD,
87 | OB_TYPE_UNKNOWN_7,
88 | OB_TYPE_EVENT,
89 | OB_TYPE_EVENT_PAIR,
90 | OB_TYPE_MUTANT,
91 | OB_TYPE_UNKNOWN_11,
92 | OB_TYPE_SEMAPHORE,
93 | OB_TYPE_TIMER,
94 | OB_TYPE_PROFILE,
95 | OB_TYPE_WINDOW_STATION,
96 | OB_TYPE_DESKTOP,
97 | OB_TYPE_SECTION,
98 | OB_TYPE_KEY,
99 | OB_TYPE_PORT,
100 | OB_TYPE_WAITABLE_PORT,
101 | OB_TYPE_UNKNOWN_21,
102 | OB_TYPE_UNKNOWN_22,
103 | OB_TYPE_UNKNOWN_23,
104 | OB_TYPE_UNKNOWN_24,
105 | //OB_TYPE_CONTROLLER,
106 | //OB_TYPE_DEVICE,
107 | //OB_TYPE_DRIVER,
108 | OB_TYPE_IO_COMPLETION,
109 | OB_TYPE_FILE
110 | };
111 |
112 | [StructLayout(LayoutKind.Sequential)]
113 | private struct SYSTEM_HANDLE_ENTRY
114 | {
115 | public int OwnerPid;
116 | public byte ObjectType;
117 | public byte HandleFlags;
118 | public short HandleValue;
119 | public int ObjectPointer;
120 | public int AccessMask;
121 | }
122 |
123 | [DllImport("ntdll.dll")]
124 | internal static extern NT_STATUS NtQuerySystemInformation(
125 | [In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
126 | [In] IntPtr SystemInformation,
127 | [In] int SystemInformationLength,
128 | [Out] out int ReturnLength);
129 |
130 | [DllImport("ntdll.dll")]
131 | internal static extern NT_STATUS NtQueryObject(
132 | [In] IntPtr Handle,
133 | [In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
134 | [In] IntPtr ObjectInformation,
135 | [In] int ObjectInformationLength,
136 | [Out] out int ReturnLength);
137 |
138 | [DllImport("kernel32.dll", SetLastError = true)]
139 | internal static extern SafeProcessHandle OpenProcess(
140 | [In] ProcessAccessRights dwDesiredAccess,
141 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
142 | [In] int dwProcessId);
143 |
144 | [DllImport("kernel32.dll", SetLastError = true)]
145 | [return: MarshalAs(UnmanagedType.Bool)]
146 | internal static extern bool DuplicateHandle(
147 | [In] IntPtr hSourceProcessHandle,
148 | [In] IntPtr hSourceHandle,
149 | [In] IntPtr hTargetProcessHandle,
150 | [Out] out SafeObjectHandle lpTargetHandle,
151 | [In] int dwDesiredAccess,
152 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
153 | [In] DuplicateHandleOptions dwOptions);
154 |
155 | [DllImport("kernel32.dll")]
156 | internal static extern IntPtr GetCurrentProcess();
157 |
158 | [DllImport("kernel32.dll", SetLastError = true)]
159 | internal static extern int GetProcessId(
160 | [In] IntPtr Process);
161 |
162 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
163 | [DllImport("kernel32.dll", SetLastError = true)]
164 | [return: MarshalAs(UnmanagedType.Bool)]
165 | internal static extern bool CloseHandle(
166 | [In] IntPtr hObject);
167 |
168 | [DllImport("kernel32.dll", SetLastError = true)]
169 | internal static extern int QueryDosDevice(
170 | [In] string lpDeviceName,
171 | [Out] StringBuilder lpTargetPath,
172 | [In] int ucchMax);
173 |
174 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
175 | internal sealed class SafeObjectHandle : SafeHandleZeroOrMinusOneIsInvalid
176 | {
177 | private SafeObjectHandle()
178 | : base(true)
179 | { }
180 |
181 | internal SafeObjectHandle(IntPtr preexistingHandle, bool ownsHandle)
182 | : base(ownsHandle)
183 | {
184 | base.SetHandle(preexistingHandle);
185 | }
186 |
187 | protected override bool ReleaseHandle()
188 | {
189 | return CloseHandle(base.handle);
190 | }
191 | }
192 |
193 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
194 | internal sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
195 | {
196 | private SafeProcessHandle()
197 | : base(true)
198 | { }
199 |
200 | internal SafeProcessHandle(IntPtr preexistingHandle, bool ownsHandle)
201 | : base(ownsHandle)
202 | {
203 | base.SetHandle(preexistingHandle);
204 | }
205 |
206 | protected override bool ReleaseHandle()
207 | {
208 | return CloseHandle(base.handle);
209 | }
210 | }
211 |
212 | private sealed class OpenFiles : IEnumerable
213 | {
214 | private readonly int processId;
215 |
216 | internal OpenFiles(int processId)
217 | {
218 | this.processId = processId;
219 | }
220 |
221 | public IEnumerator GetEnumerator()
222 | {
223 | NT_STATUS ret;
224 | int length = 0x10000;
225 | // Loop, probing for required memory.
226 |
227 | do
228 | {
229 | IntPtr ptr = IntPtr.Zero;
230 | RuntimeHelpers.PrepareConstrainedRegions();
231 | try
232 | {
233 | RuntimeHelpers.PrepareConstrainedRegions();
234 | try
235 | { }
236 | finally
237 | {
238 | // CER guarantees that the address of the allocated
239 | // memory is actually assigned to ptr if an
240 | // asynchronous exception occurs.
241 | ptr = Marshal.AllocHGlobal(length);
242 | }
243 | int returnLength;
244 | ret = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS.SystemHandleInformation, ptr, length, out returnLength);
245 | if (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH)
246 | {
247 | // Round required memory up to the nearest 64KB boundary.
248 | length = (returnLength + 0xffff) & ~0xffff;
249 | }
250 | else if (ret == NT_STATUS.STATUS_SUCCESS)
251 | {
252 | int handleCount = Marshal.ReadInt32(ptr);
253 | int offset = sizeof(int);
254 | int size = Marshal.SizeOf(typeof(SYSTEM_HANDLE_ENTRY));
255 | for (int i = 0; i < handleCount; i++)
256 | {
257 | SYSTEM_HANDLE_ENTRY handleEntry = (SYSTEM_HANDLE_ENTRY)Marshal.PtrToStructure((IntPtr)((int)ptr + offset), typeof(SYSTEM_HANDLE_ENTRY));
258 | if (handleEntry.OwnerPid == processId)
259 | {
260 | IntPtr handle = (IntPtr)handleEntry.HandleValue;
261 | SystemHandleType handleType;
262 |
263 | if (GetHandleType(handle, handleEntry.OwnerPid, out handleType) && handleType == SystemHandleType.OB_TYPE_FILE)
264 | {
265 | string devicePath;
266 | if (GetFileNameFromHandle(handle, handleEntry.OwnerPid, out devicePath))
267 | {
268 | string dosPath;
269 | if (ConvertDevicePathToDosPath(devicePath, out dosPath))
270 | {
271 | if (File.Exists(dosPath))
272 | yield return dosPath; // return new FileInfo(dosPath);
273 | else if (Directory.Exists(dosPath))
274 | yield return dosPath; // new DirectoryInfo(dosPath);
275 | }
276 | }
277 | }
278 | }
279 | offset += size;
280 | }
281 | }
282 | }
283 | finally
284 | {
285 | // CER guarantees that the allocated memory is freed,
286 | // if an asynchronous exception occurs.
287 | Marshal.FreeHGlobal(ptr);
288 | //sw.Flush();
289 | //sw.Close();
290 | }
291 | }
292 | while (ret == NT_STATUS.STATUS_INFO_LENGTH_MISMATCH);
293 | }
294 |
295 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
296 | {
297 | return GetEnumerator();
298 | }
299 | }
300 |
301 | private class FileNameFromHandleState : IDisposable
302 | {
303 | private ManualResetEvent _mr;
304 | public IntPtr Handle { get; }
305 | public string FileName { get; set; }
306 | public bool RetValue { get; set; }
307 |
308 | public FileNameFromHandleState(IntPtr handle)
309 | {
310 | _mr = new ManualResetEvent(false);
311 | this.Handle = handle;
312 | }
313 |
314 | public bool WaitOne(int wait)
315 | {
316 | return _mr.WaitOne(wait, false);
317 | }
318 |
319 | public void Set()
320 | {
321 | try
322 | {
323 | _mr.Set();
324 | }
325 | catch { }
326 | }
327 |
328 | public void Dispose()
329 | {
330 | if (_mr != null)
331 | _mr.Close();
332 | }
333 | }
334 |
335 | private static bool GetFileNameFromHandle(IntPtr handle, out string fileName)
336 | {
337 | IntPtr ptr = IntPtr.Zero;
338 | RuntimeHelpers.PrepareConstrainedRegions();
339 | try
340 | {
341 | int length = 0x200; // 512 bytes
342 | RuntimeHelpers.PrepareConstrainedRegions();
343 | try
344 | { }
345 | finally
346 | {
347 | // CER guarantees the assignment of the allocated
348 | // memory address to ptr, if an ansynchronous exception
349 | // occurs.
350 | ptr = Marshal.AllocHGlobal(length);
351 | }
352 | NT_STATUS ret = NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
353 | if (ret == NT_STATUS.STATUS_BUFFER_OVERFLOW)
354 | {
355 | RuntimeHelpers.PrepareConstrainedRegions();
356 | try
357 | { }
358 | finally
359 | {
360 | // CER guarantees that the previous allocation is freed,
361 | // and that the newly allocated memory address is
362 | // assigned to ptr if an asynchronous exception occurs.
363 | Marshal.FreeHGlobal(ptr);
364 | ptr = Marshal.AllocHGlobal(length);
365 | }
366 | ret = NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length);
367 | }
368 | if (ret == NT_STATUS.STATUS_SUCCESS)
369 | {
370 | fileName = Marshal.PtrToStringUni((IntPtr)((int)ptr + 8), (length - 9) / 2);
371 | return fileName.Length != 0;
372 | }
373 | }
374 | finally
375 | {
376 | // CER guarantees that the allocated memory is freed,
377 | // if an asynchronous exception occurs.
378 | Marshal.FreeHGlobal(ptr);
379 | }
380 |
381 | fileName = string.Empty;
382 | return false;
383 | }
384 | private static void GetFileNameFromHandle(object state)
385 | {
386 | FileNameFromHandleState s = (FileNameFromHandleState)state;
387 | string fileName;
388 | s.RetValue = GetFileNameFromHandle(s.Handle, out fileName);
389 | s.FileName = fileName;
390 | s.Set();
391 | }
392 | private static bool GetFileNameFromHandle(IntPtr handle, out string fileName, int wait)
393 | {
394 | using (FileNameFromHandleState f = new FileNameFromHandleState(handle))
395 | {
396 | ThreadPool.QueueUserWorkItem(new WaitCallback(GetFileNameFromHandle), f);
397 | if (f.WaitOne(wait))
398 | {
399 | fileName = f.FileName;
400 | return f.RetValue;
401 | }
402 | else
403 | {
404 | fileName = string.Empty;
405 | return false;
406 | }
407 | }
408 | }
409 | private static bool GetFileNameFromHandle(IntPtr handle, int processId, out string fileName)
410 | {
411 | IntPtr currentProcess = GetCurrentProcess();
412 | bool remote = processId != GetProcessId(currentProcess);
413 | SafeProcessHandle processHandle = null;
414 | SafeObjectHandle objectHandle = null;
415 | try
416 | {
417 | if (remote)
418 | {
419 | processHandle = OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
420 | if (DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
421 | handle = objectHandle.DangerousGetHandle();
422 | }
423 | return GetFileNameFromHandle(handle, out fileName, 200);
424 | }
425 | finally
426 | {
427 | if (remote)
428 | {
429 | if (processHandle != null)
430 | processHandle.Close();
431 | if (objectHandle != null)
432 | objectHandle.Close();
433 | }
434 | }
435 | }
436 |
437 | private static string GetHandleTypeToken(IntPtr handle)
438 | {
439 | int length;
440 | NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
441 | IntPtr ptr = IntPtr.Zero;
442 | RuntimeHelpers.PrepareConstrainedRegions();
443 | try
444 | {
445 | RuntimeHelpers.PrepareConstrainedRegions();
446 | try
447 | { }
448 | finally
449 | {
450 | if (length >= 0)
451 | ptr = Marshal.AllocHGlobal(length);
452 | }
453 | if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) == NT_STATUS.STATUS_SUCCESS)
454 | {
455 | return Marshal.PtrToStringUni((IntPtr)((int)ptr + 0x60));
456 | }
457 | }
458 | finally
459 | {
460 | Marshal.FreeHGlobal(ptr);
461 | }
462 | return string.Empty;
463 | }
464 | private static string GetHandleTypeToken(IntPtr handle, int processId)
465 | {
466 | IntPtr currentProcess = GetCurrentProcess();
467 | bool remote = processId != GetProcessId(currentProcess);
468 | SafeProcessHandle processHandle = null;
469 | SafeObjectHandle objectHandle = null;
470 | try
471 | {
472 | if (remote)
473 | {
474 | processHandle = OpenProcess(ProcessAccessRights.PROCESS_DUP_HANDLE, true, processId);
475 | if (DuplicateHandle(processHandle.DangerousGetHandle(), handle, currentProcess, out objectHandle, 0, false, DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
476 | {
477 | handle = objectHandle.DangerousGetHandle();
478 | }
479 | }
480 | return GetHandleTypeToken(handle);
481 | }
482 | finally
483 | {
484 | if (remote)
485 | {
486 | if (processHandle != null)
487 | {
488 | processHandle.Close();
489 | }
490 | if (objectHandle != null)
491 | {
492 | objectHandle.Close();
493 | }
494 | }
495 | }
496 | }
497 | private static bool GetHandleTypeFromToken(string token, out SystemHandleType handleType)
498 | {
499 | for (int i = 1; i < handleTypeTokenCount; i++)
500 | {
501 | if (handleTypeTokens[i] == token)
502 | {
503 | handleType = (SystemHandleType)i;
504 | return true;
505 | }
506 | }
507 | handleType = SystemHandleType.OB_TYPE_UNKNOWN;
508 | return false;
509 | }
510 | private static bool GetHandleType(IntPtr handle, out SystemHandleType handleType)
511 | {
512 | string token = GetHandleTypeToken(handle);
513 | return GetHandleTypeFromToken(token, out handleType);
514 | }
515 | private static bool GetHandleType(IntPtr handle, int processId, out SystemHandleType handleType)
516 | {
517 | string token = GetHandleTypeToken(handle, processId);
518 | return GetHandleTypeFromToken(token, out handleType);
519 | }
520 |
521 | private static bool ConvertDevicePathToDosPath(string devicePath, out string dosPath)
522 | {
523 | EnsureDeviceMap();
524 | int i = devicePath.Length;
525 | while (i > 0 && (i = devicePath.LastIndexOf('\\', i - 1)) != -1)
526 | {
527 | string drive;
528 | if (deviceMap.TryGetValue(devicePath.Substring(0, i), out drive))
529 | {
530 | dosPath = string.Concat(drive, devicePath.Substring(i));
531 | return dosPath.Length != 0;
532 | }
533 | }
534 | dosPath = string.Empty;
535 | return false;
536 | }
537 |
538 | private static void EnsureDeviceMap()
539 | {
540 | if (deviceMap == null)
541 | {
542 | Dictionary localDeviceMap = BuildDeviceMap();
543 | Interlocked.CompareExchange(ref deviceMap, localDeviceMap, null);
544 | }
545 | }
546 |
547 | private static Dictionary BuildDeviceMap()
548 | {
549 | string[] logicalDrives = Environment.GetLogicalDrives();
550 | Dictionary localDeviceMap = new Dictionary(logicalDrives.Length);
551 | StringBuilder lpTargetPath = new StringBuilder(MAX_PATH);
552 | foreach (string drive in logicalDrives)
553 | {
554 | string lpDeviceName = drive.Substring(0, 2);
555 | QueryDosDevice(lpDeviceName, lpTargetPath, MAX_PATH);
556 | localDeviceMap.Add(NormalizeDeviceName(lpTargetPath.ToString()), lpDeviceName);
557 | }
558 | localDeviceMap.Add(networkDevicePrefix.Substring(0, networkDevicePrefix.Length - 1), "\\");
559 | return localDeviceMap;
560 | }
561 |
562 | private static string NormalizeDeviceName(string deviceName)
563 | {
564 | if (string.Compare(deviceName, 0, networkDevicePrefix, 0, networkDevicePrefix.Length, StringComparison.InvariantCulture) == 0)
565 | {
566 | string shareName = deviceName.Substring(deviceName.IndexOf('\\', networkDevicePrefix.Length) + 1);
567 | return string.Concat(networkDevicePrefix, shareName);
568 | }
569 | return deviceName;
570 | }
571 |
572 | ///
573 | /// Gets the open files enumerator.
574 | ///
575 | /// The process id.
576 | ///
577 | public static IEnumerable GetOpenFilesEnumerator(int processId)
578 | {
579 | return new OpenFiles(processId);
580 | }
581 |
582 | public static List GetProcessesUsingFile(string fName)
583 | {
584 | List result = new List();
585 | foreach (Process p in Process.GetProcesses())
586 | {
587 | try
588 | {
589 | if (GetOpenFilesEnumerator(p.Id).Contains(fName))
590 | {
591 | result.Add(p);
592 | }
593 | }
594 | catch { }//some processes will fail
595 | }
596 | return result;
597 | }
598 |
599 | }
600 | }
601 |
--------------------------------------------------------------------------------
/WalkmanLib.SystemHandles.cs:
--------------------------------------------------------------------------------
1 | // Get all system open handles method - uses NTQuerySystemInformation and NTQueryObject
2 | //https://gist.github.com/i-e-b/2290426
3 | //https://stackoverflow.com/a/13735033/2999220
4 | //https://stackoverflow.com/a/6351168/2999220
5 |
6 |
7 | using System;
8 | using System.Collections.Concurrent;
9 | using System.Collections.Generic;
10 | using System.ComponentModel;
11 | using System.Diagnostics;
12 | using System.IO;
13 | using System.Runtime.ConstrainedExecution;
14 | using System.Runtime.InteropServices;
15 | using System.Text;
16 | using System.Threading;
17 |
18 | namespace WalkmanLib
19 | {
20 | class SystemHandles
21 | {
22 | #region Native Methods
23 |
24 | #region Enums
25 |
26 | //https://pinvoke.net/default.aspx/Enums.NtStatus
27 | //https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
28 | protected enum NTSTATUS : uint
29 | {
30 | STATUS_SUCCESS = 0x00000000,
31 | STATUS_BUFFER_OVERFLOW = 0x80000005,
32 | STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
33 | }
34 |
35 | //https://www.pinvoke.net/default.aspx/ntdll/SYSTEM_INFORMATION_CLASS.html
36 | protected enum SYSTEM_INFORMATION_CLASS
37 | {
38 | SystemBasicInformation = 0x00,
39 | SystemProcessorInformation = 0x01,
40 | SystemPerformanceInformation = 0x02,
41 | SystemTimeOfDayInformation = 0x03,
42 | SystemPathInformation = 0x04,
43 | SystemProcessInformation = 0x05,
44 | SystemCallCountInformation = 0x06,
45 | SystemDeviceInformation = 0x07,
46 | SystemProcessorPerformanceInformation = 0x08,
47 | SystemFlagsInformation = 0x09,
48 | SystemCallTimeInformation = 0x0A,
49 | SystemModuleInformation = 0x0B,
50 | SystemLocksInformation = 0x0C,
51 | SystemStackTraceInformation = 0x0D,
52 | SystemPagedPoolInformation = 0x0E,
53 | SystemNonPagedPoolInformation = 0x0F,
54 | SystemHandleInformation = 0x10,
55 | SystemObjectInformation = 0x11,
56 | SystemPageFileInformation = 0x12,
57 | SystemVdmInstemulInformation = 0x13,
58 | SystemVdmBopInformation = 0x14,
59 | SystemFileCacheInformation = 0x15,
60 | SystemPoolTagInformation = 0x16,
61 | SystemInterruptInformation = 0x17,
62 | SystemDpcBehaviorInformation = 0x18,
63 | SystemFullMemoryInformation = 0x19,
64 | SystemLoadGdiDriverInformation = 0x1A,
65 | SystemUnloadGdiDriverInformation = 0x1B,
66 | SystemTimeAdjustmentInformation = 0x1C,
67 | SystemSummaryMemoryInformation = 0x1D,
68 | SystemMirrorMemoryInformation = 0x1E,
69 | SystemPerformanceTraceInformation = 0x1F,
70 | SystemObsolete0 = 0x20,
71 | SystemExceptionInformation = 0x21,
72 | SystemCrashDumpStateInformation = 0x22,
73 | SystemKernelDebuggerInformation = 0x23,
74 | SystemContextSwitchInformation = 0x24,
75 | SystemRegistryQuotaInformation = 0x25,
76 | SystemExtendServiceTableInformation = 0x26,
77 | SystemPrioritySeperation = 0x27,
78 | SystemVerifierAddDriverInformation = 0x28,
79 | SystemVerifierRemoveDriverInformation = 0x29,
80 | SystemProcessorIdleInformation = 0x2A,
81 | SystemLegacyDriverInformation = 0x2B,
82 | SystemCurrentTimeZoneInformation = 0x2C,
83 | SystemLookasideInformation = 0x2D,
84 | SystemTimeSlipNotification = 0x2E,
85 | SystemSessionCreate = 0x2F,
86 | SystemSessionDetach = 0x30,
87 | SystemSessionInformation = 0x31,
88 | SystemRangeStartInformation = 0x32,
89 | SystemVerifierInformation = 0x33,
90 | SystemVerifierThunkExtend = 0x34,
91 | SystemSessionProcessInformation = 0x35,
92 | SystemLoadGdiDriverInSystemSpace = 0x36,
93 | SystemNumaProcessorMap = 0x37,
94 | SystemPrefetcherInformation = 0x38,
95 | SystemExtendedProcessInformation = 0x39,
96 | SystemRecommendedSharedDataAlignment = 0x3A,
97 | SystemComPlusPackage = 0x3B,
98 | SystemNumaAvailableMemory = 0x3C,
99 | SystemProcessorPowerInformation = 0x3D,
100 | SystemEmulationBasicInformation = 0x3E,
101 | SystemEmulationProcessorInformation = 0x3F,
102 | SystemExtendedHandleInformation = 0x40,
103 | SystemLostDelayedWriteInformation = 0x41,
104 | SystemBigPoolInformation = 0x42,
105 | SystemSessionPoolTagInformation = 0x43,
106 | SystemSessionMappedViewInformation = 0x44,
107 | SystemHotpatchInformation = 0x45,
108 | SystemObjectSecurityMode = 0x46,
109 | SystemWatchdogTimerHandler = 0x47,
110 | SystemWatchdogTimerInformation = 0x48,
111 | SystemLogicalProcessorInformation = 0x49,
112 | SystemWow64SharedInformationObsolete = 0x4A,
113 | SystemRegisterFirmwareTableInformationHandler = 0x4B,
114 | SystemFirmwareTableInformation = 0x4C,
115 | SystemModuleInformationEx = 0x4D,
116 | SystemVerifierTriageInformation = 0x4E,
117 | SystemSuperfetchInformation = 0x4F,
118 | SystemMemoryListInformation = 0x50,
119 | SystemFileCacheInformationEx = 0x51,
120 | SystemThreadPriorityClientIdInformation = 0x52,
121 | SystemProcessorIdleCycleTimeInformation = 0x53,
122 | SystemVerifierCancellationInformation = 0x54,
123 | SystemProcessorPowerInformationEx = 0x55,
124 | SystemRefTraceInformation = 0x56,
125 | SystemSpecialPoolInformation = 0x57,
126 | SystemProcessIdInformation = 0x58,
127 | SystemErrorPortInformation = 0x59,
128 | SystemBootEnvironmentInformation = 0x5A,
129 | SystemHypervisorInformation = 0x5B,
130 | SystemVerifierInformationEx = 0x5C,
131 | SystemTimeZoneInformation = 0x5D,
132 | SystemImageFileExecutionOptionsInformation = 0x5E,
133 | SystemCoverageInformation = 0x5F,
134 | SystemPrefetchPatchInformation = 0x60,
135 | SystemVerifierFaultsInformation = 0x61,
136 | SystemSystemPartitionInformation = 0x62,
137 | SystemSystemDiskInformation = 0x63,
138 | SystemProcessorPerformanceDistribution = 0x64,
139 | SystemNumaProximityNodeInformation = 0x65,
140 | SystemDynamicTimeZoneInformation = 0x66,
141 | SystemCodeIntegrityInformation = 0x67,
142 | SystemProcessorMicrocodeUpdateInformation = 0x68,
143 | SystemProcessorBrandString = 0x69,
144 | SystemVirtualAddressInformation = 0x6A,
145 | SystemLogicalProcessorAndGroupInformation = 0x6B,
146 | SystemProcessorCycleTimeInformation = 0x6C,
147 | SystemStoreInformation = 0x6D,
148 | SystemRegistryAppendString = 0x6E,
149 | SystemAitSamplingValue = 0x6F,
150 | SystemVhdBootInformation = 0x70,
151 | SystemCpuQuotaInformation = 0x71,
152 | SystemNativeBasicInformation = 0x72,
153 | SystemErrorPortTimeouts = 0x73,
154 | SystemLowPriorityIoInformation = 0x74,
155 | SystemBootEntropyInformation = 0x75,
156 | SystemVerifierCountersInformation = 0x76,
157 | SystemPagedPoolInformationEx = 0x77,
158 | SystemSystemPtesInformationEx = 0x78,
159 | SystemNodeDistanceInformation = 0x79,
160 | SystemAcpiAuditInformation = 0x7A,
161 | SystemBasicPerformanceInformation = 0x7B,
162 | SystemQueryPerformanceCounterInformation = 0x7C,
163 | SystemSessionBigPoolInformation = 0x7D,
164 | SystemBootGraphicsInformation = 0x7E,
165 | SystemScrubPhysicalMemoryInformation = 0x7F,
166 | SystemBadPageInformation = 0x80,
167 | SystemProcessorProfileControlArea = 0x81,
168 | SystemCombinePhysicalMemoryInformation = 0x82,
169 | SystemEntropyInterruptTimingInformation = 0x83,
170 | SystemConsoleInformation = 0x84,
171 | SystemPlatformBinaryInformation = 0x85,
172 | SystemPolicyInformation = 0x86,
173 | SystemHypervisorProcessorCountInformation = 0x87,
174 | SystemDeviceDataInformation = 0x88,
175 | SystemDeviceDataEnumerationInformation = 0x89,
176 | SystemMemoryTopologyInformation = 0x8A,
177 | SystemMemoryChannelInformation = 0x8B,
178 | SystemBootLogoInformation = 0x8C,
179 | SystemProcessorPerformanceInformationEx = 0x8D,
180 | SystemCriticalProcessErrorLogInformation = 0x8E,
181 | SystemSecureBootPolicyInformation = 0x8F,
182 | SystemPageFileInformationEx = 0x90,
183 | SystemSecureBootInformation = 0x91,
184 | SystemEntropyInterruptTimingRawInformation = 0x92,
185 | SystemPortableWorkspaceEfiLauncherInformation = 0x93,
186 | SystemFullProcessInformation = 0x94,
187 | SystemKernelDebuggerInformationEx = 0x95,
188 | SystemBootMetadataInformation = 0x96,
189 | SystemSoftRebootInformation = 0x97,
190 | SystemElamCertificateInformation = 0x98,
191 | SystemOfflineDumpConfigInformation = 0x99,
192 | SystemProcessorFeaturesInformation = 0x9A,
193 | SystemRegistryReconciliationInformation = 0x9B,
194 | SystemEdidInformation = 0x9C,
195 | SystemManufacturingInformation = 0x9D,
196 | SystemEnergyEstimationConfigInformation = 0x9E,
197 | SystemHypervisorDetailInformation = 0x9F,
198 | SystemProcessorCycleStatsInformation = 0xA0,
199 | SystemVmGenerationCountInformation = 0xA1,
200 | SystemTrustedPlatformModuleInformation = 0xA2,
201 | SystemKernelDebuggerFlags = 0xA3,
202 | SystemCodeIntegrityPolicyInformation = 0xA4,
203 | SystemIsolatedUserModeInformation = 0xA5,
204 | SystemHardwareSecurityTestInterfaceResultsInformation = 0xA6,
205 | SystemSingleModuleInformation = 0xA7,
206 | SystemAllowedCpuSetsInformation = 0xA8,
207 | SystemDmaProtectionInformation = 0xA9,
208 | SystemInterruptCpuSetsInformation = 0xAA,
209 | SystemSecureBootPolicyFullInformation = 0xAB,
210 | SystemCodeIntegrityPolicyFullInformation = 0xAC,
211 | SystemAffinitizedInterruptProcessorInformation = 0xAD,
212 | SystemRootSiloInformation = 0xAE,
213 | SystemCpuSetInformation = 0xAF,
214 | SystemCpuSetTagInformation = 0xB0,
215 | SystemWin32WerStartCallout = 0xB1,
216 | SystemSecureKernelProfileInformation = 0xB2,
217 | SystemCodeIntegrityPlatformManifestInformation = 0xB3,
218 | SystemInterruptSteeringInformation = 0xB4,
219 | SystemSuppportedProcessorArchitectures = 0xB5,
220 | SystemMemoryUsageInformation = 0xB6,
221 | SystemCodeIntegrityCertificateInformation = 0xB7,
222 | SystemPhysicalMemoryInformation = 0xB8,
223 | SystemControlFlowTransition = 0xB9,
224 | SystemKernelDebuggingAllowed = 0xBA,
225 | SystemActivityModerationExeState = 0xBB,
226 | SystemActivityModerationUserSettings = 0xBC,
227 | SystemCodeIntegrityPoliciesFullInformation = 0xBD,
228 | SystemCodeIntegrityUnlockInformation = 0xBE,
229 | SystemIntegrityQuotaInformation = 0xBF,
230 | SystemFlushInformation = 0xC0,
231 | SystemProcessorIdleMaskInformation = 0xC1,
232 | SystemSecureDumpEncryptionInformation = 0xC2,
233 | SystemWriteConstraintInformation = 0xC3,
234 | SystemKernelVaShadowInformation = 0xC4,
235 | SystemHypervisorSharedPageInformation = 0xC5,
236 | SystemFirmwareBootPerformanceInformation = 0xC6,
237 | SystemCodeIntegrityVerificationInformation = 0xC7,
238 | SystemFirmwarePartitionInformation = 0xC8,
239 | SystemSpeculationControlInformation = 0xC9,
240 | SystemDmaGuardPolicyInformation = 0xCA,
241 | SystemEnclaveLaunchControlInformation = 0xCB,
242 | SystemWorkloadAllowedCpuSetsInformation = 0xCC,
243 | SystemCodeIntegrityUnlockModeInformation = 0xCD,
244 | SystemLeapSecondInformation = 0xCE,
245 | SystemFlags2Information = 0xCF,
246 | SystemSecurityModelInformation = 0xD0,
247 | SystemCodeIntegritySyntheticCacheInformation = 0xD1,
248 | MaxSystemInfoClass = 0xD2
249 | }
250 |
251 | //https://www.pinvoke.net/default.aspx/Enums.OBJECT_INFORMATION_CLASS
252 | protected enum OBJECT_INFORMATION_CLASS
253 | {
254 | ObjectBasicInformation = 0,
255 | ObjectNameInformation = 1,
256 | ObjectTypeInformation = 2,
257 | ObjectAllTypesInformation = 3,
258 | ObjectHandleInformation = 4
259 | }
260 |
261 | //https://docs.microsoft.com/en-za/windows/win32/procthread/process-security-and-access-rights
262 | //https://www.pinvoke.net/default.aspx/Enums.ProcessAccess
263 | protected enum PROCESS_ACCESS_RIGHTS
264 | {
265 | PROCESS_TERMINATE = 0x00000001,
266 | PROCESS_CREATE_THREAD = 0x00000002,
267 | PROCESS_SET_SESSION_ID = 0x00000004,
268 | PROCESS_VM_OPERATION = 0x00000008,
269 | PROCESS_VM_READ = 0x00000010,
270 | PROCESS_VM_WRITE = 0x00000020,
271 | PROCESS_DUP_HANDLE = 0x00000040,
272 | PROCESS_CREATE_PROCESS = 0x00000080,
273 | PROCESS_SET_QUOTA = 0x00000100,
274 | PROCESS_SET_INFORMATION = 0x00000200,
275 | PROCESS_QUERY_INFORMATION = 0x00000400,
276 | PROCESS_SUSPEND_RESUME = 0x00000800,
277 | PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000,
278 | DELETE = 0x00010000,
279 | READ_CONTROL = 0x00020000,
280 | WRITE_DAC = 0x00040000,
281 | WRITE_OWNER = 0x00080000,
282 | STANDARD_RIGHTS_REQUIRED = 0x000F0000,
283 | SYNCHRONIZE = 0x00100000,
284 |
285 | PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF
286 | }
287 |
288 | //https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle#DUPLICATE_CLOSE_SOURCE
289 | protected enum DUPLICATE_HANDLE_OPTIONS
290 | {
291 | DUPLICATE_CLOSE_SOURCE = 0x00000001,
292 | DUPLICATE_SAME_ACCESS = 0x00000002
293 | }
294 |
295 | //http://www.jasinskionline.com/TechnicalWiki/SYSTEM_HANDLE_INFORMATION-WinApi-Struct.ashx
296 | internal enum SYSTEM_HANDLE_FLAGS : byte
297 | {
298 | PROTECT_FROM_CLOSE = 0x01,
299 | INHERIT = 0x02
300 | }
301 |
302 | //https://www.winehq.org/pipermail/wine-patches/2005-October/021642.html
303 | //https://github.com/olimsaidov/autorun-remover/blob/b558df6487ae1cb4cb998fab3330c07bb7de0f21/NativeAPI.pas#L108
304 | internal enum SYSTEM_HANDLE_TYPE
305 | {
306 | UNKNOWN = 00,
307 | TYPE = 01,
308 | DIRECTORY = 02,
309 | SYMBOLIC_LINK = 03,
310 | TOKEN = 04,
311 | PROCESS = 05,
312 | THREAD = 06,
313 | JOB = 07,
314 | EVENT = 08,
315 | EVENT_PAIR = 09,
316 | MUTANT = 10,
317 | UNKNOWN_11 = 11,
318 | SEMAPHORE = 12,
319 | TIMER = 13,
320 | PROFILE = 14,
321 | WINDOW_STATION = 15,
322 | DESKTOP = 16,
323 | SECTION = 17,
324 | KEY = 18,
325 | PORT = 19,
326 | WAITABLE_PORT = 20,
327 | ADAPTER = 21,
328 | CONTROLLER = 22,
329 | DEVICE = 23,
330 | DRIVER = 24,
331 | IO_COMPLETION = 25,
332 | FILE = 28,
333 |
334 | // From my own research
335 | TP_WORKER_FACTORY,
336 | ALPC_PORT,
337 | KEYED_EVENT,
338 | SESSION,
339 | IO_COMPLETION_RESERVE,
340 | WMI_GUID,
341 | USER_APC_RESERVE,
342 | IR_TIMER,
343 | COMPOSITION,
344 | WAIT_COMPLETION_PACKET,
345 | DXGK_SHARED_RESOURCE,
346 | DXGK_SHARED_SYNC_OBJECT,
347 | DXGK_DISPLAY_MANAGER_OBJECT,
348 | DXGK_COMPOSITION_OBJECT,
349 | OTHER
350 | }
351 |
352 | #endregion
353 |
354 | #region Structs
355 |
356 | //https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=18975&zep=OpenedFileFinder%2fUtils.h&rzp=%2fKB%2fshell%2fOpenedFileFinder%2f%2fopenedfilefinder_src.zip
357 | [StructLayout(LayoutKind.Sequential)]
358 | protected struct SYSTEM_HANDLE_INFORMATION
359 | {
360 | //public IntPtr dwCount;
361 | public uint dwCount;
362 |
363 | // see https://stackoverflow.com/a/38884095/2999220 - MarshalAs doesn't allow variable sized arrays
364 | //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct)]
365 | //public SYSTEM_HANDLE[] Handles;
366 | public IntPtr Handles;
367 | }
368 |
369 | //https://stackoverflow.com/a/5163277/2999220
370 | //http://www.jasinskionline.com/TechnicalWiki/SYSTEM_HANDLE_INFORMATION-WinApi-Struct.ashx
371 | [StructLayout(LayoutKind.Sequential)]
372 | internal struct SYSTEM_HANDLE
373 | {
374 | /// Handle Owner Process ID
375 | public uint dwProcessId;
376 | /// Object Type
377 | public byte bObjectType;
378 | /// Handle Flags
379 | public SYSTEM_HANDLE_FLAGS bFlags;
380 | /// Handle Value
381 | public ushort wValue;
382 | /// Object Pointer
383 | IntPtr pAddress;
384 | /// Access Mask
385 | public uint dwGrantedAccess;
386 | }
387 |
388 | //https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_unicode_string
389 | //https://www.pinvoke.net/default.aspx/Structures/UNICODE_STRING.html
390 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
391 | protected struct UNICODE_STRING
392 | {
393 | public readonly ushort Length;
394 | public readonly ushort MaximumLength;
395 | [MarshalAs(UnmanagedType.LPWStr)]
396 | public readonly string Buffer;
397 |
398 | public UNICODE_STRING(string s)
399 | {
400 | Length = (ushort)(s.Length * 2);
401 | MaximumLength = (ushort)(Length + 2);
402 | Buffer = s;
403 | }
404 | }
405 |
406 | //https://www.pinvoke.net/default.aspx/Structures.GENERIC_MAPPING
407 | //http://www.jasinskionline.com/technicalwiki/GENERIC_MAPPING-WinApi-Struct.ashx
408 | [StructLayout(LayoutKind.Sequential)]
409 | protected struct GENERIC_MAPPING
410 | {
411 | public uint GenericRead;
412 | public uint GenericWrite;
413 | public uint GenericExecute;
414 | public uint GenericAll;
415 | }
416 |
417 | //http://www.jasinskionline.com/technicalwiki/OBJECT_NAME_INFORMATION-WinApi-Struct.ashx
418 | [StructLayout(LayoutKind.Sequential)]
419 | protected struct OBJECT_NAME_INFORMATION
420 | {
421 | public UNICODE_STRING Name;
422 | }
423 |
424 | //https://docs.microsoft.com/en-za/windows-hardware/drivers/ddi/ntifs/ns-ntifs-__public_object_type_information
425 | //http://www.jasinskionline.com/technicalwiki/OBJECT_TYPE_INFORMATION-WinApi-Struct.ashx
426 | [StructLayout(LayoutKind.Sequential)]
427 | protected struct OBJECT_TYPE_INFORMATION
428 | {
429 | public UNICODE_STRING TypeName;
430 | public int ObjectCount;
431 | public int HandleCount;
432 | int Reserved1;
433 | int Reserved2;
434 | int Reserved3;
435 | int Reserved4;
436 | public int PeakObjectCount;
437 | public int PeakHandleCount;
438 | int Reserved5;
439 | int Reserved6;
440 | int Reserved7;
441 | int Reserved8;
442 | public int InvalidAttributes;
443 | public GENERIC_MAPPING GenericMapping;
444 | public int ValidAccess;
445 | byte Unknown;
446 | public byte MaintainHandleDatabase;
447 | public int PoolType;
448 | public int PagedPoolUsage;
449 | public int NonPagedPoolUsage;
450 | }
451 |
452 | #endregion
453 |
454 | #region Methods
455 |
456 | //https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
457 | [DllImport("ntdll.dll")]
458 | protected static extern NTSTATUS NtQuerySystemInformation(
459 | [In] SYSTEM_INFORMATION_CLASS SystemInformationClass,
460 | [Out] IntPtr SystemInformation,
461 | [In] uint SystemInformationLength,
462 | [Out] out uint ReturnLength
463 | );
464 |
465 | //https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryobject
466 | [DllImport("ntdll.dll")]
467 | protected static extern NTSTATUS NtQueryObject(
468 | [In] IntPtr Handle,
469 | [In] OBJECT_INFORMATION_CLASS ObjectInformationClass,
470 | [In] IntPtr ObjectInformation,
471 | [In] uint ObjectInformationLength,
472 | [Out] out uint ReturnLength
473 | );
474 |
475 | //https://docs.microsoft.com/en-za/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
476 | [DllImport("kernel32.dll", SetLastError = true)]
477 | protected static extern IntPtr OpenProcess(
478 | [In] PROCESS_ACCESS_RIGHTS dwDesiredAccess,
479 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
480 | [In] uint dwProcessId
481 | );
482 |
483 | //https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
484 | [DllImport("kernel32.dll", SetLastError = true)]
485 | [return: MarshalAs(UnmanagedType.Bool)]
486 | protected static extern bool DuplicateHandle(
487 | [In] IntPtr hSourceProcessHandle,
488 | [In] IntPtr hSourceHandle,
489 | [In] IntPtr hTargetProcessHandle,
490 | [Out] out IntPtr lpTargetHandle,
491 | [In] uint dwDesiredAccess,
492 | [In, MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
493 | [In] DUPLICATE_HANDLE_OPTIONS dwOptions
494 | );
495 |
496 | //https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
497 | [DllImport("kernel32.dll")]
498 | protected static extern IntPtr GetCurrentProcess();
499 |
500 | //https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
501 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
502 | [DllImport("kernel32.dll", SetLastError = true)]
503 | [return: MarshalAs(UnmanagedType.Bool)]
504 | protected static extern bool CloseHandle(
505 | [In] IntPtr hObject
506 | );
507 |
508 | //https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-querydosdevicea
509 | //https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-querydosdevicew
510 | [DllImport("kernel32.dll", SetLastError = true)]
511 | protected static extern uint QueryDosDevice(
512 | [In] string lpDeviceName,
513 | [Out] StringBuilder lpTargetPath,
514 | [In] uint ucchMax
515 | );
516 |
517 | #endregion
518 |
519 | #endregion
520 |
521 | #region Public Methods
522 |
523 | #region GetSystemHandles
524 |
525 | /// Gets all the open handles on the system. Use GetHandleInfo to retrieve proper type and name information.
526 | /// Enumerable list of system handles
527 | internal static IEnumerable GetSystemHandles()
528 | {
529 | uint length = 0x1000;
530 | IntPtr ptr = IntPtr.Zero;
531 | bool done = false;
532 | try
533 | {
534 | while (!done)
535 | {
536 | ptr = Marshal.AllocHGlobal((int)length);
537 | uint wantedLength;
538 | switch (NtQuerySystemInformation(
539 | SYSTEM_INFORMATION_CLASS.SystemHandleInformation,
540 | ptr, length, out wantedLength))
541 | {
542 | case NTSTATUS.STATUS_SUCCESS:
543 | done = true; // can't double-break in C#
544 | break;
545 | case NTSTATUS.STATUS_INFO_LENGTH_MISMATCH:
546 | length = Math.Max(length, wantedLength);
547 | Marshal.FreeHGlobal(ptr);
548 | ptr = IntPtr.Zero;
549 | break;
550 | default:
551 | throw new Exception("Failed to retrieve system handle information.", new Win32Exception());
552 | }
553 | }
554 |
555 | long handleCount = IntPtr.Size == 4 ? Marshal.ReadInt32(ptr) : Marshal.ReadInt64(ptr);
556 | long offset = IntPtr.Size;
557 | long size = Marshal.SizeOf(typeof(SYSTEM_HANDLE));
558 |
559 | for (long i = 0; i < handleCount; i++)
560 | {
561 | SYSTEM_HANDLE struc = Marshal.PtrToStructure((IntPtr)((long)ptr + offset));
562 | yield return struc;
563 |
564 | offset += size;
565 | }
566 | }
567 | finally
568 | {
569 | if (ptr != IntPtr.Zero)
570 | Marshal.FreeHGlobal(ptr);
571 | }
572 | }
573 |
574 | #endregion
575 |
576 | #region GetHandleInfo
577 |
578 | internal struct HandleInfo
579 | {
580 | public uint ProcessID;
581 | public ushort HandleID;
582 | public uint GrantedAccess;
583 | public byte RawType;
584 | public SYSTEM_HANDLE_FLAGS Flags;
585 | public string Name;
586 | public string TypeString;
587 | public SYSTEM_HANDLE_TYPE Type;
588 | }
589 |
590 | private static ConcurrentDictionary rawTypeMap = new ConcurrentDictionary();
591 |
592 | private static SYSTEM_HANDLE_TYPE HandleTypeFromString(string typeString)
593 | {
594 | switch (typeString)
595 | {
596 | case null:
597 | return SYSTEM_HANDLE_TYPE.UNKNOWN;
598 | case "Directory":
599 | return SYSTEM_HANDLE_TYPE.DIRECTORY;
600 | case "SymbolicLink":
601 | return SYSTEM_HANDLE_TYPE.SYMBOLIC_LINK;
602 | case "Token":
603 | return SYSTEM_HANDLE_TYPE.TOKEN;
604 | case "Process":
605 | return SYSTEM_HANDLE_TYPE.PROCESS;
606 | case "Thread":
607 | return SYSTEM_HANDLE_TYPE.THREAD;
608 | case "Job":
609 | return SYSTEM_HANDLE_TYPE.JOB;
610 | case "Event":
611 | return SYSTEM_HANDLE_TYPE.EVENT;
612 | case "Mutant":
613 | return SYSTEM_HANDLE_TYPE.MUTANT;
614 | case "Semaphore":
615 | return SYSTEM_HANDLE_TYPE.SEMAPHORE;
616 | case "Timer":
617 | return SYSTEM_HANDLE_TYPE.TIMER;
618 | case "WindowStation":
619 | return SYSTEM_HANDLE_TYPE.WINDOW_STATION;
620 | case "Desktop":
621 | return SYSTEM_HANDLE_TYPE.DESKTOP;
622 | case "Section":
623 | return SYSTEM_HANDLE_TYPE.SECTION;
624 | case "Key":
625 | return SYSTEM_HANDLE_TYPE.KEY;
626 | case "IoCompletion":
627 | return SYSTEM_HANDLE_TYPE.IO_COMPLETION;
628 | case "File":
629 | return SYSTEM_HANDLE_TYPE.FILE;
630 | case "TpWorkerFactory":
631 | return SYSTEM_HANDLE_TYPE.TP_WORKER_FACTORY;
632 | case "ALPC Port":
633 | return SYSTEM_HANDLE_TYPE.ALPC_PORT;
634 | case "KeyedEvent":
635 | return SYSTEM_HANDLE_TYPE.KEYED_EVENT;
636 | case "Session":
637 | return SYSTEM_HANDLE_TYPE.SESSION;
638 | case "IoCompletionReserve":
639 | return SYSTEM_HANDLE_TYPE.IO_COMPLETION_RESERVE;
640 | case "WmiGuid":
641 | return SYSTEM_HANDLE_TYPE.WMI_GUID;
642 | case "UserApcReserve":
643 | return SYSTEM_HANDLE_TYPE.USER_APC_RESERVE;
644 | case "IRTimer":
645 | return SYSTEM_HANDLE_TYPE.IR_TIMER;
646 | case "Composition":
647 | return SYSTEM_HANDLE_TYPE.COMPOSITION;
648 | case "WaitCompletionPacket":
649 | return SYSTEM_HANDLE_TYPE.WAIT_COMPLETION_PACKET;
650 | case "DxgkSharedResource":
651 | return SYSTEM_HANDLE_TYPE.DXGK_SHARED_RESOURCE;
652 | case "DxgkSharedSyncObject":
653 | return SYSTEM_HANDLE_TYPE.DXGK_SHARED_SYNC_OBJECT;
654 | case "DxgkDisplayManagerObject":
655 | return SYSTEM_HANDLE_TYPE.DXGK_DISPLAY_MANAGER_OBJECT;
656 | case "DxgkCompositionObject":
657 | return SYSTEM_HANDLE_TYPE.DXGK_COMPOSITION_OBJECT;
658 | default:
659 | return SYSTEM_HANDLE_TYPE.OTHER;
660 | }
661 | }
662 |
663 | ///
664 | /// Gets the handle type and name, and puts the other properties into more user-friendly fields.
665 | ///
666 | /// This function gets typeInfo from an internal type map (rawType to typeString) that is built as types are retrieved.
667 | /// To get full type information of handle types that could not be retrieved,
668 | /// either put the handles into a list, build a second map and apply them retroactively,
669 | /// or call this function on all System Handles beforehand with getting names Disabled.
670 | ///
671 | /// Handle struct returned by GetSystemHandles
672 | /// False (default) to ignore certain names that cause the system query to hang. Only set to true in a thread that can be killed.
673 | /// Set this to only attempt to get Handle names for a specific handle type. Set to int.MaxValue to disable getting file names.
674 | /// HandleInfo struct with retrievable information populated.
675 | internal static HandleInfo GetHandleInfo(SYSTEM_HANDLE handle, bool getAllNames = false, SYSTEM_HANDLE_TYPE onlyGetNameFor = SYSTEM_HANDLE_TYPE.UNKNOWN)
676 | {
677 | HandleInfo handleInfo = new HandleInfo
678 | {
679 | ProcessID = handle.dwProcessId,
680 | HandleID = handle.wValue,
681 | GrantedAccess = handle.dwGrantedAccess,
682 | RawType = handle.bObjectType,
683 | Flags = handle.bFlags,
684 | Name = null,
685 | TypeString = null,
686 | Type = SYSTEM_HANDLE_TYPE.UNKNOWN
687 | };
688 |
689 | // get type from cached map if it exists
690 | if (rawTypeMap.ContainsKey(handleInfo.RawType))
691 | {
692 | handleInfo.TypeString = rawTypeMap[handleInfo.RawType];
693 | handleInfo.Type = HandleTypeFromString(handleInfo.TypeString);
694 | }
695 |
696 | IntPtr sourceProcessHandle = IntPtr.Zero;
697 | IntPtr handleDuplicate = IntPtr.Zero;
698 | try
699 | {
700 | sourceProcessHandle = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, handleInfo.ProcessID);
701 |
702 | // To read info about a handle owned by another process we must duplicate it into ours
703 | // For simplicity, current process handles will also get duplicated; remember that process handles cannot be compared for equality
704 | if (!DuplicateHandle(sourceProcessHandle, (IntPtr)handleInfo.HandleID, GetCurrentProcess(), out handleDuplicate, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS))
705 | return handleInfo;
706 |
707 | // Get the object type if it hasn't been retrieved from cache map above
708 | if (!rawTypeMap.ContainsKey(handleInfo.RawType))
709 | {
710 | uint length;
711 | NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, IntPtr.Zero, 0, out length);
712 |
713 | IntPtr ptr = IntPtr.Zero;
714 | try
715 | {
716 | ptr = Marshal.AllocHGlobal((int)length);
717 | if (NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, ptr, length, out length) != NTSTATUS.STATUS_SUCCESS)
718 | return handleInfo;
719 |
720 | OBJECT_TYPE_INFORMATION typeInfo = Marshal.PtrToStructure(ptr);
721 | handleInfo.TypeString = typeInfo.TypeName.Buffer;
722 | }
723 | finally
724 | {
725 | Marshal.FreeHGlobal(ptr);
726 | }
727 |
728 | rawTypeMap.TryAdd(handleInfo.RawType, handleInfo.TypeString);
729 | handleInfo.Type = HandleTypeFromString(handleInfo.TypeString);
730 | }
731 |
732 | // Get the object name
733 | if (handleInfo.TypeString != null &&
734 | // only check onlyGetNameFor if it isn't UNKNOWN
735 | (onlyGetNameFor == SYSTEM_HANDLE_TYPE.UNKNOWN || handleInfo.Type == onlyGetNameFor) &&
736 | (getAllNames == true || (
737 | // this type can hang for ~15mins, but excluding it cuts a lot of results, and it does eventually resolve...
738 | //!(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x120089 && handleInfo.Flags == 0x00 ) &&
739 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x120089 && handleInfo.Flags == SYSTEM_HANDLE_FLAGS.INHERIT) &&
740 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x120189 && handleInfo.Flags == 0x00 ) &&
741 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x120189 && handleInfo.Flags == SYSTEM_HANDLE_FLAGS.INHERIT) &&
742 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x12019f && handleInfo.Flags == 0x00 ) &&
743 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x12019f && handleInfo.Flags == SYSTEM_HANDLE_FLAGS.INHERIT) &&
744 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x1a019f && handleInfo.Flags == 0x00 ) &&
745 | !(handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && handleInfo.GrantedAccess == 0x1a019f && handleInfo.Flags == SYSTEM_HANDLE_FLAGS.INHERIT)
746 | )))// don't query some objects that get stuck (NtQueryObject hangs on NamedPipes)
747 | {
748 | uint length;
749 | NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, IntPtr.Zero, 0, out length);
750 |
751 | IntPtr ptr = IntPtr.Zero;
752 | try
753 | {
754 | ptr = Marshal.AllocHGlobal((int)length);
755 | if (NtQueryObject(handleDuplicate, OBJECT_INFORMATION_CLASS.ObjectNameInformation, ptr, length, out length) != NTSTATUS.STATUS_SUCCESS)
756 | return handleInfo;
757 |
758 | OBJECT_NAME_INFORMATION nameInfo = Marshal.PtrToStructure(ptr);
759 | handleInfo.Name = nameInfo.Name.Buffer;
760 | }
761 | finally
762 | {
763 | Marshal.FreeHGlobal(ptr);
764 | }
765 | }
766 | }
767 | finally
768 | {
769 | CloseHandle(sourceProcessHandle);
770 | if (handleDuplicate != IntPtr.Zero)
771 | CloseHandle(handleDuplicate);
772 | }
773 |
774 | return handleInfo;
775 | }
776 |
777 | #endregion
778 |
779 | #region CloseSystemHandle
780 |
781 | // https://www.codeproject.com/Articles/18975/Listing-Used-Files
782 | /// Attempts to close a handle in a different process. Fails silently if the handle exists but could not be closed.
783 | /// Process ID of the process containing the handle to close
784 | /// Handle value in the target process to close
785 | internal static void CloseSystemHandle(uint ProcessID, ushort HandleID)
786 | {
787 | IntPtr sourceProcessHandle = IntPtr.Zero;
788 | IntPtr handleDuplicate = IntPtr.Zero;
789 | try
790 | {
791 | sourceProcessHandle = OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, true, ProcessID);
792 | if ((int)sourceProcessHandle < 1)
793 | throw new ArgumentException("Process ID Not Found!", "ProcessID", new Win32Exception());
794 |
795 | // always returns false, no point in checking
796 | DuplicateHandle(sourceProcessHandle, (IntPtr)HandleID, GetCurrentProcess(), out handleDuplicate, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_CLOSE_SOURCE);
797 | if ((int)handleDuplicate < 1 && Marshal.GetLastWin32Error() == 6) // ERROR_INVALID_HANDLE: The handle is invalid.
798 | throw new ArgumentException("Handle ID Not Found!", "HandleID", new Win32Exception(6));
799 | }
800 | finally
801 | {
802 | CloseHandle(sourceProcessHandle);
803 | if (handleDuplicate != IntPtr.Zero)
804 | CloseHandle(handleDuplicate);
805 | }
806 | }
807 |
808 | #endregion
809 |
810 | #region ConvertDevicePathToDosPath
811 |
812 | private static Dictionary deviceMap;
813 | private const string networkDeviceQueryDosDevicePrefix = "\\Device\\LanmanRedirector\\";
814 | private const string networkDeviceSystemHandlePrefix = "\\Device\\Mup\\";
815 | private const int MAX_PATH = 260;
816 |
817 | private static string NormalizeDeviceName(string deviceName)
818 | {
819 | if (string.Compare( // if deviceName.StartsWith(networkDeviceQueryDosDevicePrefix)
820 | deviceName, 0,
821 | networkDeviceQueryDosDevicePrefix, 0,
822 | networkDeviceQueryDosDevicePrefix.Length, StringComparison.InvariantCulture) == 0)
823 | {
824 | string shareName = deviceName.Substring(deviceName.IndexOf('\\', networkDeviceQueryDosDevicePrefix.Length) + 1);
825 | return string.Concat(networkDeviceSystemHandlePrefix, shareName);
826 | }
827 | return deviceName;
828 | }
829 |
830 | private static Dictionary BuildDeviceMap()
831 | {
832 | string[] logicalDrives = Environment.GetLogicalDrives();
833 | Dictionary localDeviceMap = new Dictionary(logicalDrives.Length);
834 |
835 | StringBuilder lpTargetPath = new StringBuilder(MAX_PATH);
836 | foreach (string drive in logicalDrives)
837 | {
838 | string lpDeviceName = drive.Substring(0, 2);
839 |
840 | QueryDosDevice(lpDeviceName, lpTargetPath, MAX_PATH);
841 |
842 | localDeviceMap.Add(
843 | NormalizeDeviceName(lpTargetPath.ToString()),
844 | lpDeviceName
845 | );
846 | }
847 | // add a map so \\COMPUTER\ shares get picked up correctly - these will come as \Device\Mup\COMPUTER\share
848 | localDeviceMap.Add(
849 | // remove the last slash from networkDeviceSystemHandlePrefix:
850 | networkDeviceSystemHandlePrefix.Substring(0, networkDeviceSystemHandlePrefix.Length - 1),
851 | "\\");
852 | return localDeviceMap;
853 | }
854 |
855 | private static void EnsureDeviceMap()
856 | {
857 | if (deviceMap == null)
858 | {
859 | Dictionary localDeviceMap = BuildDeviceMap();
860 | Interlocked.CompareExchange(ref deviceMap, localDeviceMap, null);
861 | }
862 | }
863 |
864 | ///
865 | /// Converts a device path to a DOS path. Requires a trailing slash if just the device path is passed.
866 | /// Returns string.Empty if no device is found.
867 | ///
868 | /// Full path including a device. Device paths usually start with \Device\HarddiskVolume[n]\
869 | /// DOS Path or string.Empty if none found
870 | public static string ConvertDevicePathToDosPath(string devicePath)
871 | {
872 | EnsureDeviceMap();
873 | int i = devicePath.Length;
874 |
875 | // search in reverse, to catch network shares that are mapped before returning general network path
876 | while (i > 0 && (i = devicePath.LastIndexOf('\\', i - 1)) != -1)
877 | {
878 | string drive;
879 | if (deviceMap.TryGetValue(devicePath.Remove(i), out drive))
880 | return string.Concat(drive, devicePath.Substring(i));
881 | }
882 | return devicePath;
883 | }
884 |
885 | #endregion
886 |
887 | #region GetFileHandles / GetLockingProcesses
888 |
889 | ///
890 | /// Searches through all the open handles on the system, and returns handles with a path containing .
891 | /// If on a network share, should refer to the deepest mapped drive.
892 | ///
893 | /// Path to look for handles to.
894 | /// Enumerable list of handles matching
895 | internal static IEnumerable GetFileHandles(string filePath)
896 | {
897 | if (File.Exists(filePath))
898 | filePath = new FileInfo(filePath).FullName;
899 | else if (Directory.Exists(filePath))
900 | filePath = new DirectoryInfo(filePath).FullName;
901 |
902 | foreach (SYSTEM_HANDLE systemHandle in GetSystemHandles())
903 | {
904 | HandleInfo handleInfo = GetHandleInfo(systemHandle, onlyGetNameFor: SYSTEM_HANDLE_TYPE.FILE);
905 | if (handleInfo.Type == SYSTEM_HANDLE_TYPE.FILE && !String.IsNullOrEmpty(handleInfo.Name))
906 | {
907 | handleInfo.Name = ConvertDevicePathToDosPath(handleInfo.Name);
908 | if (handleInfo.Name.Contains(filePath))
909 | yield return handleInfo;
910 | }
911 | }
912 | }
913 |
914 | ///
915 | /// Gets a list of processes locking .
916 | /// Processes that can't be retrieved by PID (if they have exited) will be excluded.
917 | /// If on a network share, should refer to the deepest mapped drive.
918 | ///
919 | /// Path to look for locking processes.
920 | /// List of processes locking .
921 | public static List GetLockingProcesses(string filePath)
922 | {
923 | List processes = new List();
924 | foreach (HandleInfo handleInfo in GetFileHandles(filePath))
925 | {
926 | try
927 | {
928 | Process process = Process.GetProcessById((int)handleInfo.ProcessID);
929 | processes.Add(process);
930 | } // process has exited
931 | catch (ArgumentException) { }
932 | }
933 | return processes;
934 | }
935 |
936 | #endregion
937 |
938 | #endregion
939 | }
940 | }
941 |
--------------------------------------------------------------------------------