summaryrefslogtreecommitdiff
path: root/src/ext/Util/ca/CloseApps.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext/Util/ca/CloseApps.cpp')
-rw-r--r--src/ext/Util/ca/CloseApps.cpp568
1 files changed, 568 insertions, 0 deletions
diff --git a/src/ext/Util/ca/CloseApps.cpp b/src/ext/Util/ca/CloseApps.cpp
new file mode 100644
index 00000000..d4256c43
--- /dev/null
+++ b/src/ext/Util/ca/CloseApps.cpp
@@ -0,0 +1,568 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5#define DEFAULT_PROCESS_EXIT_WAIT_TIME 5000
6
7// structs
8LPCWSTR wzQUERY_CLOSEAPPS = L"SELECT `Wix4CloseApplication`, `Target`, `Description`, `Condition`, `Attributes`, `Property`, `TerminateExitCode`, `Timeout` FROM `Wix4CloseApplication` ORDER BY `Sequence`";
9enum eQUERY_CLOSEAPPS { QCA_ID = 1, QCA_TARGET, QCA_DESCRIPTION, QCA_CONDITION, QCA_ATTRIBUTES, QCA_PROPERTY, QCA_TERMINATEEXITCODE, QCA_TIMEOUT };
10
11// CloseApplication.Attributes
12enum CLOSEAPP_ATTRIBUTES
13{
14 CLOSEAPP_ATTRIBUTE_NONE = 0x0,
15 CLOSEAPP_ATTRIBUTE_CLOSEMESSAGE = 0x1,
16 CLOSEAPP_ATTRIBUTE_REBOOTPROMPT = 0x2,
17 CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE = 0x4,
18 CLOSEAPP_ATTRIBUTE_ENDSESSIONMESSAGE = 0x8,
19 CLOSEAPP_ATTRIBUTE_ELEVATEDENDSESSIONMESSAGE = 0x10,
20 CLOSEAPP_ATTRIBUTE_TERMINATEPROCESS = 0x20,
21 CLOSEAPP_ATTRIBUTE_PROMPTTOCONTINUE = 0x40,
22};
23
24struct PROCESS_AND_MESSAGE
25{
26 DWORD dwProcessId;
27 DWORD dwMessageId;
28 DWORD dwTimeout;
29};
30
31
32/******************************************************************
33 EnumWindowsProc - callback function which sends message if the
34 current window matches the passed in process ID
35
36******************************************************************/
37BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
38{
39 PROCESS_AND_MESSAGE* pPM = reinterpret_cast<PROCESS_AND_MESSAGE*>(lParam);
40 DWORD dwProcessId = 0;
41 DWORD_PTR dwResult = 0;
42 BOOL fQueryEndSession = WM_QUERYENDSESSION == pPM->dwMessageId;
43 BOOL fContinueWindowsInProcess = TRUE; // assume we will send message to all top-level windows in a process.
44
45 ::GetWindowThreadProcessId(hwnd, &dwProcessId);
46
47 // check if the process Id is the one we're looking for
48 if (dwProcessId != pPM->dwProcessId)
49 {
50 return TRUE;
51 }
52
53 WcaLog(LOGMSG_VERBOSE, "Sending message to process id 0x%x", dwProcessId);
54
55 if (::SendMessageTimeoutW(hwnd, pPM->dwMessageId, 0, fQueryEndSession ? ENDSESSION_CLOSEAPP : 0, SMTO_BLOCK, pPM->dwTimeout, &dwResult))
56 {
57 WcaLog(LOGMSG_VERBOSE, "Result 0x%x", dwResult);
58
59 if (fQueryEndSession)
60 {
61 // If application said it was okay to close, do that.
62 if (dwResult)
63 {
64 ::SendMessageTimeoutW(hwnd, WM_ENDSESSION, TRUE, ENDSESSION_CLOSEAPP, SMTO_BLOCK, pPM->dwTimeout, &dwResult);
65 }
66 else // application said don't try to close it, so don't bother sending messages to any other top-level windows.
67 {
68 fContinueWindowsInProcess = FALSE;
69 }
70 }
71 }
72 else // log result message.
73 {
74 WcaLog(LOGMSG_VERBOSE, "Failed to send message id: %u, error: 0x%x", pPM->dwMessageId, ::GetLastError());
75 }
76
77 // so we know we succeeded
78 ::SetLastError(ERROR_SUCCESS);
79
80 return fContinueWindowsInProcess;
81}
82
83/******************************************************************
84 PromptToContinue - displays the prompt if the application is still
85 running.
86
87******************************************************************/
88static HRESULT PromptToContinue(
89 __in_z LPCWSTR wzApplication,
90 __in_z LPCWSTR wzPrompt
91 )
92{
93 HRESULT hr = S_OK;
94 UINT er = ERROR_SUCCESS;
95 PMSIHANDLE hRecMessage = NULL;
96 DWORD *prgProcessIds = NULL;
97 DWORD cProcessIds = 0;
98
99 hRecMessage = ::MsiCreateRecord(1);
100 ExitOnNull(hRecMessage, hr, E_OUTOFMEMORY, "Failed to create record for prompt.");
101
102 er = ::MsiRecordSetStringW(hRecMessage, 0, wzPrompt);
103 ExitOnWin32Error(er, hr, "Failed to set prompt record field string");
104
105 do
106 {
107 hr = ProcFindAllIdsFromExeName(wzApplication, &prgProcessIds, &cProcessIds);
108 if (SUCCEEDED(hr) && 0 < cProcessIds)
109 {
110 er = WcaProcessMessage(static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_WARNING | MB_ABORTRETRYIGNORE | MB_DEFBUTTON3 | MB_ICONWARNING), hRecMessage);
111 if (IDABORT == er)
112 {
113 hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
114 }
115 else if (IDRETRY == er)
116 {
117 hr = S_FALSE;
118 }
119 else if (IDIGNORE == er)
120 {
121 hr = S_OK;
122 }
123 else
124 {
125 ExitOnWin32Error(er, hr, "Unexpected return value from prompt to continue.");
126 }
127 }
128
129 ReleaseNullMem(prgProcessIds);
130 cProcessIds = 0;
131 } while (S_FALSE == hr);
132
133LExit:
134 ReleaseMem(prgProcessIds);
135 return hr;
136}
137
138/******************************************************************
139 SendProcessMessage - helper function to enumerate the top-level
140 windows and send to all matching a process ID.
141
142******************************************************************/
143void SendProcessMessage(
144 __in DWORD dwProcessId,
145 __in DWORD dwMessageId,
146 __in DWORD dwTimeout
147 )
148{
149 WcaLog(LOGMSG_VERBOSE, "Attempting to send process id 0x%x message id: %u", dwProcessId, dwMessageId);
150
151 PROCESS_AND_MESSAGE pm = { };
152 pm.dwProcessId = dwProcessId;
153 pm.dwMessageId = dwMessageId;
154 pm.dwTimeout = dwTimeout;
155
156 if (!::EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&pm)))
157 {
158 DWORD dwLastError = ::GetLastError();
159 if (ERROR_SUCCESS != dwLastError)
160 {
161 WcaLog(LOGMSG_VERBOSE, "CloseApp enumeration error: 0x%x", dwLastError);
162 }
163 }
164}
165
166/******************************************************************
167 SendApplicationMessage - helper function to iterate through the
168 processes for the specified application and send all
169 applicable process Ids a message and give them time to process
170 the message.
171
172******************************************************************/
173void SendApplicationMessage(
174 __in LPCWSTR wzApplication,
175 __in DWORD dwMessageId,
176 __in DWORD dwTimeout
177 )
178{
179 DWORD *prgProcessIds = NULL;
180 DWORD cProcessIds = 0, iProcessId;
181 HRESULT hr = S_OK;
182
183 WcaLog(LOGMSG_VERBOSE, "Checking App: %ls ", wzApplication);
184
185 hr = ProcFindAllIdsFromExeName(wzApplication, &prgProcessIds, &cProcessIds);
186
187 if (SUCCEEDED(hr) && 0 < cProcessIds)
188 {
189 WcaLog(LOGMSG_VERBOSE, "App: %ls found running, %d processes, attempting to send message.", wzApplication, cProcessIds);
190
191 for (iProcessId = 0; iProcessId < cProcessIds; ++iProcessId)
192 {
193 SendProcessMessage(prgProcessIds[iProcessId], dwMessageId, dwTimeout);
194 }
195
196 ProcWaitForIds(prgProcessIds, cProcessIds, dwTimeout);
197 }
198
199 ReleaseMem(prgProcessIds);
200}
201
202/******************************************************************
203 SetRunningProcessProperty - helper function that sets the specified
204 property if there are any instances of the specified executable
205 running. Useful to show custom UI to ask for shutdown.
206******************************************************************/
207void SetRunningProcessProperty(
208 __in LPCWSTR wzApplication,
209 __in LPCWSTR wzProperty
210 )
211{
212 DWORD *prgProcessIds = NULL;
213 DWORD cProcessIds = 0;
214 HRESULT hr = S_OK;
215
216 WcaLog(LOGMSG_VERBOSE, "Checking App: %ls ", wzApplication);
217
218 hr = ProcFindAllIdsFromExeName(wzApplication, &prgProcessIds, &cProcessIds);
219
220 if (SUCCEEDED(hr) && 0 < cProcessIds)
221 {
222 WcaLog(LOGMSG_VERBOSE, "App: %ls found running, %d processes, setting '%ls' property.", wzApplication, cProcessIds, wzProperty);
223 WcaSetIntProperty(wzProperty, cProcessIds);
224 }
225
226 ReleaseMem(prgProcessIds);
227}
228
229/******************************************************************
230 TerminateProcesses - helper function that kills the provided set of
231 process ids such that they return a particular exit code.
232******************************************************************/
233void TerminateProcesses(
234 __in_ecount(cProcessIds) DWORD rgdwProcessIds[],
235 __in DWORD cProcessIds,
236 __in DWORD dwExitCode
237 )
238{
239 for (DWORD i = 0; i < cProcessIds; ++i)
240 {
241 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, rgdwProcessIds[i]);
242 if (hProcess)
243 {
244 ::TerminateProcess(hProcess, dwExitCode);
245 ::CloseHandle(hProcess);
246 }
247 }
248}
249
250/******************************************************************
251 WixCloseApplications - entry point for WixCloseApplications Custom Action
252
253 called as Type 1 CustomAction (binary DLL) from Windows Installer
254 in InstallExecuteSequence before InstallFiles
255******************************************************************/
256extern "C" UINT __stdcall WixCloseApplications(
257 __in MSIHANDLE hInstall
258 )
259{
260 //AssertSz(FALSE, "debug WixCloseApplications");
261 HRESULT hr = S_OK;
262 UINT er = ERROR_SUCCESS;
263
264 LPWSTR pwzData = NULL;
265 LPWSTR pwzId = NULL;
266 LPWSTR pwzTarget = NULL;
267 LPWSTR pwzDescription = NULL;
268 LPWSTR pwzCondition = NULL;
269 LPWSTR pwzProperty = NULL;
270 DWORD dwAttributes = 0;
271 DWORD dwTimeout = 0;
272 DWORD dwTerminateExitCode = 0;
273 MSICONDITION condition = MSICONDITION_NONE;
274
275 DWORD cCloseApps = 0;
276
277 PMSIHANDLE hView = NULL;
278 PMSIHANDLE hRec = NULL;
279 MSIHANDLE hListboxTable = NULL;
280 MSIHANDLE hListboxColumns = NULL;
281
282 LPWSTR pwzCustomActionData = NULL;
283 //DWORD cchCustomActionData = 0;
284
285 //
286 // initialize
287 //
288 hr = WcaInitialize(hInstall, "WixCloseApplications");
289 ExitOnFailure(hr, "failed to initialize");
290
291 //
292 // loop through all the objects to be secured
293 //
294 hr = WcaOpenExecuteView(wzQUERY_CLOSEAPPS, &hView);
295 ExitOnFailure(hr, "failed to open view on Wix4CloseApplication table");
296 while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
297 {
298 hr = WcaGetRecordString(hRec, QCA_ID, &pwzId);
299 ExitOnFailure(hr, "failed to get id from Wix4CloseApplication table");
300
301 hr = WcaGetRecordString(hRec, QCA_CONDITION, &pwzCondition);
302 ExitOnFailure(hr, "failed to get condition from Wix4CloseApplication table");
303
304 if (pwzCondition && *pwzCondition)
305 {
306 condition = ::MsiEvaluateConditionW(hInstall, pwzCondition);
307 if (MSICONDITION_ERROR == condition)
308 {
309 hr = E_INVALIDARG;
310 ExitOnFailure(hr, "failed to process condition for Wix4CloseApplication '%ls'", pwzId);
311 }
312 else if (MSICONDITION_FALSE == condition)
313 {
314 continue; // skip processing this target
315 }
316 }
317
318 hr = WcaGetRecordFormattedString(hRec, QCA_TARGET, &pwzTarget);
319 ExitOnFailure(hr, "failed to get target from Wix4CloseApplication table");
320
321 hr = WcaGetRecordFormattedString(hRec, QCA_DESCRIPTION, &pwzDescription);
322 ExitOnFailure(hr, "failed to get description from Wix4CloseApplication table");
323
324 hr = WcaGetRecordInteger(hRec, QCA_ATTRIBUTES, reinterpret_cast<int*>(&dwAttributes));
325 ExitOnFailure(hr, "failed to get attributes from Wix4CloseApplication table");
326
327 hr = WcaGetRecordFormattedString(hRec, QCA_PROPERTY, &pwzProperty);
328 ExitOnFailure(hr, "failed to get property from Wix4CloseApplication table");
329
330 hr = WcaGetRecordInteger(hRec, QCA_TERMINATEEXITCODE, reinterpret_cast<int*>(&dwTerminateExitCode));
331 if (S_FALSE == hr)
332 {
333 dwTerminateExitCode = 0;
334 hr = S_OK;
335 }
336 ExitOnFailure(hr, "failed to get terminate exit-code from Wix4CloseApplication table");
337
338 hr = WcaGetRecordInteger(hRec, QCA_TIMEOUT, reinterpret_cast<int*>(&dwTimeout));
339 if (S_FALSE == hr)
340 {
341 dwTimeout = DEFAULT_PROCESS_EXIT_WAIT_TIME;
342 hr = S_OK;
343 }
344 ExitOnFailure(hr, "failed to get timeout from Wix4CloseApplication table");
345
346 // Before trying any changes to the machine, prompt if requested.
347 if (dwAttributes & CLOSEAPP_ATTRIBUTE_PROMPTTOCONTINUE)
348 {
349 hr = PromptToContinue(pwzTarget, pwzDescription ? pwzDescription : L"");
350 if (HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hr)
351 {
352 // Skip error message if user canceled.
353 ExitFunction();
354 }
355 ExitOnFailure(hr, "Failure while prompting user to continue to close application.");
356 }
357
358 //
359 // send WM_CLOSE or WM_QUERYENDSESSION to currently running applications
360 //
361 if (dwAttributes & CLOSEAPP_ATTRIBUTE_CLOSEMESSAGE)
362 {
363 SendApplicationMessage(pwzTarget, WM_CLOSE, dwTimeout);
364 }
365
366 if (dwAttributes & CLOSEAPP_ATTRIBUTE_ENDSESSIONMESSAGE)
367 {
368 SendApplicationMessage(pwzTarget, WM_QUERYENDSESSION, dwTimeout);
369 }
370
371 //
372 // Pass the targets to the deferred action in case the app comes back
373 // even if we close it now.
374 //
375 if (dwAttributes & (CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE | CLOSEAPP_ATTRIBUTE_ELEVATEDENDSESSIONMESSAGE | CLOSEAPP_ATTRIBUTE_REBOOTPROMPT | CLOSEAPP_ATTRIBUTE_TERMINATEPROCESS))
376 {
377 hr = WcaWriteStringToCaData(pwzTarget, &pwzCustomActionData);
378 ExitOnFailure(hr, "failed to add target data to CustomActionData");
379
380 hr = WcaWriteIntegerToCaData(dwAttributes, &pwzCustomActionData);
381 ExitOnFailure(hr, "failed to add attribute data to CustomActionData");
382
383 hr = WcaWriteIntegerToCaData(dwTimeout, &pwzCustomActionData);
384 ExitOnFailure(hr, "failed to add timeout data to CustomActionData");
385
386 hr = WcaWriteIntegerToCaData(dwTerminateExitCode, &pwzCustomActionData);
387 ExitOnFailure(hr, "failed to add timeout data to CustomActionData");
388 }
389
390 if (pwzProperty && *pwzProperty)
391 {
392 SetRunningProcessProperty(pwzTarget, pwzProperty);
393 }
394
395 ++cCloseApps;
396 }
397
398 // if we looped through all records all is well
399 if (E_NOMOREITEMS == hr)
400 {
401 hr = S_OK;
402 }
403 ExitOnFailure(hr, "failed while looping through all apps to close");
404
405 //
406 // Do the UI dance now.
407 //
408 /*
409
410 TODO: Do this eventually
411
412 if (cCloseApps)
413 {
414 while (TRUE)
415 {
416 for (DWORD i = 0; i < cCloseApps; ++i)
417 {
418 hr = WcaAddTempRecord(&hListboxTable, &hListboxColumns, L"ListBox", NULL, 0, 4, L"FileInUseProcess", i, target, description);
419 if (FAILED(hr))
420 {
421 }
422 }
423 }
424 }
425 */
426
427 //
428 // schedule the custom action and add to progress bar
429 //
430 if (pwzCustomActionData && *pwzCustomActionData)
431 {
432 Assert(0 < cCloseApps);
433
434 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CloseApplicationsDeferred"), pwzCustomActionData, cCloseApps * COST_CLOSEAPP);
435 ExitOnFailure(hr, "failed to schedule CloseApplicationsDeferred action");
436 }
437
438LExit:
439 if (hListboxColumns)
440 {
441 ::MsiCloseHandle(hListboxColumns);
442 }
443 if (hListboxTable)
444 {
445 ::MsiCloseHandle(hListboxTable);
446 }
447
448 ReleaseStr(pwzCustomActionData);
449 ReleaseStr(pwzData);
450 ReleaseStr(pwzProperty);
451 ReleaseStr(pwzCondition);
452 ReleaseStr(pwzDescription);
453 ReleaseStr(pwzTarget);
454 ReleaseStr(pwzId);
455
456 if (FAILED(hr))
457 {
458 er = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) == hr ? ERROR_INSTALL_USEREXIT : ERROR_INSTALL_FAILURE;
459 }
460 return WcaFinalize(er);
461}
462
463
464/******************************************************************
465 WixCloseApplicationsDeferred - entry point for
466 WixCloseApplicationsDeferred Custom Action
467 called as Type 1025 CustomAction
468 (deferred binary DLL)
469
470 NOTE: deferred CustomAction since it modifies the machine
471 NOTE: CustomActionData == wzTarget\tdwAttributes\tdwTimeout\tdwTerminateExitCode\t...
472******************************************************************/
473extern "C" UINT __stdcall WixCloseApplicationsDeferred(
474 __in MSIHANDLE hInstall
475 )
476{
477 //AssertSz(FALSE, "debug WixCloseApplicationsDeferred");
478 HRESULT hr = S_OK;
479 DWORD er = ERROR_SUCCESS;
480
481 LPWSTR pwz = NULL;
482 LPWSTR pwzData = NULL;
483 LPWSTR pwzTarget = NULL;
484 DWORD dwAttributes = 0;
485 DWORD dwTimeout = 0;
486 DWORD dwTerminateExitCode = 0;
487
488 DWORD *prgProcessIds = NULL;
489 DWORD cProcessIds = 0;
490
491 //
492 // initialize
493 //
494 hr = WcaInitialize(hInstall, "WixCloseApplicationsDeferred");
495 ExitOnFailure(hr, "failed to initialize");
496
497 hr = WcaGetProperty(L"CustomActionData", &pwzData);
498 ExitOnFailure(hr, "failed to get CustomActionData");
499
500 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
501
502 pwz = pwzData;
503
504 //
505 // loop through all the passed in data
506 //
507 while (pwz && *pwz)
508 {
509 hr = WcaReadStringFromCaData(&pwz, &pwzTarget);
510 ExitOnFailure(hr, "failed to process target from CustomActionData");
511
512 hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwAttributes));
513 ExitOnFailure(hr, "failed to process attributes from CustomActionData");
514
515 hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwTimeout));
516 ExitOnFailure(hr, "failed to process timeout from CustomActionData");
517
518 hr = WcaReadIntegerFromCaData(&pwz, reinterpret_cast<int*>(&dwTerminateExitCode));
519 ExitOnFailure(hr, "failed to process terminate exit code from CustomActionData");
520
521 WcaLog(LOGMSG_VERBOSE, "Checking for App: %ls Attributes: %d", pwzTarget, dwAttributes);
522
523 //
524 // send WM_CLOSE or WM_QUERYENDSESSION to currently running applications
525 //
526 if (dwAttributes & CLOSEAPP_ATTRIBUTE_ELEVATEDCLOSEMESSAGE)
527 {
528 SendApplicationMessage(pwzTarget, WM_CLOSE, dwTimeout);
529 }
530
531 if (dwAttributes & CLOSEAPP_ATTRIBUTE_ELEVATEDENDSESSIONMESSAGE)
532 {
533 SendApplicationMessage(pwzTarget, WM_QUERYENDSESSION, dwTimeout);
534 }
535
536 // If we find that an app that we need closed is still runing, require a
537 // restart or kill the process as directed.
538 ProcFindAllIdsFromExeName(pwzTarget, &prgProcessIds, &cProcessIds);
539 if (0 < cProcessIds)
540 {
541 if (dwAttributes & CLOSEAPP_ATTRIBUTE_REBOOTPROMPT)
542 {
543 WcaLog(LOGMSG_VERBOSE, "App: %ls found running, requiring a reboot.", pwzTarget);
544
545 WcaDeferredActionRequiresReboot();
546 }
547 else if (dwAttributes & CLOSEAPP_ATTRIBUTE_TERMINATEPROCESS)
548 {
549 TerminateProcesses(prgProcessIds, cProcessIds, dwTerminateExitCode);
550 }
551 }
552
553 hr = WcaProgressMessage(COST_CLOSEAPP, FALSE);
554 ExitOnFailure(hr, "failed to send progress message");
555 }
556
557LExit:
558 ReleaseMem(prgProcessIds);
559
560 ReleaseStr(pwzTarget);
561 ReleaseStr(pwzData);
562
563 if (FAILED(hr))
564 {
565 er = ERROR_INSTALL_FAILURE;
566 }
567 return WcaFinalize(er);
568}