aboutsummaryrefslogtreecommitdiff
path: root/src/ext/Dependency/ca/wixdepca.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext/Dependency/ca/wixdepca.cpp')
-rw-r--r--src/ext/Dependency/ca/wixdepca.cpp516
1 files changed, 516 insertions, 0 deletions
diff --git a/src/ext/Dependency/ca/wixdepca.cpp b/src/ext/Dependency/ca/wixdepca.cpp
new file mode 100644
index 00000000..d6433707
--- /dev/null
+++ b/src/ext/Dependency/ca/wixdepca.cpp
@@ -0,0 +1,516 @@
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 IDNOACTION 0
6#define INITIAL_STRINGDICT_SIZE 4
7
8LPCWSTR vcsDependencyProviderQuery =
9 L"SELECT `WixDependencyProvider`.`WixDependencyProvider`, `WixDependencyProvider`.`Component_`, `WixDependencyProvider`.`ProviderKey`, `WixDependencyProvider`.`Attributes` "
10 L"FROM `WixDependencyProvider`";
11enum eDependencyProviderQuery { dpqId = 1, dpqComponent, dpqProviderKey, dpqAttributes };
12
13LPCWSTR vcsDependencyQuery =
14 L"SELECT `WixDependency`.`WixDependency`, `WixDependencyProvider`.`Component_`, `WixDependency`.`ProviderKey`, `WixDependency`.`MinVersion`, `WixDependency`.`MaxVersion`, `WixDependency`.`Attributes` "
15 L"FROM `WixDependencyProvider`, `WixDependency`, `WixDependencyRef` "
16 L"WHERE `WixDependency`.`WixDependency` = `WixDependencyRef`.`WixDependency_` AND `WixDependencyProvider`.`WixDependencyProvider` = `WixDependencyRef`.`WixDependencyProvider_`";
17enum eDependencyComponentQuery { dqId = 1, dqComponent, dqProviderKey, dqMinVersion, dqMaxVersion, dqAttributes };
18
19static HRESULT EnsureRequiredDependencies(
20 __in MSIHANDLE hInstall,
21 __in BOOL fMachineContext
22 );
23
24static HRESULT EnsureAbsentDependents(
25 __in MSIHANDLE hInstall,
26 __in BOOL fMachineContext
27 );
28
29static HRESULT SplitIgnoredDependents(
30 __deref_inout STRINGDICT_HANDLE* psdIgnoredDependents
31 );
32
33static HRESULT CreateDependencyRecord(
34 __in int iMessageId,
35 __in_ecount(cDependencies) const DEPENDENCY* rgDependencies,
36 __in UINT cDependencies,
37 __out MSIHANDLE *phRecord
38 );
39
40static LPCWSTR LogDependencyName(
41 __in_z LPCWSTR wzName
42 );
43
44/***************************************************************************
45 WixDependencyRequire - Checks that all required dependencies are installed.
46
47***************************************************************************/
48extern "C" UINT __stdcall WixDependencyRequire(
49 __in MSIHANDLE hInstall
50 )
51{
52 HRESULT hr = S_OK;
53 UINT er = ERROR_SUCCESS;
54 BOOL fMachineContext = FALSE;
55
56 hr = WcaInitialize(hInstall, "WixDependencyRequire");
57 ExitOnFailure(hr, "Failed to initialize.");
58
59 hr = RegInitialize();
60 ExitOnFailure(hr, "Failed to initialize the registry functions.");
61
62 // Determine whether we're installing per-user or per-machine.
63 fMachineContext = WcaIsPropertySet("ALLUSERS");
64
65 // Check for any provider components being (re)installed that their requirements are already installed.
66 hr = EnsureRequiredDependencies(hInstall, fMachineContext);
67 ExitOnFailure(hr, "Failed to ensure required dependencies for (re)installing components.");
68
69LExit:
70 RegUninitialize();
71
72 er = FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS;
73 return WcaFinalize(er);
74}
75
76/***************************************************************************
77 WixDependencyCheck - Check dependencies based on component state.
78
79 Note: may return ERROR_NO_MORE_ITEMS to terminate the session early.
80***************************************************************************/
81extern "C" UINT __stdcall WixDependencyCheck(
82 __in MSIHANDLE hInstall
83 )
84{
85 HRESULT hr = S_OK;
86 UINT er = ERROR_SUCCESS;
87 BOOL fMachineContext = FALSE;
88
89 hr = WcaInitialize(hInstall, "WixDependencyCheck");
90 ExitOnFailure(hr, "Failed to initialize.");
91
92 hr = RegInitialize();
93 ExitOnFailure(hr, "Failed to initialize the registry functions.");
94
95 // Determine whether we're installing per-user or per-machine.
96 fMachineContext = WcaIsPropertySet("ALLUSERS");
97
98 // Check for any dependents of provider components being uninstalled.
99 hr = EnsureAbsentDependents(hInstall, fMachineContext);
100 ExitOnFailure(hr, "Failed to ensure absent dependents for uninstalling components.");
101
102LExit:
103 RegUninitialize();
104
105 er = FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS;
106 return WcaFinalize(er);
107}
108
109/***************************************************************************
110 EnsureRequiredDependencies - Check that dependencies are installed for
111 any provider component that is being installed or reinstalled.
112
113 Note: Skipped if DISABLEDEPENDENCYCHECK is set.
114***************************************************************************/
115static HRESULT EnsureRequiredDependencies(
116 __in MSIHANDLE /*hInstall*/,
117 __in BOOL fMachineContext
118 )
119{
120 HRESULT hr = S_OK;
121 DWORD er = ERROR_SUCCESS;
122 STRINGDICT_HANDLE sdDependencies = NULL;
123 HKEY hkHive = NULL;
124 PMSIHANDLE hView = NULL;
125 PMSIHANDLE hRec = NULL;
126 LPWSTR sczId = NULL;
127 LPWSTR sczComponent = NULL;
128 LPWSTR sczProviderKey = NULL;
129 LPWSTR sczMinVersion = NULL;
130 LPWSTR sczMaxVersion = NULL;
131 int iAttributes = 0;
132 WCA_TODO tComponentAction = WCA_TODO_UNKNOWN;
133 DEPENDENCY* rgDependencies = NULL;
134 UINT cDependencies = 0;
135 PMSIHANDLE hDependencyRec = NULL;
136
137 // Skip the dependency check if the WixDependency table is missing (no dependencies to check for).
138 hr = WcaTableExists(L"WixDependency");
139 if (S_FALSE == hr)
140 {
141 WcaLog(LOGMSG_STANDARD, "Skipping the dependency check since no dependencies are authored.");
142 ExitFunction1(hr = S_OK);
143 }
144
145 // If the table exists but not the others, the database was not authored correctly.
146 ExitOnFailure(hr, "Failed to check if the WixDependency table exists.");
147
148 // Initialize the dictionary to keep track of unique dependency keys.
149 hr = DictCreateStringList(&sdDependencies, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE);
150 ExitOnFailure(hr, "Failed to initialize the unique dependency string list.");
151
152 // Set the registry hive to use depending on install context.
153 hkHive = fMachineContext ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
154
155 // Loop over the provider components.
156 hr = WcaOpenExecuteView(vcsDependencyQuery, &hView);
157 ExitOnFailure(hr, "Failed to open the query view for dependencies.");
158
159 while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
160 {
161 hr = WcaGetRecordString(hRec, dqId, &sczId);
162 ExitOnFailure(hr, "Failed to get WixDependency.WixDependency.");
163
164 hr = WcaGetRecordString(hRec, dqComponent, &sczComponent);
165 ExitOnFailure(hr, "Failed to get WixDependencyProvider.Component_.");
166
167 // Skip the current component if its not being installed or reinstalled.
168 tComponentAction = WcaGetComponentToDo(sczComponent);
169 if (WCA_TODO_INSTALL != tComponentAction && WCA_TODO_REINSTALL != tComponentAction)
170 {
171 WcaLog(LOGMSG_STANDARD, "Skipping dependency check for %ls because the component %ls is not being (re)installed.", sczId, sczComponent);
172 continue;
173 }
174
175 hr = WcaGetRecordString(hRec, dqProviderKey, &sczProviderKey);
176 ExitOnFailure(hr, "Failed to get WixDependency.ProviderKey.");
177
178 hr = WcaGetRecordString(hRec, dqMinVersion, &sczMinVersion);
179 ExitOnFailure(hr, "Failed to get WixDependency.MinVersion.");
180
181 hr = WcaGetRecordString(hRec, dqMaxVersion, &sczMaxVersion);
182 ExitOnFailure(hr, "Failed to get WixDependency.MaxVersion.");
183
184 hr = WcaGetRecordInteger(hRec, dqAttributes, &iAttributes);
185 ExitOnFailure(hr, "Failed to get WixDependency.Attributes.");
186
187 // Check the registry to see if the required providers (dependencies) exist.
188 hr = DepCheckDependency(hkHive, sczProviderKey, sczMinVersion, sczMaxVersion, iAttributes, sdDependencies, &rgDependencies, &cDependencies);
189 if (E_NOTFOUND != hr)
190 {
191 ExitOnFailure(hr, "Failed dependency check for %ls.", sczId);
192 }
193 }
194
195 if (E_NOMOREITEMS != hr)
196 {
197 ExitOnFailure(hr, "Failed to enumerate all of the rows in the dependency query view.");
198 }
199 else
200 {
201 hr = S_OK;
202 }
203
204 // If we collected any dependencies in the previous check, pump a message and prompt the user.
205 if (0 < cDependencies)
206 {
207 hr = CreateDependencyRecord(msierrDependencyMissingDependencies, rgDependencies, cDependencies, &hDependencyRec);
208 ExitOnFailure(hr, "Failed to create the dependency record for message %d.", msierrDependencyMissingDependencies);
209
210 // Send a yes/no message with a warning icon since continuing could be detrimental.
211 // This is sent as a USER message to better detect whether a user or dependency-aware bootstrapper is responding
212 // or if Windows Installer or a dependency-unaware boostrapper is returning a typical default response.
213 er = WcaProcessMessage(static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_USER | MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2), hDependencyRec);
214 switch (er)
215 {
216 // Only a user or dependency-aware bootstrapper that prompted the user should return IDYES to continue anyway.
217 case IDYES:
218 ExitFunction1(hr = S_OK);
219
220 // Only a user or dependency-aware bootstrapper that prompted the user should return IDNO to terminate the operation.
221 case IDNO:
222 WcaSetReturnValue(ERROR_INSTALL_USEREXIT);
223 ExitFunction1(hr = S_OK);
224
225 // A dependency-aware bootstrapper should return IDCANCEL if running silently and the operation should be canceled.
226 case IDCANCEL:
227 __fallthrough;
228
229 // Bootstrappers which are not dependency-aware may return IDOK for unhandled messages.
230 case IDOK:
231 __fallthrough;
232
233 // Windows Installer returns 0 for USER messages when silent or passive, or when a bootstrapper does not handle the message.
234 case IDNOACTION:
235 WcaSetReturnValue(ERROR_INSTALL_FAILURE);
236 ExitFunction1(hr = S_OK);
237
238 default:
239 ExitOnFailure(hr = E_UNEXPECTED, "Unexpected message response %d from user or bootstrapper application.", er);
240 }
241 }
242
243LExit:
244 ReleaseDependencyArray(rgDependencies, cDependencies);
245 ReleaseStr(sczId);
246 ReleaseStr(sczComponent);
247 ReleaseStr(sczProviderKey);
248 ReleaseStr(sczMinVersion);
249 ReleaseStr(sczMaxVersion);
250 ReleaseDict(sdDependencies);
251
252 return hr;
253}
254
255/***************************************************************************
256 EnsureAbsentDependents - Checks that there are no dependents
257 registered for providers that are being uninstalled.
258
259 Note: Skipped if UPGRADINGPRODUCTCODE is set.
260***************************************************************************/
261static HRESULT EnsureAbsentDependents(
262 __in MSIHANDLE /*hInstall*/,
263 __in BOOL fMachineContext
264 )
265{
266 HRESULT hr = S_OK;
267 DWORD er = ERROR_SUCCESS;
268 STRINGDICT_HANDLE sdIgnoredDependents = NULL;
269 HKEY hkHive = NULL;
270 PMSIHANDLE hView = NULL;
271 PMSIHANDLE hRec = NULL;
272 LPWSTR sczId = NULL;
273 LPWSTR sczComponent = NULL;
274 LPWSTR sczProviderKey = NULL;
275 int iAttributes = 0;
276 WCA_TODO tComponentAction = WCA_TODO_UNKNOWN;
277 DEPENDENCY* rgDependents = NULL;
278 UINT cDependents = 0;
279 PMSIHANDLE hDependencyRec = NULL;
280
281 // Skip the dependent check if the WixDependencyProvider table is missing (no dependency providers).
282 hr = WcaTableExists(L"WixDependencyProvider");
283 if (S_FALSE == hr)
284 {
285 WcaLog(LOGMSG_STANDARD, "Skipping the dependents check since no dependency providers are authored.");
286 ExitFunction1(hr = S_OK);
287 }
288
289 ExitOnFailure(hr, "Failed to check if the WixDependencyProvider table exists.");
290
291 // Split the IGNOREDEPENDENCIES property for use below if set. If it is "ALL", then quit now.
292 hr = SplitIgnoredDependents(&sdIgnoredDependents);
293 ExitOnFailure(hr, "Failed to get the ignored dependents.");
294
295 hr = DictKeyExists(sdIgnoredDependents, L"ALL");
296 if (E_NOTFOUND != hr)
297 {
298 ExitOnFailure(hr, "Failed to check if \"ALL\" was set in IGNOREDEPENDENCIES.");
299
300 // Otherwise...
301 WcaLog(LOGMSG_STANDARD, "Skipping the dependencies check since IGNOREDEPENDENCIES contains \"ALL\".");
302 ExitFunction();
303 }
304 else
305 {
306 // Key was not found, so proceed.
307 hr = S_OK;
308 }
309
310 // Set the registry hive to use depending on install context.
311 hkHive = fMachineContext ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
312
313 // Loop over the provider components.
314 hr = WcaOpenExecuteView(vcsDependencyProviderQuery, &hView);
315 ExitOnFailure(hr, "Failed to open the query view for dependency providers.");
316
317 while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
318 {
319 hr = WcaGetRecordString(hRec, dpqId, &sczId);
320 ExitOnFailure(hr, "Failed to get WixDependencyProvider.WixDependencyProvider.");
321
322 hr = WcaGetRecordString(hRec, dpqComponent, &sczComponent);
323 ExitOnFailure(hr, "Failed to get WixDependencyProvider.Component.");
324
325 // Skip the current component if its not being uninstalled.
326 tComponentAction = WcaGetComponentToDo(sczComponent);
327 if (WCA_TODO_UNINSTALL != tComponentAction)
328 {
329 WcaLog(LOGMSG_STANDARD, "Skipping dependents check for %ls because the component %ls is not being uninstalled.", sczId, sczComponent);
330 continue;
331 }
332
333 hr = WcaGetRecordString(hRec, dpqProviderKey, &sczProviderKey);
334 ExitOnFailure(hr, "Failed to get WixDependencyProvider.ProviderKey.");
335
336 hr = WcaGetRecordInteger(hRec, dpqAttributes, &iAttributes);
337 ExitOnFailure(hr, "Failed to get WixDependencyProvider.Attributes.");
338
339 // Check the registry to see if the provider has any dependents registered.
340 hr = DepCheckDependents(hkHive, sczProviderKey, iAttributes, sdIgnoredDependents, &rgDependents, &cDependents);
341 ExitOnFailure(hr, "Failed dependents check for %ls.", sczId);
342 }
343
344 if (E_NOMOREITEMS != hr)
345 {
346 ExitOnFailure(hr, "Failed to enumerate all of the rows in the dependency provider query view.");
347 }
348 else
349 {
350 hr = S_OK;
351 }
352
353 // If we collected any providers with dependents in the previous check, pump a message and prompt the user.
354 if (0 < cDependents)
355 {
356 hr = CreateDependencyRecord(msierrDependencyHasDependents, rgDependents, cDependents, &hDependencyRec);
357 ExitOnFailure(hr, "Failed to create the dependency record for message %d.", msierrDependencyHasDependents);
358
359 // Send a yes/no message with a warning icon since continuing could be detrimental.
360 // This is sent as a USER message to better detect whether a user or dependency-aware bootstrapper is responding
361 // or if Windows Installer or a dependency-unaware boostrapper is returning a typical default response.
362 er = WcaProcessMessage(static_cast<INSTALLMESSAGE>(INSTALLMESSAGE_USER | MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2), hDependencyRec);
363 switch (er)
364 {
365 // Only a user or dependency-aware bootstrapper that prompted the user should return IDYES to continue anyway.
366 case IDYES:
367 ExitFunction1(hr = S_OK);
368
369 // Only a user or dependency-aware bootstrapper that prompted the user should return IDNO to terminate the operation.
370 case IDNO:
371 __fallthrough;
372
373 // Bootstrappers which are not dependency-aware may return IDOK for unhandled messages.
374 case IDOK:
375 __fallthrough;
376
377 // Windows Installer returns 0 for USER messages when silent or passive, or when a bootstrapper does not handle the message.
378 case IDNOACTION:
379 WcaSetReturnValue(ERROR_NO_MORE_ITEMS);
380 ExitFunction1(hr = S_OK);
381
382 // A dependency-aware bootstrapper should return IDCANCEL if running silently and the operation should be canceled.
383 case IDCANCEL:
384 WcaSetReturnValue(ERROR_INSTALL_FAILURE);
385 ExitFunction1(hr = S_OK);
386
387 default:
388 hr = E_UNEXPECTED;
389 ExitOnFailure(hr, "Unexpected message response %d from user or bootstrapper application.", er);
390 }
391 }
392
393LExit:
394 ReleaseDependencyArray(rgDependents, cDependents);
395 ReleaseStr(sczId);
396 ReleaseStr(sczComponent);
397 ReleaseStr(sczProviderKey);
398
399 return hr;
400}
401
402/***************************************************************************
403 SplitIgnoredDependents - Splits the IGNOREDEPENDENCIES property into a map.
404
405***************************************************************************/
406static HRESULT SplitIgnoredDependents(
407 __deref_inout STRINGDICT_HANDLE* psdIgnoredDependents
408 )
409{
410 HRESULT hr = S_OK;
411 LPWSTR sczIgnoreDependencies = NULL;
412 LPCWSTR wzDelim = L";";
413 LPWSTR wzContext = NULL;
414
415 hr = WcaGetProperty(L"IGNOREDEPENDENCIES", &sczIgnoreDependencies);
416 ExitOnFailure(hr, "Failed to get the string value of the IGNOREDEPENDENCIES property.");
417
418 hr = DictCreateStringList(psdIgnoredDependents, INITIAL_STRINGDICT_SIZE, DICT_FLAG_CASEINSENSITIVE);
419 ExitOnFailure(hr, "Failed to create the string dictionary.");
420
421 // Parse through the semicolon-delimited tokens and add to the string dictionary.
422 for (LPCWSTR wzToken = ::wcstok_s(sczIgnoreDependencies, wzDelim, &wzContext); wzToken; wzToken = ::wcstok_s(NULL, wzDelim, &wzContext))
423 {
424 hr = DictAddKey(*psdIgnoredDependents, wzToken);
425 ExitOnFailure(hr, "Failed to ignored dependency \"%ls\" to the string dictionary.", wzToken);
426 }
427
428LExit:
429 ReleaseStr(sczIgnoreDependencies);
430
431 return hr;
432}
433
434/***************************************************************************
435 CreateDependencyRecord - Creates a record containing the message template
436 and records to send to the UI handler.
437
438 Notes: Callers should call WcaProcessMessage and handle return codes.
439***************************************************************************/
440static HRESULT CreateDependencyRecord(
441 __in int iMessageId,
442 __in_ecount(cDependencies) const DEPENDENCY* rgDependencies,
443 __in UINT cDependencies,
444 __out MSIHANDLE *phRecord
445 )
446{
447 HRESULT hr = S_OK;
448 UINT er = ERROR_SUCCESS;
449 UINT cParams = 0;
450 UINT iParam = 0;
451
452 // Should not be PMSIHANDLE.
453 MSIHANDLE hRec = NULL;
454
455 // Calculate the number of parameters based on the format:
456 // msgId, count, key1, name1, key2, name2, etc.
457 cParams = 2 + 2 * cDependencies;
458
459 hRec = ::MsiCreateRecord(cParams);
460 ExitOnNull(hRec, hr, E_OUTOFMEMORY, "Not enough memory to create the message record.");
461
462 er = ::MsiRecordSetInteger(hRec, ++iParam, iMessageId);
463 ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the message identifier into the message record.");
464
465 er = ::MsiRecordSetInteger(hRec, ++iParam, cDependencies);
466 ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the number of dependencies into the message record.");
467
468 // Now loop through each dependency and add the key and name to the record.
469 for (UINT i = 0; i < cDependencies; i++)
470 {
471 const DEPENDENCY* pDependency = &rgDependencies[i];
472
473 // Log message type-specific information.
474 switch (iMessageId)
475 {
476 // Send a user message when installing a component that is missing some dependencies.
477 case msierrDependencyMissingDependencies:
478 WcaLog(LOGMSG_VERBOSE, "The dependency \"%ls\" is missing or is not the required version.", pDependency->sczKey);
479 break;
480
481 // Send a user message when uninstalling a component that still has registered dependents.
482 case msierrDependencyHasDependents:
483 WcaLog(LOGMSG_VERBOSE, "Found dependent \"%ls\", name: \"%ls\".", pDependency->sczKey, LogDependencyName(pDependency->sczName));
484 break;
485 }
486
487 er = ::MsiRecordSetStringW(hRec, ++iParam, pDependency->sczKey);
488 ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the dependency key \"%ls\" into the message record.", pDependency->sczKey);
489
490 er = ::MsiRecordSetStringW(hRec, ++iParam, pDependency->sczName);
491 ExitOnFailure(hr = HRESULT_FROM_WIN32(er), "Failed to set the dependency name \"%ls\" into the message record.", pDependency->sczName);
492 }
493
494 // Only assign the out parameter if successful to this point.
495 *phRecord = hRec;
496 hRec = NULL;
497
498LExit:
499 if (hRec)
500 {
501 ::MsiCloseHandle(hRec);
502 }
503
504 return hr;
505}
506
507/***************************************************************************
508 LogDependencyName - Returns the dependency name or "Unknown" if null.
509
510***************************************************************************/
511static LPCWSTR LogDependencyName(
512 __in_z LPCWSTR wzName
513 )
514{
515 return wzName ? wzName : L"Unknown";
516}