Listing Processes – Part 4: Using Remote Desktop Services API

Using Remote Desktop Services API

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 
#pragma comment(lib, "Wtsapi32.lib")

DWORD RDSAPI_EnumProcesses(CArray<WTS_PROCESS_INFO>& arrProcInfo)
{
    // clear output array
    arrProcInfo.RemoveAll();
    HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // local machine processes
    PWTS_PROCESS_INFO pProcessInfo = NULL;
    DWORD dwCount = 0;
    // enumerate processes
    if(!::WTSEnumerateProcesses(hServer, 0, 1, &pProcessInfo, &dwCount))
    {
        return ::GetLastError();
    }
    // fill output array
    arrProcInfo.SetSize(dwCount);
    for(DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        arrProcInfo[dwIndex] = pProcessInfo[dwIndex];
    }
    // free the memory allocated in WTSEnumerateProcesses
    ::WTSFreeMemory(pProcessInfo);
    return NO_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 array
    arrProcInfo.RemoveAll();
    // init WTSEnumerateProcessesEx parameters
    HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // get local machine processes
    DWORD dwLevel = 1;                          // get array of WTS_PROCESS_INFO_EX
    DWORD dwSessionID = WTS_ANY_SESSION;        // get processes in all sessions
    DWORD dwCount = 0;                          // returns the number of processes
    PWTS_PROCESS_INFO_EX  pProcessInfo = NULL;  // output data

    if(!::WTSEnumerateProcessesEx(hServer, &dwLevel, dwSessionID, (LPTSTR*)&pProcessInfo, &dwCount))
    {
        return ::GetLastError();
    }

    // fill output array
    arrProcInfo.SetSize(dwCount);
    for(DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        CProcessInfoEx processInfoEx(pProcessInfo[dwIndex]); 
        arrProcInfo[dwIndex] = processInfoEx;
    }
    // free the memory allocated in WTSEnumerateProcessesEx
    if(!::WTSFreeMemoryEx(WTSTypeProcessInfoLevel1, pProcessInfo, dwCount))
    {
        return ::GetLastError();
    }
    return NO_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 control
    const INT_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 text
        const CProcessInfoEx& 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.
Download: Listing_Processes_Using_RDS_API.zip (1640 downloads)

Using Remote Desktop Services API - Demo Application
Using Remote Desktop Services API – Demo Application

References

See also

Leave a Comment