Enumerating Windows Processes
Download Source Code for this Article (.zip, 193kb) Original Article (In Russian)
Introduction Win32 offers several methods to enumerate currently running processes. Unfortunately, none of them works on every Win32 platform. Software developers have to combine several methods in their applications to ensure they will run on every Windows version.
In this Article We Will Examine The Following Enumeration Methods:
Using Process Status Helper (PSAPI) library Using ToolHelp32 API Using the undocumented ZwQuerySystemInformation function Using performance counters Using Windows Management Instrumentation interfaces We will illustrate each method with a
Function That Follows The Prototype Shown Below:
// this callback function is called for each process in the enumeration typedef BOOL (CALLBACK * PFNENUMPROC) (IN DWORD dwProcessId, // process identifier IN PCTSTR pszProcessName, // process name IN LPARAM lParam // application-defined parameter); // this function enumerates processes BOOL EnumProcesses_Method (IN PCTSTR pszMachineName, // computer name IN pFNENUMPROC pfnEnumProc, // application-defined callback function IN LPARAM lParam // application-defined parameter); The enumeration
Function Calls An Application-Defined Callback
Function for Each Running Process, Passing Process Information As The Callback
Function Arguments. The Callback
.
Some of Methods Covered in This Article Allows Enumeration of Processes On Remote Computers, Therefore The Enumeration
Function Prototype Has The
pszMachineName parameter. The NULL value for this parameter means enumeration of processes on the local machine.Method 1. Using Process Status Helper Library The Process Status Helper library, also known as PSAPI, offers a set of
Functions That Return Certain Information About Processes and Device Drivers. The Library Is Shipped with Windows 2000 / XP and Also Available As a Redistributable Package for Windows NT 4.0.
For process enumeration purposes, psapi provides the
Enumprocesses
Function That Returns An Array Of Identifiers of Currently Running Processes. Below Is The Source Code of To
Enumprocesses_PSAPI
Function That Implements Process Enumeration Using PSAPI.
οnclick = "Togglecode ('1')> Listing 1. Enumerating Processes with PSAPI
#include
(DWORD *) HeapAlloc (hHeap, 0, cbAlloc); if (pdwIds == NULL) {FreeLibrary (hPsApi); return SetLastError (ERROR_NOT_ENOUGH_MEMORY), FALSE;} // get processes identifiers if (pEnumProcesses (pdwIds, cbAlloc, & cbReturned! )) {dwError = GetLastError (); HeapFree (hHeap, 0, pdwIds); FreeLibrary (hPsApi); return SetLastError (dwError), FALSE;}} while (cbReturned == cbAlloc); // now enumerate all identifiers in the arrary And call // the user-defined callback function for every process for (dword i = 0; i Freelibrary (HPSAPI); Return True;} Note That We Link with Psapi.dll Dynamically, Loading The Library with Loadlibrary and the Obtaining Addresses of Necessary Functions Using GetProcaddress. This will allow us to incrude this Function Into An Application That Has To Run On Windows 9x / Me, WHERE PSAPI.DLL IS NOT AVAILABLE. We will use this technique genimenting Other Enumeration Methods, TOO. THE Enumprocesses Function Does Not Provide a Way To Know How much space is needed to receive all the identifiers. To Deal with this limited, WE CALL Enumprocesses in a loop, increasing the buffer size.. Since We Want To Obtain Not Only Identifiers of Processes But Also Their Names, We Have To Do Some Additional Work. For Each Process, We First Get ITS Handle with OpenProcess and the Call EnumProcessModules, which returns a list of modules loaded into the process address space. The first module in the list is always the module representing the EXE-file used to create the process, so we get this handle and pass it to the GetModuleFileNameex . Note The Special Handling of Two Processes in The Source Code of ENUMPROCESS_PSAPI. WE NEED To Handle The System Idle Process, Which Identifier Is Always Zero, And The System Process, Which Identifier Depends on The Operating System Version, Separately, Because OpenProcess Does Not Allow To Obtain A Handle To The Processes and Fails with Error_Access_Denied Error. If you run the demo application accompanying this article, you'll find that for at least one process the name is returned as "(name unavailable)". Using the Task Manager, it is easy to determine that this process is CSRSS.EXE. ........................... On one hand, we could use the same approach as with two system processes. However, there is no guarantee that the identifier of this process is always the same. On the other hand, this issue can be resolved by enabling the SE_DEBUG_NAME privilege. When This Privilege is Turned On, The Calling Thread Can Open Process Handles with Any Access Rights Regardless The Security DE scriptor assigned to the process. Since this privilege opens great opportunities to peneterate system security, it is normally granted only to system administrators. Because of this, we decided to not include the code for enabling this privilege into the Enumprocesses_PSAPI Function. if Necessary, You Can Enable this Privile Before Calling Enumprocesses_psapi, and the Function Will Be Able To Return A Name for the Csrss.exe Process As Well. Method 2. Using Toolhelp32 API Microsoft Corporation Had Added A Set of Functions named Toolhelp API to Windows 3.1 To Get Independed Software Vendors Access To System Information Which Was Previously Available Only to Microsoft Engineers. When Windows 95 Was Born, There Functions migrate Into the new system with Toolhelp32 name. The Windows NT operating system from very beginning provides an interface known as "performance data" to obtain similar information. This interface is very intricate and inconvenient (to be honest, beginning with Windows NT 4.0, Microsoft provides the Performance Data Helper library that greatly simplifies accessing performance data; we will use this library in one of our methods) Rumors are the Windows NT development team was refusing to include ToolHelp32 API into the system for long time, but anyway, starting with Windows 2000, you can see this API in. The Windows Nt Family of Operating Systems.when Using Toolhelp32 API, We First Create a Snapshot of The List of Processes with the list of processes CreateToolhelp32snapshot Function and the walk through the list using Process32first and Process32next Functions. the Processentry32 Structure, Which is Filled by There Functions, Contains All the information we need. Below is the source code of there Enumprocesses_Toolhelp Function That Implements Process Enumeration Using Toolhelp32 API. οnclick = "Togglecode ('2')> Listing 2. ENUMERANG Processes with Toolhelp32 API #include _tcsrchr (Entry.szExeFile, _T ( '//')); if (pszProcessName == NULL) pszProcessName = Entry.szExeFile;} if break (pfnEnumProc (Entry.th32ProcessID, pszProcessName, lParam)!); Entry.dwSize = sizeof (} While (pprocess32next (hsnapshot, & entry); CloseHandle (HSnapshot); Return True;} Before Calling Process32first OR Process32next, we have to initialize the DWSIZE FIELD of The Processentry32 structure with the structure size. The Structure Size. The Structure Size. The Structure Size. The Functions, in Their Turn, Fill this field with the number of bytes stiled into the structure. we compare this value with the offset of there Szexefile Field to Determine WHether The Process Name Was Returned. Method 3. Using the ZwQuerySystemInformation function Despite the fact that a documented way exists to obtain the list of processes using performance data, the Windows NT Task Manager never used it. Instead, it calls the undocumented ZWQuerySystemInformation Function. THIS Function is exported from ntdll.dll and provides access to Various System Information and, in Particular, To The List of currently Running Processes. THE ZWQuerySystemInformation Function Has The Following Prototype [2]: NTSTATUS ZwQuerySystemInformation (IN ULONG SystemInformationClass, // information class IN OUT PVOID SystemInformation, // information buffer IN ULONG SystemInformationLength, // size of information buffer OUT PULONG ReturnLength OPTIONAL // receives information length); SystemInformationClass Specifier The Type of The Information To Retrieve. We are intended in The SystemProcesSandthreadsInformation (5). SystemInformation Pointer to a Buffer That Receives The Information Requested. SystemInformationLength Specifier The size of the receiving buffer. ReturnLength Optional Pointer to Avariable Into Which To Function Stores The Number of Bytes Actually Written Into The Output Buffer. The format of processes and threads information buffer is described by the System_Process_information Structure, Which Contains Almost All the information displayed by the task manager. Below you can find the source code for the Enumprocesses_NTAPI Function That Implements Process Enumeration Using ZwQuerySystemInformation. οnclick = "Togglecode ('3')> Listing 3. Enumerating Processes Using ZWQuerySystemInformation typedef LONG NTSTATUS; typedef LONG KPRIORITY; #define NT_SUCCESS (Status) ((NTSTATUS) (Status)> = 0) #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS) 0xC0000004L) #define SystemProcessesAndThreadsInformation 5 typedef struct _CLIENT_ID {DWORD UniqueProcess; DWORD UniqueThread;} CLIENT_ID ; typedef struct _UNICODE_STRING {USHORT Length; USHORT MaximumLength; PWSTR Buffer;} UNICODE_STRING; typedef struct _VM_COUNTERS {SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage ; SIZE_T PeakPagefileUsage;} VM_COUNTERS; typedef struct _SYSTEM_THREAD_INFORMATION {LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; KPRIORITY Priority; KPRIORITY BasePriority; ULONG ContextSwitchCount; LONG State; LONG WaitReason; } SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION; typedef struct _SYSTEM_PROCESS_INFORMATION {ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1 [6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2 [2 ]; VM_COUNTERS VmCounters; #if _WIN32_WINNT> = 0x500 IO_COUNTERS IoCounters; #endif SYSTEM_THREAD_INFORMATION Threads [1];} SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION; BOOL EnumProcesses_NtApi (IN PCTSTR pszMachineName, IN pFNENUMPROC pfnEnumProc, IN LPARAM lParam) {_ASSERTE (pfnEnumProc = NULL! ); if (pszmachinename! = NULL) return SetLastError (ERROR_INVALID_PARAMETER), FALSE; HINSTANCE hNtDll; NTSTATUS (WINAPI * pZwQuerySystemInformation) (UINT, PVOID, ULONG, PULONG); // get NTDLL.DLL handle hNtDll = GetModuleHandle (_T ( "ntdll.dll")); _ASSERTE (hNtDll = NULL!); // find ZwQuerySystemInformation address * (FARPROC *) & _ ZwQuerySystemInformation = GetProcAddress (hNtDll, "ZwQuerySystemInformation"); if (_ZwQuerySystemInformation == NULL) return SetLastError (ERROR_PROC_NOT_FOUND), FALSE; // get default process heap handle hANDLE hHeap = GetProcessHeap (); NTSTATUS Status; ULONG cbBuffer = 0x8000; PVOID pBuffer = NULL; // it is difficult to predict what buffer size will be // enough, so we start with 32K buffer and increase its // size as needed do {pBuffer = HeapAlloc (hHeap, 0, cbBuffer); if (pBuffer == NULL) return SetLastError (ERROR_NOT_ENOUGH_MEMORY), FALSE; Status = pZwQuerySystemInformation (SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL); if (Status == STATUS_INFO_LENGTH_MISMATCH) { HEAPF ree (hHeap, 0, pBuffer); cbBuffer * = 2;} else if {HeapFree (hHeap, 0, pBuffer) (NT_SUCCESS (Status)!); return SetLastError (Status), FALSE;}} while (Status == STATUS_INFO_LENGTH_MISMATCH ); PSYSTEM_PROCESS_INFORMATION pProcesses = (PSYSTEM_PROCESS_INFORMATION) pBuffer; for (;;) {PCWSTR pszProcessName = pProcesses-> ProcessName.Buffer; if (pszProcessName == NULL) pszProcessName = L "Idle"; #ifdef UNICODE if (pfnEnumProc (pProcesses-! > Processid, pszprocessname, lparam) Break; #else char spprocessname [max_path]; Widechartomultibyte (CP_ACP, 0, PSZProcessName, -1, SzprocessName, max_path, null, null); if (pfnEnumProc (pProcesses-> ProcessId, szProcessName, lParam)!) break; #endif if (pProcesses-> NextEntryDelta == 0) break; // find the address of the next process structure pProcesses = (PSYSTEM_PROCESS_INFORMATION) (((LPBYTE ) PPROCESSES) PPROCESS-> NEXTENTRYDELTA);} HeapFree (HHEAP, 0, PBuffer; Return True;} When Calling ZwQuerySystemInformation, IT IS Difficult to Predict Which Buffer Size Will Be Enough To Receive All The Information. Thus, We Start With A 32k Buffer and Increase ITS Size Until The Function Returns Success. THE System_process_information structure is a Variable-size structure, the NextEntryDelta field specifies the offset to the next structure in the array. This field allows us to ignore the difference in size of the fixed part of the structure between Windows NT 4.0 and Windows 2000, where the Iocounters Field Was Added to The Structure. Method 4. Using Performance Counters As It Was Already NOTED, The Windows Nt Operating System from Very Beginning Had An Interface To Obtain various system information in the form of performance counters. This interface is far from intuitive. In order to get the information, you have to read a value with a specially formed name from the HKEY_PERFORMANCE_DATA registry key. The information is returned in the form of deeply Nested Structure, Many of Which Areral Variable size. Dealing with these Data Structures Requires a Certain Degree of Diligence. Fortunately, the situation had changed with introducing the Performance Data Helper (PDH) library in Windows NT 4.0. This library provides more convenient interface to performance data. However, the library was not being shipped with Windows NT 4.0, it was available as a redistributable in the Microsoft Platform SDK. Beginning with Windows 2000, PDH became a part of the system.A detailed discussion of performance monitoring is beyond the scope of this article. Just let me note that the Windows NT performance monitoring architecture defines a concept of an Object, for Which Performance Counting is Made. Examples of Objects Are Processor and Disk Drive. Each Object Can Have Ooney OR More ORE Instances along with a set of Performance Counters. Our.. EnumprocesSses_Perfdata Function, Which Uses PDH for process enumeration. οnclick = "Togglecode ('4')> Listing 4. Enumerating Processes Using PDH #include // open query Status = pPdhOpenQuery (NULL, 0, & hQuery); if (Status = ERROR_SUCCESS!) {FreeLibrary (hPdh); return SetLastError (Status), FALSE;} // add a counter to the query Status = pPdhAddCounter (hQuery , szCounterPath, 0, & hCounter); if (Status = ERROR_SUCCESS) {pPdhCloseQuery (hQuery); FreeLibrary (hPdh); return SetLastError (Status), FALSE;}! // get current counter values Status = pPdhCollectQueryData (hQuery); if ( ! Status = ERROR_SUCCESS) {pPdhCloseQuery (hQuery); FreeLibrary (hPdh); return SetLastError (Status), FALSE;} DWORD cbSize = 0; DWORD cItems = 0; HANDLE hHeap = GetProcessHeap (); // determine required buffer size Status = pPdhGetRawCounterArray (hCounter, & cbSize, & cItems, NULL); if (Status = ERROR_SUCCESS!) {pPdhCloseQuery (hQuery); FreeLibrary (hPdh); return SetLastError (Status), FALSE;} // allocate memory for the buffer PDH_RAW_COUNTER_ITEM_W * pRaw = ( PDH_RAW_CUNTER_ITEM_W *) HeapAlloc (HHEAP, 0, CBSIZE); if (PRAW == NULL) {PPDHCloseQuery (HQuery); FreeElibrary (HPDH); Retur n SetLastError (ERROR_NOT_ENOUGH_MEMORY), FALSE;} // retrieve counter values Status = pPdhGetRawCounterArray (hCounter, & cbSize, & cItems, pRaw); // close query pPdhCloseQuery (hQuery); if (! Status = ERROR_SUCCESS) {HeapFree (hHeap, 0, PRAW); FreeElibrary (hpdh); return setlasterror (status), false;} // enumerate all counter instances for (dword i = 0; i pszProcessName = szProcessName; #endif // exclude the instance named _Total if (dwProcessId == 0 && lstrcmp (pszProcessName, _T ( "_ Total")) == 0) continue; if (! pfnEnumProc (dwProcessId, pszProcessName, lParam)) break ;} HeapFree (hHeap, 0, pRaw); FreeLibrary (hPdh); return TRUE;.} We return instance names as process names If you run the demo application, you'll see that instance names are names of EXE-files of corresponding Processes WITHOUT AN Extension. Object names and performance counter names are localizable. That means that, for example, on Russian version of Windows NT, process object and process identifier counter are no longer called "Process" and "ID Process", localized names are used instead. To get THE LOCALIZED NAMES AND FORMAT A FULL PATH TO The Performance Counter We Are Interested in, WE Use A Helper Function named PerfformatCounterpath. Its Source Code Is Provided Below. οnclick = "Togglecode ('5')> Listing 5. PerfformatCounterpath Source Code static PDH_STATUS PerfFormatCounterPath (IN HINSTANCE hPdh, IN LPCTSTR pszMachineName, IN LPWSTR pszPath, IN ULONG cchPath) {_ASSERTE (hPdh = NULL!); _ASSERTE (_CrtIsValidPointer (pszPath, cchPath * sizeof (WCHAR), 1)); PDH_STATUS (WINAPI * pPdhMakeCounterPath) (PDH_COUNTER_PATH_ELEMENTS_W *, LPWSTR, LPDWORD, DWORD); PDH_STATUS (WINAPI * pPdhLookupPerfNameByIndex) (LPCWSTR, DWORD, LPWSTR, LPDWORD); * (FARPROC *) & pPdhMakeCounterPath = GetProcAddress (hPdh, "PdhMakeCounterPathW"); * (FARPROC *) & pPdhLookupPerfNameByIndex = GetProcAddress (hPdh, "PdhLookupPerfNameByIndexW"); if (pPdhMakeCounterPath == NULL || pPdhLookupPerfNameByIndex == NULL) return ERROR_PROC_NOT_FOUND; PDH_STATUS Status; DWORD cbName; WCHAR szObjectName [80]; WCHAR szCounterName [80]; LPWSTR pszMachineW = NULL; #ifndef _UNICODE WCHAR szMachineName [256]; if (pszMachineName = NULL!) {MultiByteToWideChar (CP_ACP, 0, pszMachineName, -1, szMachineName, 256); pszMachineW = szMachineName;} #else pszMachineW = (LPWSTR) pszMachineName; #endif // find localize Process object name cbName = sizeof (szObjectName); Status = pPdhLookupPerfNameByIndex (pszMachineW, 230, szObjectName, & cbName); if (! Status = ERROR_SUCCESS) return Status; // find localized ID Process counter name cbName = sizeof (szCounterName); Status = pPdhLookupPerfNameByIndex (pszMachineW, 784, szCounterName, & cbName); if (Status = ERROR_SUCCESS!) return Status; PDH_COUNTER_PATH_ELEMENTS_W pcpe; pcpe.szMachineName = pszMachineW; pcpe.szObjectName = szObjectName; pcpe.szInstanceName = L "*" pcpe.szparentInstance = NULL; pcpe.dwinstanceIndex = 0; pcpe.szcountername = szcountername; // Format Full Counter Path Return Ppdhmakecounterpath (& PCPE, PSZPATH, & CCHPATH, 0);} We use PdhLookupPerfNameByIndex to obtain localized names from corresponding indices. Indices of standard objects and performance counters are well-defined and do not depend on the operating system version, thus it is safe to specify them directly in the code. Note, Among All Process Enumeration Methods We discussed so far, this is the first method this allows to enumerate processes on another machine. All we have to do is to do do more is to set the machine name in the Szmachinename Field of The PDH_COUNTER_PATH_ELEMENTS STRUCTURE. Method 5. Using Windows Management Instrumentation Windows Management Instrumentation (WMI) is Microsoft implementation of the Web-Based Enterprise Management (WBEM) initiative. WBEM provides a point of integration through which data from different management sources can be uniformly accessed, and it complements and extends existing management protocols such as SNMP. WBEM is based on the Common Information Model (CIM) schema, which is an industry standard maintained by DMTF (Distributed Management Task Force). WMI is included in Windows 2000 and Windows XP, but is also available As a redistributable package for Windows 9x / me and windows NT 4.0. . οnclick = "Togglecode ('6')> Listing 6. Enumerating Processes Using WMI Interfaces #include IF (! pfnenumproc (lpctstr) _BSTR_T (ValName), LParam) Break; spobject = null;}} catch (_COM_ERROR & ERR) {Return setLastError (Err.Error ()), false;} Return True; } The fact that WMI is based on the COM technology saves us from explicit loading of necessary libraries as we did in all previous methods. The same fact requires you to initialize COM run-time before calling the process enumeration Function. in an MFC Application You Can Do IT with Afxoleinit, in Other Cases You Should Use Coinitialize or Coinitializeex Functions. Furthermore, USING WMI Requires Initialization of the Com Security with a call to CoinitializeigureCurity: CoInitializeSecurity (NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0);. Note, same as the previous process enumeration method, this method allows enumeration of processes on remote machines To enumerate processes remotely, you should specify Computer name to Iwbemlocator :: ConnectServer. Important note:. Remote process enumeration in the form as it presented in this article will work only if the current user of the local machine has administrative privileges on the remote machine The samples provided above do not allow to change credentials with which a connection to the Remote Machine Is Made. To Specify Alternative Credentials in PDH Method, you Should Call NetuseAdd OR WetaddConnection2 To Establish a connection with ipc $ resource on the remote machine prior to calling the enumeration Function. in WMI Method, You Should Use Standard Com Methods for Supplying Alternative Credentials. Wrap Up Well, we've seen five different methods to enumerate processes on Windows. None of them is universal, since there is alvays a Windows version there a particular method will not run. The table below summarizes applicability of the described methods.Windows 9x / meWindows NT 4.0Windows 2000 / XPMethod 1NOYES * YESMETHOD 2YESNOYESMETHOD 3NOYESYESMETHOD 4NOYES * YESMETHOD 5YES * YES * YES * Requires Installation of Additional Components. Using this Table and The Functions Provided in The Previous Sections, IT IS Easy To Make A Function for Process Enumeration, Which Worts. For example, if you want to use psapi on window WINDOWS NT / 2000 / XP and Toolhelp32 API ON Windows 9X / ME, Such A Function CAN Look Like Following: BOOL MyEnumProcesses (IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam) {OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof (osvi); if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) return EnumProcesses_ToolHelp (pfnEnumProc, lParam); else if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT ) return EnumProcesses_PsApi (pfnEnumProc, lParam); else return SetLastError (ERROR_CALL_NOT_IMPLEMENTED), FALSE;} This acticle is accompanied by a small demo application, ambitiously named Process Viewer, that demonstrates all five methods presented in the article The implementation of all methods is. in ENUMPROC.H AND ENUMPROC.CPP FILES. I Tried TO Arrange Sources in Such A Way To make Easier Their Reuse in Other Projects. To build the application you will need Microsoft Platform SDK. Happy Coding! References . HOWTO: Enumerate Applications in Win32, Q175030, Microsoft Knowledge Base Gary Nebbett, Windows NT / 2000 Native API Reference New Riders Publishing, 2000. INFO: Using PDH APIs Correctly in a Localized Language, Q287159, Microsoft Knowledge Base <-..! - Article Copyright -> This Arctile Was First Published on Rsdn.ru.copyright (C) 2001-2003 The RSDN Group. All Rights Reserved.