Programmatically listing running processes is a fundamental task in Windows system administration and development. While standard APIs like EnumProcesses or WMI's Win32_Process class are common, they might not provide the necessary session context, especially in multi-user environments like Remote Desktop Services (RDS), formerly known as Windows Terminal Services (WTS).
The Windows Terminal Services API offers a specialized set of functions designed for managing RDS sessions, users, and processes within those sessions. This article delves into two key WTS functions for process enumeration: WTSEnumerateProcesses and its more detailed counterpart, WTSEnumerateProcessesEx. We will explore their syntax, associated data structures, usage with C++ code examples, memory management considerations, and inherent limitations.
The WTSEnumerateProcesses function provides a foundational method to retrieve information about active processes running on a specified RD Session Host server.
This function retrieves a list of processes along with basic information like Process ID (PID), Session ID, process name, and the user's Security Identifier (SID).
Syntax (C/C++):
BOOL WTSEnumerateProcessesA(
[in] HANDLE hServer,
[in] DWORD Reserved,
[in] DWORD Version,
[out] PWTS_PROCESS_INFOA *ppProcessInfo,
[out] DWORD *pCount
);
BOOL WTSEnumerateProcessesW(
[in] HANDLE hServer,
[in] DWORD Reserved,
[in] DWORD Version,
[out] PWTS_PROCESS_INFOW *ppProcessInfo,
[out] DWORD *pCount
);(Note: The WTSEnumerateProcesses macro automatically selects the ANSI (A) or Unicode (W) version based on project settings.)
Parameters:
hServer: A handle to the target RD Session Host server. UseWTSOpenServerto get a handle to a remote server, orWTS_CURRENT_SERVER_HANDLEfor the local machine.Reserved: This parameter is reserved and must be set to0.Version: Specifies the version of the request. This must be set to1.ppProcessInfo: A pointer to a variable that receives a pointer to an array ofWTS_PROCESS_INFOstructures. The API allocates this buffer.pCount: A pointer to aDWORDvariable that receives the number ofWTS_PROCESS_INFOstructures returned in theppProcessInfobuffer.
This structure contains the basic information returned for each process.
Definition (C/C++):
typedef struct _WTS_PROCESS_INFOA {
DWORD SessionId;
DWORD ProcessId;
LPSTR pProcessName;
PSID pUserSid;
} WTS_PROCESS_INFOA, *PWTS_PROCESS_INFOA;
typedef struct _WTS_PROCESS_INFOW {
DWORD SessionId;
DWORD ProcessId;
LPWSTR pProcessName;
PSID pUserSid;
} WTS_PROCESS_INFOW, *PWTS_PROCESS_INFOW;Members:
SessionId: The RDS session identifier associated with the process.ProcessId: The unique process identifier (PID).pProcessName: Pointer to a null-terminated string containing the executable file name.pUserSid: Pointer to the Security Identifier (SID) of the user associated with the process's primary access token.
A crucial aspect of using WTSEnumerateProcesses is memory management. The function allocates the memory buffer pointed to by ppProcessInfo. It is the caller's responsibility to free this memory once it's no longer needed using the WTSFreeMemory function.
void WTSFreeMemory(
[in] PVOID pMemory
);Failure to call WTSFreeMemory will result in memory leaks.
Here's a C++ example demonstrating how to use WTSEnumerateProcesses to list processes on the current machine. This example requires linking against wtsapi32.lib.
#include <windows.h>
#include <wtsapi32.h>
#include <sddl.h>
#include <stdio.h>
#include <string>
#include <locale>
#include <codecvt>
#pragma comment(lib, "wtsapi32.lib")
// Helper function to convert SID to Username (Domain\User)
std::wstring GetUserNameFromSid(PSID sid) {
if (sid == nullptr) {
return L"";
}
WCHAR name[128];
WCHAR domain[64];
DWORD nameLen = _countof(name);
DWORD domainLen = _countof(domain);
SID_NAME_USE use;
if (!LookupAccountSidW(nullptr, sid, name, &nameLen, domain, &domainLen, &use)) {
// Optionally check GetLastError() for specific errors
return L"<Error LookupAccountSid>";
}
// Handle well-known SIDs or cases where domain might be empty
if (domainLen > 0) {
return std::wstring(domain) + L"\\" + name;
} else {
return std::wstring(name); // e.g., "NT AUTHORITY\SYSTEM"
}
}
bool EnumerateProcessesBasic() {
PWTS_PROCESS_INFOW pProcessInfo = nullptr;
DWORD processCount = 0;
if (!WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pProcessInfo, &processCount)) {
fprintf(stderr, "WTSEnumerateProcesses failed. Error %lu\n", GetLastError());
return false;
}
printf("--- Processes (Basic Info) ---\n");
printf("%5s %3s %-30s %s\n", "PID", "SID", "User", "Process Name");
printf("--------------------------------------------------------------------\n");
for (DWORD i = 0; i < processCount; ++i) {
PWTS_PROCESS_INFOW pi = &pProcessInfo[i];
std::wstring userName = GetUserNameFromSid(pi->pUserSid);
// Convert wstring to narrow string for printf if needed (basic example)
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::string narrowUserName = converter.to_bytes(userName);
std::string narrowProcessName = converter.to_bytes(pi->pProcessName ? pi->pProcessName : L"<N/A>");
printf("%5lu %3lu %-30s %s\n",
pi->ProcessId,
pi->SessionId,
narrowUserName.c_str(),
narrowProcessName.c_str());
}
// CRITICAL: Free the memory allocated by WTSEnumerateProcesses
if (pProcessInfo) {
WTSFreeMemory(pProcessInfo);
pProcessInfo = nullptr; // Good practice
}
return true;
}
/*
int main() {
if (!EnumerateProcessesBasic()) {
return 1;
}
return 0;
}
*/- Basic Information: This function provides only a limited set of process details.
- Administrator Privileges: To see processes running in sessions other than the caller's own, or system processes, the calling application typically needs to run with administrative privileges.
- NULL SID/Name: For certain system processes (like the Idle process, PID 0) or when running without sufficient rights, the
pUserSidmight beNULL, andpProcessNamemight be missing or generic (e.g., "System"). - Memory Management: Correctly calling
WTSFreeMemoryis essential to prevent leaks.
While useful for basic process listing, especially when session context is important, WTSEnumerateProcesses lacks the detailed metrics often required for in-depth analysis. For that, we turn to its extended counterpart.
While WTSEnumerateProcesses provides basic process information, WTSEnumerateProcessesEx offers a more comprehensive view with additional metrics like memory usage, thread count, handle count, and CPU time. This function was introduced in Windows 7 and Windows Server 2008 R2.
This enhanced function allows for retrieving either basic or extended process information based on the specified level parameter.
Syntax (C/C++):
BOOL WTSEnumerateProcessesExA(
[in] HANDLE hServer,
[in, out] DWORD *pLevel,
[in] DWORD SessionId,
[out] LPSTR *ppProcessInfo,
[out] DWORD *pCount
);
BOOL WTSEnumerateProcessesExW(
[in] HANDLE hServer,
[in, out] DWORD *pLevel,
[in] DWORD SessionId,
[out] LPWSTR *ppProcessInfo,
[out] DWORD *pCount
);Parameters:
hServer: A handle to the target RD Session Host server. UseWTSOpenServerto get a handle to a remote server, orWTS_CURRENT_SERVER_HANDLEfor the local machine.pLevel: A pointer to aDWORDthat, on input, specifies the type of information to return:0: Return an array ofWTS_PROCESS_INFOstructures (basic information).1: Return an array ofWTS_PROCESS_INFO_EXstructures (extended information).
SessionId: The session ID for which to enumerate processes. UseWTS_ANY_SESSIONto enumerate processes across all sessions.ppProcessInfo: A pointer to a variable that receives a pointer to an array of structures. The type of structure depends on thepLevelparameter.pCount: A pointer to aDWORDvariable that receives the number of structures returned.
When pLevel is set to 1, the function returns an array of WTS_PROCESS_INFO_EX structures, which contain extended process information.
Definition (C/C++):
typedef struct _WTS_PROCESS_INFO_EXA {
DWORD SessionId;
DWORD ProcessId;
LPSTR pProcessName;
PSID pUserSid;
DWORD NumberOfThreads;
DWORD HandleCount;
DWORD PagefileUsage;
DWORD PeakPagefileUsage;
DWORD WorkingSetSize;
DWORD PeakWorkingSetSize;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
} WTS_PROCESS_INFO_EXA, *PWTS_PROCESS_INFO_EXA;
typedef struct _WTS_PROCESS_INFO_EXW {
DWORD SessionId;
DWORD ProcessId;
LPWSTR pProcessName;
PSID pUserSid;
DWORD NumberOfThreads;
DWORD HandleCount;
DWORD PagefileUsage;
DWORD PeakPagefileUsage;
DWORD WorkingSetSize;
DWORD PeakWorkingSetSize;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
} WTS_PROCESS_INFO_EXW, *PWTS_PROCESS_INFO_EXW;Members:
SessionId,ProcessId,pProcessName,pUserSid: Same as inWTS_PROCESS_INFO.NumberOfThreads: The number of threads in the process.HandleCount: The number of handles the process has open.PagefileUsage: The amount of page file space being used by the process, in bytes.PeakPagefileUsage: The peak amount of page file space used by the process, in bytes.WorkingSetSize: The current working set size of the process, in bytes.PeakWorkingSetSize: The peak working set size of the process, in bytes.UserTime: The amount of time the process has executed in user mode, in 100-nanosecond intervals.KernelTime: The amount of time the process has executed in kernel mode, in 100-nanosecond intervals.
Unlike WTSEnumerateProcesses which uses WTSFreeMemory, the extended version requires a different function for memory cleanup: WTSFreeMemoryEx. This function takes additional parameters to specify the type of memory being freed.
BOOL WTSFreeMemoryExA(
[in] WTS_TYPE_CLASS WTSTypeClass,
[in] PVOID pMemory,
[in] ULONG NumberOfEntries
);
BOOL WTSFreeMemoryExW(
[in] WTS_TYPE_CLASS WTSTypeClass,
[in] PVOID pMemory,
[in] ULONG NumberOfEntries
);Parameters:
WTSTypeClass: Specifies the type of structure being freed. ForWTSEnumerateProcessesExwithpLevel=1, useWTSTypeProcessInfoLevel1.pMemory: Pointer to the memory to free.NumberOfEntries: The number of structures in the array.
Here's a C++ example demonstrating how to use WTSEnumerateProcessesEx to retrieve extended process information:
#include <windows.h>
#include <wtsapi32.h>
#include <sddl.h>
#include <stdio.h>
#include <string>
#include <locale>
#include <codecvt>
#pragma comment(lib, "wtsapi32.lib")
// Helper function to convert SID to Username (reused from previous example)
std::wstring GetUserNameFromSid(PSID sid) {
// Implementation same as in previous example
// ...
}
// Helper function to format CPU time
std::wstring GetCpuTime(const LARGE_INTEGER& kernelTime, const LARGE_INTEGER& userTime) {
// Convert 100-nanosecond intervals to seconds
ULONGLONG totalTime = (kernelTime.QuadPart + userTime.QuadPart) / 10000000ULL;
ULONGLONG hours = totalTime / 3600;
ULONGLONG minutes = (totalTime % 3600) / 60;
ULONGLONG seconds = totalTime % 60;
wchar_t buffer[32];
swprintf_s(buffer, L"%02llu:%02llu:%02llu", hours, minutes, seconds);
return std::wstring(buffer);
}
bool EnumerateProcessesExtended() {
PWTS_PROCESS_INFO_EXW pProcessInfo = nullptr;
DWORD processCount = 0;
DWORD level = 1; // Request extended information
if (!WTSEnumerateProcessesExW(WTS_CURRENT_SERVER_HANDLE, &level,
WTS_ANY_SESSION, (LPWSTR*)&pProcessInfo, &processCount)) {
fprintf(stderr, "WTSEnumerateProcessesEx failed. Error %lu\n", GetLastError());
return false;
}
printf("--- Processes (Extended Info) ---\n");
printf("%5s %3s %4s %5s %10s %10s %10s %s\n",
"PID", "SID", "Thds", "Hdls", "WS(KB)", "Peak(KB)", "CPU Time", "Process Name");
printf("-------------------------------------------------------------------------\n");
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
for (DWORD i = 0; i < processCount; ++i) {
PWTS_PROCESS_INFO_EXW pi = &pProcessInfo[i];
std::wstring userName = GetUserNameFromSid(pi->pUserSid);
std::wstring cpuTime = GetCpuTime(pi->KernelTime, pi->UserTime);
// Convert to narrow strings for printf
std::string narrowProcessName = converter.to_bytes(pi->pProcessName ? pi->pProcessName : L"<N/A>");
printf("%5lu %3lu %4lu %5lu %10lu %10lu %10s %s\n",
pi->ProcessId,
pi->SessionId,
pi->NumberOfThreads,
pi->HandleCount,
pi->WorkingSetSize / 1024, // Convert to KB
pi->PeakWorkingSetSize / 1024, // Convert to KB
converter.to_bytes(cpuTime).c_str(),
narrowProcessName.c_str());
}
// CRITICAL: Free the memory allocated by WTSEnumerateProcessesEx
if (pProcessInfo) {
WTSFreeMemoryEx(WTSTypeProcessInfoLevel1, pProcessInfo, processCount);
pProcessInfo = nullptr;
}
return true;
}
/*
int main() {
if (!EnumerateProcessesExtended()) {
return 1;
}
return 0;
}
*/-
Memory Usage Limitations: A significant limitation of
WTS_PROCESS_INFO_EXis that memory-related fields (PagefileUsage,WorkingSetSize, etc.) are 32-bitDWORDvalues even on 64-bit systems. This means they can only represent values up to 4GB. For processes using more memory, these fields will overflow and show incorrect values. -
CPU Time Representation: The
UserTimeandKernelTimefields are stored asLARGE_INTEGERvalues representing 100-nanosecond intervals. Proper conversion is needed to display them in a human-readable format. -
Administrator Privileges: Like
WTSEnumerateProcesses, this function requires administrative privileges to see processes in sessions other than the caller's own. -
Memory Management: The memory allocated by
WTSEnumerateProcessesExmust be freed usingWTSFreeMemoryExwith the correct type class (WTSTypeProcessInfoLevel1for extended information) and count. -
Availability: This function is available starting from Windows 7 and Windows Server 2008 R2, so it won't work on older Windows versions.
| Feature | WTSEnumerateProcesses | WTSEnumerateProcessesEx |
|---|---|---|
| Available Since | Windows Vista | Windows 7/Server 2008 R2 |
| Information Detail | Basic (PID, Session, Name, SID) | Extended (adds threads, handles, memory, CPU time) |
| Structure | WTS_PROCESS_INFO |
WTS_PROCESS_INFO_EX (when pLevel=1) |
| Memory Freeing | WTSFreeMemory |
WTSFreeMemoryEx |
| Session Filtering | No (all sessions) | Yes (can specify SessionId parameter) |
| Memory Metrics | None | Yes, but limited to 32-bit values |
| CPU Time | None | Yes (UserTime and KernelTime) |
-
Use
WTSEnumerateProcesseswhen:- You only need basic process identification (PID, name, session, user).
- You're working with older Windows versions (pre-Windows 7).
- Simplicity is preferred over detailed metrics.
-
Use
WTSEnumerateProcessesExwhen:- You need detailed process metrics (threads, handles, memory, CPU time).
- You want to filter processes by session ID.
- You're working with Windows 7/Server 2008 R2 or newer.
- You're aware of and can handle the 32-bit limitation for memory metrics.
-
RDS Session Management:
- Monitor per-session resource usage to identify problematic users or applications.
- Track process distribution across sessions for load balancing.
-
Security Monitoring:
- Identify unexpected processes running in user sessions.
- Detect processes running with elevated privileges.
- Monitor for processes running in disconnected sessions.
-
Performance Analysis:
- Track resource-intensive processes across multiple user sessions.
- Identify memory leaks or handle leaks in long-running processes.
- Compare CPU usage patterns between different user sessions.
-
Custom Administration Tools:
- Build session-aware task managers for RDS environments.
- Create automated session cleanup tools that target specific process types.
- Develop monitoring dashboards for multi-user environments.
The Windows Terminal Services API provides specialized functions for process enumeration in multi-user environments, particularly useful for Remote Desktop Services scenarios. WTSEnumerateProcesses offers basic process information with a simple interface, while WTSEnumerateProcessesEx provides more detailed metrics at the cost of slightly more complex usage.
When working with these functions, remember these key points:
- Choose the right function based on your information needs and target Windows version.
- Handle memory correctly by using the appropriate free function (
WTSFreeMemoryorWTSFreeMemoryEx). - Be aware of limitations, particularly the 32-bit size of memory metrics in
WTS_PROCESS_INFO_EX. - Consider privilege requirements for accessing processes across different sessions.
By leveraging these WTS functions, developers and administrators can build more effective tools for managing and monitoring multi-user Windows environments, particularly in Remote Desktop Services deployments where session context is crucial for proper process management.