Asked 1 month ago by CosmicScholar268
How can I enumerate detailed open handles of a process using PowerShell?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by CosmicScholar268
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
When investigating which files a process is actively working with, I often rely on Sysinternals tools like handle.exe
or procexp.exe
that display file patterns. However, when I try to obtain open handles via PowerShell, I only get the count of handles rather than the actual handle references.
For example, using PowerShell:
POWERSHELLGet-Process -Id $SomePid | select Handles
Similarly, using .NET calls:
POWERSHELL[System.Diagnostics.Process]::GetProcessById($SomePid).Handles
And with WMI:
POWERSHELLGet-CimSession Win32_Process -Filter "Process = $SomePid" | select HandleCount
How can I retrieve the detailed open handle information (such as handle references) for a process in PowerShell? I understand that getting such details might require lower-level Windows API calls. Any guidance or alternative approaches would be appreciated.
Comments 0
•Answers 3
•Views 79
Answer by TychoAI Agent • 1 month ago
The behavior you're seeing is by design. The common APIs in .NET (and WMI) expose only summary information—specifically, a count of open handles—because they don't provide direct access to the underlying OS handle table. This low-level detail is considered outside the scope of the managed APIs, which focus primarily on higher-level process and resource management.
If you need actual handle details (such as file names or specific object types), you'll have to resort to lower-level Windows APIs. Tools like Sysinternals' Handle.exe and Process Explorer use undocumented or native (often NT API) methods like NtQuerySystemInformation to enumerate and inspect the handles in a process. These methods go beyond what is publicly available in .NET or WMI.
In summary, while .NET and WMI give you a handle count (via properties like Process.Handles or WMI’s HandleCount), they intentionally don't provide a list of the detailed handle objects. For that level of detail, you must use native OS tools or write interop code to tap into the lower-level Windows API functionality.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by SupernovaNomad187 • 1 month ago
As stated in comments, it would be a big chunk of P/Invoke code, hopefully this help you giving you a start.
The current implementation will list all open handles in your system using NtQuerySystemInformation
function. You can later use PowerShell to filter by the UniqueProcessId
property of these objects. However, note this implementation provides limited information, see the SystemHandle
class in the code.
To get the handle TypeName
the implementation uses NtQueryObject
function, however from here gets more complex as you need handle each type differently and call different Win APIs. For example, for File
handles, you would use GetFileInformationByHandleEx
function and so on.
Put the code below in a file, call it whatever you want (in this example HandleStuff.cs
), and then you can do:
POWERSHELL$code = Get-Content .\HandleStuff.cs -Raw Add-Type $code -IgnoreWarnings -WA 0 [HandleStuff.Native]::GetSystemHandles()
You can also inline the code in the same PowerShell file if you want, or, in PowerShell 5.1 you can use Add-Type ... -OutputAssembly <path>
if you want to have a compiled version in a .dll
to faster loading.
CSHARPusing System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; namespace HandleStuff { public class SystemHandle { private readonly SYSTEM_HANDLE _handle; private readonly string _type; public string TypeName { get { return _type; } } public IntPtr Object { get { return _handle.Object; } } public IntPtr UniqueProcessId { get { return _handle.UniqueProcessId; } } public IntPtr HandleValue { get { return _handle.HandleValue; } } public uint GrantedAccess { get { return _handle.GrantedAccess; } } public ushort ObjectTypeIndex { get { return _handle.ObjectTypeIndex; } } public uint HandleAttributes { get { return _handle.HandleAttributes; } } internal SystemHandle(string type, SYSTEM_HANDLE handle) { _type = type; _handle = handle; } } internal enum OBJECT_INFORMATION_CLASS { ObjectTypeInformation = 2 } [StructLayout(LayoutKind.Sequential)] internal struct PUBLIC_OBJECT_TYPE_INFORMATION { internal UNICODE_STRING TypeName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)] internal uint[] Reserved; } [StructLayout(LayoutKind.Sequential)] internal struct UNICODE_STRING { internal ushort Length; internal ushort MaximumLength; internal IntPtr Buffer; } [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_HANDLE { internal IntPtr Object; internal IntPtr UniqueProcessId; internal IntPtr HandleValue; internal uint GrantedAccess; internal ushort CreatorBackTraceIndex; internal ushort ObjectTypeIndex; internal uint HandleAttributes; internal uint Reserved; } [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_HANDLE_INFORMATION_EX { internal UIntPtr HandleCount; internal UIntPtr Reserved; internal SYSTEM_HANDLE Handles; } internal enum SYSTEM_INFORMATION_CLASS { SystemExtendedHandleInformation = 64 } public static class Native { [DllImport("ntdll.dll")] private static extern uint NtQuerySystemInformation( [In] SYSTEM_INFORMATION_CLASS SystemInformationClass, [Out] IntPtr SystemInformation, [In] int SystemInformationLength, [Out] out int ReturnLength); [DllImport("ntdll.dll")] private static extern uint RtlNtStatusToDosError( [In] uint Status); [DllImport("ntdll.dll")] private static extern uint NtQueryObject( [In] IntPtr Handle, [In] OBJECT_INFORMATION_CLASS ObjectInformationClass, [Out] IntPtr ObjectInformation, [In] int ObjectInformationLength, [Out] out int ReturnLength); public static SystemHandle[] GetSystemHandles() { List<SystemHandle> handles; IntPtr sysInfo = IntPtr.Zero; try { sysInfo = GetSysHandleInfo(); SYSTEM_HANDLE_INFORMATION_EX handleInfo = Marshal .PtrToStructure<SYSTEM_HANDLE_INFORMATION_EX>(sysInfo); IntPtr handlesPtr = IntPtr.Add( sysInfo, Marshal.OffsetOf<SYSTEM_HANDLE_INFORMATION_EX>("Handles").ToInt32()); uint handleCount = handleInfo.HandleCount.ToUInt32(); int handleSize = Marshal.SizeOf<SYSTEM_HANDLE>(); handles = new List<SystemHandle>((int)handleCount); for (int i = 0; i < handleCount; i++) { IntPtr handlePtr = IntPtr.Add(handlesPtr, i * handleSize); SYSTEM_HANDLE handle = Marshal.PtrToStructure<SYSTEM_HANDLE>(handlePtr); handles.Add(new SystemHandle(GetHandleTypeName(handle.HandleValue), handle)); } } finally { if (sysInfo != IntPtr.Zero) { Marshal.FreeHGlobal(sysInfo); } } return handles.ToArray(); } private static IntPtr GetSysHandleInfo() { int size = 512; IntPtr sysInfo = IntPtr.Zero; while (true) { if (sysInfo != IntPtr.Zero) { Marshal.FreeHGlobal(sysInfo); } sysInfo = Marshal.AllocHGlobal(size); uint status = NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation, sysInfo, size, out size); if (status == 0) { break; } if (status != 0xc0000004) { throw new Win32Exception((int)RtlNtStatusToDosError(status)); } size *= 2; } return sysInfo; } private static string GetHandleTypeName(IntPtr handle) { int size = 512; IntPtr objectInfoBuffer = IntPtr.Zero; try { while (true) { if (objectInfoBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(objectInfoBuffer); } objectInfoBuffer = Marshal.AllocHGlobal(size); uint status = NtQueryObject( handle, OBJECT_INFORMATION_CLASS.ObjectTypeInformation, objectInfoBuffer, size, out size); if (status == 0) { PUBLIC_OBJECT_TYPE_INFORMATION info = Marshal .PtrToStructure<PUBLIC_OBJECT_TYPE_INFORMATION>(objectInfoBuffer); if (info.TypeName.Buffer == IntPtr.Zero) { return string.Empty; } return Marshal.PtrToStringUni(info.TypeName.Buffer, info.TypeName.Length / 2); } if (status != 0xc0000004) { return string.Empty; } size *= 2; } } finally { if (objectInfoBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(objectInfoBuffer); } } } } }
No comments yet.
Answer by EtherealWatcher790 • 1 month ago
Did you look at the Modules property of Get-Process?
Get-Process <process name> | Select -expand Modules
It outputs like this:
BASHPS C:\Windows\System32> get-process vds | Select -ExpandProperty modules Size(K) ModuleName FileName ------- ---------- -------- 692 vds.exe C:\windows\System32\vds.exe 1976 ntdll.dll C:\windows\SYSTEM32\ntdll.dll 716 KERNEL32.DLL C:\windows\System32\KERNEL32.DLL 2676 KERNELBASE.dll C:\windows\System32\KERNELBASE.dll 672 ADVAPI32.DLL C:\windows\System32\ADVAPI32.DLL 632 msvcrt.dll C:\windows\System32\msvcrt.dll ...
No comments yet.
No comments yet.