A previous article shows how to use Win32_PhysicalMedia WMI class to get physical drive info. We can write something similar for getting a list of running processes. All we have to do is to replace the WQL query and get specific properties for Win32_Process. However, to simplify the things, I wrote few C++ wrapper classes over the WMI stuff. Here is a brief description for each one:
CWMIConnection – opens and keeps a connection to WMI namespace.
CWMIQuery – executes WQL (SQL for WMI) queries and navigates through results.
CWMIWin32_Process – is derived from CWMIQuery and is specialized for Win32_Process.
CWMIValue – a class that gets user readable strings from different CIM types.
The implementation details can be found in the attached demo project. Let’s show now just a usage example, that fills a list-view control with info about running processes.
void CDemoDlg::_FillProcessesList()
{
// clear listview control
m_listProcesses.DeleteAllItems();
try
{
// Open WMI connection
CWMIConnection wmiConnection;
wmiConnection.Open(L"ROOT\\CIMV2");
// Query Win32_Process
CWMIWin32_Process wmiQuery(wmiConnection);
wmiQuery.Open();
// Fill the list
int nItem = 0;
while(wmiQuery.MoveNext())
{
m_listProcesses.InsertItem(nItem, NULL);
m_listProcesses.SetItemText(nItem, ePID, WMI_GETPROPERTYSTR(wmiQuery, Handle));
m_listProcesses.SetItemText(nItem, eSessionID, WMI_GETPROPERTYSTR(wmiQuery, SessionId));
m_listProcesses.SetItemText(nItem, eImageName, WMI_GETPROPERTYSTR(wmiQuery, Caption));
m_listProcesses.SetItemText(nItem, eCommandLine, WMI_GETPROPERTYSTR(wmiQuery, CommandLine));
// ...
// NOTE: This is just for demo purpose and can be completed.
// For a compelte list of Win32_Process properties, see MSDN documentation.
// https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process
nItem++;
}
}
catch(COleException* e)
{
e->ReportError();
e->Delete();
}
}
We can call WTSEnumerateProcesses function to get information about the active processes on a specified Remote Desktop Session Host server. However, if pass WTS_CURRENT_SERVER_HANDLE in first argument (server handle), we can enumerate and get info about processes which are running on local machine. The following example calls WTSEnumerateProcesses function, then fills an array of WTS_PROCESS_INFO structures.
// Windows Terminal Server API header & lib#include<wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")
DWORD RDSAPI_EnumProcesses(CArray<WTS_PROCESS_INFO>& arrProcInfo)
{
// clear output arrayarrProcInfo.RemoveAll();
// enumerate processesHANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // local machinePWTS_PROCESS_INFO pProcessInfo = NULL;
DWORD dwCount = 0;
if (!::WTSEnumerateProcesses(hServer, 0, 1, &pProcessInfo, &dwCount))
{
return ::GetLastError();
}
// fill output arrayarrProcInfo.SetSize(dwCount);
for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
{
arrProcInfo[dwIndex] = pProcessInfo[dwIndex];
}
// free the memory allocated in WTSEnumerateProcesses
::WTSFreeMemory(pProcessInfo);
returnNO_ERROR;
}
If the target OS is Windows Vista or newer, we can alternatively use WTSEnumerateProcessesEx which can get additional info in WTS_PROCESS_INFO_EX structures. Here is an example that enumerates processes, then fills an array of application-defined structures, (CProcessInfoEx) which contain the following info:
the process identifier;
the identifier of session associated with the process;
the name of the executable file associated with the process;
the user account name;
the user domain name;
the number of threads in the process;
the number of handles in the process;
the page file usage of the process, in bytes;
the peak page file usage of the process, in bytes;
the working set size of the process, in bytes;
the peak working set size of the process, in bytes;
the time, in milliseconds, the process has been running in user mode;
the time, in milliseconds, the process has been running in kernel mode.
DWORD RDSAPI_EnumProcessesEx(CArray<CProcessInfoEx>& arrProcInfo)
{
// clear output arrayarrProcInfo.RemoveAll();
// init WTSEnumerateProcessesEx parametersHANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // get local machine processesDWORD dwLevel = 1; // get array of WTS_PROCESS_INFO_EXDWORD dwSessionID = WTS_ANY_SESSION; // get processes in all sessionsDWORD/ dwCount = 0; // returns the number of processesPWTS_PROCESS_INFO_EX pProcessInfo = NULL; // output dataif (!::WTSEnumerateProcessesEx(hServer, &dwLevel, dwSessionID, (LPTSTR*)&pProcessInfo, &dwCount))
{
return ::GetLastError();
}
// fill output arrayarrProcInfo.SetSize(dwCount);
for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
{
CProcessInfoEx processInfoEx(pProcessInfo[dwIndex]);
arrProcInfo[dwIndex] = processInfoEx;
}
// free the memory allocated in WTSEnumerateProcessesExif (!::WTSFreeMemoryEx(WTSTypeProcessInfoLevel1, pProcessInfo, dwCount))
{
return ::GetLastError();
}
returnNO_ERROR;
}
Now, let’s fill a listview control with the processes info grabbed by RDSAPI_EnumProcessesEx.
void CDemoDlg::_FillProcessesList()
{
// clear listview control
m_listProcesses.DeleteAllItems();
// get an array of CProcessInfoEx
CArray<CProcessInfoEx> arrProcInfo;
VERIFY(NO_ERROR == RDSAPI_EnumProcessesEx(arrProcInfo));
// fill the listview controlconstINT_PTR nSize = arrProcInfo.GetSize();
for (INT_PTR nItem = 0; nItem < nSize; nItem++)
{
// add new item to listview control
m_listProcesses.InsertItem(nItem, NULL);
// set list imtem textconstCProcessInfoEx& processInfoEx = arrProcInfo.ElementAt(nItem);
m_listProcesses.SetItemText(nItem, ePID, processInfoEx.GetProcessIdStr());
m_listProcesses.SetItemText(nItem, eSessionID, processInfoEx.GetSessionIdStr());
m_listProcesses.SetItemText(nItem, eImageName, processInfoEx.GetProcessNameStr());
m_listProcesses.SetItemText(nItem, eUserName, processInfoEx.GetUserNameStr());
m_listProcesses.SetItemText(nItem, eDomain, processInfoEx.GetDomainNameStr());
m_listProcesses.SetItemText(nItem, eThreads, processInfoEx.GetNumberOfThreadsStr());
m_listProcesses.SetItemText(nItem, eHandles, processInfoEx.GetHandleCountStr());
m_listProcesses.SetItemText(nItem, ePagefileUsage, processInfoEx.GetPagefileUsageStr());
m_listProcesses.SetItemText(nItem, ePeakPagefileUsage, processInfoEx.GetPeakPagefileUsageStr());
m_listProcesses.SetItemText(nItem, eWorkingSetSize, processInfoEx.GetWorkingSetSizeStr());
m_listProcesses.SetItemText(nItem, ePeakWorkingSetSize, processInfoEx.GetPeakWorkingSetSizeStr());
m_listProcesses.SetItemText(nItem, eUserTime, processInfoEx.GetUserTimeStr());
m_listProcesses.SetItemText(nItem, eKernelTime, processInfoEx.GetKernelTimeStr());
}
}
More implementation details can be found in the attached demo project.
Notes
WTSEnumerateProcesses function requires at least Windows XP or Windows Server 2003;
WTSEnumerateProcessesEx function requires at least Windows 7 or Windows Server 2008 R2;
WTS prefix comes from Windows Terminal Services which is the former name of Remote Desktop Services.
Demo Project
The demo project is a simple MFC dialog-based application that uses the above functions.
Tool Help Library makes it easy to enumerate and get info about processes, threads, modules, and heaps. Let’s use it to get a list of running processes.
Using Tool Help Library
First, take a snapshot by passing TH32CS_SNAPPROCESS flag to CreateToolhelp32Snapshot function. Next, pass the snapshot handle to Process32First, then to Process32Next to get running processes info in a list of PROCESSENTRY32 structures.
#include <Tlhelp32.h>
DWORD CDemoDlg::ToolHelp_EnumProcesses(CList<PROCESSENTRY32>& listProcInfo)
{
DWORD dwRet = NO_ERROR;
// take a snapshot of processes
DWORD dwFlags = TH32CS_SNAPPROCESS;
HANDLE hSnapshot = ::CreateToolhelp32Snapshot(dwFlags, 0);
if(INVALID_HANDLE_VALUE == hSnapshot)
{
return ::GetLastError();
}
PROCESSENTRY32 processEntry = {0};
processEntry.dwSize = sizeof(PROCESSENTRY32);
// get info for each process in the snapshot
if(::Process32First(hSnapshot, &processEntry))
{
do
{
listProcInfo.AddTail(processEntry);
}
while(::Process32Next(hSnapshot, &processEntry));
}
else
{
dwRet = ::GetLastError();
}
::CloseHandle(hSnapshot);
return dwRet;
}
Next example fills a listview control with the following info of each process.
PID (process identifier);
process image name (the name of the executable file);
parent PID;
number of threads started by the process;
base priority of any threads created by this process.
void CDemoDlg::_FillProcessesList()
{
// clear listview control
m_listProcesses.DeleteAllItems();
// get a list of PROCESSENTRY32 structures
CList<PROCESSENTRY32> listProcInfo;
VERIFY(NO_ERROR == ToolHelp_EnumProcesses(listProcInfo));
// fill the listview control
POSITION pos = listProcInfo.GetHeadPosition();
int nIndex = 0;
while(NULL != pos)
{
// get next process from the list
const PROCESSENTRY32& procInfo = listProcInfo.GetNext(pos);
// format info strings
CString strPID, strImageName, strParentPID, strThreads, strPriority;
strPID.Format(_T("%u"), procInfo.th32ProcessID);
strImageName = procInfo.szExeFile;
strParentPID.Format(_T("%u"), procInfo.th32ParentProcessID);
strThreads.Format(_T("%u"), procInfo.cntThreads);
strPriority.Format(_T("%d"), procInfo.pcPriClassBase);
// set listview item text
m_listProcesses.InsertItem(nIndex, strPID);
m_listProcesses.SetItemText(nIndex, 1, strImageName);
m_listProcesses.SetItemText(nIndex, 2, strParentPID);
m_listProcesses.SetItemText(nIndex, 3, strThreads);
m_listProcesses.SetItemText(nIndex++, 4, strPriority);
}
}
Many users know about Task Manager that allows, between others, to quickly list the currently running processes and get info about each one. Some advanced users may also deal with Windows Resource Monitor, for real-time monitoring the running processes. Other ones may use command line tools lile Tasklist or Tlist as well as complex third-party applications, like SysinternalsProcess Explorer and Process Monitor.
However, let’s say that we have to get a list of running processes and obtain additional info about them in our own application. This series of articles presents several methods for doing that in C/C++ programs, by using Windows API or other libraries. But first, let’s have a brief look at command-line tools.
Tasklist
Tasklist is a command-line utility tool, shipped with Windows XP (except home edition) and newer Windows versions. What you have to do is to open cmd console window and run tasklist.exe. Here are few examples:
Displays info about all processes in LIST format
tasklist /v /fo LIST
Displays services running under svchost.exe
tasklist /fi "Imagename eq svchost.exe" /svc
Displays the modules loaded in Internet Explorer
tasklist /m /fi "imagename eq iexplore.exe"
Displays tasklist help.
tasklist /?
For a complete reference, see the link under Resources section.
Tlist
Tlist isn’t shipped with Windows operating system but is included in Debugging Tools for Windows suite that can be downloaded from Microsoft site. Examples:
Displays the command line that started each process
C:\Program Files\Debugging Tools for Windows>tlist /c
Displays the services that run in each process
C:\Program Files\Debugging Tools for Windows>tlist /s
Also, for a complete reference see the link, below.
Can we use these tools in our own program?
The answer is yes. We can launch tasklist.exe or tlist.exe with CreateProcess, then use a pipe to catch the output, then… But that may be a real overkill, so let’s forget it! Next articles will show how to directly use APIs to list processes.