Figure 3   CheezMan.c

/*
    CheezMan.c - (c) 1998, Microsoft Corporation. All Rights Reserved.
    
    1998 James M. Finnegan - Microsoft Systems Journal

    Cheezy Process (and 16-bit task) manager for Windows 2000, to demonstrate
    the use of PSAPI and VDMDBG in a manner that is similar to TASKMAN.EXE
*/
#include <windows.h>
#include <vdmdbg.h>
#include "psapi.h"
#include "resource.h"

VOID CenterWindow(HWND hWnd);
BOOL CALLBACK DlgProc(HWND hWnd,UINT uMessage,WPARAM wParam,LPARAM lParam);
void EnumerateProcesses();

HWND ghWnd;

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine,
                   int nCmdShow)
{
    static char szAppName[]="CheezMan";

    DialogBox(hInstance,szAppName,0,DlgProc); 

    return 0;
}

VOID CenterWindow(HWND hWnd)
{
    RECT rect;
    WORD wWidth,
         wHeight;
         
         
    GetWindowRect(hWnd,&rect);

    wWidth =GetSystemMetrics(SM_CXSCREEN);
    wHeight=GetSystemMetrics(SM_CYSCREEN);

    MoveWindow(hWnd,(wWidth/2)   - ((rect.right -  rect.left)/2),
                    (wHeight/2)  - ((rect.bottom - rect.top) /2),
                     rect.right  -   rect.left,
                     rect.bottom -   rect.top, 
                     FALSE);
}

BOOL CALLBACK DlgProc(HWND hWnd,UINT uMessage,WPARAM wParam,LPARAM lParam)
{
    static WORD wTimer;

    switch(uMessage)
    {
        case WM_INITDIALOG:
            ghWnd = hWnd;

            // Create a timer to refresh the dialog's contents every
            // 1000 milliseconds.
            wTimer = SetTimer(hWnd, 1, 1000, NULL); 
            CenterWindow(hWnd);
            PostMessage(hWnd,WM_TIMER, 0, 0L);
            return FALSE;
            break;

        case WM_COMMAND:
            switch(wParam)
            {
                case IDB_CLOSE:
                    PostMessage(hWnd,WM_CLOSE,0,0L);
                    break;
            }
            break;

        case WM_TIMER:
            // Clear the listbox
            SendDlgItemMessage(hWnd,IDC_LIST1,LB_RESETCONTENT,0,0L);
            // Refresh the process list
            EnumerateProcesses();
            break;
            
        case WM_CLOSE:
            KillTimer(hWnd,wTimer);
            EndDialog(hWnd, FALSE);
            break;

        default:
            return FALSE;
    }
    
    return TRUE;
}

/*
EnumerateWin16Processes - This is a callback function (referenced by 
                          VDMEnumTaskWOWEx below) that is called in turn
                          for each 16-bit Windows task that is executing in 
                          a Windows 2000 VDM.
*/
BOOL WINAPI EnumerateWin16Processes(DWORD dwThreadId, WORD hMod16, WORD hTask16,
                            PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined)
{
    char szTemp[80];

    // Indent the task name, to visually show that it is a "child" of the
    // process listed above it
    wsprintf(szTemp,"    %s",pszModName);
    SendDlgItemMessage(ghWnd,IDC_LIST1,LB_ADDSTRING,0,(LONG)(LPSTR)szTemp);

    // Return FALSE to continue enumeration
    return FALSE;
}

void PrintProcessName(DWORD processID)
{
    char szProcessName[MAX_PATH] = "unknown";
    char szTemp[80];

    // Get a handle to the passed-in process ID
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                                  FALSE, processID );

    // Get the process name
    if(hProcess)
    {
        HMODULE hMod;
        DWORD   cbNeeded;

        if(EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
        {
            GetModuleBaseName(hProcess, hMod, szProcessName, 
                              sizeof(szProcessName));
        }
    }

    // Print the process name and ID
    wsprintf(szTemp, "%s   (PID: %u)", szProcessName, processID );
    SendDlgItemMessage(ghWnd,IDC_LIST1,LB_ADDSTRING,0,(LONG)(LPSTR)szTemp);

    // If the process name is NTVDM.EXE, try walking it to see if it is
    // a WOW box.  The callback will not be called if NTVDM is not WOW
    if(!stricmp(szProcessName,"ntvdm.exe"))
    {
        // VDMEnumTaskWOWEx() is NOT documented in the VDMDBG.HLP
        // file -- but it is in VDMDBG.H.  Explanations there make its use
        // clear (and documented!)  Basically, you need modname and filename
        // as additional parameters in the callback.
        VDMEnumTaskWOWEx(processID, EnumerateWin16Processes, 0);
    }

    CloseHandle(hProcess);
}

/*
EnumerateProcesses - Enumerates all process IDs currently running within
                     a system.  The print function (above) will deal with 16-bit 
                     apps.
*/
void EnumerateProcesses()
{
    DWORD        aProcesses[1024],  // Array of process IDs
                 cbNeeded,          // Byte count returned...
                 cProcesses;        // The number of 'em obtained
    unsigned int i;

    // Get a list of all current PIDs running
    if(!EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded))
        return;

    // Get the count of PIDs in the array
    cProcesses = cbNeeded / sizeof(DWORD);

    // Print the name and process identifier for each process
    for(i = 0; i < cProcesses; i++)
        PrintProcessName(aProcesses[i]);
}

Figure 4   Process Structure APIs

API
Documented in Windows NT 4.0
Documented in Windows 2000
Examined in the Nerditorium
PsCreateSystemThread



PsGetCurrentProcess



PsGetCurrentProcessId



PsGetCurrentThread



PsGetCurrentThreadId



PsGetVersion



PsSetCreateProcessNotifyRoutine



PsSetCreateThreadNotifyRoutine



PsSetLoadImageNotifyRoutine



PsTerminateSystemThread




Figure 5   Callback Function Prototypes

typedef VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE)(
    IN HANDLE ParentId,
    IN HANDLE ProcessId,
    IN BOOLEAN Create
    );

typedef VOID (*PCREATE_THREAD_NOTIFY_ROUTINE)(
    IN HANDLE ProcessId,
    IN HANDLE ThreadId,
    IN BOOLEAN Create
    );

typedef VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)(
    IN PUNICODE_STRING FullImageName,
    IN HANDLE ProcessId, // pid into which image is being mapped
    IN PIMAGE_INFO ImageInfo
    );

Figure 6   ProcView.c

/*
  ProcView.C - 1998 James M. Finnegan - Microsoft Systems Journal
  Copyright (c)1998 Microsoft Corporation.  All Rights Reserved.
  This module implements the kernel calls required to utilize the 
  Process Structure APIs.

  ** Danger ** Don't try to unload this driver, since the Process Structure
  callbacks cannot be unregistered.  If the callbacks disappear, evil things
  will happen!
*/

#include "ntddk.h"
#include <stdio.h>

#define FILE_DEVICE_UNKNOWN             0x00000022
#define IOCTL_UNKNOWN_BASE              FILE_DEVICE_UNKNOWN

#define IOCTL_PROCVIEW_GET_PROCINFO     CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0800, 
    METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_PROCVIEW_GET_THREADINFO   CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0801, 
    METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_PROCVIEW_GET_IMAGEINFO    CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0802, 
    METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

// Boilerplate KM device functions...
void     ProcViewUnloadDriver(PDRIVER_OBJECT DriverObject);
NTSTATUS ProcViewDispatchCreateClose(IN PDEVICE_OBJECT DeviceObject, 
                                     IN PIRP Irp);
NTSTATUS ProcViewDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

// Our Process Structure callbacks!
VOID ProcViewProcessCallback(IN HANDLE ParentId, IN HANDLE ProcessId, 
                             IN BOOLEAN Create);
VOID ProcViewThreadCallback(IN HANDLE ParentId, IN HANDLE ProcessId, 
                            IN BOOLEAN Create);
VOID ProcViewImageCallback(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId,
                           IN PIMAGE_INFO ImageInfo);

// Structure for Process callback information
typedef struct _CallbackInfo
{
    HANDLE  ParentId;
    HANDLE  ProcessId;
    BOOLEAN Create;
}CALLBACK_INFO, *PCALLBACK_INFO;

// Structure for Thread callback information
typedef struct _CallbackThreadInfo
{
    HANDLE  ProcessId;
    HANDLE  ThreadId;
    BOOLEAN Create;
}CALLBACK_THREAD_INFO, *PCALLBACK_THREAD_INFO;

// Structure for image loading information
typedef struct _CallbackImageInfo
{
    UCHAR      ImageNameA[255];
    HANDLE     IProcessId;
    IMAGE_INFO ImageInfo;
}CALLBACK_IMAGE_INFO, *PCALLBACK_IMAGE_INFO;

// Private storage for us to squirrel things away...
typedef struct _DEVICE_EXTENSION 
{
    PDEVICE_OBJECT DeviceObject;
    HANDLE  ProcessHandle;
    PKEVENT ProcessEvent;
    HANDLE  ThreadHandle;
    PKEVENT ThreadEvent;
    HANDLE  ImageHandle;
    PKEVENT ImageEvent;

    HANDLE  PParentId;
    HANDLE  PProcessId;
    BOOLEAN PCreate;

    HANDLE  TProcessId;
    HANDLE  TThreadId;
    BOOLEAN TCreate;

    UCHAR   ImageNameA[255];
    HANDLE  IProcessId;
    IMAGE_INFO ImageInfo;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

PDEVICE_OBJECT gpDeviceObject;

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, 
                     IN PUNICODE_STRING RegistryPath)
{
    NTSTATUS        ntStatus;
    UNICODE_STRING  uszDriverString;
    UNICODE_STRING  uszDeviceString;
    UNICODE_STRING  uszProcessEventString,
                    uszThreadEventString,
                    uszImageEventString;

    PDEVICE_OBJECT    pDeviceObject;
    PDEVICE_EXTENSION extension;

    // Point uszDriverString at the driver name
    RtlInitUnicodeString(&uszDriverString, L"\\Device\\ProcView");

    // Create and initialize device object
    ntStatus = IoCreateDevice(DriverObject,
                              sizeof(DEVICE_EXTENSION),
                              &uszDriverString,
                              FILE_DEVICE_UNKNOWN,
                              0,
                              FALSE,
                              &pDeviceObject);

    if(ntStatus != STATUS_SUCCESS)
        return ntStatus;

    // Assign extension variable...
    extension = pDeviceObject->DeviceExtension;

    // Point uszDeviceString at the device name
    RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\ProcView");

    // Create symbolic link to the user-visible name
    ntStatus = IoCreateSymbolicLink(&uszDeviceString, &uszDriverString);

    if(ntStatus != STATUS_SUCCESS)
    {
        // Delete device object if not successful
        IoDeleteDevice(pDeviceObject);
        return ntStatus;
    }

    // Assign global pointer to the device object for use by the callback 
    // functions...
    gpDeviceObject = pDeviceObject;

    // Load structure to point to IRP handlers...
    DriverObject->DriverUnload = ProcViewUnloadDriver;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = ProcViewDispatchCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]  = ProcViewDispatchCreateClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ProcViewDispatchIoctl;

    // Create events for user-mode processes to monitor
    RtlInitUnicodeString(&uszProcessEventString, 
                         L"\\BaseNamedObjects\\ProcViewProcessEvent");
    extension->ProcessEvent = IoCreateNotificationEvent (&uszProcessEventString, 
                                                       &extension->ProcessHandle);

    RtlInitUnicodeString(&uszThreadEventString, 
                         L"\\BaseNamedObjects\\ProcViewThreadEvent");
    extension->ThreadEvent = IoCreateNotificationEvent (&uszThreadEventString, 
                                                        &extension->ThreadHandle);

    RtlInitUnicodeString(&uszImageEventString, 
                         L"\\BaseNamedObjects\\ProcViewImageEvent");
    extension->ImageEvent = IoCreateNotificationEvent (&uszImageEventString,
                                                       &extension->ImageHandle);

    // Clear 'em out!
    KeClearEvent(extension->ProcessEvent);
    KeClearEvent(extension->ThreadEvent);
    KeClearEvent(extension->ImageEvent);

    // Set up callback routines...
    ntStatus = PsSetCreateProcessNotifyRoutine(ProcViewProcessCallback, 0);
    ntStatus = PsSetCreateThreadNotifyRoutine(ProcViewThreadCallback);
    ntStatus = PsSetLoadImageNotifyRoutine(ProcViewImageCallback);

    // Return success
    return ntStatus;
}

NTSTATUS ProcViewDispatchCreateClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information=0;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return(STATUS_SUCCESS);
}

VOID ProcViewProcessCallback(IN HANDLE ParentId, IN HANDLE ProcessId, 
                             IN BOOLEAN Create)
{
    PDEVICE_EXTENSION extension;

    // Assign extension variable...
    extension = gpDeviceObject->DeviceExtension;

    // Slam current values into device extension.  User-mode apps will pick it
    // up using DeviceIoControl calls...
    extension->PParentId  = ParentId;
    extension->PProcessId = ProcessId;
    extension->PCreate    = Create;

    // Pulse the event so any user-mode apps listening will know something
    // interesting has happened.  Sadly, user-mode apps can't reset a KM
    // event, which is why we're pulsing it here...
    KeSetEvent(extension->ProcessEvent, 0, FALSE);
    KeClearEvent(extension->ProcessEvent);
}

VOID ProcViewThreadCallback(IN HANDLE ProcessId, IN HANDLE ThreadId, 
                            IN BOOLEAN Create)
{
    PDEVICE_EXTENSION extension;

    // Assign extension variable...
    extension = gpDeviceObject->DeviceExtension;

    // Slam current values into device extension.  User-mode apps will pick it
    // up using DeviceIoControl calls...
    extension->TProcessId = ProcessId;
    extension->TThreadId  = ThreadId;
    extension->TCreate    = Create;

    // Pulse the event so any user-mode apps listening will know something
    // interesting has happened.  Sadly, user-mode apps can't reset a KM
    // event, which is why we're pulsing it here...
    KeSetEvent(extension->ThreadEvent, 0, FALSE);
    KeClearEvent(extension->ThreadEvent);
}

VOID ProcViewImageCallback(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId,
                           IN PIMAGE_INFO ImageInfo)
{
    PDEVICE_EXTENSION extension;

    // Assign extension variable...
    extension = gpDeviceObject->DeviceExtension;

    // Slam current values into device extension.  User-mode apps will pick it
    // up using DeviceIoControl calls...
    extension->IProcessId = ProcessId;
    RtlCopyMemory(&extension->ImageInfo, ImageInfo, sizeof(IMAGE_INFO));
    
    // Hokey method for converting a KM Unicode string into something
    // easily printable by our user-mode app :-)
    sprintf(extension->ImageNameA,"%S",FullImageName->Buffer);

    // Pulse the event so any user-mode apps listening will know something
    // interesting has happened.  Sadly, user-mode apps can't reset a KM
    // event, which is why we're pulsing it here...
    KeSetEvent(extension->ImageEvent, 0, FALSE);
    KeClearEvent(extension->ImageEvent);
}

NTSTATUS ProcViewDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS              ntStatus = STATUS_UNSUCCESSFUL;
    PIO_STACK_LOCATION    irpStack  = IoGetCurrentIrpStackLocation(Irp);
    PDEVICE_EXTENSION     extension = DeviceObject->DeviceExtension;
    PCALLBACK_INFO        pCallbackInfo;
    PCALLBACK_THREAD_INFO pCallbackThreadInfo;
    PCALLBACK_IMAGE_INFO  pCallbackImageInfo;   

    // These IOCTL handlers simply rip the current data out of the device
    // extension structure.  I assume that the user-mode app is calling this
    // because the KM event was signaled in the callbacks above.  No provisions
    // are made here to ensure that the same data isn't picked up twice, or
    // that data isn't missed.  A more full-fledged impelmentation should build
    // a queue or list of events as they occur, so user-mode gets 'em all.
    switch(irpStack->Parameters.DeviceIoControl.IoControlCode)
    {
        case IOCTL_PROCVIEW_GET_PROCINFO:
            if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >= 
               sizeof(CALLBACK_INFO))
            {
                pCallbackInfo = Irp->AssociatedIrp.SystemBuffer;
                pCallbackInfo->ParentId  = extension->PParentId;
                pCallbackInfo->ProcessId = extension->PProcessId;
                pCallbackInfo->Create    = extension->PCreate;
                ntStatus = STATUS_SUCCESS;
            }
            break;

        case IOCTL_PROCVIEW_GET_THREADINFO:
            if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >= 
               sizeof(CALLBACK_INFO))

            {
                pCallbackThreadInfo = Irp->AssociatedIrp.SystemBuffer;
                pCallbackThreadInfo->ProcessId = extension->TProcessId;
                pCallbackThreadInfo->ThreadId  = extension->TThreadId;
                pCallbackThreadInfo->Create    = extension->TCreate;

                ntStatus = STATUS_SUCCESS;
            }
            break;

        case IOCTL_PROCVIEW_GET_IMAGEINFO:
            if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >= 
               sizeof(CALLBACK_IMAGE_INFO))
            {
                pCallbackImageInfo = Irp->AssociatedIrp.SystemBuffer;
                strcpy(pCallbackImageInfo->ImageNameA, extension->ImageNameA);
                pCallbackImageInfo->IProcessId = extension->IProcessId;
                RtlCopyMemory(&pCallbackImageInfo->ImageInfo, 
                              &extension->ImageInfo, sizeof(IMAGE_INFO));

                ntStatus = STATUS_SUCCESS;
            }
            break;

        default:
            break;
    }

    Irp->IoStatus.Status = ntStatus;
    
    // Set # of bytes to copy back to user-mode...
    if(ntStatus == STATUS_SUCCESS)
        Irp->IoStatus.Information = 
            irpStack->Parameters.DeviceIoControl.OutputBufferLength;
    else
        Irp->IoStatus.Information = 0;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return ntStatus;
}

void ProcViewUnloadDriver(IN PDRIVER_OBJECT DriverObject)
{
    UNICODE_STRING  uszDeviceString;

    IoDeleteDevice(DriverObject->DeviceObject);

    RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\ProcView");
    IoDeleteSymbolicLink(&uszDeviceString);
}