WinAPI's VirtualQueryEx Shows an Inaccurate Amount of Memory Used by a Process - c++

I have the following function, written in C++ using WinAPI:
The function receives a handle to a process, and should return the size of the memory used by the process.
DWORD GetProcessCommitedMemorySize(const HANDLE hProcess)
{
MEMORY_BASIC_INFORMATION mbi;
DWORD totalMemory = 0;
DWORD currAddr = 0;
//for checking repetion of pages base addresses
std::unordered_set<DWORD> tempAddresses;
while (VirtualQueryEx(hProcess, (LPVOID)currAddr, &mbi, (SIZE_T)sizeof(mbi)) == (SIZE_T)sizeof(mbi))
{
if (tempAddresses.find((DWORD)mbi.BaseAddress) == tempAddresses.end()) // not found
{
currAddr = (DWORD)mbi.BaseAddress + mbi.RegionSize;
tempAddresses.insert((DWORD)mbi.BaseAddress);
if (mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE)
{
totalMemory += mbi.RegionSize;
}
}
else
{
break;
}
}
return totalMemory;
}
The function always returns a number which is bigger than the amount of memory which is shown to be used on the 'details' page of the Task Manager.
for example, 86155 KB as the function's output, and 56924 KB on the Task Manager.
The program still isn't complete so I need to hard-code the PID, so I know I'm looking at the right one.
Does this issue occur because the number I'm seeking with this function and the number on the Task Manager have different meanings, or is my code just not doing what's it supposed to do?
Thanks.

Related

WNetOpenEnum returns ERROR_NETWORK_UNREACHABLE for the "Microsoft Windows Network" node

Our program has a piece of code that calculates the list of computers on our local network. It uses the Windows Networking API (WNetOpenEnum/WNetEnumResource) to unwind the network. For many years, the resulting list was identical to the one that can be seen in Windows Explorer under the "Network" entry. However, recently we have noticed that the same code returns an empty list. During debugging I found that WNetOpenEnum returns error 1231 (ERROR_NETWORK_UNREACHABLE) when it is called for the "Microsoft Windows Network" under the root node.
I have to mention, though I'm pretty sure it has nothing to do with the matter, that the network unwinding is done multithreaded, to avoid possible delays in the main GUI thread. Each time a node of type RESOURCEUSAGE_CONTAINER is encountered, a new worker thread is launched. The thread function calls the following procedure:
DWORD WINAPI EnumNetwork(NETRESOURCE_M* lpNR)
{
const int BUF_SIZE = 16384; // 16K is a good size.
HANDLE hEnum;
DWORD Result;
// Call the WNetOpenEnum function to begin the enumeration.
Result = ::WNetOpenEnum(RESOURCE_GLOBALNET, // all network
RESOURCETYPE_ANY, // all resource types
0, // enumerate all
(LPNETRESOURCE)lpNR,// parent resource
&hEnum); // enumeration handle
if (Result != NO_ERROR) // -> for "Microsoft Windows Network" Result = 1231
return Result;
std::vector<std::wstring> SrvList;
// Allocate buffer for enumeration.
LPNETRESOURCE lpEnumNR = (LPNETRESOURCE)new char[BUF_SIZE];
if (lpEnumNR == 0)
Result = ERROR_OUTOFMEMORY;
else
{
while (1)
{
::ZeroMemory(lpEnumNR, BUF_SIZE); // Initialize the buffer.
DWORD NumEntries = -1; // Enumerate all entries.
DWORD BufSize = BUF_SIZE;
// Call WNetEnumResource to continue the enumeration.
Result = ::WNetEnumResource(hEnum, // enumeration handle
&NumEntries,// number of entries to enumerate
lpEnumNR, // array of resources to return
&BufSize); // buffer size
if (Result == NO_ERROR)
{
// If the call succeeds, loop through the array.
for (unsigned i = 0; i < NumEntries; ++i)
{
if (lpEnumNR[i].dwDisplayType == RESOURCEDISPLAYTYPE_SERVER)
{
// Collect servers.
LPCWSTR SrvName = lpEnumNR[i].lpRemoteName;
if (PathHelpers::IsFullPath(SrvName))
SrvList.push_back(SrvName);
}
else if ((lpEnumNR[i].dwUsage & RESOURCEUSAGE_CONTAINER) &&
lpEnumNR[i].lpRemoteName != 0)
{
TCHAR PathBuf[1024] = {0};
if (lpNR && lpNR->Path)
{
_tcscpy(PathBuf, lpNR->Path);
::PathAddBackslash(PathBuf);
}
_tcscat(PathBuf, lpEnumNR[i].lpRemoteName);
if (RegisterServer(PathBuf))
{
// Start new thread for recursive enumeration.
NETRESOURCE_M* lpChildNR = DeepCopyNR(&lpEnumNR[i], PathBuf);
ExploreNetwork(lpChildNR); // -> this starts a worker thread
}
else
{
GetLogger().LogMessage(
_T("Cycles found while unwinding network: %s"), PathBuf);
}
}
}
}
else
{
if (Result == ERROR_NO_MORE_ITEMS)
Result = NO_ERROR;
break;
}
} // end while
delete [] (char*)lpEnumNR;
} // end if
::WNetCloseEnum(hEnum);
if (!SrvList.empty())
NotifyServerAdded(SrvList);
return Result;
}
where NETRESOURCE_M is the structure
struct NETRESOURCE_M
{
NETRESOURCE NR;
LPTSTR Path;
};
Trying to figure out what could have caused such a sudden change in behavior, I found in Google that a few years ago Microsoft disabled the SMB1 protocol, which could affect Network Discovery. However, I can't believe they could have damaged their own API without saying a word in the documentation.
EDIT: At the same time, Windows Explorer has a bunch of computers under its "Network" node. In the network settings, the network type is "Domain", and the network discovery is ON. Services "Function Discovery Provider Host" and "Function Discovery Resources Publication" are running. Windows OS build is 19042.685.
Edit 2: The Sysinternals' "ShareEnum" tool also fails with the error: "No domains or workgroups where found on your network". Because of this, and also because some time ago our company moved all of its computers to a different network, I got the feeling that the problem is in the network configuration. Such as though the network is declared as "Domain", the computers were not enrolled to this domain. I do not understand much in that, but something like this.

Ejecting dll by calling CreateRemoteThread : crash

I am trying to make for myself a tool for extracting/releasing dlls from processes. I have already experienced with LoadLibrary and injecting but this time the logic doesn't seem to apply.
This is my code:
HMODULE findModuleOffset(HANDLE proc, char *mod_name) {
//Finds module address in specified process. 0 if not found
HMODULE hMods[2048];
DWORD modules_byte_size;
if (EnumProcessModules(proc, hMods, sizeof(hMods), &modules_byte_size))
{
for (unsigned long i = 0; i < (modules_byte_size / sizeof(HMODULE)); i++) {
CHAR module_name[MAX_PATH];
// Get the full path to the module's file.
if (GetModuleFileNameExA(proc, hMods[i], module_name, sizeof(module_name))) {
if (strcmp(strrchr(module_name,'.')+1,"exe")!=0 && compareExeName(module_name, mod_name)) {
return hMods[i];
}
}
}
}
return 0;
}
bool compareExeName(char *path, char *partial_name) {
//This will substract the filename from path and compare it with partial_name
char *lastSlash = strrchr(path, '\\') + 1;
if (lastSlash != NULL && strstr(lastSlash, partial_name) == lastSlash) return 1;
return 0;
}
void unload_all_dll(char *dll_name) {
DWORD process_ids[2048];
DWORD process_byte_size; //size of filled process_ids in BYTES (after the call)
DWORD process_count; //count of all elements in process_ids
HMODULE ext_dll_module;
HANDLE opened_process;
HANDLE Hthread;
DWORD thread_exit_code = 1;
CHAR exe_path[1024];
if (EnumProcesses(process_ids, sizeof(process_ids), &process_byte_size)) {
process_count = process_byte_size / sizeof(DWORD);
for (int i = 0; i < process_count; i++) {
thread_exit_code = 0;
if ((opened_process = OpenProcess(PROCESS_ALL_ACCESS, false, process_ids[i])) == NULL) continue;
GetModuleFileNameExA(opened_process, 0, exe_path, MAX_PATH);
if ((ext_dll_module = findModuleOffset(opened_process, dll_name)) != 0) {
while (thread_exit_code == 0) {
if ((Hthread = CreateRemoteThread(opened_process, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary"), (void*)ext_dll_module, 0, NULL)) == NULL) {
cout<<"Process closed meanwhile or dll unloaded";
break; //process has closed meanwhile
}
while (WaitForSingleObject(Hthread, 1000) == WAIT_TIMEOUT);
GetExitCodeThread(Hthread, &thread_exit_code);
}
cout << "Dll unloaded from " << exe_path << endl;
}
}
}
}
Warning:some variables names might be confusing(I am in hurry)
But every time I try to eject a dll everything crashes(of course,only the apps that contained the specfied dll). I tested everything I could and everything seems fine: the module address returned by findModuleOffset is good(checked against the value given by process explorer). I have no ideea what the return value of createremotethread or thread_exit_code is because the app crashes(it contasins the dll to be ejected..so...). Can you help me?
(moving from the comments)
Given that there are threads in the target process that are running code from the dll being unloaded, they are going to crash immediately after the dll gets freed - after all, the very pages of code that the CPU is executing are being unmapped!
To avoid the problem, the running threads have to be notified in some way, so they can terminate before unloading the dll; Windows provides many IPC methods, one rarely used is particularly well-fitted for this case, namely mailslots.
When the dll is injected, the "master" thread that gets created will create a mailslot with a well-known name, and periodically check if there's any message for him. When you want to unload the dlls, instead of brutally injecting a thread that forcefully frees the dll, just ask to your "inside man": post a message to the mailslot asking it to terminate1.
The thread will see that there's a message in the mailslot, take care to possibly terminate the other threads that were started inside the target process (a shared atomic variable + WaitForSingleObject can be used) and, when the cleanup is finished, call FreeLibraryAndExitThread to suicide both the last thread and the dll.
Notes
A particularly interesting peculiarity of mailslots is that, if they are created multiple times with the same name, messages sent to such a name will be delivered to all them, so if, as it seems, you want to shut down all the injected dlls at the same time, this greatly simplifies the controlling program - there's not even need to enumerate the running processes.

Can i block a program using ASM?

I am trying for a few days to block a cheat program for my game, i talked with several coders, and one said i can block it using ASM. The program hides very good, i cannot find it in memory, i cannot detect it scanning processes, so maybe this could be the solution? Can someone give me a example how can i detect and block a program with c++ and ASM?
This is my current method to detect and block cheats, using memory dumps:
void SystemProcessesScan()
{
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap != INVALID_HANDLE_VALUE)
{
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if(Process32First(hProcessSnap, &pe32))
{
do
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
if(hProcess != NULL)
{
if(ScanProcessMemory(hProcess))
{
ExitProcess(0);
}
}
}
while(Process32Next(hProcessSnap, &pe32));
}
}
CloseHandle(hProcessSnap);
}
bool ScanProcessMemory(HANDLE hProcess)
{
for(int i = 0; i < MAX_PROCESS_DUMP; i++)
{
char aTmpBuffer[MAX_DUMP_SIZE];
SIZE_T aBytesRead = 0;
ReadProcessMemory(hProcess, (LPCVOID)g_ProcessesDumps[i].m_aOffset, (LPVOID)aTmpBuffer, sizeof(aTmpBuffer), &aBytesRead);
if(memcmp(aTmpBuffer, g_ProcessesDumps[i].m_aMemDump, MAX_DUMP_SIZE) == 0)
{
return true;
break;
}
}
return false;
}
The cheat injects a DLL into your game and it sounds like it uses manual mapping to hide the DLL in memory.
The best solution to detect it is to find a sequence of bytes that are unique to this DLL. Pattern scan your own game processes, if the signature is found, close the game or whatever you want to do at that point.
Search for C++ internal pattern scanning or signature scanning, there are dozens of sources floating around that are uses regularly for this purpose.
Due to it being manually mapped, you must scan all committed memory regions rather than looping through the list of modules. Use VirtualQuery() in a while loop starting with address 0 until it fails.

Getting Base Address not working

I need the base Address of the exe "tibia.exe". This is what I got so far but it doesn't work. It always returns 0.
What's wrong?
DWORD MainWindow::getBaseAddress(DWORD dwProcessIdentifier)
{
TCHAR lpszModuleName[] = {'t','i','b','i','a','.','e','x','e','\0'}; //tibia.exe
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
dwProcessIdentifier);
DWORD dwModuleBaseAddress = 0;
if(hSnapshot != INVALID_HANDLE_VALUE)
{
MODULEENTRY32 ModuleEntry32;
ModuleEntry32.dwSize = sizeof(MODULEENTRY32);
if(Module32First(hSnapshot, &ModuleEntry32))
{
do
{
if( wcscmp(ModuleEntry32.szModule, lpszModuleName) == 0)
{
dwModuleBaseAddress = (DWORD)ModuleEntry32.modBaseAddr;
break;
}
}
while(Module32Next(hSnapshot, &ModuleEntry32));
}
CloseHandle(hSnapshot);
}
return dwModuleBaseAddress;
}
//Call it here
tibiaWindow = FindWindow( L"TibiaClient", NULL);
DWORD PID;
GetWindowThreadProcessId( tibiaWindow, &PID );
DWORD baseAddress = getBaseAddress( PID );
if( baseAddress == 0 )
return false ;
Perhaps it's just because I was using them before ToolHelp32 was available (at least on the NT-based operating systems), but I tend to use the PSAPI functions for this kind of task. Using them, the code would look like this:
#include <windows.h>
#include <string>
#include <psapi.h>
#include <iostream>
int main(int argc, char **argv) {
HANDLE process = GetCurrentProcess();
if (argc != 1)
process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, atoi(argv[1]));
HMODULE handles[2048];
DWORD needed;
EnumProcessModules(process, handles, sizeof(handles), &needed);
for (int i = 0; i < needed / sizeof(handles[0]); i++) {
MODULEINFO info;
char name[1024];
GetModuleBaseName(process, handles[i], name, sizeof(name));
if (std::string(name).find(".exe") != std::string::npos) {
GetModuleInformation(process, handles[i], &info, sizeof(info));
std::cout << name << ": " << info.lpBaseOfDll << "\n";
break;
}
}
}
As it stands right now, this will let you enter a process ID on the command line, and show the load address of the first module it finds in that process with a name that includes ".exe". If you don't specify a process ID, it'll search through its own process (demos how the functions work, but otherwise pretty much useless).
Using either ToolHelp32 or PSAPI, you end up with a similar limitation: you need to compile this into a 64-bit executable for it to be able to "see" other 64-bit processes (i.e., when compiled as 32-bit code, they see only other 32-bit processes).
There are also some processes (e.g., CSRSS.exe) that neither will be able to open/enumerate successfully. As far as I know, the same processes will succeed/fail with PSAPI vs. ToolHelp32.
PSAPI does have one bit of clumsiness compared to ToolHelp32: dealing (well) with processes that have lots of modules is clumsy (at best). You call EnumProcessModules, and if you haven't given room for enough modules, the "Needed" parameter will be set to the space needed for the number of modules it contains. There's a race condition though: between the time that returns and the time you call EnumProcessModules again, the process could have loaded more DLLs, so that second call could fail the same way.
For the moment, I've just assumed that no process will use more than 2048 modules. To be really correct, you should have a while loop (or maybe a do/while loop) that starts with zero space, calls EnumProcessModules to find out how much space is needed, allocate that (perhaps with a little extra in case it loads more DLLs) and repeat until it succeeds.

When calling EnumServicesStatusEx() twice, i still get EROR_MORE_DATA in C++

I call EnumServicesStatusEx() twice in my code, the first time should fail and put the correct buffer size in dwBuffNeeded so that when i call it the second time the buffer size should be correct. But, sometimes, not always i still get ERROR_MORE_DATA after the second call. Any ideas why? Thanks
DWORD pId=GetCurrentProcessId();
SC_HANDLE hSCM = NULL;
PUCHAR pBuf = NULL;
ULONG dwBufSize = 0x00;
ULONG dwBufNeed = 0x00;
ULONG dwNumberOfService = 0x00;
LPENUM_SERVICE_STATUS_PROCESS pInfo = NULL;
hSCM = OpenSCManager( NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT );
if (hSCM == NULL)
{
GetCustomLog().Log( SV_ERROR, 10004807, "Could not open Service Control Manager: %s", GetLastOSErrorString().c_str() );
return;
}
//Query services once to get correct buffer size, always fails
if ( EnumServicesStatusEx(
hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_ACTIVE,
NULL,
dwBufSize,
&dwBufNeed,
&dwNumberOfService,
NULL,
NULL) == 0 )
{
DWORD err = GetLastError();
if ( ERROR_MORE_DATA == err )
{
dwBufSize = dwBufNeed + 0x10;
pBuf = (PUCHAR) malloc(dwBufSize);
//Query services again with correct buffer size
if ( EnumServicesStatusEx(
hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_ACTIVE,
pBuf,
dwBufSize,
&dwBufNeed,
&dwNumberOfService,
NULL,
NULL ) == 0 )
{
//FAILS HERE
GetCustomLog().Log( SV_ERROR, 10004808, "Could not enumerate services, error: %s", GetLastOSErrorString().c_str() );
free(pBuf);
return;
}
}
else
{
GetCustomLog().Log( SV_ERROR, 10004809, "Could not enumerate services, error: %s", GetLastOSErrorString().c_str() );
return;
}
I too had the same problem, but when querying SERVICE_STATE_ALL, which I thought would require the same amount of memory on each call (unless a service had been installed/uninstalled). It wasn't enough to simply retry with a buffer of the size returned in the pcbBytesNeeded argument:
BOOL WINAPI EnumServicesStatusEx(
_In_ SC_HANDLE hSCManager,
_In_ SC_ENUM_TYPE InfoLevel,
_In_ DWORD dwServiceType,
_In_ DWORD dwServiceState,
_Out_opt_ LPBYTE lpServices,
_In_ DWORD cbBufSize,
_Out_ LPDWORD pcbBytesNeeded,
_Out_ LPDWORD lpServicesReturned,
_Inout_opt_ LPDWORD lpResumeHandle,
_In_opt_ LPCTSTR pszGroupName
);
Unlike other WIN32 API calls, this is not returning the absolute number of bytes needed, but the additional bytes needed relative to the cbBufSize parameter. As an experiment, I provided a deliberately small buffer and just doubled it each time to find out what the system would return in pcbBytesNeeded in response. On this system sizeof(ENUM_SERVICE_STATUS_PROCESS) is 56 bytes. The last row is the smallest buffer resulting in a successful call.
+-----------+----------------+
| cbBufSize | pcbBytesNeeded |
+-----------+----------------+
| 112 | 37158 |
| 224 | 37013 |
| 448 | 36766 |
| 896 | 36374 |
| 1792 | 35280 |
| 3584 | 33202 |
| 7168 | 28972 |
| 14336 | 20765 |
| 28672 | 4215 |
| 32032 | 0 |
+-----------+----------------+
You can see each row roughly adds up to the required buffer size, and also that the required buffer size as far as the system is concerned is not very predictable. The number of service entries returned in the successful call in this case was 233, which only requires 13048 bytes. It turns out the strings pointed to by the ENUM_SERVICE_STATUS_PROCESS lpDisplayName and lpServiceName pointers are just stored at the tail-end of the provided buffer (I always wondered where the backing for those was, and why it was stable and didn't need to be separately freed). Anyway this explains the somewhat non-deterministic responses as well as the odd numbers: the system likely has the current entry which doesn't fit and knows exactly how much size it needs for that, but guesses about the remainder.
The code below is reliable and usually takes exactly two calls but sometimes can take three (even with SERVICE_STATE_ALL). I don't know why three are needed and the underestimation by the system sticks around for multiple minutes at a time but eventually resolves itself. I've never seen it need four calls.
int EnumerateAllServices(SC_HANDLE hSCM) {
void* buf = NULL;
DWORD bufSize = 0;
DWORD moreBytesNeeded, serviceCount;
for (;;) {
printf("Calling EnumServiceStatusEx with bufferSize %d\n", bufSize);
if (EnumServicesStatusEx(
hSCM,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL,
(LPBYTE)buf,
bufSize,
&moreBytesNeeded,
&serviceCount,
NULL,
NULL)) {
ENUM_SERVICE_STATUS_PROCESS* services = (ENUM_SERVICE_STATUS_PROCESS*)buf;
for (DWORD i = 0; i < serviceCount; ++i) {
printf("%s\n", services[i].lpServiceName);
}
free(buf);
return 0;
}
int err = GetLastError();
if (ERROR_MORE_DATA != err) {
free(buf);
return err;
}
bufSize += moreBytesNeeded;
free(buf);
buf = malloc(bufSize);
}
}
The "+=" is the 'trick', and is not documented very clearly (but, in hindsight, I now understand the nuance in the MSDN explanation of the pcbBytesNeeded parameter):
pcbBytesNeeded [out]
A pointer to a variable that receives the number of bytes
needed to return the remaining service entries, if the buffer
is too small.
Because service information isn't static and theoretically anything can happen between two calls to EnumServicesStatusEx(), this API is best used iteratively.
As Kat Marsen suggests, you may of course repeatedly try to fetch all services (looping and growing a buffer as long as the API returns ERROR_MORE_DATA).
But this has 2 drawbacks:
You tell the SCM to constantly fill in information it actually has sent you back already (even it's rather rare, I actually want to avoid it).
According to the MSDN documentation, there's a hard upper limit of 256K bytes for the buffer. As I mentioned in a comment above, the latter is practically very unlikely; still you want to play safe as a programmer.
The iterative approach
In contrary we can IMO only presume that the author responsible for EnumServicesStatusEx() had a reason to design it this way:
A buffer which is filled immediately if valid and big enough for at least one service.
An output parameter for the remaining bytes the SCM assumes for the services yet to be iterated.
Most importantly there's an output parameter telling you the resume point.
Both, the resume point and the remainder allow for a much easier iterative approach.
Let me showcase with complete real-world functions, working reasonably well, and featuring some C++ goodies.
chunk_fetch_all_win32_services pre-allocates some amount of memory, invokes a callback for every chunk, and lets the callback decide whether it consumes the memory or leaves it in order to be reused.
enum_all_win32_services consumes each chunk and in turn invokes a callback for each single service.
#include <type_traits>
#include <memory>
#include <stddef.h>
#include <Windows.h>
using std::unique_ptr;
using std::error_code;
using std::system_error;
using std::system_category;
using std::function;
using std::make_unique;
/** #short Fetch all win32 services in chunks.
This function fetches all services of type `SERVICE_WIN32=(SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS)`,
in chunks as decided by the SCM (refer to EnumServicesStatusEx()).
#param scmHandle Handle to the SCM with access right SC_MANAGER_ENUMERATE_SERVICE
#param stateMask One of SERVICE_ACTIVE, SERVICE_INACTIVE, SERVICE_STATE_ALL
In the callback you can decide whether you want to consume the passed memory or leave
it in order to be reused.
#note This is most probably rare but expect the callback being invoked multiple times
in case the SCM didn't return information about all services at once.
*/
bool chunk_fetch_all_win32_services(SC_HANDLE scmHandle, DWORD stateMask, const function<bool(unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]>&, DWORD /*nServices*/)>& cb, error_code* ec)
{
// (optionally) preallocate
// (the amount stems from Win XP's upper size of 64k;
// on a typical Win7 system there are about 220 services (about 34k))
unique_ptr<BYTE[]> mem = make_unique<BYTE[]>(64 * 1024);
DWORD nAllocated = 64 * 1024, nRemaining;
DWORD resumePoint = 0;
do
{
DWORD nServices;
if (!EnumServicesStatusEx(scmHandle, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, stateMask, mem.get(), nAllocated, &nRemaining, &nServices, &resumePoint, nullptr))
{
const int errorCode = GetLastError();
if (errorCode != ERROR_MORE_DATA)
{
if (!ec)
throw system_error{ errorCode, system_category(), "Can't enumerate services" };
ec->assign(errorCode, system_category());
return false;
}
}
if (nServices)
{
// memory initialized, transfer ownership to typed pointer
unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]> cache{ LPENUM_SERVICE_STATUS_PROCESS(mem.release()) };
if (!cb(cache, nServices))
// early bail-out requested
return true;
// document that the typed pointer can be 'downgraded' again without the need to destroy objects
static_assert(std::is_trivially_destructible_v<ENUM_SERVICE_STATUS_PROCESS>);
// possibly reuse existing buffer
mem.reset(PBYTE(cache.release()));
}
if (nRemaining)
{
// re-allocate if buffer too small or consumed by callback
if (!mem || nAllocated < nRemaining)
mem = make_unique<BYTE[]>(nRemaining),
nAllocated = nRemaining;
}
// loop as long as there are more services to be enumerated
} while (nRemaining);
return true;
}
/** #short Enumerate all win32 services.
This function enumerates all services of type `SERVICE_WIN32=(SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS)`.
#param scmHandle Handle to the SCM with access right SC_MANAGER_ENUMERATE_SERVICE
#param stateMask One of SERVICE_ACTIVE, SERVICE_INACTIVE, SERVICE_STATE_ALL
#param cb Callback receiving process information of a single service process information and the number of services.
#note This is most probably rare but expect the number of services passed to the callback to change
in case the SCM didn't return information about all services at once; if, then this of course happens
after the provided number of information items have been iterated.
*/
bool enum_all_win32_services(SC_HANDLE scmHandle, DWORD stateMask, const function<bool(ENUM_SERVICE_STATUS_PROCESS&, DWORD /*nServices*/)>& cb, error_code* ec)
{
return chunk_fetch_all_win32_services(scmHandle, stateMask, [&cb](unique_ptr<ENUM_SERVICE_STATUS_PROCESS[]>& cache, DWORD nServices)
{
for (size_t idx = 0; idx < nServices; ++idx)
{
if (!cb(cache[idx], nServices))
// early bail-out requested
return true;
}
return true;
}, ec);
}