// -------------------------------------------------------------------------- // Module Name: GracefulTerminateApplication.cpp // // Copyright (c) 2000, Microsoft Corporation // // Class to manager terminating applications gracefully. // // History: 2000-10-27 vtan created // 2000-11-04 vtan split into separate file // -------------------------------------------------------------------------- #ifdef _X86_ #include "StandardHeader.h" #include "GracefulTerminateApplication.h" #include "KernelResources.h" #include "Thread.h" #include "WarningDialog.h" // -------------------------------------------------------------------------- // CProgressDialog // // Purpose: A class to manage displaying a progress dialog on a separate // thread if a certain period of time elapses. This is so that // in case the process doesn't terminate in a period of time // a dialog indicating wait is shown so the user is not left // staring at a blank screen. // // History: 2000-11-04 vtan created // -------------------------------------------------------------------------- class CProgressDialog : public CThread { private: CProgressDialog (void); public: CProgressDialog (CWarningDialog *pWarningDialog); virtual ~CProgressDialog (void); void SignalTerminated (void); protected: virtual DWORD Entry (void); private: CWarningDialog* _pWarningDialog; CEvent _event; }; // -------------------------------------------------------------------------- // CProgressDialog::CProgressDialog // // Arguments: // // Returns: // // Purpose: Constructor for CProgressDialog. Keeps a reference to the // given CWarningDialog. // // History: 2000-11-04 vtan created // -------------------------------------------------------------------------- CProgressDialog::CProgressDialog (CWarningDialog *pWarningDialog) : _pWarningDialog(NULL), _event(NULL) { if (IsCreated()) { pWarningDialog->AddRef(); _pWarningDialog = pWarningDialog; Resume(); } } // -------------------------------------------------------------------------- // CProgressDialog::~CProgressDialog // // Arguments: // // Returns: // // Purpose: Releases the CWarningDialog reference. // // History: 2000-11-04 vtan created // -------------------------------------------------------------------------- CProgressDialog::~CProgressDialog (void) { _pWarningDialog->Release(); _pWarningDialog = NULL; } // -------------------------------------------------------------------------- // CProgressDialog::SignalTerminated // // Arguments: // // Returns: // // Purpose: Signals the internal event that the process being watched is // termination. This is necessary because there is no handle to // the actual process to watch as it's kept on the server side // and not given to us the client. However, the result of the // termination is. Signaling this object will release the waiting // thread and cause it to exit. // // History: 2000-11-04 vtan created // -------------------------------------------------------------------------- void CProgressDialog::SignalTerminated (void) { TSTATUS(_event.Set()); } // -------------------------------------------------------------------------- // CProgressDialog::Entry // // Arguments: // // Returns: DWORD // // Purpose: Thread which waits for the internal event to be signaled. If // the event is signaled it will "cancel" the 3 second wait and // the thread will exit. Otherwise the wait times out and the // progress dialog is shown - waiting for termination. // // History: 2000-11-04 vtan created // -------------------------------------------------------------------------- DWORD CProgressDialog::Entry (void) { DWORD dwWaitResult; // Wait for the event to be signaled or for it to timeout. If signaled // then the process is terminated and no progress is required. Otherwise // prepare to show progress while the process is being terminated. if (NT_SUCCESS(_event.Wait(2000, &dwWaitResult)) && (WAIT_TIMEOUT == dwWaitResult)) { _pWarningDialog->ShowProgress(100, 7500); } return(0); } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::CGracefulTerminateApplication // // Arguments: // // Returns: // // Purpose: Constructor for CGracefulTerminateApplication. // // History: 2000-10-27 vtan created // -------------------------------------------------------------------------- CGracefulTerminateApplication::CGracefulTerminateApplication (void) : _dwProcessID(0), _fFoundWindow(false) { } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::~CGracefulTerminateApplication // // Arguments: // // Returns: // // Purpose: Destructor for CGracefulTerminateApplication. // // History: 2000-10-27 vtan created // -------------------------------------------------------------------------- CGracefulTerminateApplication::~CGracefulTerminateApplication (void) { } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::Terminate // // Arguments: dwProcessID = Process ID of process to terminate. // // Returns: // // Purpose: Walk the window list for top level windows that correspond // to this process ID and are visible. Close them. The callback // handles the work and this function returns the result in the // process exit code which the server examines. // // History: 2000-10-27 vtan created // -------------------------------------------------------------------------- void CGracefulTerminateApplication::Terminate (DWORD dwProcessID) { DWORD dwExitCode; _dwProcessID = dwProcessID; TBOOL(EnumWindows(EnumWindowsProc, reinterpret_cast(this))); if (_fFoundWindow) { dwExitCode = WAIT_WINDOWS_FOUND; } else { dwExitCode = NO_WINDOWS_FOUND; } ExitProcess(dwExitCode); } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::Prompt // // Arguments: hInstance = HINSTANCE of this DLL. // hProcess = Inherited handle to parent process. // // Returns: // // Purpose: Shows a prompt that handles termination of the parent of this // process. The parent is assumed to be a bad application type 1 // that caused this stub to be executed via the // "rundll32 shsvcs.dll,FUSCompatibilityEntry prompt" command. // Because there can only be a single instance of the type 1 // application running and the parent of this process hasn't // registered yet querying for this process by image name will // always find the correct process. // // History: 2000-11-03 vtan created // -------------------------------------------------------------------------- void CGracefulTerminateApplication::Prompt (HINSTANCE hInstance, HANDLE hProcess) { bool fTerminated; ULONG ulReturnLength; PROCESS_BASIC_INFORMATION processBasicInformation; // Read the parent's image name from the RTL_USER_PROCESS_PARAMETERS. fTerminated = false; if (hProcess != NULL) { if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &processBasicInformation, sizeof(processBasicInformation), &ulReturnLength))) { SIZE_T dwNumberOfBytesRead; PEB peb; if (ReadProcessMemory(hProcess, processBasicInformation.PebBaseAddress, &peb, sizeof(peb), &dwNumberOfBytesRead) != FALSE) { RTL_USER_PROCESS_PARAMETERS processParameters; if (ReadProcessMemory(hProcess, peb.ProcessParameters, &processParameters, sizeof(processParameters), &dwNumberOfBytesRead) != FALSE) { WCHAR *pszImageName; pszImageName = (WCHAR*)LocalAlloc(LMEM_FIXED, processParameters.ImagePathName.Length + sizeof(WCHAR)); // sizeof(WCHAR) for null terminator if (pszImageName != NULL) { if (ReadProcessMemory(hProcess, processParameters.ImagePathName.Buffer, pszImageName, processParameters.ImagePathName.Length, &dwNumberOfBytesRead) != FALSE) { pszImageName[processParameters.ImagePathName.Length / sizeof(WCHAR)] = L'\0'; // And show a prompt for this process. fTerminated = ShowPrompt(hInstance, pszImageName); } LocalFree(pszImageName); } } } } TBOOL(CloseHandle(hProcess)); } ExitProcess(fTerminated); } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::ShowPrompt // // Arguments: hInstance = HINSTANCE of this DLL. // pszImagename = Image name of process to terminate. // // Returns: bool // // Purpose: Shows the appropriate prompt for termination of the first // instance of a BAM type 1 process. If the current user does // not have administrator privileges then a "STOP" dialog is // shown that the user must get the other user to close the // program. Otherwise the "PROMPT" dialog is shown which gives // the user the option to terminate the process. // // If termination is requested and the termatinion fails the // another warning dialog to that effect is shown. // // History: 2000-11-03 vtan created // -------------------------------------------------------------------------- bool CGracefulTerminateApplication::ShowPrompt (HINSTANCE hInstance, const WCHAR *pszImageName) { bool fTerminated; ULONG ulConnectionInfoLength; HANDLE hPort; UNICODE_STRING portName; SECURITY_QUALITY_OF_SERVICE sqos; WCHAR szConnectionInfo[32]; fTerminated = false; RtlInitUnicodeString(&portName, FUS_PORT_NAME); sqos.Length = sizeof(sqos); sqos.ImpersonationLevel = SecurityImpersonation; sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; sqos.EffectiveOnly = TRUE; lstrcpyW(szConnectionInfo, FUS_CONNECTION_REQUEST); ulConnectionInfoLength = sizeof(szConnectionInfo); if (NT_SUCCESS(NtConnectPort(&hPort, &portName, &sqos, NULL, NULL, NULL, szConnectionInfo, &ulConnectionInfoLength))) { bool fCanTerminateFirstInstance; CWarningDialog *pWarningDialog; WCHAR szUser[256]; // Get the user's privilege level for this operation. This API also // returns the current user for the BAM type 1 process. fCanTerminateFirstInstance = CanTerminateFirstInstance(hPort, pszImageName, szUser, ARRAYSIZE(szUser)); pWarningDialog = new CWarningDialog(hInstance, NULL, pszImageName, szUser); if (pWarningDialog != NULL) { // Show the appropriate dialog based on the privilege level. if (pWarningDialog->ShowPrompt(fCanTerminateFirstInstance) == IDOK) { CProgressDialog *pProgressDialog; // Create a progress dialog object in case of delayed termination. // This will create the watcher thread. pProgressDialog = new CProgressDialog(pWarningDialog); if ((pProgressDialog != NULL) && !pProgressDialog->IsCreated()) { pProgressDialog->Release(); pProgressDialog = NULL; } // Attempt to terminate the process if requested by the user. fTerminated = TerminatedFirstInstance(hPort, pszImageName); // Once this function returns signal the event (in case the // thread is still waiting). If the thread is still waiting this // effectively cancels the dialog. Either way if the dialog is // shown then close it, wait for the thread to exit and release // the reference to destroy the object. if (pProgressDialog != NULL) { pProgressDialog->SignalTerminated(); pWarningDialog->CloseDialog(); pProgressDialog->WaitForCompletion(INFINITE); pProgressDialog->Release(); } // If there was some failure then let the user know. if (!fTerminated) { pWarningDialog->ShowFailure(); } } pWarningDialog->Release(); } TBOOL(CloseHandle(hPort)); } return(fTerminated); } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::CanTerminateFirstInstance // // Arguments: hPort = Port to server. // pszImageName = Image name of process to terminate. // pszUser = User of process (returned). // cchUser = Count of characters for buffer. // // Returns: bool // // Purpose: Asks the server whether the current user has privileges to // terminate the BAM type 1 process of the given image name // which is known to be running. The API returns whether the // operation is allowed and who the current user of the process // is. // // History: 2000-11-03 vtan created // -------------------------------------------------------------------------- bool CGracefulTerminateApplication::CanTerminateFirstInstance (HANDLE hPort, const WCHAR *pszImageName, WCHAR *pszUser, int cchUser) { bool fCanTerminate; fCanTerminate = false; if ((hPort != NULL) && (pszImageName != NULL)) { FUSAPI_PORT_MESSAGE portMessageIn, portMessageOut; ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_QUERYUSERPERMISSION; portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszImageName = pszImageName; portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchImageName = lstrlen(pszImageName) + sizeof('\0'); portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszUser = pszUser; portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchUser = cchUser; portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(FUSAPI_PORT_MESSAGE)); if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) && NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status)) { fCanTerminate = portMessageOut.apiBAM.apiSpecific.apiQueryUserPermission.out.fCanShutdownApplication; pszUser[cchUser - sizeof('\0')] = L'\0'; } else { pszUser[0] = L'\0'; } } return(fCanTerminate); } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::TerminatedFirstInstance // // Arguments: hPort = Port to server. // pszImageName = Image name to terminate. // // Returns: bool // // Purpose: Asks the server to terminate the first running instance of the // BAM type 1 process. // // History: 2000-11-03 vtan created // -------------------------------------------------------------------------- bool CGracefulTerminateApplication::TerminatedFirstInstance (HANDLE hPort, const WCHAR *pszImageName) { bool fTerminated; fTerminated = false; if (hPort != NULL) { FUSAPI_PORT_MESSAGE portMessageIn, portMessageOut; ZeroMemory(&portMessageIn, sizeof(portMessageIn)); ZeroMemory(&portMessageOut, sizeof(portMessageOut)); portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_TERMINATERUNNING; portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.pszImageName = pszImageName; portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.cchImageName = lstrlen(pszImageName) + sizeof('\0'); portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM); portMessageIn.portMessage.u1.s1.TotalLength = static_cast(sizeof(FUSAPI_PORT_MESSAGE)); if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) && NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status)) { fTerminated = portMessageOut.apiBAM.apiSpecific.apiTerminateRunning.out.fResult; } } return(fTerminated); } // -------------------------------------------------------------------------- // CGracefulTerminateApplication::EnumWindowsProc // // Arguments: See the platform SDK under EnumWindowsProc. // // Returns: See the platform SDK under EnumWindowsProc. // // Purpose: Top level window enumerator callback which compares the // process IDs of the windows and whether they are visible. If // there is a match on BOTH counts the a WM_CLOSE message is // posted to the window to allow a graceful termination. // // History: 2000-10-27 vtan created // -------------------------------------------------------------------------- BOOL CALLBACK CGracefulTerminateApplication::EnumWindowsProc (HWND hwnd, LPARAM lParam) { DWORD dwThreadID, dwProcessID; CGracefulTerminateApplication *pThis; pThis = reinterpret_cast(lParam); dwThreadID = GetWindowThreadProcessId(hwnd, &dwProcessID); if ((dwProcessID == pThis->_dwProcessID) && IsWindowVisible(hwnd)) { pThis->_fFoundWindow = true; TBOOL(PostMessage(hwnd, WM_CLOSE, 0, 0)); } return(TRUE); } #endif /* _X86_ */