Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: src/installer-ca/close_application.cpp

Issue 11521026: initial custom action library, "hello, world" quality (Closed)
Patch Set: Created Sept. 3, 2013, 12:48 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
+}

Powered by Google App Engine
This is Rietveld