Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Windows Process Tree

62 views
Skip to first unread message

Bonita Montero

unread,
Nov 22, 2022, 3:16:38 PM11/22/22
to
I developed a little program which lists the process tree of Windows.

#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS 1
#endif
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <vector>
#include <array>
#include <set>
#include <algorithm>
#include <compare>
#include <sstream>

#if defined(_MSC_VER)
#pragma warning(disable: 6319) // comma-statement with unused return-value
#pragma warning(disable: 26495) // uninitialized members
#endif

using namespace std;

using XHANDLE = unique_ptr<void, decltype([]( void *h ) { h && h !=
INVALID_HANDLE_VALUE && CloseHandle( h ); })>;

int main( int argc, char ** )
{
try
{
auto throwSysErr = []( char const *what ) { throw system_error(
GetLastError(), system_category(), what ); };
struct pe_t;
using process_vec = vector<pe_t>;
using process_it = vector<pe_t>::iterator;
struct pe_t
{
PROCESSENTRY32W pe;
bool root;
process_it pFirstChild, pNextSibling;
};
process_vec processes;
XHANDLE xhSnapshot( CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ) );
if( xhSnapshot.get() == INVALID_HANDLE_VALUE )
throwSysErr( "can't create toolhelp snapshot" );
for( ; ; )
{
processes.resize( processes.size() + 1 );
pe_t *pPe = &processes.back();
pPe->root = true;
pPe->pe.dwSize = sizeof(PROCESSENTRY32W);
if( processes.size() == 1 )
if( Process32FirstW( xhSnapshot.get(), &pPe->pe ) )
continue;
else
break;
if( !Process32NextW( xhSnapshot.get(), &pPe->pe ) )
break;
}
if( GetLastError() != ERROR_NO_MORE_FILES )
throwSysErr( "can't enumerate processes" );
processes.resize( processes.size() - 1 );
xhSnapshot.reset();
using proces_it_vec = vector<process_it>;
using process_itit = proces_it_vec::iterator;
proces_it_vec pProcesses( processes.size() );
for( size_t i = processes.size(); i--; pProcesses[i] =
processes.begin() + i );
bool sortProcessName = argc > 1;
auto sortPred = [sortProcessName = sortProcessName]( process_it pLeft,
process_it pRight ) -> bool
{
DWORD
dwLeftParent = pLeft->pe.th32ParentProcessID,
dwRightParent = pRight->pe.th32ParentProcessID;
if( dwLeftParent < dwRightParent )
return true;
if( dwLeftParent > dwRightParent ) [[likely]]
return false;
if( sortProcessName )
return pLeft->pe.th32ProcessID < pRight->pe.th32ProcessID;
else
return _wcsicmp( pLeft->pe.szExeFile, pRight->pe.szExeFile ) < 0;
};
sort( pProcesses.begin(), pProcesses.end(), sortPred );
for( process_it pProcess : pProcesses )
{
if( pProcess->root )
pProcess->pNextSibling = processes.end();
DWORD dwPpId = pProcess->pe.th32ProcessID;
process_itit ppSibling = lower_bound( pProcesses.begin(),
pProcesses.end(), dwPpId,
[]( process_it pChildProcess, DWORD dwPpId )
{
DWORD dwChildPpId = pChildProcess->pe.th32ParentProcessID;
return dwChildPpId < dwPpId
|| dwChildPpId == dwPpId && !pChildProcess->pe.th32ProcessID;
} );
process_it pSibling;
if( ppSibling == pProcesses.end()
|| (pSibling = *ppSibling)->pe.th32ParentProcessID != dwPpId )
[[unlikely]]
{
pProcess->pFirstChild = processes.end();
continue;
}
pProcess->pFirstChild = pSibling;
for( ; ; )
{
pSibling->root = false;
process_itit ppNextSibling = ppSibling + 1;
process_it pNextSibling;
if( ppNextSibling == pProcesses.end()
|| (pNextSibling = *ppNextSibling)->pe.th32ParentProcessID !=
dwPpId ) [[unlikely]]
{
pSibling->pNextSibling = processes.end();
break;
}
pSibling->pNextSibling = pNextSibling;
ppSibling = ppNextSibling;
pSibling = pNextSibling;
}
}
wstring indent;
indent.reserve( 64 );
wostringstream woss;
process_it pEnd = processes.end();
auto recurse = [&]( auto &self, process_it pSibling ) -> void
{
for( ; pSibling != pEnd; pSibling = pSibling->pNextSibling )
{
woss << indent << L"\"" << pSibling->pe.szExeFile << " (" <<
pSibling->pe.th32ProcessID << ")" << endl;
if( pSibling->pFirstChild != pEnd )
indent += L'\t',
self( self, pSibling->pFirstChild ),
indent.pop_back();
}
};
for( process_it pProcess = processes.begin(); pProcess != pEnd;
++pProcess )
if( pProcess->root )
recurse( recurse, pProcess );
wcout << woss.str() << endl;
}
catch( exception const &exc )
{
cout << exc.what() << endl;
}
}

The process tree is built inside a single vector and printed with a
recursive lambda.

DFS

unread,
Nov 22, 2022, 11:11:34 PM11/22/22
to
On 11/22/2022 3:16 PM, Bonita Montero wrote:

> I developed a little program which lists the process tree of Windows.


Where's the output?

Does it look like the data from SysInternals pslist64.exe?

https://imgur.com/zYcFpRk

Bonita Montero

unread,
Nov 23, 2022, 2:11:27 AM11/23/22
to
Am 23.11.2022 um 05:11 schrieb DFS:

> Where's the output?
> Does it look like the data from SysInternals pslist64.exe?
> https://imgur.com/zYcFpRk

Similar, but I only show the executable name and the process ID.

Mut...@dastardlyhq.com

unread,
Nov 23, 2022, 3:16:15 AM11/23/22
to
On Tue, 22 Nov 2022 21:16:29 +0100
Bonita Montero <Bonita....@gmail.com> wrote:
>I developed a little program which lists the process tree of Windows.

[snip usual Bonita mess]

>The process tree is built inside a single vector and printed with a
>recursive lambda.

Thank god you didn't write task manager.

Bonita Montero

unread,
Nov 23, 2022, 7:27:39 AM11/23/22
to
Am 23.11.2022 um 09:15 schrieb Mut...@dastardlyhq.com:

> [snip usual Bonita mess]

If that is a mess for you this is rather a statement about you.

> Thank god you didn't write task manager.

It would be much more safe and efficient - for no practical reason.


Mut...@dastardlyhq.com

unread,
Nov 25, 2022, 5:15:16 AM11/25/22
to
On Wed, 23 Nov 2022 13:27:30 +0100
Bonita Montero <Bonita....@gmail.com> wrote:
>Am 23.11.2022 um 09:15 schrieb Mut...@dastardlyhq.com:
>
>> [snip usual Bonita mess]
>
>If that is a mess for you this is rather a statement about you.

Indeed it does. It says I like tidy, readable code that doesn't look like it was
written by a drunk cat walking around the keyboard.

>> Thank god you didn't write task manager.
>
>It would be much more safe and efficient - for no practical reason.

Your modesty matches your coding skills.

Bonita Montero

unread,
Nov 25, 2022, 5:57:26 AM11/25/22
to
Am 25.11.2022 um 11:14 schrieb Mut...@dastardlyhq.com:

> Indeed it does. It says I like tidy, readable code that doesn't
> look like it was written by a drunk cat walking around the keyboard.

I understand C++, you don't.

Mut...@dastardlyhq.com

unread,
Nov 25, 2022, 10:42:31 AM11/25/22
to
Oh you are priceless :)

Alf P. Steinbach

unread,
Nov 26, 2022, 4:09:33 PM11/26/22
to
On 22 Nov 2022 21:16, Bonita Montero wrote:
> I developed a little program which lists the process tree of Windows.
>
> [snip code]
>
> The process tree is built inside a single vector and printed with a
> recursive lambda.

There is one thing I wonder about, namely the use of the type of a
non-capturing lambda as the type of a unique_ptr deleter. Does the
standard guarantee that such type is default-constructible?

Anyway, when I ran your program it displayed a lot of apparently top
level processes, but I found that many processes have an ultimate top
level ancestor process that no longer exists.

So to get a view of all groups (e.g. Google stuff in a group because
started by a common no longer existing process), I used code like this:

#include <windows-api-header-wrappers/windows-h.for-utf-16.hpp>
#include <tlhelp32.h>

#include <fmt/core.h>
#include <fmt/xchar.h> // wchar_t support

#include <map>
#include <stack>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>

#include <stdlib.h>


#define FAIL( message ) \
support_machinery::fail( std::string() + __func__ + " - " + (message) )

namespace support_machinery {
using std::stack, // <stack>
std::runtime_error, // <stdexcept>
std::string; // <string>

template< class T > using in_ = const T&;

auto hopefully( const bool condition ) -> bool { return condition; }
auto fail( in_<string> s ) -> bool { throw runtime_error( s ); }

template< class Container >
auto is_empty( in_<Container> c ) -> bool { return c.empty(); }

template< class Item >
auto popped_top_of( stack<Item>& st )
-> Item
{
Item result = st.top();
st.pop();
return result;
}
} // namespace support_machinery

namespace process {
namespace sm = support_machinery;
using std::map, // <map>
std::wstring, // <string>
std::vector; // <vector>

struct Snapshot
{
const HANDLE handle;

struct Entry: PROCESSENTRY32
{
Entry() { dwSize = sizeof( PROCESSENTRY32 ); }
};

~Snapshot() { ::CloseHandle( handle ); }

Snapshot():
handle( ::CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ) )
{
sm::hopefully( handle != INVALID_HANDLE_VALUE )
or FAIL( "::CreateToolhelp32Snapshot failed" );
}
};

using Id = DWORD;
constexpr auto unknown_id = Id( -1 );

struct Info
{
Id id;
Id parent_id;
vector<Id> child_ids;
wstring exe_name;
};

struct Tree: public map<Id, Info>
{
Tree()
{
auto& self = *this;
auto ss = process::Snapshot();
auto entry = process::Snapshot::Entry();

::Process32First( ss.handle, &entry )
or FAIL( "::Process32First failed to retrieve the first
process entry." );
do {
const auto id = entry.th32ProcessID;
const auto parent_id = entry.th32ParentProcessID;

const auto it = self.find( id );
if( it == self.end() ) {
self.emplace( id, process::Info{ id, parent_id, {},
entry.szExeFile } );
} else {
auto& info = it->second;
info.parent_id = parent_id;
info.exe_name = entry.szExeFile;
}

if( id != 0 ) {
const auto it_parent = self.find( parent_id );
if( it_parent == self.end() ) {
self.emplace( parent_id, process::Info{
parent_id, unknown_id, {id}, L"?" } );
} else {
auto& parent_info = it_parent->second;
parent_info.child_ids.push_back( id );
}
}
} while( ::Process32Next( ss.handle, &entry ) );

sm::hopefully( ::GetLastError() == ERROR_NO_MORE_FILES )
or FAIL( "Failed to enumerate processes." );
}
};
} // namespace process

namespace app {
namespace sm = support_machinery;
using sm::in_, sm::is_empty, sm::popped_top_of;
using std::stack, // <stack>
std::wstring, // <string>
std::wstring_view; // <string_view>

auto output( in_<wstring_view> s )
{
// Writing directly to console because fmt::print fouls up wide
non-ASCII.
static const HANDLE out = GetStdHandle( STD_OUTPUT_HANDLE );
WriteConsole( out, s.data(), DWORD( s.size() ), nullptr, nullptr );
}

auto indent_string( const int n )
-> wstring
{
static const auto unit = L"." + wstring( 3, L' ' );

wstring result;
for( int i = 1; i <= n; ++i ) { result += unit; }
return result;
}

void display( const process::Id id, const int level, in_<wstring>
name )
{
const wstring quoted_name = (name.front() == L'?'? L"???" :
fmt::format( L"“{}”", name ));
const wstring indent = indent_string( level );
output( fmt::format( L"{:8d}{:2}{}{:s}\n", id, L"", indent,
quoted_name ) );
}

void display_with_root( const process::Id root_id, const
process::Tree& tree )
{
struct Node{ process::Id process_id; int level; };
auto remaining = stack<Node>();
remaining.push( {root_id, 0} );
while( not is_empty( remaining ) ) {
const Node node = popped_top_of(
remaining );
const process::Info& info = tree.at(
node.process_id );
display( node.process_id, node.level, info.exe_name );
for( const process::Id child_id: info.child_ids ) {
remaining.push( Node{ child_id, node.level + 1 } );
}
}
}

void run()
{
const auto tree = process::Tree();
for( const auto& [id, info]: tree ) {
if( id == 0 or info.parent_id == process::unknown_id ) {
display_with_root( id, tree );
}
}
}
} // namespace app

auto main() -> int
{
using std::exception;
using support_machinery::in_;

try {
app::run();
return EXIT_SUCCESS;
} catch( in_<exception> x ) {
fmt::print( "!{}\n", x.what() );
}
return EXIT_FAILURE;
}


Typical result:


0 “[System Process]”
4 . “System”
2960 . . “Memory Compression”
684 . . “smss.exe”
176 . . “Registry”
808 ???
748 . “wininit.exe”
1300 . . “fontdrvhost.exe”
1128 . . “lsass.exe”
1076 . . “services.exe”
20520 . . . “svchost.exe”
43056 . . . “svchost.exe”
30436 . . . “svchost.exe”
36384 . . . “NisSrv.exe”
5540 . . . “svchost.exe”
17952 . . . “svchost.exe”
16808 . . . “svchost.exe”
2520 . . . “svchost.exe”
8488 . . . “svchost.exe”
11972 . . . “svchost.exe”
7408 . . . “svchost.exe”
10308 . . . “svchost.exe”
7916 . . . “svchost.exe”
11176 . . . “SgrmBroker.exe”
10900 . . . “svchost.exe”
6196 . . . “svchost.exe”
4424 . . . “svchost.exe”
1920 . . . “svchost.exe”
10876 . . . “svchost.exe”
10780 . . . “svchost.exe”
10296 . . . “SecurityHealthService.exe”
9388 . . . “svchost.exe”
9152 . . . “SearchIndexer.exe”
8740 . . . “svchost.exe”
8584 . . . “svchost.exe”
7996 . . . “svchost.exe”
7584 . . . “svchost.exe”
7524 . . . “svchost.exe”
7468 . . . “svchost.exe”
7456 . . . “svchost.exe”
7256 . . . “svchost.exe”
4356 . . . “svchost.exe”
6576 . . . “svchost.exe”
6736 . . . “svchost.exe”
42056 . . . . “MoUsoCoreWorker.exe”
6032 . . . “svchost.exe”
4036 . . . “svchost.exe”
5840 . . . “svchost.exe”
4892 . . . “svchost.exe”
4716 . . . “jhi_service.exe”
4536 . . . “Intel_PIE_Service.exe”
4516 . . . “MBAMService.exe”
7232 . . . . “mbamtray.exe”
4504 . . . “svchost.exe”
4396 . . . “LMS.exe”
4308 . . . “WMIRegistrationService.exe”
4288 . . . “RstMwService.exe”
4260 . . . “OfficeClickToRun.exe”
4252 . . . “MsMpEng.exe”
4244 . . . “RtkAudUService64.exe”
4220 . . . “svchost.exe”
4212 . . . “svchost.exe”
4196 . . . “Lenovo.Modern.ImController.exe”
27020 . . . .
“Lenovo.Modern.ImController.PluginHost.Device.exe”
4188 . . . “svchost.exe”
4180 . . . “svchost.exe”
4172 . . . “svchost.exe”
4164 . . . “LenovoVantageService.exe”
3416 . . . . “LenovoVantage-(LenovoCompanionAppAddin).exe”
780 . . . . “LenovoVantage-(LenovoBoostSystemAddin).exe”
3496 . . . . “LenovoVantage-(LenovoBoostAddin).exe”
11184 . . . . “LenovoVantage-(LenovoServiceBridgeAddin).exe”
11852 . . . . “LenovoVantage-(VantageCoreAddin).exe”
4156 . . . “svchost.exe”
4148 . . . “OneApp.IGCC.WinService.exe”
4140 . . . “svchost.exe”
5472 . . . . “AggregatorHost.exe”
3912 . . . “svchost.exe”
3860 . . . “svchost.exe”
3800 . . . “spoolsv.exe”
3744 . . . “svchost.exe”
3712 . . . “svchost.exe”
4016 . . . . “wlanext.exe”
4024 . . . . . “conhost.exe”
3632 . . . “svchost.exe”
3436 . . . “svchost.exe”
3428 . . . “svchost.exe”
3420 . . . “svchost.exe”
3260 . . . “svchost.exe”
10136 . . . . “ctfmon.exe”
3232 . . . “svchost.exe”
3112 . . . “WUDFHost.exe”
1604 . . . “svchost.exe”
2888 . . . “svchost.exe”
2880 . . . “svchost.exe”
2808 . . . “svchost.exe”
2800 . . . “svchost.exe”
2776 . . . “svchost.exe”
2768 . . . “svchost.exe”
2728 . . . “svchost.exe”
2600 . . . “svchost.exe”
2592 . . . “svchost.exe”
2456 . . . “igfxCUIService.exe”
7504 . . . . “igfxEM.exe”
2296 . . . “svchost.exe”
2280 . . . “IntelCpHeciSvc.exe”
2244 . . . “svchost.exe”
2068 . . . “svchost.exe”
1516 . . . “svchost.exe”
1276 . . . “svchost.exe”
1168 . . . “svchost.exe”
716 . . . “svchost.exe”
7396 . . . . “sihost.exe”
10588 . . . . . “IGCCTray.exe”
1036 . . . . “winlogon.exe”
1624 . . . . . “dwm.exe”
1296 . . . . . “fontdrvhost.exe”
704 . . . . “csrss.exe”
2028 . . . “IntelCpHDCPSvc.exe”
1884 . . . “svchost.exe”
1872 . . . “svchost.exe”
1860 . . . “svchost.exe”
1844 . . . “svchost.exe”
7684 . . . . “taskhostw.exe”
2532 . . . . “helperservice.exe”
3224 . . . . . “conhost.exe”
1732 . . . “svchost.exe”
1724 . . . “svchost.exe”
1716 . . . “svchost.exe”
1492 . . . “svchost.exe”
1452 . . . “svchost.exe”
1368 . . . “WUDFHost.exe”
1344 . . . “svchost.exe”
1264 . . . “svchost.exe”
32548 . . . . “RuntimeBroker.exe”
41928 . . . . “LockApp.exe”
31396 . . . . “backgroundTaskHost.exe”
26204 . . . . “backgroundTaskHost.exe”
36372 . . . . “FESearchHost.exe”
40420 . . . . “backgroundTaskHost.exe”
34948 . . . . “explorer.exe”
30600 . . . . “backgroundTaskHost.exe”
21008 . . . . “backgroundTaskHost.exe”
19548 . . . . “RuntimeBroker.exe”
17424 . . . . “explorer.exe”
15712 . . . . “explorer.exe”
15876 . . . . . “vlc.exe”
19184 . . . . “UserOOBEBroker.exe”
19004 . . . . “SystemSettings.exe”
19392 . . . . “SystemSettingsBroker.exe”
19112 . . . . “RuntimeBroker.exe”
14380 . . . . “ShellExperienceHost.exe”
4440 . . . . “backgroundTaskHost.exe”
12392 . . . . “backgroundTaskHost.exe”
10604 . . . . “RuntimeBroker.exe”
12440 . . . . “CalculatorApp.exe”
3832 . . . . “dllhost.exe”
8444 . . . . “RuntimeBroker.exe”
10416 . . . . “Video.UI.exe”
4132 . . . . “ApplicationFrameHost.exe”
2840 . . . . “RuntimeBroker.exe”
9076 . . . . “IGCC.exe”
11084 . . . . “explorer.exe”
11120 . . . . . “SumatraPDF.exe”
14148 . . . . . “ClocX.exe”
4008 . . . . . “Notepad.exe”
10440 . . . . . “WinSnap.exe”
8344 . . . . . “notepad++.exe”
9536 . . . . “TextInputHost.exe”
5696 . . . . “RuntimeBroker.exe”
8976 . . . . “PhoneExperienceHost.exe”
9648 . . . . “dllhost.exe”
9248 . . . . “RuntimeBroker.exe”
1236 . . . . “RuntimeBroker.exe”
8620 . . . . “Widgets.exe”
7840 . . . . “StartMenuExperienceHost.exe”
7832 . . . . “SearchHost.exe”
956 . “csrss.exe”
2080 ???
3672 . “cmd.exe”
1468 . . “LSB.exe”
2680 . . “conhost.exe”
7380 ???
7792 . “explorer.exe”
30724 . . “foobar2000.exe”
9840 . . “Taskmgr.exe”
11984 . . “chrome.exe”
40128 . . . “chrome.exe”
33304 . . . “chrome.exe”
33716 . . . “chrome.exe”
27036 . . . “chrome.exe”
40008 . . . “chrome.exe”
13780 . . . “chrome.exe”
17792 . . . “chrome.exe”
6564 . . . “chrome.exe”
18400 . . . “chrome.exe”
18028 . . . “chrome.exe”
16628 . . . “chrome.exe”
17680 . . . “chrome.exe”
1052 . . “ONENOTEM.EXE”
10932 . . “ROTELAudioCplApp.exe”
10488 . . “fdm.exe”
10352 . . “RtkAudUService64.exe”
10276 . . “SecurityHealthSystray.exe”
7756 ???
8848 . “GoogleCrashHandler64.exe”
8836 . “GoogleCrashHandler.exe”
21096 ???
21172 . “WindowsTerminal.exe”
328 . . “cmd.exe”
27888 . . . “b.exe”
9444 . . “OpenConsole.exe”
42024 ???
41224 . “vctip.exe”


- Alf

Bonita Montero

unread,
Nov 27, 2022, 2:56:32 AM11/27/22
to
Am 26.11.2022 um 22:09 schrieb Alf P. Steinbach:

> There is one thing I wonder about, namely the use of the type of a
> non-capturing lambda as the type of a unique_ptr deleter. Does the
> standard guarantee that such type is default-constructible?

C++20 allows non-capturing lambdas in a non-evaluating context.

0 new messages