Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Of Services and Processes - Part 1

0.00/5 (No votes)
3 Nov 2010 1  
How to programmatically control process and service in Windows NT

Introduction

This tutorial is for programmatically controlling processes on the Windows 2000 Platform using VC++ 6. This tutorial may consist of a minimum of three parts. The following installment is mentioned at the end of every tutorial. At the time of writing this tutorial I was using Windows 2000 Professional with VC++ 6, but porting it to other versions of Windows or VC should not require too many changes, though I haven't tested that.

This tutorial, though exhaustive, is not the place to look for a quick patch. It is designed to enable you to learn how to handle Processes in relation to Services. The need for this tutorial came from the lack of such material on the internet, especially regarding Processes. Feedback and suggestions should be sent to me via email at r_thampi@hotmail.com. Kindly use a relevant subject line so as to differentiate your email from spam.

You may distribute this tutorial on the conditions that you do not change, edit, or delete anything in the tutorial including but not limited to the credits and that the tutorial is not sold for personal gains – monetary or other.

The examples in this tutorial do not follow a conventional Windows programming model. I have maintained the project as a simple console .exe. This is done to concentrate on the intended tasks, rather than illustrating the Windows programming model, which would include so much more code.

Outline

  1. Getting all services and their Pid's.
    1. What you need
    2. Functions involved, including a brief description of how they work
    3. Process.cpp
    4. Process.cpp dissected
  2. Part II.
  3. Appendix

A. Getting all services and their PID's

1:- What you need

To make playing around with processes easier, there is a helper library. In this tutorial I use the PSAPI (Process Status Application Programming Interface) library provided by Microsoft to explain Processes. The files in question are Psapi.h, Psapi.lib and Psapi.dll.

Psapi.h and Psapi.lib: These files contain all of the functions that will be discussed in the examples. These files must be included in the code and referenced in the project. You can download the installable for Windows 200 from http://www.microsoft.com/downloads/details.aspx?familyid=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en. (Incase the file location has changed, you might have to google for the installable.)

The installable file, when run, offers to install a lot more than needed for this project. To install just what is required, choose Custom and install Windows Core SDK.

Paspi.dll: After compilation, the files Psapi.h and Psapi.lib will attempt to refer to Psapi.dll. Therefore, this is a necessary file to get your exe working, so make sure to include this DLL along with your project before distribution. Unfortunately, this DLL does not generally come packaged with Windows, but comes along with the Windows Core SDK installer, but in case you need only the DLL you can download it using the following link:
http://www.microsoft.com/downloads/release.asp?releaseid=30337

It is expected that the reader understands basic C and is familiar with VC. If you are good at C programming, but have issues with setting up the project in VC you should turn to Appendix B.

2: - Functions involved, including a brief description of how they work

Before beginning the project it is worthwhile to understand the following functions. The functions are EnumProcesses, OpenProcess, and GetModuleBaseName. It is OK if you do not understand them right away, because they are explained in detail in the code as well.

EnumProcesses :- The EnumProcesses function retrieves the process identifier (PID) for each process in the system.
BOOL EnumProcesses(DWORD* pProcessIds, DWORD cb, DWORD* pBytesReturned);
The Parameters:

  • pProcessIds: [out] This is a pointer to an array we define. The function will fill the list of process identifiers in this array.
  • cb: [in] This is a DWORD that contains the size of the pProcessIDs array, in bytes.
  • pBytesReturned: [out] This is a pointer to a DWORD that windows will use to store the number of bytes returned into the pProcessIds array.

As you can see, once you call this function you get all the data necessary to list the processes running on your computer system! The return value for EnumProcesses, if there are no errors will be zero.

OpenProcess :- The next function that you need to understand is OpenProcess and as indicated by its name, the function gives you the handle to the process. It requires three parameters.
HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
The Parameters

  • dwDesiredAccess: [in] This DWORD should contain the desired (one or more) access rights you wish to have to the process and is crosschecked against the security descriptor for the process. If the caller has enabled the SeDebugPrivilege privilege, the requested access is granted regardless of the contents of the security descriptor. Refer to Appendix A for a list of Access rights.
  • bInheritHandle: [in] If the value in this variable is TRUE, processes created will inherit the handle, otherwise the processes will not inherit this handle. You will understand what this implies when we get to the third part of this tutorial.
  • dwProcessId: [in] This variable should contain the identifier of the process to open, in other words, the PID of the process.

If the function succeeds, the return value is an open handle to the specified process and if it fails, the return value is NULL.

GetModuleBaseName :- The GetModuleBaseName function retrieves the base name of the specified module. Part 2 of this tutorial deals with "modules" in depth. For now you just need to know that many modules make up a Process.
DWORD GetModuleBaseName(HANDLE hProcess, HMODULE hModule, LPTSTR lpBaseName, DWORD nSize);
The Parameters

  • hProcess: [in] The Handle to the process that contains the module is available in this variable. To use this function, the handle must have PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access rights. (Refer to Appendix A for what they stand for.)
  • hModule: [in] This should contain the handle to the module. If this parameter is NULL, this function returns the name of the file used to create the calling process.
  • lpBaseName: [out] This variable gives you a pointer to the buffer that receives the base name of the module. If the base name is longer than the maximum number of characters specified by the nSize parameter, the base name is truncated.
  • nSize: [in] This should contain the size of the lpBaseName buffer, in characters.

If the function succeeds, the return value specifies the length of the string copied to the buffer in characters and if the function fails the return value is zero.

3: - Process.cpp

include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <psapi.h>

void main()
{
  DWORD ProcessesIDs[50], cbNeeded, cProcesses;
  unsigned int i;

  // The default of <unknown> is given so that if GetModuleBaseName does not return
  // the base name of the module then <unknown> will be printed instead of the base name.
  TCHAR szProcessName[50] = TEXT("<unknown>");

  // if Enumprocess returns zero (fails) then quit the program.
  if ( !EnumProcesses( ProcessesIDs, sizeof(ProcessesIDs), &cbNeeded ) )
    return;

  // Calculate how many process identifiers were returned.
  cProcesses = cbNeeded / sizeof(DWORD);

  // This for loop will be enumerating each process.
  for ( i = 0; i < cProcesses; i++ )
  {
    // Get a handle to the process. The process to which the handle will be returned 
    // will depend on the variable i.
    HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                                   FALSE, ProcessesIDs[i] );

    // Get the process name.
    if (NULL != hProcess )
    {
      GetModuleBaseName( hProcess, NULL, szProcessName,
                         sizeof(szProcessName)/sizeof(TCHAR) );
    }
    // Print the process name and identifier.
    _tprintf( TEXT("Processname = %s, PID = %u \n\n"), szProcessName, ProcessesIDs[i]);
    //Every handle is to be closed after its use is over.
    CloseHandle( hProcess );
    //End of for loop.
  }
}

4: - Process.cpp dissected

The code above can be copied to VC++ 6 and compiled, BUT remember to link in the include file Psapi.h and the library Psapi.lib. Refer to Appendix B for details.

Now let's try and understand the code itself. First you declare a couple of variables. The first three are ProcessesIDs[50], cbNeeded and cProcesses, all of type DWORD.

The first, ProcessIDs is an array of type DWORD. This is used to store the PID's. If you have more than 50 processes running on your system, then you will need to increase the size of this array.

The second is cbNeeded and is used to store the number of bytes returned into the array ProcessIDs.

The third is cProcesses. This is used to get the count of the number of processes running in the system.

The last variable szProcessName[50] (The i variable is simply used for the for loop). This variable is used to store the module name. If you have processes running with a name more than 50 TCHAR long, then you can increase the size of the array accordingly.

Now, after the declarations, lets move to the statements.

if ( !EnumProcesses( ProcessesIDs, sizeof(ProcessesIDs), &cbNeeded ) )
  return; 

The function EnumProcesses returns zero if it fails and a non-zero value if it succeeds, so you can safely use an if check. If the function fails, you can stop further execution.

You can see that there are three arguments for EnumProcess. Of the three arguments, the only argument that is providing a value into the function is the second argument that indicates the size of the first argument variable. In this case, the first argument variable (ProcessIDs) is an array of DWORD 50 elements long. Since a DWORD is four bytes long, the return value of sizeof(ProcessesIDs) would be 4*50 = 200 bytes.

It does not matter what is contained in the argument variable ProcessIDs. The function returns with ProcessesIDs as a pointer to an array that will contain the list of PID's. After the function is executed, you can go through the ProcessesIDs array and read each PID from each element.

The third argument is a pointer to a DWORD. The function returns the actual count of bytes that the function returned in the ProcessIDs array, so after the function is run you will have in cbNeeded the number of bytes in ProcessesIDs that contain the required data.

As an example, lets say there are 15 PID's. When EnumProcesses is run, it returns with 15 PIDs in the ProcessesIDs array and cbNeeded indicates that only 15 elements have been filled. It does so by returning 15 * 4 = 60 bytes, where 4 is the size of one element, a DWORD in bytes.

Now armed with the array of the PID's running in the system in ProcessesIDs, we can move to the next step, but before moving ahead you can make things easier if you store the number of PID's returned. Therefore, add in the line:

cProcesses = cbNeeded / sizeof(DWORD); 

The above code takes the size of a DWORD, 4 bytes, and divides it with the value in cbNeeded, which contains the total number of bytes returned to in array ProcessesIDs. This will give the total count of PID's stored in ProcessesIDs and is stored in cProcesses.

Now you need a loop to go through each PID in the array ProcessesIDs. For this you loop cProcesses number of times. Once in the loop, you need the handle to the PID that is in the element in ProcessesIDs[i]. And that is exactly where the OpenProcess function comes in.

HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 
                               FALSE, ProcessesIDs[i] );

The OpenProcess function also takes three arguments. The first indicates the kind of access you would like to have on that process. For a list of access rights and their meanings turn to Appendix A. The second is a BOOL value. If this value is TRUE, then the processes created by this process will inherit the handle. Since you do not need to create any other processes you can leave it as FALSE. The last argument is the PID to which you need a handle.

Once you put in all these inputs and call the function, the function returns the handle to the process, which is the stored in a variable called hProcess and is checked to make sure that hProcess is not NULL.

Now look at the last function that will get the name of the process!

GetModuleBaseName( hProcess, NULL, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) );

This function has four arguments, for three of which you have to provide the data and it returns one argument. The first argument provides the handle to the Process, that is a return value from OpenProcess. All you have to do is to use hProcess here.

For the second argument (related to modules and explained in detail in Part 2 of this tutorial) just enter NULL for now.

For the fourth argument, provide the the size of szProcessName in relation as to how many characters it can hold. Remember, anything returned more than the array size will be truncated. For this take the size of the whole array in bytes and divide it with the size of a TCHAR.

The third argument is the array to be returned from the function. It will contain the name of the Process. Viola…

And before the for loop ends, you print the process name and PID with this line:

_tprintf( TEXT("Processname = %s, PID = %u \n\n"), szProcessName, ProcessesIDs[i]);

Close the handle using OpenProcess as follows:

CloseHandle( hProcess ); 

B.Part II

The second part of this tutorial will enumerate more about processes and take a detailed view of modules in Processes.

C. Appendix

Appendix A:

The access rights can be:

Process name Description
PROCESS_ALL_ACCESS All possible access rights for a process object
PROCESS_CREATE_PROCESS Required to create a process
PROCESS_CREATE_THREAD Required to create a thread
PROCESS_DUP_HANDLE Required to duplicate a handle using DuplicateHandle
PROCESS_QUERY_INFORMATION Required to retrieve certain information about a process, such as its token, exit code, and priority class
PROCESS_QUERY_LIMITED_INFORMATION Required to retrieve certain information about a process
PROCESS_SET_QUOTA Required to set memory limits using SetProcessWorkingSetSize
PROCESS_SET_INFORMATION Required to set certain information about a process, such as its priority class
PROCESS_SUSPEND_RESUME Required to suspend or resume a process
PROCESS_TERMINATE Required to terminate a process using TerminateProcess
PROCESS_VM_OPERATION Required to perform an operation on the address space of a process
PROCESS_VM_READ Required to read memory in a process using ReadProcessMemory
PROCESS_VM_WRITE Required to write to memory in a process using WriteProcessMemory
SYNCHRONIZE Required to wait for the process to terminate using the wait functions

Appendix B

Setting up VC for the project.

First determine whether your system already has the following files: Psapi.h, Psapi.lib and Psapi.dll. If you do, go to the "setting VC" section of this Appendix.

If not, download the PSAPI SDK re-distributable for NT from the following link: http://www.microsoft.com/downloads/details.aspx?familyid=A55B6B43-E24F-4EA3-A93E-40C0EC4F68E5&displaylang=en. You may have to search for the SDK if the site has moved or is temporarily unavailable. The alternate sites are MSDN or Microsoft home.

Install this package, making a note of the directory where it is being installed. For example, if it installs in C:\Program Files\, then within the Program Files directory you will find another directory named Microsoft Platform SDK. This folder in turn would have two more folders named Lib and Include, both containing Psapi.lib and psapi.h respectively. The psapi.dll will be found inC:\WINNT\system32.

Setting Up VC

Open VC and go to File | New | Projects | Win32 Console Application. Enter a Project name and click OK.

Then click File | New | Files | C++ Source File. Enter a file name and click OK. The C++ source file opens up where you write your code.

  1. Paste the code from this document to the C++ source file you just created.
  2. Click Tools | Options | Directories. Under Show Directories for choose Include files.
  3. Double click on the empty path under the Directories label. (This space looks like a text area). After you double click, you will see an ellipse.
  4. Click the ellipse and browse to C:\Program Files\Microsoft Platform SDK\Include (or the path to the Include folder in Microsoft Platform SDK&).
  5. Click OK.
  6. Under Show Directories for select Library files and follow the earlier procedure to point to C:\Program Files\Microsoft Platform SDK\Lib.
  7. Click OK.
  8. To the left of the VC main window, under the File View tab, right click on Resource Files and click Add files to folder.
  9. On the browser that opens, point it to Psapi.lib.
  10. Click OK
  11. Press F5 to build
  12. Go to the command prompt and navigate to the project location.
  13. Execute the exe file and you will see all the PIDs and names of all the processes in your system.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here