Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Running multiple instances of Task Manager

4.84/5 (18 votes)
28 Feb 2010CPOL2 min read 1   1.8K  
A simple application that allows the user to run more then one instance of the Task Manager.

Introduction

As we all know, Task Manager, whatever the reasons are, does not allow the user to run multiple instances. This article explains a simple application that allows the user to start as many instances of the Task Manager as he/she wishes to. The technique described here is only valid for Windows 7 (and maybe Vista); XP's Task Manager uses a different technique.

Background

To do so, it employs simple tactics. When it begins execution, it tries to create a named kernel object (in this case, a mutex), and if there is already an object with the name Task Manager, it assumes that another instance of it is already active and it closes the current process.

The nme of the offending mutex is TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0.

Here's an example of how it can be done:

C++
if( ::CreateMutex( NULL, FALSE, 
  TEXT( "TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0" ) ) == NULL )
{
  if( ::GetLastError() == ERROR_ALREADY_EXISTS )
  {
    // task manager is already running - close current process
  }
}

// active task manage wasn't detected - continue

Code

But first, we must find out the full name of the mutex, which is in a format like this: \Sessions\<SESSION_ID>\BaseNamedObjects\TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0, and we can obtain the session ID using the ProcessIdToSessionId API call.

C++
DWORD sid;
if( !::ProcessIdToSessionId( ::GetCurrentProcessId(), &sid ) )
{
  wprintf( L"Unable to get session ID. Error code: %d\n", ::GetLastError() );
  return 2;
}

WCHAR mutexName[ MAX_PATH ];
mutexName[ 0 ] = 0;

// full name of the mutex (we want to mess only with session of the current user)
wcscat( mutexName, L"\\Sessions\\" );
_itow( sid, mutexName + wcslen( mutexName ), 10 );
wcscat( mutexName, L"\\BaseNamedObjects\\" );
wcscat( mutexName, MUTEX_NAME );

The basic idea is to destroy the mutex object before we start another instance of the Task Manager. To destroy it, we need to find all the handles to the object and close them. In the process, we need to use these undocumented API calls: NtQuerySystemInformation, NtQueryObject, and DuplicateHandle.

We use NtQuerySystemInformation to get the list of all open handles in the system, and then we iterate through the list, duplicating handles, to gain access to the object, and call NtQueryObject to get the name of the object. When we find the handle, we make a duplicate using the DuplicateHandle API call, but this time, we pass the DUPLICATE_CLOSE_SOURCE flag that instructs the system to close the original handle after the copy is made (effectively taking the ownership of the object), and immediately after that, we also close the new handle, and the end result is destroying the object.

C++
// Searches for handle to an object with specified name 
// Returns -1 if it cannot obtain list of handles, 
// 0 if there's no handles to the object or 
// 1 if the object if found and handle is closed
INT SeekAndDestory(WCHAR* handleName)
{
  INT found = 0;

  // get list of opened handles
  DWORD size = 0;
  PSYSTEM_HANDLE_INFORMATION handles = 
    (PSYSTEM_HANDLE_INFORMATION)malloc( sizeof( SYSTEM_HANDLE_INFORMATION ) );
  if( !NT_SUCCESS( ::pNtQuerySystemInformation(
    (SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handles,
    sizeof( SYSTEM_HANDLE_INFORMATION ), &size ) ) )
  {
    free( handles );

    if( size == 0 )
      return -1;

    DWORD newSize = size + sizeof(HANDLE) * 512;
    handles = (PSYSTEM_HANDLE_INFORMATION)malloc( newSize );
    if( !NT_SUCCESS( ::pNtQuerySystemInformation( 
      (SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handles,
      newSize, &size ) ) )
    {
      free( handles );
      return -1;
    }
  }

  for( DWORD i = 0; i < handles->dwCount; i++ )
  {
    HANDLE process = ::OpenProcess( PROCESS_ALL_ACCESS, FALSE, 
      handles->Handles[ i ].dwProcessId );
    if( process )
    {
      HANDLE myHandle;

      if( ::DuplicateHandle( process, 
        (HANDLE)handles->Handles[ i ].wValue, ::GetCurrentProcess(),
        &myHandle, DUPLICATE_SAME_ACCESS, FALSE, 0 ) )
      {
        // get object name
        PPUBLIC_OBJECT_TYPE_INFORMATION nameInfo =
          (PPUBLIC_OBJECT_TYPE_INFORMATION)malloc( 
          sizeof( PUBLIC_OBJECT_TYPE_INFORMATION ) );
        if( !NT_SUCCESS( pNtQueryObject( myHandle, ObjectNameInformation,
          nameInfo, sizeof( PUBLIC_OBJECT_TYPE_INFORMATION ), &size ) ) )
        {
          free( nameInfo );

          if( (int)size <= 0 )
          {
            ::CloseHandle( myHandle );
            continue;
          }

          DWORD newSize = size;
          nameInfo = (PPUBLIC_OBJECT_TYPE_INFORMATION)malloc( newSize );
          if( !NT_SUCCESS( pNtQueryObject( myHandle, ObjectNameInformation,
            nameInfo, newSize, &size ) ) )
          {
            ::CloseHandle( myHandle );
            continue;
          }
        }

        ::CloseHandle( myHandle );

        if( lstrcmp( handleName, nameInfo->TypeName.Buffer ) == 0 )
        {
          // take ownership of the handle
          // (copy handle and close original and then close the copy)
          if( ::DuplicateHandle( process, 
            (HANDLE)handles->Handles[ i ].wValue, ::GetCurrentProcess(),
            &myHandle, 0, FALSE,
            DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE ) )
          {
            ::CloseHandle( myHandle );
            found = 1;
          }
        }

        free( nameInfo );
      }

      ::CloseHandle( process );
    }
  }

  free( handles );
  return found;
}

Note: To get access to NtQuerySystemInformation and NtQueryObject, we need to use GetProcAddress because they are not available to the linker during the building process. The NtLib.h file contains the definitions required for using the undocumented API.

C++
pfnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
pfnNtQueryObject pNtQueryObject = NULL;

BOOL LoadNtLib()
{
  pNtQuerySystemInformation = (pfnNtQuerySystemInformation)::GetProcAddress(
    GetModuleHandle( TEXT( "ntdll" ) ), "NtQuerySystemInformation" );
  pNtQueryObject = (pfnNtQueryObject)::GetProcAddress(
    GetModuleHandle( TEXT( "ntdll" ) ), "NtQueryObject" );

  return pNtQuerySystemInformation && pNtQueryObject;
}

Some more information about NtQuerySystemInformation can be found in Naveen's article: Listing Used Files.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)