//  --------------------------------------------------------------------------
//  Module Name: BadApplicationManager.cpp
//
//  Copyright (c) 2000, Microsoft Corporation
//
//  Classes to manage bad applications in the fast user switching environment.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

#ifdef      _X86_

#include "StandardHeader.h"
#include "BadApplicationManager.h"

#include <wtsapi32.h>
#include <winsta.h>

#include "GracefulTerminateApplication.h"
#include "RestoreApplication.h"
#include "SingleThreadedExecution.h"
#include "StatusCode.h"
#include "TokenInformation.h"

//  --------------------------------------------------------------------------
//  CBadApplicationManager::INDEX_EVENT
//  CBadApplicationManager::INDEX_HANDLES
//  CBadApplicationManager::INDEX_RESERVED
//  CBadApplicationManager::s_szDefaultDesktop
//
//  Purpose:    Constant indicies into a HANDLE array passed to
//              user32!MsgWaitForMultipleObjects. The first handle is always
//              the synchronization event. Subsequent HANDLEs are built into
//              a static ARRAY passed with the dynamic amount.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

const int       CBadApplicationManager::INDEX_EVENT             =   0;
const int       CBadApplicationManager::INDEX_HANDLES           =   INDEX_EVENT + 1;
const int       CBadApplicationManager::INDEX_RESERVED          =   2;
const WCHAR     CBadApplicationManager::s_szDefaultDesktop[]    =   L"WinSta0\\Default";

//  --------------------------------------------------------------------------
//  CBadApplicationManager::CBadApplicationManager
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Constructor for CBadApplicationManager. This creates a thread
//              that watches HANDLEs in the bad application list. The watcher
//              knows when the offending process dies. It also creates a
//              synchronization event that is signalled when the array of
//              bad applications changes (is incremented). The thread
//              maintains removal cases.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

CBadApplicationManager::CBadApplicationManager (HINSTANCE hInstance) :
    CThread(),
    _hInstance(hInstance),
    _hModule(NULL),
    _atom(NULL),
    _hwnd(NULL),
    _fTerminateWatcherThread(false),
    _fRegisteredNotification(false),
    _dwSessionIDLastConnect(static_cast<DWORD>(-1)),
    _hTokenLastUser(NULL),
    _hEvent(NULL),
    _badApplications(sizeof(BAD_APPLICATION_INFO)),
    _restoreApplications()

{
    Resume();
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::~CBadApplicationManager
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Destructor for CBadApplicationManager. Releases any resources
//              used.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

CBadApplicationManager::~CBadApplicationManager (void)

{

    //  In case the token hasn't been released yet - release it.

    ReleaseHandle(_hTokenLastUser);
    Cleanup();
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Terminate
//
//  Arguments:  <none>
//
//  Returns:    NTSTATUS
//
//  Purpose:    Forces the watcher thread to terminate. Acquire the lock. Walk
//              the list of entries and release the HANDLE on the process
//              objects so they don't leak. Set the bool to terminate the
//              thread. Set the event to wake the thread up. Release the lock.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::Terminate (void)

{
    int                         i;
    CSingleThreadedExecution    listLock(_lock);

    for (i = _badApplications.GetCount() - 1; i >= 0; --i)
    {
        BAD_APPLICATION_INFO    badApplicationInfo;

        if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
        {
            TBOOL(CloseHandle(badApplicationInfo.hProcess));
        }
        _badApplications.Remove(i);
    }
    _fTerminateWatcherThread = true;
    return(_hEvent.Set());
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::QueryRunning
//
//  Arguments:  badApplication  =   Bad application identifier to query.
//              dwSessionID     =   Session ID of the request.
//
//  Returns:    bool
//
//  Purpose:    Queries the current running known bad applications list
//              looking for a match. Again because this typically runs on a
//              different thread to the watcher thread access to the list is
//              protected by a critical section.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

bool    CBadApplicationManager::QueryRunning (const CBadApplication& badApplication, DWORD dwSessionID)

{
    bool                        fResult;
    NTSTATUS                    status;
    int                         i;
    CSingleThreadedExecution    listLock(_lock);

    status = STATUS_SUCCESS;
    fResult = false;

    //  Loop looking for a match. This uses the overloaded operator ==.

    for (i = _badApplications.GetCount() - 1; !fResult && (i >= 0); --i)
    {
        BAD_APPLICATION_INFO    badApplicationInfo;

        status = _badApplications.Get(&badApplicationInfo, i);
        if (NT_SUCCESS(status))
        {

            //  Make sure the client is not in the same session as the running
            //  bad application. This API exists to prevent cross session instances.
            //  It's assumed that applications have their own mechanisms for multiple
            //  instances in the same session (or object name space).

            fResult = ((badApplicationInfo.dwSessionID != dwSessionID) &&
                       (badApplicationInfo.badApplication == badApplication));
        }
    }
    TSTATUS(status);
    return(fResult);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::RegisterRunning
//
//  Arguments:  badApplication  =   Bad application identifier to add.
//              hProcess        =   HANDLE to the process.
//
//  Returns:    NTSTATUS
//
//  Purpose:    Adds the given bad application to the known running list. The
//              process object is added as well so that when the process
//              terminates it can be cleaned up out of the list.
//
//              Access to the bad application list is serialized with a
//              critical section. This is important because the thread
//              watching for termination always run on a different thread to
//              the thread on which this function executes. Because they both
//              access the same member variables this must be protected with
//              a critical section.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::RegisterRunning (const CBadApplication& badApplication, HANDLE hProcess, BAM_TYPE bamType)

{
    NTSTATUS                    status;
    CSingleThreadedExecution    listLock(_lock);

    ASSERTMSG((bamType > BAM_TYPE_MINIMUM) && (bamType < BAM_TYPE_MAXIMUM), "Invalid BAM_TYPE value passed to CBadApplicationManager::AddRunning");

    //  Have we reached the maximum number of wait object allowed? If not
    //  then proceed to add this. Otherwise reject the call. This is a
    //  hard coded limit in the kernel so we abide by it.

    if (_badApplications.GetCount() < (MAXIMUM_WAIT_OBJECTS - INDEX_RESERVED))
    {
        BOOL                    fResult;
        BAD_APPLICATION_INFO    badApplicationInfo;

        //  Duplicate the HANDLE with SYNCHRONIZE access. That's
        //  all we need to call the wait function.

        fResult = DuplicateHandle(GetCurrentProcess(),
                                  hProcess,
                                  GetCurrentProcess(),
                                  &badApplicationInfo.hProcess,
                                  SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
                                  FALSE,
                                  0);
        if (fResult != FALSE)
        {
            PROCESS_SESSION_INFORMATION     processSessionInformation;
            ULONG                           ulReturnLength;

            //  Add the information to the list.

            badApplicationInfo.bamType = bamType;
            badApplicationInfo.badApplication = badApplication;
            status = NtQueryInformationProcess(badApplicationInfo.hProcess,
                                               ProcessSessionInformation,
                                               &processSessionInformation,
                                               sizeof(processSessionInformation),
                                               &ulReturnLength);
            if (NT_SUCCESS(status))
            {
                badApplicationInfo.dwSessionID = processSessionInformation.SessionId;
                status = _badApplications.Add(&badApplicationInfo);
                if (NT_SUCCESS(status))
                {
                    status = _hEvent.Set();
                }
            }
        }
        else
        {
            status = CStatusCode::StatusCodeOfLastError();
        }
    }
    else
    {
        status = STATUS_UNSUCCESSFUL;
    }
    return(status);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::QueryInformation
//
//  Arguments:  badApplication  =   Bad application identifier to query.
//              hProcess        =   Handle to running process.
//
//  Returns:    NTSTATUS
//
//  Purpose:    Finds the given application in the running bad application
//              list and returns a duplicated handle to the caller.
//
//  History:    2000-08-25  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::QueryInformation (const CBadApplication& badApplication, HANDLE& hProcess)

{
    NTSTATUS                    status;
    bool                        fResult;
    int                         i;
    CSingleThreadedExecution    listLock(_lock);

    //  Assume failure
    
    hProcess = NULL;
    status = STATUS_OBJECT_NAME_NOT_FOUND;

    fResult = false;

    //  Loop looking for a match. This uses the overloaded operator ==.

    for (i = _badApplications.GetCount() - 1; !fResult && (i >= 0); --i)
    {
        BAD_APPLICATION_INFO    badApplicationInfo;

        if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
        {

            //  Make sure the client is not in the same session as the running
            //  bad application. This API exists to prevent cross session instances.
            //  It's assumed that applications have their own mechanisms for multiple
            //  instances in the same session (or object name space).

            fResult = (badApplicationInfo.badApplication == badApplication);
            if (fResult)
            {
                if (DuplicateHandle(GetCurrentProcess(),
                                    badApplicationInfo.hProcess,
                                    GetCurrentProcess(),
                                    &hProcess,
                                    0,
                                    FALSE,
                                    DUPLICATE_SAME_ACCESS) != FALSE)
                {
                    status = STATUS_SUCCESS;
                }
                else
                {
                    status = CStatusCode::StatusCodeOfLastError();
                }
            }
        }
    }

    return(status);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::RequestSwitchUser
//
//  Arguments:  <none>
//
//  Returns:    NTSTATUS
//
//  Purpose:    Execute terminate of BAM_TYPE_SWITCH_USER. These appications
//              are really poorly behaved. A good example is a DVD player
//              which bypasses GDI and draws directly into the VGA stream.
//
//              Try to kill these and reject the request if it fails.
//
//  History:    2000-11-02  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::RequestSwitchUser (void)

{
    NTSTATUS    status;
    int         i;

    //  Walk the _badApplications list.

    status = STATUS_SUCCESS;
    _lock.Acquire();
    i = _badApplications.GetCount() - 1;
    while (NT_SUCCESS(status) && (i >= 0))
    {
        BAD_APPLICATION_INFO    badApplicationInfo;

        if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
        {

            //  Look for BAM_TYPE_SWITCH_USER processes. It doesn't matter
            //  what session ID is tagged. This process is getting terminated.

            if (badApplicationInfo.bamType == BAM_TYPE_SWITCH_USER)
            {

                //  In any case release the lock, kill the process
                //  remove it from the watch list. Then reset the
                //  index back to the end of the list. Make sure to
                //  account for the "--i;" instruction below by not
                //  decrementing by 1.

                _lock.Release();
                status = PerformTermination(badApplicationInfo.hProcess, false);
                _lock.Acquire();
                i = _badApplications.GetCount();
            }
        }
        --i;
    }
    _lock.Release();
    return(status);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::PerformTermination
//
//  Arguments:  hProcess        =   Handle to running process.
//
//  Returns:    NTSTATUS
//
//  Purpose:    Terminates the given process. This is a common routine used
//              by both the internal wait thread of this class as well as
//              externally by bad application server itself.
//
//  History:    2000-10-23  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::PerformTermination (HANDLE hProcess, bool fAllowForceTerminate)

{
    NTSTATUS    status;

    status = TerminateGracefully(hProcess);
    if (!NT_SUCCESS(status) && fAllowForceTerminate)
    {
        status = TerminateForcibly(hProcess);
    }
    return(status);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Entry
//
//  Arguments:  <none>
//
//  Returns:    DWORD
//
//  Purpose:    Watcher thread for process objects. This thread builds the
//              array of proces handles to wait on as well as including the
//              synchronization event that gets signaled by the Add member
//              function. When that event is signaled the wait is re-executed
//              with the new array of objects to wait on.
//
//              When a process object is signaled it is cleared out of the
//              known list to allow further creates to succeed.
//
//              Acquisition of the critical section is carefully placed in
//              this function so that the critical section is not held when
//              the wait call is made.
//
//              Added to this is a window and a message pump to enable
//              listening for session notifications from terminal server.
//
//  History:    2000-08-25  vtan        created
//              2000-10-23  vtan        added HWND message pump mechanism
//  --------------------------------------------------------------------------

DWORD   CBadApplicationManager::Entry (void)

{
    WNDCLASSEX  wndClassEx;

    //  Register this window class.

    ZeroMemory(&wndClassEx, sizeof(wndClassEx));
    wndClassEx.cbSize = sizeof(WNDCLASSEX);
    wndClassEx.lpfnWndProc = NotificationWindowProc;
    wndClassEx.hInstance = _hInstance;
    wndClassEx.lpszClassName = TEXT("BadApplicationNotificationWindowClass");
    _atom = RegisterClassEx(&wndClassEx);

    //  Create the notification window

    _hwnd = CreateWindow(MAKEINTRESOURCE(_atom),
                         TEXT("BadApplicationNotificationWindow"),
                         WS_OVERLAPPED,
                         0, 0,
                         0, 0,
                         NULL,
                         NULL,
                         _hInstance,
                         this);

    if (_hwnd != NULL)
    {
        _fRegisteredNotification = (WinStationRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd, NOTIFY_FOR_ALL_SESSIONS) != FALSE);
        if (!_fRegisteredNotification)
        {
            _hModule = LoadLibrary(TEXT("shsvcs.dll"));
            if (_hModule != NULL)
            {
                DWORD   dwThreadID;
                HANDLE  hThread;

                //  If the register fails then create a thread to wait on the event
                //  and then register onces it's available. If the thread cannot be
                //  created it's no biggy. The notification mechanism fails and the
                //  welcome screen isn't updated.

                AddRef();
                hThread = CreateThread(NULL,
                                       0,
                                       RegisterThreadProc,
                                       this,
                                       0,
                                       &dwThreadID);
                if (hThread != NULL)
                {
                    TBOOL(CloseHandle(hThread));
                }
                else
                {
                    Release();
                    TBOOL(FreeLibrary(_hModule));
                    _hModule = NULL;
                }
            }
        }
    }

    //  Acquire the lock. This is necessary because to fill the array of
    //  handles to wait on requires access to the internal list.

    _lock.Acquire();
    do
    {
        DWORD                   dwWaitResult;
        int                     i, iLimit;
        BAD_APPLICATION_INFO    badApplicationInfo;
        HANDLE                  hArray[MAXIMUM_WAIT_OBJECTS];

        ZeroMemory(&hArray, sizeof(hArray));
        hArray[INDEX_EVENT] = _hEvent;
        iLimit = _badApplications.GetCount();
        for (i = 0; i < iLimit; ++i)
        {
            if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
            {
                hArray[INDEX_HANDLES + i] = badApplicationInfo.hProcess;
            }
        }

        //  Release the lock before we enter the wait state.
        //  Wait on ANY of the objects to be signaled.

        _lock.Release();
        dwWaitResult = MsgWaitForMultipleObjects(INDEX_HANDLES + iLimit,
                                                 hArray,
                                                 FALSE,
                                                 INFINITE,
                                                 QS_ALLINPUT);
        ASSERTMSG(dwWaitResult != WAIT_FAILED, "WaitForMultipleObjects failed in CBadApplicationManager::Entry");

        //  We were woken up by an object being signaled. Is this the
        //  synchronization object?

        dwWaitResult -= WAIT_OBJECT_0;
        if (dwWaitResult == INDEX_EVENT)
        {

            //  Yes. Acquire the lock. Reset the synchronization event. It's
            //  important to acquire the lock before resetting the event because
            //  the Add function could have the lock and be adding to the list.
            //  Once the Add function releases the lock it cannot signal the event.
            //  Otherwise we could reset the event during the Add function adding
            //  a new object and this would be missed.

            _lock.Acquire();
            TSTATUS(_hEvent.Reset());
        }

        //  No. Is this a message that requires dispatching as part of the
        //  message pump?

        else if (dwWaitResult == WAIT_OBJECT_0 + INDEX_HANDLES + static_cast<DWORD>(iLimit))
        {

            //  Yes. Remove the message from the message queue and dispatch it.

            MSG     msg;

            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE)
            {
                (BOOL)TranslateMessage(&msg);
                (LRESULT)DispatchMessage(&msg);
            }
            _lock.Acquire();
        }
        else
        {

            //  No. One of the bad applications we are watching has terminated
            //  and its proces object is now signaled. Go to the correct index
            //  in the array. Acquire the lock. Close the HANDLE. It's not needed
            //  anymore. Then remove the entry from the list.

            dwWaitResult -= INDEX_HANDLES;
            _lock.Acquire();
            if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, dwWaitResult)))
            {
                TBOOL(CloseHandle(badApplicationInfo.hProcess));
            }
            TSTATUS(_badApplications.Remove(dwWaitResult));
        }

        //  At this point we still hold the lock. This is important because the top
        //  of the loop expects the lock to be held to build the HANDLE array.

    } while (!_fTerminateWatcherThread);

    //  Clean up stuff that happened on this thread.

    Cleanup();

    //  If we here then the thread is being terminated for some reason.
    //  Release the lock. It doesn't matter what happens now anyway.

    _lock.Release();
    return(0);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::TerminateForcibly
//
//  Arguments:  hProcess    =   Process to terminate.
//
//  Returns:    NTSTATUS
//
//  Purpose:    Inject a user mode thread into the process which calls
//              kernel32!ExitProcess. If the thread injection fails then fall
//              back to kernel32!TerminatProcess to force in.
//
//  History:    2000-10-27  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::TerminateForcibly (HANDLE hProcess)

{
    NTSTATUS    status;
    HANDLE      hProcessTerminate;

    //  Duplicate the process handle and request all the access required
    //  to create a remote thread in the process.

    if (DuplicateHandle(GetCurrentProcess(),
                        hProcess,
                        GetCurrentProcess(),
                        &hProcessTerminate,
                        SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
                        FALSE,
                        0) != FALSE)
    {
        DWORD   dwWaitResult;
        HANDLE  hThread, hWaitArray[2];

        //  Go and create the remote thread that immediately turns
        //  around and calls kernel32!ExitProcess. This allows
        //  a clean process shutdown to occur. If this times out
        //  then kill the process with terminate process.

        status = RtlCreateUserThread(hProcessTerminate,
                                     NULL,
                                     FALSE,
                                     0,
                                     0,
                                     0,
                                     reinterpret_cast<PUSER_THREAD_START_ROUTINE>(ExitProcess),
                                     NULL,
                                     &hThread,
                                     NULL);
        if (NT_SUCCESS(status))
        {

            hWaitArray[0] = hThread;
            hWaitArray[1] = hProcessTerminate;
            dwWaitResult = WaitForMultipleObjects(ARRAYSIZE(hWaitArray),
                                                  hWaitArray,
                                                  TRUE,
                                                  5000);
            TBOOL(CloseHandle(hThread));
            if (dwWaitResult != WAIT_TIMEOUT)
            {
                status = STATUS_SUCCESS;
            }
            else
            {
                status = STATUS_TIMEOUT;
            }
        }
        if (status != STATUS_SUCCESS)
        {
            if (TerminateProcess(hProcessTerminate, 0) != FALSE)
            {
                status = STATUS_SUCCESS;
            }
            else
            {
                status = CStatusCode::StatusCodeOfLastError();
            }
        }
        TBOOL(CloseHandle(hProcessTerminate));
    }
    else
    {
        status = CStatusCode::StatusCodeOfLastError();
    }
    return(status);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::TerminateGracefully
//
//  Arguments:  hProcess    =   Process to terminate.
//
//  Returns:    NTSTATUS
//
//  Purpose:    Creates a rundll32 process on the session of the target
//              process in WinSta0\Default which will re-enter this dll and
//              call the "terminate" functionality. This allows the process to
//              walk the window list corresponding to that session and send
//              those windows close messages and wait for graceful
//              termination.
//
//  History:    2000-10-24  vtan        created
//  --------------------------------------------------------------------------

NTSTATUS    CBadApplicationManager::TerminateGracefully (HANDLE hProcess)

{
    NTSTATUS                    status;
    ULONG                       ulReturnLength;
    PROCESS_BASIC_INFORMATION   processBasicInformation;

    status = NtQueryInformationProcess(hProcess,
                                       ProcessBasicInformation,
                                       &processBasicInformation,
                                       sizeof(processBasicInformation),
                                       &ulReturnLength);
    if (NT_SUCCESS(status))
    {
        HANDLE  hToken;

        if (OpenProcessToken(hProcess,
                             TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
                             &hToken) != FALSE)
        {
            STARTUPINFOW            startupInfo;
            PROCESS_INFORMATION     processInformation;
            WCHAR                   szCommandLine[MAX_PATH];

            ZeroMemory(&startupInfo, sizeof(startupInfo));
            ZeroMemory(&processInformation, sizeof(processInformation));
            startupInfo.cb = sizeof(startupInfo);
            startupInfo.lpDesktop = const_cast<WCHAR*>(s_szDefaultDesktop);
            wsprintfW(szCommandLine, L"rundll32 shsvcs.dll,FUSCompatibilityEntry terminate %d", static_cast<DWORD>(processBasicInformation.UniqueProcessId));
            if (CreateProcessAsUserW(hToken,
                                     NULL,
                                     szCommandLine,
                                     NULL,
                                     NULL,
                                     FALSE,
                                     0,
                                     NULL,
                                     NULL,
                                     &startupInfo,
                                     &processInformation) != FALSE)
            {
                DWORD   dwWaitResult;
                HANDLE  hArray[2];

                //  Assume that this whole thing failed.

                status = STATUS_UNSUCCESSFUL;
                TBOOL(CloseHandle(processInformation.hThread));

                //  Wait on both process objects. If the process to be terminated
                //  is signaled then the rundll32 stub did its job. If the rundll32
                //  stub is signaled then find out what its exit code is and either
                //  continue waiting on the process to be terminated or return back
                //  a code to the caller indicating success or failure. Failure
                //  forces the process to be terminated abruptly.

                hArray[0] = hProcess;
                hArray[1] = processInformation.hProcess;
                dwWaitResult = WaitForMultipleObjects(ARRAYSIZE(hArray),
                                                      hArray,
                                                      FALSE,
                                                      10000);

                //  If the process to be terminated is signaled then we're done.

                if (dwWaitResult == WAIT_OBJECT_0)
                {
                    status = STATUS_SUCCESS;
                }

                //  If the rundll32 stub is signaled then find out what it found.

                else if (dwWaitResult == WAIT_OBJECT_0 + 1)
                {
                    DWORD   dwExitCode;

                    dwExitCode = STILL_ACTIVE;
                    if (GetExitCodeProcess(processInformation.hProcess, &dwExitCode) != FALSE)
                    {
                        ASSERTMSG((dwExitCode == CGracefulTerminateApplication::NO_WINDOWS_FOUND) || (dwExitCode == CGracefulTerminateApplication::WAIT_WINDOWS_FOUND), "Unexpected process exit code in CBadApplicationManager::TerminateGracefully");

                        //  If the rundll32 stub says it found some windows then
                        //  wait for the process to terminate itself.

                        if (dwExitCode == CGracefulTerminateApplication::WAIT_WINDOWS_FOUND)
                        {

                            //  If the process terminates within the timeout period
                            //  then we're done.

                            if (WaitForSingleObject(hProcess, 10000) == WAIT_OBJECT_0)
                            {
                                status = STATUS_SUCCESS;
                            }
                        }
                    }
                }
                TBOOL(CloseHandle(processInformation.hProcess));
            }
            else
            {
                status = CStatusCode::StatusCodeOfLastError();
            }
            TBOOL(CloseHandle(hToken));
        }
        else
        {
            status = CStatusCode::StatusCodeOfLastError();
        }
    }
    return(status);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Cleanup
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Releases used resources in the class. Used by both the
//              constructor and the thread - whoever wins.
//
//  History:    2000-12-12  vtan        created
//  --------------------------------------------------------------------------

void    CBadApplicationManager::Cleanup (void)

{
    if (_fRegisteredNotification)
    {
        (BOOL)WinStationUnRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd);
        _fRegisteredNotification = false;
    }
    if (_hwnd != NULL)
    {
        TBOOL(DestroyWindow(_hwnd));
        _hwnd = NULL;
    }
    if (_atom != 0)
    {
        TBOOL(UnregisterClass(MAKEINTRESOURCE(_atom), _hInstance));
        _atom = 0;
    }
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Handle_Logon
//
//  Arguments:  <none>
//
//  Returns:    <none>
//
//  Purpose:    Nothing at present.
//
//  History:    2000-10-24  vtan        created
//  --------------------------------------------------------------------------

void    CBadApplicationManager::Handle_Logon (void)

{
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Handle_Logoff
//
//  Arguments:  dwSessionID     =   Session ID that is logging off.
//
//  Returns:    <none>
//
//  Purpose:    Remove any restore processes we have in the list. The user
//              is logging off so they shouldn't come back. Releases the last
//              user to actively connect to the machine.
//
//  History:    2000-10-24  vtan        created
//  --------------------------------------------------------------------------

void    CBadApplicationManager::Handle_Logoff (DWORD dwSessionID)

{
    int                         i;
    CSingleThreadedExecution    listLock(_lock);

    for (i = _restoreApplications.GetCount() - 1; i >= 0; --i)
    {
        CRestoreApplication     *pRestoreApplication;

        pRestoreApplication = static_cast<CRestoreApplication*>(_restoreApplications.Get(i));
        if ((pRestoreApplication != NULL) &&
            pRestoreApplication->IsEqualSessionID(dwSessionID))
        {
            TSTATUS(_restoreApplications.Remove(i));
        }
    }
    ReleaseHandle(_hTokenLastUser);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Handle_Connect
//
//  Arguments:  dwSessionID     =   Session ID connecting.
//              hToken          =   Handle to token of user connecting.
//
//  Returns:    <none>
//
//  Purpose:    Handles BAM3. This is the save for restoration all processes
//              that use resources that aren't easily shared and restore all
//              processes that were saved which aren't easily shared.
//
//              It's optimized for not closing the processes of the same user
//              should that user re-connect. This allows the screen saver to
//              kick in and return to welcome without killing the user's
//              processes unnecessarily.
//
//              Also handles BAM4.
//
//  History:    2000-10-24  vtan        created
//  --------------------------------------------------------------------------

void    CBadApplicationManager::Handle_Connect (DWORD dwSessionID, HANDLE hToken)

{
    if ((_hTokenLastUser != NULL) && (hToken != NULL))
    {
        PSID                pSIDLastUser, pSIDCurrentUser;
        CTokenInformation   tokenLastUser(_hTokenLastUser);
        CTokenInformation   tokenCurrentUser(hToken);

        pSIDLastUser = tokenLastUser.GetUserSID();
        pSIDCurrentUser = tokenCurrentUser.GetUserSID();
        if ((pSIDLastUser != NULL) && (pSIDCurrentUser != NULL) && !EqualSid(pSIDLastUser, pSIDCurrentUser))
        {
            int                     i;
            DWORD                   dwSessionIDMatch;
            ULONG                   ulReturnLength;
            CRestoreApplication     *pRestoreApplication;

            if (NT_SUCCESS(NtQueryInformationToken(_hTokenLastUser,
                                                   TokenSessionId,
                                                   &dwSessionIDMatch,
                                                   sizeof(dwSessionIDMatch),
                                                   &ulReturnLength)))
            {

                //  Walk the _badApplications list.

                _lock.Acquire();
                i = _badApplications.GetCount() - 1;
                while (i >= 0)
                {
                    BAD_APPLICATION_INFO    badApplicationInfo;

                    if (NT_SUCCESS(_badApplications.Get(&badApplicationInfo, i)))
                    {
                        bool    fTerminateProcess;

                        fTerminateProcess = false;

                        //  Look for BAM_TYPE_SWITCH_TO_NEW_USER_WITH_RESTORE processes
                        //  which have token session IDs that match the _hTokenLastUser
                        //  session ID. These processes must be terminated and added to
                        //  a list to be restarted on reconnection.

                        if ((badApplicationInfo.bamType == BAM_TYPE_SWITCH_TO_NEW_USER_WITH_RESTORE) &&
                            (badApplicationInfo.dwSessionID == dwSessionIDMatch))
                        {
                            pRestoreApplication = new CRestoreApplication;
                            if (pRestoreApplication != NULL)
                            {
                                if (NT_SUCCESS(pRestoreApplication->GetInformation(badApplicationInfo.hProcess)))
                                {
                                    TSTATUS(_restoreApplications.Add(pRestoreApplication));
                                    fTerminateProcess = true;
                                }
                                pRestoreApplication->Release();
                            }
                        }

                        //  Look for BAM_TYPE_SWITCH_TO_NEW_USER (even though this is
                        //  a connect/reconnect). Always kill these processes.

                        if (badApplicationInfo.bamType == BAM_TYPE_SWITCH_TO_NEW_USER)
                        {
                            fTerminateProcess = true;
                        }
                        if (fTerminateProcess)
                        {

                            //  In any case release the lock, kill the process
                            //  remove it from the watch list. Then reset the
                            //  index back to the end of the list. Make sure to
                            //  account for the "--i;" instruction below by not
                            //  decrementing by 1.

                            _lock.Release();
                            TSTATUS(PerformTermination(badApplicationInfo.hProcess, true));
                            _lock.Acquire();
                            TBOOL(CloseHandle(badApplicationInfo.hProcess));
                            TSTATUS(_badApplications.Remove(i));
                            i = _badApplications.GetCount();
                        }
                    }
                    --i;
                }
                _lock.Release();
            }

            //  Now walk the restore list looking for matches against the
            //  connecting session ID. Restore these processes.

            _lock.Acquire();
            i = _restoreApplications.GetCount() - 1;
            while (i >= 0)
            {
                pRestoreApplication = static_cast<CRestoreApplication*>(_restoreApplications.Get(i));
                if ((pRestoreApplication != NULL) &&
                    pRestoreApplication->IsEqualSessionID(dwSessionID))
                {
                    HANDLE  hProcess;

                    _lock.Release();
                    if (NT_SUCCESS(pRestoreApplication->Restore(&hProcess)))
                    {
                        CBadApplication     badApplication(pRestoreApplication->GetCommandLine());

                        TBOOL(CloseHandle(hProcess));
                    }
                    _lock.Acquire();
                    TSTATUS(_restoreApplications.Remove(i));
                    i = _restoreApplications.GetCount();
                }
                --i;
            }
            _lock.Release();
        }
    }
    if (hToken != NULL)
    {
        _dwSessionIDLastConnect = static_cast<DWORD>(-1);
    }
    else
    {
        _dwSessionIDLastConnect = dwSessionID;
    }
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Handle_Disconnect
//
//  Arguments:  dwSessionID     =   Session ID that is disconnecting.
//              hToken          =   Token of the user disconnecting.
//
//  Returns:    <none>
//
//  Purpose:    If the session isn't the same as the last connected session
//              then release the last user token and save the current one.
//
//  History:    2000-10-24  vtan        created
//  --------------------------------------------------------------------------

void    CBadApplicationManager::Handle_Disconnect (DWORD dwSessionID, HANDLE hToken)

{
    if (_dwSessionIDLastConnect != dwSessionID)
    {
        ReleaseHandle(_hTokenLastUser);
        if (hToken != NULL)
        {
            TBOOL(DuplicateHandle(GetCurrentProcess(),
                                  hToken,
                                  GetCurrentProcess(),
                                  &_hTokenLastUser,
                                  0,
                                  FALSE,
                                  DUPLICATE_SAME_ACCESS));
        }
    }
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::Handle_WM_WTSSESSION_CHANGE
//
//  Arguments:  wParam  =   Type of session change.
//              lParam  =   Pointer to WTSSESSION_NOTIFICATION struct.
//
//  Returns:    LRESULT
//
//  Purpose:    Handles WM_WTSSESSION_CHANGE messages.
//
//  History:    2000-10-23  vtan        created
//  --------------------------------------------------------------------------

LRESULT     CBadApplicationManager::Handle_WM_WTSSESSION_CHANGE (WPARAM wParam, LPARAM lParam)

{
    ULONG                       ulReturnLength;
    WINSTATIONUSERTOKEN         winStationUserToken;

    winStationUserToken.ProcessId = reinterpret_cast<HANDLE>(GetCurrentProcessId());
    winStationUserToken.ThreadId = reinterpret_cast<HANDLE>(GetCurrentThreadId());
    winStationUserToken.UserToken = NULL;
    (BOOLEAN)WinStationQueryInformation(SERVERNAME_CURRENT,
                                        lParam,
                                        WinStationUserToken,
                                        &winStationUserToken,
                                        sizeof(winStationUserToken),
                                        &ulReturnLength);
    switch (wParam)
    {
        case WTS_SESSION_LOGOFF:
            Handle_Logoff(lParam);
            break;
        case WTS_SESSION_LOGON:
            Handle_Logon();
            //  Fall thru to connect case.
        case WTS_CONSOLE_CONNECT:
        case WTS_REMOTE_CONNECT:
            Handle_Connect(lParam, winStationUserToken.UserToken);
            break;
        case WTS_CONSOLE_DISCONNECT:
        case WTS_REMOTE_DISCONNECT:
            Handle_Disconnect(lParam, winStationUserToken.UserToken);
            break;
        default:
            break;
    }
    if (winStationUserToken.UserToken != NULL)
    {
        TBOOL(CloseHandle(winStationUserToken.UserToken));
    }
    return(1);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::NotificationWindowProc
//
//  Arguments:  See the platform SDK under WindowProc.
//
//  Returns:    LRESULT
//
//  Purpose:    Handles messages for the Notification window.
//
//  History:    2000-10-23  vtan        created
//  --------------------------------------------------------------------------

LRESULT CALLBACK    CBadApplicationManager::NotificationWindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{
    LRESULT                 lResult;
    CBadApplicationManager  *pThis;

    pThis = reinterpret_cast<CBadApplicationManager*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
    switch (uMsg)
    {
        case WM_CREATE:
        {
            CREATESTRUCT    *pCreateStruct;

            pCreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);
            (LONG_PTR)SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCreateStruct->lpCreateParams));
            lResult = 0;
            break;
        }
        case WM_WTSSESSION_CHANGE:
            lResult = pThis->Handle_WM_WTSSESSION_CHANGE(wParam, lParam);
            break;
        default:
            lResult = DefWindowProc(hwnd, uMsg, wParam, lParam);
            break;
    }
    return(lResult);
}

//  --------------------------------------------------------------------------
//  CBadApplicationManager::RegisterThreadProc
//
//  Arguments:  pParameter  =   Object pointer.
//
//  Returns:    DWORD
//
//  Purpose:    Opens the TermSrvReadyEvent and waits on it. Once ready it
//              registers for a notifications.
//
//  History:    2000-10-23  vtan        created
//  --------------------------------------------------------------------------

DWORD   WINAPI  CBadApplicationManager::RegisterThreadProc (void *pParameter)

{
    int                     iCounter;
    HANDLE                  hTermSrvReadyEvent;
    HMODULE                 hModule;
    CBadApplicationManager  *pThis;

    pThis = reinterpret_cast<CBadApplicationManager*>(pParameter);
    hModule = pThis->_hModule;
    ASSERTMSG(hModule != NULL, "NULL HMODULE in CBadApplicationManager::RegisterThreadProc");
    iCounter = 0;
    hTermSrvReadyEvent = OpenEvent(SYNCHRONIZE,
                                   FALSE,
                                   TEXT("TermSrvReadyEvent"));
    while ((hTermSrvReadyEvent == NULL) && (iCounter < 60))
    {
        ++iCounter;
        Sleep(1000);
        hTermSrvReadyEvent = OpenEvent(SYNCHRONIZE,
                                       FALSE,
                                       TEXT("TermSrvReadyEvent"));
    }
    if (hTermSrvReadyEvent != NULL)
    {
        if (WaitForSingleObject(hTermSrvReadyEvent, 60000) == WAIT_OBJECT_0)
        {
            pThis->_fRegisteredNotification = (WinStationRegisterConsoleNotification(SERVERNAME_CURRENT, pThis->_hwnd, NOTIFY_FOR_ALL_SESSIONS) != FALSE);
        }
        TBOOL(CloseHandle(hTermSrvReadyEvent));
    }
    pThis->Release();
    FreeLibraryAndExitThread(hModule, 0);
}

#endif  /*  _X86_   */