| Index: src/installer-ca/close_application.cpp |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/src/installer-ca/close_application.cpp |
| @@ -0,0 +1,408 @@ |
| +//------------------------------------------------------------------------------------------------- |
| +// <copyright file="CloseApps.cpp" company="Outercurve Foundation"> |
| +// Copyright (c) 2004, Outercurve Foundation. |
| +// This software is released under Microsoft Reciprocal License (MS-RL). |
| +// The license and further copyright text can be found in the file |
| +// LICENSE.TXT at the root directory of the distribution. |
| +// </copyright> |
| +// |
| +// <summary> |
| +// Code to close applications via custom actions when the installer cannot. |
| +// </summary> |
| +//------------------------------------------------------------------------------------------------- |
| + |
| +#include "precomp.h" |
| + |
| +#define PROCESS_CLOSE_WAIT_TIME 5000 |
| + |
| +// WixCloseApplication Target Description Condition Attributes Sequence |
| + |
| +// structs |
| +LPCWSTR wzQUERY_CLOSEAPPS = L"SELECT `WixCloseApplication`, `Target`, `Description`, `Condition`, `Attributes`, `Property` FROM `WixCloseApplication` ORDER BY `Sequence`"; |
| +enum eQUERY_CLOSEAPPS { QCA_ID = 1, QCA_TARGET, QCA_DESCRIPTION, QCA_CONDITION, QCA_ATTRIBUTES, QCA_PROPERTY }; |
| + |
| +// CloseApplication.Attributes |
| +enum CLOSEAPP_ATTRIBUTES |
| +{ |
| + CLOSEAPP_ATTRIBUTE_NONE = 0, |
| + CLOSEAPP_ATTRIBUTE_CLOSEMESSAGE = 1, |
| + CLOSEAPP_ATTRIBUTE_REBOOTPROMPT = 2, |
| + CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE = 4, |
| +}; |
| + |
| + |
| +/****************************************************************** |
| + EnumCloseWindowsProc - callback function which sends WM_CLOSE if the |
| + current window matches the passed in process ID |
| + |
| +******************************************************************/ |
| +BOOL CALLBACK EnumCloseWindowsProc(HWND hwnd, LPARAM lParam) |
| +{ |
| + DWORD dwProcessId = 0; |
| + |
| + ::GetWindowThreadProcessId(hwnd, &dwProcessId); |
| + |
| + // check if the process Id is the one we're looking for |
| + if (dwProcessId != static_cast<DWORD>(lParam)) |
| + { |
| + return TRUE; |
| + } |
| + |
| + WcaLog(LOGMSG_VERBOSE, "Sending close message to process id 0x%x", dwProcessId); |
| + |
| + ::PostMessageW(hwnd, WM_CLOSE, 0, 0); |
| + |
| + WcaLog(LOGMSG_VERBOSE, "Result 0x%x", ::GetLastError()); |
| + |
| + // so we know we succeeded |
| + ::SetLastError(ERROR_SUCCESS); |
| + |
| + // A process may have more than one top-level window, continue searching through all windows |
| + return TRUE; |
| +} |
| + |
| +/****************************************************************** |
| + SendProcessCloseMessage - helper function to enumerate the top-level |
| + windows and send WM_CLOSE to all matching a process ID |
| + |
| +******************************************************************/ |
| +void SendProcessCloseMessage(DWORD dwProcessId) |
| +{ |
| + DWORD dwLastError; |
| + |
| + WcaLog(LOGMSG_VERBOSE, "Attempting to send close message to process id 0x%x", dwProcessId); |
| + |
| + if (::EnumWindows(EnumCloseWindowsProc, dwProcessId)) |
| + return; |
| + |
| + dwLastError = GetLastError(); |
| + if (dwLastError != ERROR_SUCCESS) |
| + { |
| + WcaLog(LOGMSG_VERBOSE, "CloseApp enumeration error: 0x%x", dwLastError); |
| + } |
| +} |
| + |
| +/****************************************************************** |
| + SendApplicationCloseMessage - helper function to iterate through the |
| + processes for the specified application and send all |
| + applicable process Ids a WM_CLOSE message |
| + |
| +******************************************************************/ |
| +void SendApplicationCloseMessage(__in LPCWSTR wzApplication) |
| +{ |
| + DWORD *prgProcessIds = NULL; |
| + DWORD cProcessIds = 0, iProcessId; |
| + HRESULT hr = S_OK; |
| + |
| + WcaLog(LOGMSG_VERBOSE, "Checking App: %ls ", wzApplication); |
| + |
| + hr = ProcFindAllIdsFromExeName(wzApplication, &prgProcessIds, &cProcessIds); |
| + |
| + if (SUCCEEDED(hr) && 0 < cProcessIds) |
| + { |
| + WcaLog(LOGMSG_VERBOSE, "App: %ls found running, %d processes, attempting to send close message.", wzApplication, cProcessIds); |
| + |
| + for (iProcessId = 0; iProcessId < cProcessIds; ++iProcessId) |
| + { |
| + SendProcessCloseMessage(prgProcessIds[iProcessId]); |
| + } |
| + |
| + ProcWaitForIds(prgProcessIds, cProcessIds, PROCESS_CLOSE_WAIT_TIME); |
| + } |
| + |
| + ReleaseMem(prgProcessIds); |
| +} |
| + |
| +/****************************************************************** |
| + SetRunningProcessProperty - helper function that sets the specified |
| + property if there are any instances of the specified executable |
| + running. Useful to show custom UI to ask for shutdown. |
| +******************************************************************/ |
| +void SetRunningProcessProperty( |
| + __in LPCWSTR wzApplication, |
| + __in LPCWSTR wzProperty |
| + ) |
| +{ |
| + DWORD *prgProcessIds = NULL; |
| + DWORD cProcessIds = 0; |
| + HRESULT hr = S_OK; |
| + |
| + WcaLog(LOGMSG_VERBOSE, "Checking App: %ls ", wzApplication); |
| + |
| + hr = ProcFindAllIdsFromExeName(wzApplication, &prgProcessIds, &cProcessIds); |
| + |
| + if (SUCCEEDED(hr) && 0 < cProcessIds) |
| + { |
| + WcaLog(LOGMSG_VERBOSE, "App: %ls found running, %d processes, setting '%ls' property.", wzApplication, cProcessIds, wzProperty); |
| + WcaSetIntProperty(wzProperty, cProcessIds); |
| + } |
| + |
| + ReleaseMem(prgProcessIds); |
| +} |
| + |
| +/****************************************************************** |
| + WixCloseApplications - entry point for WixCloseApplications Custom Action |
| + |
| + called as Type 1 CustomAction (binary DLL) from Windows Installer |
| + in InstallExecuteSequence before InstallFiles |
| +******************************************************************/ |
| +extern "C" UINT __stdcall abp_close_applications( |
| + __in MSIHANDLE hInstall |
| + ) |
| +{ |
| + //AssertSz(FALSE, "debug WixCloseApplications"); |
| + HRESULT hr = S_OK; |
| + UINT er = ERROR_SUCCESS; |
| + |
| + LPWSTR pwzData = NULL; |
| + LPWSTR pwzId = NULL; |
| + LPWSTR pwzTarget = NULL; |
| + LPWSTR pwzDescription = NULL; |
| + LPWSTR pwzCondition = NULL; |
| + LPWSTR pwzProperty = NULL; |
| + DWORD dwAttributes = 0; |
| + MSICONDITION condition = MSICONDITION_NONE; |
| + |
| + DWORD cCloseApps = 0; |
| + |
| + PMSIHANDLE hView = NULL; |
| + PMSIHANDLE hRec = NULL; |
| + MSIHANDLE hListboxTable = NULL; |
| + MSIHANDLE hListboxColumns = NULL; |
| + |
| + LPWSTR pwzCustomActionData = NULL; |
| + //DWORD cchCustomActionData = 0; |
| + |
| + // |
| + // initialize |
| + // |
| + hr = WcaInitialize(hInstall, "abp_close_applications"); |
| + ExitOnFailure(hr, "failed to initialize"); |
| + |
| + // |
| + // loop through all the objects to be secured |
| + // |
| + hr = WcaOpenExecuteView(wzQUERY_CLOSEAPPS, &hView); |
| + ExitOnFailure(hr, "failed to open view on abp_close_applications table"); |
| + while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) |
| + { |
| + hr = WcaGetRecordString(hRec, QCA_ID, &pwzId); |
| + ExitOnFailure(hr, "failed to get id from abp_close_applications table"); |
| + |
| + hr = WcaGetRecordString(hRec, QCA_CONDITION, &pwzCondition); |
| + ExitOnFailure(hr, "failed to get condition from abp_close_applications table"); |
| + |
| + if (pwzCondition && *pwzCondition) |
| + { |
| + condition = ::MsiEvaluateConditionW(hInstall, pwzCondition); |
| + if (MSICONDITION_ERROR == condition) |
| + { |
| + hr = E_INVALIDARG; |
| + ExitOnFailure1(hr, "failed to process condition for abp_close_applications '%ls'", pwzId); |
| + } |
| + else if (MSICONDITION_FALSE == condition) |
| + { |
| + continue; // skip processing this target |
| + } |
| + } |
| + |
| + hr = WcaGetRecordFormattedString(hRec, QCA_TARGET, &pwzTarget); |
| + ExitOnFailure(hr, "failed to get target from abp_close_applications table"); |
| + |
| + hr = WcaGetRecordFormattedString(hRec, QCA_DESCRIPTION, &pwzDescription); |
| + ExitOnFailure(hr, "failed to get description from abp_close_applications table"); |
| + |
| + hr = WcaGetRecordInteger(hRec, QCA_ATTRIBUTES, reinterpret_cast<int*>(&dwAttributes)); |
| + ExitOnFailure(hr, "failed to get attributes from abp_close_applications table"); |
| + |
| + hr = WcaGetRecordFormattedString(hRec, QCA_PROPERTY, &pwzProperty); |
| + ExitOnFailure(hr, "failed to get property from abp_close_applications table"); |
| + |
| + // |
| + // send WM_CLOSE to currently running applications |
| + // |
| + if (dwAttributes & CLOSEAPP_ATTRIBUTE_CLOSEMESSAGE) |
| + { |
| + SendApplicationCloseMessage(pwzTarget); |
| + } |
| + |
| + // |
| + // Pass the targets to the deferred action in case the app comes back |
| + // even if we close it now. |
| + // |
| + if ((dwAttributes & CLOSEAPP_ATTRIBUTE_REBOOTPROMPT) || (dwAttributes & CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE)) |
| + { |
| + hr = WcaWriteStringToCaData(pwzTarget, &pwzCustomActionData); |
| + ExitOnFailure(hr, "failed to add target data to CustomActionData"); |
| + |
| + hr = WcaWriteIntegerToCaData(dwAttributes, &pwzCustomActionData); |
| + ExitOnFailure(hr, "failed to add attribute data to CustomActionData"); |
| + } |
| + |
| + if (pwzProperty && *pwzProperty) |
| + { |
| + SetRunningProcessProperty(pwzTarget, pwzProperty); |
| + } |
| + |
| + ++cCloseApps; |
| + } |
| + |
| + // if we looped through all records all is well |
| + if (E_NOMOREITEMS == hr) |
| + { |
| + hr = S_OK; |
| + } |
| + ExitOnFailure(hr, "failed while looping through all apps to close"); |
| + |
| + // |
| + // Do the UI dance now. |
| + // |
| + /* |
| + |
| + TODO: Do this eventually |
| + |
| + if (cCloseApps) |
| + { |
| + while (TRUE) |
| + { |
| + for (DWORD i = 0; i < cCloseApps; ++i) |
| + { |
| + hr = WcaAddTempRecord(&hListboxTable, &hListboxColumns, L"ListBox", NULL, 0, 4, L"FileInUseProcess", i, target, description); |
| + if (FAILED(hr)) |
| + { |
| + } |
| + } |
| + } |
| + } |
| + */ |
| + |
| + // |
| + // schedule the custom action and add to progress bar |
| + // |
| + if (pwzCustomActionData && *pwzCustomActionData) |
| + { |
| + Assert(0 < cCloseApps); |
| + |
| + hr = WcaDoDeferredAction(PLATFORM_DECORATION(L"WixCloseApplicationsDeferred"), pwzCustomActionData, cCloseApps * COST_CLOSEAPP); |
| + ExitOnFailure(hr, "failed to schedule WixCloseApplicationsDeferred action"); |
| + } |
| + |
| +LExit: |
| + if (hListboxColumns) |
| + { |
| + ::MsiCloseHandle(hListboxColumns); |
| + } |
| + if (hListboxTable) |
| + { |
| + ::MsiCloseHandle(hListboxTable); |
| + } |
| + |
| + ReleaseStr(pwzCustomActionData); |
| + ReleaseStr(pwzData); |
| + ReleaseStr(pwzProperty); |
| + ReleaseStr(pwzCondition); |
| + ReleaseStr(pwzDescription); |
| + ReleaseStr(pwzTarget); |
| + ReleaseStr(pwzId); |
| + |
| + if (FAILED(hr)) |
| + er = ERROR_INSTALL_FAILURE; |
| + |
| + // TEST BEGIN |
| + // For testing, just fail. |
| + er = ERROR_INSTALL_FAILURE; |
| + // TEST END |
| + |
| + return WcaFinalize(er); |
| +} |
| + |
| + |
| +/****************************************************************** |
| + WixCloseApplicationsDeferred - entry point for |
| + WixCloseApplicationsDeferred Custom Action |
| + called as Type 1025 CustomAction |
| + (deferred binary DLL) |
| + |
| + NOTE: deferred CustomAction since it modifies the machine |
| + NOTE: CustomActionData == wzTarget\tdwAttributes\t... |
| +******************************************************************/ |
| +extern "C" UINT __stdcall WixCloseApplicationsDeferred( |
| + __in MSIHANDLE hInstall |
| + ) |
| +{ |
| +// AssertSz(FALSE, "debug WixCloseApplicationsDeferred"); |
| + HRESULT hr = S_OK; |
| + DWORD er = ERROR_SUCCESS; |
| + |
| + LPWSTR pwz = NULL; |
| + LPWSTR pwzData = NULL; |
| + LPWSTR pwzTarget = NULL; |
| + DWORD dwAttributes = 0; |
| + |
| + DWORD *prgProcessIds = NULL; |
| + DWORD cProcessIds = 0; |
| + |
| + // |
| + // initialize |
| + // |
| + hr = WcaInitialize(hInstall, "WixCloseApplicationsDeferred"); |
| + ExitOnFailure(hr, "failed to initialize"); |
| + |
| + hr = WcaGetProperty(L"CustomActionData", &pwzData); |
| + ExitOnFailure(hr, "failed to get CustomActionData"); |
| + |
| + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); |
| + |
| + pwz = pwzData; |
| + |
| + // |
| + // loop through all the passed in data |
| + // |
| + while (pwz && *pwz) |
| + { |
| + hr = WcaReadStringFromCaData(&pwz, &pwzTarget); |
| + ExitOnFailure(hr, "failed to process CustomActionData"); |
| + hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwAttributes)); |
| + ExitOnFailure(hr, "failed to processCustomActionData"); |
| + |
| + WcaLog(LOGMSG_VERBOSE, "Checking for App: %ls Attributes: %d", pwzTarget, dwAttributes); |
| + |
| + // |
| + // send WM_CLOSE to currently running applications |
| + // |
| + if (dwAttributes & CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE) |
| + { |
| + SendApplicationCloseMessage(pwzTarget); |
| + } |
| + |
| + // |
| + // If we find that an app that we need closed is still runing, require a |
| + // reboot and bail. Keep iterating through the list in case other apps set |
| + // CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE. |
| + // Since the close here happens async the process may still be running, |
| + // resulting in a false positive reboot message. |
| + // |
| + ProcFindAllIdsFromExeName(pwzTarget, &prgProcessIds, &cProcessIds); |
| + if ((0 < cProcessIds) && (dwAttributes & CLOSEAPP_ATTRIBUTE_REBOOTPROMPT)) |
| + { |
| + WcaLog(LOGMSG_VERBOSE, "App: %ls found running, requiring a reboot.", pwzTarget); |
| + |
| + WcaDeferredActionRequiresReboot(); |
| + } |
| + |
| + hr = WcaProgressMessage(COST_CLOSEAPP, FALSE); |
| + ExitOnFailure(hr, "failed to send progress message"); |
| + } |
| + |
| +LExit: |
| + ReleaseMem(prgProcessIds); |
| + |
| + ReleaseStr(pwzTarget); |
| + ReleaseStr(pwzData); |
| + |
| + if (FAILED(hr)) |
| + { |
| + er = ERROR_INSTALL_FAILURE; |
| + } |
| + return WcaFinalize(er); |
| +} |