aboutsummaryrefslogtreecommitdiff
path: root/src/dutil/wiutil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dutil/wiutil.cpp')
-rw-r--r--src/dutil/wiutil.cpp1494
1 files changed, 1494 insertions, 0 deletions
diff --git a/src/dutil/wiutil.cpp b/src/dutil/wiutil.cpp
new file mode 100644
index 00000000..1a489d54
--- /dev/null
+++ b/src/dutil/wiutil.cpp
@@ -0,0 +1,1494 @@
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
6// constants
7
8const DWORD WIU_MSI_PROGRESS_INVALID = 0xFFFFFFFF;
9const DWORD WIU_GOOD_ENOUGH_PROPERTY_LENGTH = 64;
10
11
12// structs
13
14
15static PFN_MSIENABLELOGW vpfnMsiEnableLogW = ::MsiEnableLogW;
16static PFN_MSIGETPRODUCTINFOW vpfnMsiGetProductInfoW = ::MsiGetProductInfoW;
17static PFN_MSIQUERYFEATURESTATEW vpfnMsiQueryFeatureStateW = ::MsiQueryFeatureStateW;
18static PFN_MSIGETCOMPONENTPATHW vpfnMsiGetComponentPathW = ::MsiGetComponentPathW;
19static PFN_MSILOCATECOMPONENTW vpfnMsiLocateComponentW = ::MsiLocateComponentW;
20static PFN_MSIINSTALLPRODUCTW vpfnMsiInstallProductW = ::MsiInstallProductW;
21static PFN_MSICONFIGUREPRODUCTEXW vpfnMsiConfigureProductExW = ::MsiConfigureProductExW;
22static PFN_MSIREMOVEPATCHESW vpfnMsiRemovePatchesW = ::MsiRemovePatchesW;
23static PFN_MSISETINTERNALUI vpfnMsiSetInternalUI = ::MsiSetInternalUI;
24static PFN_MSISETEXTERNALUIW vpfnMsiSetExternalUIW = ::MsiSetExternalUIW;
25static PFN_MSIENUMPRODUCTSW vpfnMsiEnumProductsW = ::MsiEnumProductsW;
26static PFN_MSIENUMRELATEDPRODUCTSW vpfnMsiEnumRelatedProductsW = ::MsiEnumRelatedProductsW;
27
28// MSI 3.0+
29static PFN_MSIDETERMINEPATCHSEQUENCEW vpfnMsiDeterminePatchSequenceW = NULL;
30static PFN_MSIDETERMINEAPPLICABLEPATCHESW vpfnMsiDetermineApplicablePatchesW = NULL;
31static PFN_MSIENUMPRODUCTSEXW vpfnMsiEnumProductsExW = NULL;
32static PFN_MSIGETPATCHINFOEXW vpfnMsiGetPatchInfoExW = NULL;
33static PFN_MSIGETPRODUCTINFOEXW vpfnMsiGetProductInfoExW = NULL;
34static PFN_MSISETEXTERNALUIRECORD vpfnMsiSetExternalUIRecord = NULL;
35static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExW = NULL;
36
37static HMODULE vhMsiDll = NULL;
38static PFN_MSIDETERMINEPATCHSEQUENCEW vpfnMsiDeterminePatchSequenceWFromLibrary = NULL;
39static PFN_MSIDETERMINEAPPLICABLEPATCHESW vpfnMsiDetermineApplicablePatchesWFromLibrary = NULL;
40static PFN_MSIENUMPRODUCTSEXW vpfnMsiEnumProductsExWFromLibrary = NULL;
41static PFN_MSIGETPATCHINFOEXW vpfnMsiGetPatchInfoExWFromLibrary = NULL;
42static PFN_MSIGETPRODUCTINFOEXW vpfnMsiGetProductInfoExWFromLibrary = NULL;
43static PFN_MSISETEXTERNALUIRECORD vpfnMsiSetExternalUIRecordFromLibrary = NULL;
44static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExWFromLibrary = NULL;
45static BOOL vfWiuInitialized = FALSE;
46
47// globals
48static DWORD vdwMsiDllMajorMinor = 0;
49static DWORD vdwMsiDllBuildRevision = 0;
50
51
52// internal function declarations
53
54static DWORD CheckForRestartErrorCode(
55 __in DWORD dwErrorCode,
56 __out WIU_RESTART* pRestart
57 );
58static INT CALLBACK InstallEngineCallback(
59 __in LPVOID pvContext,
60 __in UINT uiMessage,
61 __in_z_opt LPCWSTR wzMessage
62 );
63static INT CALLBACK InstallEngineRecordCallback(
64 __in LPVOID pvContext,
65 __in UINT uiMessage,
66 __in_opt MSIHANDLE hRecord
67 );
68static INT HandleInstallMessage(
69 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
70 __in INSTALLMESSAGE mt,
71 __in UINT uiFlags,
72 __in_z LPCWSTR wzMessage,
73 __in_opt MSIHANDLE hRecord
74 );
75static INT HandleInstallProgress(
76 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
77 __in_z_opt LPCWSTR wzMessage,
78 __in_opt MSIHANDLE hRecord
79 );
80static INT SendMsiMessage(
81 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
82 __in INSTALLMESSAGE mt,
83 __in UINT uiFlags,
84 __in_z LPCWSTR wzMessage,
85 __in_opt MSIHANDLE hRecord
86 );
87static INT SendErrorMessage(
88 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
89 __in UINT uiFlags,
90 __in_z LPCWSTR wzMessage,
91 __in_opt MSIHANDLE hRecord
92 );
93static INT SendFilesInUseMessage(
94 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
95 __in_opt MSIHANDLE hRecord,
96 __in BOOL fRestartManagerRequest
97 );
98static INT SendProgressUpdate(
99 __in WIU_MSI_EXECUTE_CONTEXT* pContext
100 );
101static void ResetProgress(
102 __in WIU_MSI_EXECUTE_CONTEXT* pContext
103 );
104static DWORD CalculatePhaseProgress(
105 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
106 __in DWORD dwProgressIndex,
107 __in DWORD dwWeightPercentage
108 );
109void InitializeMessageData(
110 __in MSIHANDLE hRecord,
111 __out LPWSTR** prgsczData,
112 __out DWORD* pcData
113 );
114void UninitializeMessageData(
115 __in LPWSTR* rgsczData,
116 __in DWORD cData
117 );
118
119
120/********************************************************************
121 WiuInitialize - initializes wiutil
122
123*********************************************************************/
124extern "C" HRESULT DAPI WiuInitialize(
125 )
126{
127 HRESULT hr = S_OK;
128 LPWSTR sczMsiDllPath = NULL;
129
130 hr = LoadSystemLibraryWithPath(L"Msi.dll", &vhMsiDll, &sczMsiDllPath);
131 ExitOnFailure(hr, "Failed to load Msi.DLL");
132
133 // Ignore failures
134 FileVersion(sczMsiDllPath, &vdwMsiDllMajorMinor, &vdwMsiDllBuildRevision);
135
136 vpfnMsiDeterminePatchSequenceWFromLibrary = reinterpret_cast<PFN_MSIDETERMINEPATCHSEQUENCEW>(::GetProcAddress(vhMsiDll, "MsiDeterminePatchSequenceW"));
137 if (NULL == vpfnMsiDeterminePatchSequenceW)
138 {
139 vpfnMsiDeterminePatchSequenceW = vpfnMsiDeterminePatchSequenceWFromLibrary;
140 }
141
142 vpfnMsiDetermineApplicablePatchesWFromLibrary = reinterpret_cast<PFN_MSIDETERMINEAPPLICABLEPATCHESW>(::GetProcAddress(vhMsiDll, "MsiDetermineApplicablePatchesW"));
143 if (NULL == vpfnMsiDetermineApplicablePatchesW)
144 {
145 vpfnMsiDetermineApplicablePatchesW = vpfnMsiDetermineApplicablePatchesWFromLibrary;
146 }
147
148 vpfnMsiEnumProductsExWFromLibrary = reinterpret_cast<PFN_MSIENUMPRODUCTSEXW>(::GetProcAddress(vhMsiDll, "MsiEnumProductsExW"));
149 if (NULL == vpfnMsiEnumProductsExW)
150 {
151 vpfnMsiEnumProductsExW = vpfnMsiEnumProductsExWFromLibrary;
152 }
153
154 vpfnMsiGetPatchInfoExWFromLibrary = reinterpret_cast<PFN_MSIGETPATCHINFOEXW>(::GetProcAddress(vhMsiDll, "MsiGetPatchInfoExW"));
155 if (NULL == vpfnMsiGetPatchInfoExW)
156 {
157 vpfnMsiGetPatchInfoExW = vpfnMsiGetPatchInfoExWFromLibrary;
158 }
159
160 vpfnMsiGetProductInfoExWFromLibrary = reinterpret_cast<PFN_MSIGETPRODUCTINFOEXW>(::GetProcAddress(vhMsiDll, "MsiGetProductInfoExW"));
161 if (NULL == vpfnMsiGetProductInfoExW)
162 {
163 vpfnMsiGetProductInfoExW = vpfnMsiGetProductInfoExWFromLibrary;
164 }
165
166 vpfnMsiSetExternalUIRecordFromLibrary = reinterpret_cast<PFN_MSISETEXTERNALUIRECORD>(::GetProcAddress(vhMsiDll, "MsiSetExternalUIRecord"));
167 if (NULL == vpfnMsiSetExternalUIRecord)
168 {
169 vpfnMsiSetExternalUIRecord = vpfnMsiSetExternalUIRecordFromLibrary;
170 }
171
172 //static PFN_MSISOURCELISTADDSOURCEEXW vpfnMsiSourceListAddSourceExW = NULL;
173 vpfnMsiSourceListAddSourceExWFromLibrary = reinterpret_cast<PFN_MSISOURCELISTADDSOURCEEXW>(::GetProcAddress(vhMsiDll, "MsiSourceListAddSourceExW"));
174 if (NULL == vpfnMsiSourceListAddSourceExW)
175 {
176 vpfnMsiSourceListAddSourceExW = vpfnMsiSourceListAddSourceExWFromLibrary;
177 }
178
179 vfWiuInitialized = TRUE;
180
181LExit:
182 ReleaseStr(sczMsiDllPath);
183 return hr;
184}
185
186
187/********************************************************************
188 WiuUninitialize - uninitializes wiutil
189
190*********************************************************************/
191extern "C" void DAPI WiuUninitialize(
192 )
193{
194 if (vhMsiDll)
195 {
196 ::FreeLibrary(vhMsiDll);
197 vhMsiDll = NULL;
198 vpfnMsiSetExternalUIRecordFromLibrary = NULL;
199 vpfnMsiGetProductInfoExWFromLibrary = NULL;
200 vpfnMsiGetPatchInfoExWFromLibrary = NULL;
201 vpfnMsiEnumProductsExWFromLibrary = NULL;
202 vpfnMsiDetermineApplicablePatchesWFromLibrary = NULL;
203 vpfnMsiDeterminePatchSequenceWFromLibrary = NULL;
204 vpfnMsiSourceListAddSourceExWFromLibrary = NULL;
205 }
206
207 vfWiuInitialized = FALSE;
208}
209
210
211/********************************************************************
212 WiuFunctionOverride - overrides the Windows installer functions. Typically used
213 for unit testing.
214
215*********************************************************************/
216extern "C" void DAPI WiuFunctionOverride(
217 __in_opt PFN_MSIENABLELOGW pfnMsiEnableLogW,
218 __in_opt PFN_MSIGETCOMPONENTPATHW pfnMsiGetComponentPathW,
219 __in_opt PFN_MSILOCATECOMPONENTW pfnMsiLocateComponentW,
220 __in_opt PFN_MSIQUERYFEATURESTATEW pfnMsiQueryFeatureStateW,
221 __in_opt PFN_MSIGETPRODUCTINFOW pfnMsiGetProductInfoW,
222 __in_opt PFN_MSIGETPRODUCTINFOEXW pfnMsiGetProductInfoExW,
223 __in_opt PFN_MSIINSTALLPRODUCTW pfnMsiInstallProductW,
224 __in_opt PFN_MSICONFIGUREPRODUCTEXW pfnMsiConfigureProductExW,
225 __in_opt PFN_MSISETINTERNALUI pfnMsiSetInternalUI,
226 __in_opt PFN_MSISETEXTERNALUIW pfnMsiSetExternalUIW,
227 __in_opt PFN_MSIENUMRELATEDPRODUCTSW pfnMsiEnumRelatedProductsW,
228 __in_opt PFN_MSISETEXTERNALUIRECORD pfnMsiSetExternalUIRecord,
229 __in_opt PFN_MSISOURCELISTADDSOURCEEXW pfnMsiSourceListAddSourceExW
230 )
231{
232 vpfnMsiEnableLogW = pfnMsiEnableLogW ? pfnMsiEnableLogW : ::MsiEnableLogW;
233 vpfnMsiGetComponentPathW = pfnMsiGetComponentPathW ? pfnMsiGetComponentPathW : ::MsiGetComponentPathW;
234 vpfnMsiLocateComponentW = pfnMsiLocateComponentW ? pfnMsiLocateComponentW : ::MsiLocateComponentW;
235 vpfnMsiQueryFeatureStateW = pfnMsiQueryFeatureStateW ? pfnMsiQueryFeatureStateW : ::MsiQueryFeatureStateW;
236 vpfnMsiGetProductInfoW = pfnMsiGetProductInfoW ? pfnMsiGetProductInfoW : vpfnMsiGetProductInfoW;
237 vpfnMsiInstallProductW = pfnMsiInstallProductW ? pfnMsiInstallProductW : ::MsiInstallProductW;
238 vpfnMsiConfigureProductExW = pfnMsiConfigureProductExW ? pfnMsiConfigureProductExW : ::MsiConfigureProductExW;
239 vpfnMsiSetInternalUI = pfnMsiSetInternalUI ? pfnMsiSetInternalUI : ::MsiSetInternalUI;
240 vpfnMsiSetExternalUIW = pfnMsiSetExternalUIW ? pfnMsiSetExternalUIW : ::MsiSetExternalUIW;
241 vpfnMsiEnumRelatedProductsW = pfnMsiEnumRelatedProductsW ? pfnMsiEnumRelatedProductsW : ::MsiEnumRelatedProductsW;
242 vpfnMsiGetProductInfoExW = pfnMsiGetProductInfoExW ? pfnMsiGetProductInfoExW : vpfnMsiGetProductInfoExWFromLibrary;
243 vpfnMsiSetExternalUIRecord = pfnMsiSetExternalUIRecord ? pfnMsiSetExternalUIRecord : vpfnMsiSetExternalUIRecordFromLibrary;
244 vpfnMsiSourceListAddSourceExW = pfnMsiSourceListAddSourceExW ? pfnMsiSourceListAddSourceExW : vpfnMsiSourceListAddSourceExWFromLibrary;
245}
246
247
248extern "C" HRESULT DAPI WiuGetComponentPath(
249 __in_z LPCWSTR wzProductCode,
250 __in_z LPCWSTR wzComponentId,
251 __out INSTALLSTATE* pInstallState,
252 __out_z LPWSTR* psczValue
253 )
254{
255 HRESULT hr = S_OK;
256 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
257 DWORD cchCompare;
258
259 hr = StrAlloc(psczValue, cch);
260 ExitOnFailure(hr, "Failed to allocate string for component path.");
261
262 cchCompare = cch;
263 *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
264 if (INSTALLSTATE_MOREDATA == *pInstallState)
265 {
266 ++cch;
267 hr = StrAlloc(psczValue, cch);
268 ExitOnFailure(hr, "Failed to reallocate string for component path.");
269
270 cchCompare = cch;
271 *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
272 }
273
274 if (INSTALLSTATE_INVALIDARG == *pInstallState)
275 {
276 hr = E_INVALIDARG;
277 ExitOnRootFailure(hr, "Invalid argument when getting component path.");
278 }
279 else if (INSTALLSTATE_UNKNOWN == *pInstallState)
280 {
281 ExitFunction();
282 }
283
284 // If the actual path length is greater than or equal to the original buffer
285 // allocate a larger buffer and get the path again, just in case we are
286 // missing any part of the path.
287 if (cchCompare <= cch)
288 {
289 ++cch;
290 hr = StrAlloc(psczValue, cch);
291 ExitOnFailure(hr, "Failed to reallocate string for component path.");
292
293 *pInstallState = vpfnMsiGetComponentPathW(wzProductCode, wzComponentId, *psczValue, &cch);
294 }
295
296LExit:
297 return hr;
298}
299
300
301extern "C" HRESULT DAPI WiuLocateComponent(
302 __in_z LPCWSTR wzComponentId,
303 __out INSTALLSTATE* pInstallState,
304 __out_z LPWSTR* psczValue
305 )
306{
307 HRESULT hr = S_OK;
308 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
309 DWORD cchCompare;
310
311 hr = StrAlloc(psczValue, cch);
312 ExitOnFailure(hr, "Failed to allocate string for component path.");
313
314 cchCompare = cch;
315 *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
316 if (INSTALLSTATE_MOREDATA == *pInstallState)
317 {
318 ++cch;
319 hr = StrAlloc(psczValue, cch);
320 ExitOnFailure(hr, "Failed to reallocate string for component path.");
321
322 cchCompare = cch;
323 *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
324 }
325
326 if (INSTALLSTATE_INVALIDARG == *pInstallState)
327 {
328 hr = E_INVALIDARG;
329 ExitOnRootFailure(hr, "Invalid argument when locating component.");
330 }
331 else if (INSTALLSTATE_UNKNOWN == *pInstallState)
332 {
333 ExitFunction();
334 }
335
336 // If the actual path length is greater than or equal to the original buffer
337 // allocate a larger buffer and get the path again, just in case we are
338 // missing any part of the path.
339 if (cchCompare <= cch)
340 {
341 ++cch;
342 hr = StrAlloc(psczValue, cch);
343 ExitOnFailure(hr, "Failed to reallocate string for component path.");
344
345 *pInstallState = vpfnMsiLocateComponentW(wzComponentId, *psczValue, &cch);
346 }
347
348LExit:
349 return hr;
350}
351
352
353extern "C" HRESULT DAPI WiuQueryFeatureState(
354 __in_z LPCWSTR wzProduct,
355 __in_z LPCWSTR wzFeature,
356 __out INSTALLSTATE* pInstallState
357 )
358{
359 HRESULT hr = S_OK;
360
361 *pInstallState = vpfnMsiQueryFeatureStateW(wzProduct, wzFeature);
362 if (INSTALLSTATE_INVALIDARG == *pInstallState)
363 {
364 hr = E_INVALIDARG;
365 ExitOnRootFailure(hr, "Failed to query state of feature: %ls in product: %ls", wzFeature, wzProduct);
366 }
367
368LExit:
369 return hr;
370}
371
372
373extern "C" HRESULT DAPI WiuGetProductInfo(
374 __in_z LPCWSTR wzProductCode,
375 __in_z LPCWSTR wzProperty,
376 __out LPWSTR* psczValue
377 )
378{
379 HRESULT hr = S_OK;
380 UINT er = ERROR_SUCCESS;
381 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
382
383 hr = StrAlloc(psczValue, cch);
384 ExitOnFailure(hr, "Failed to allocate string for product info.");
385
386 er = vpfnMsiGetProductInfoW(wzProductCode, wzProperty, *psczValue, &cch);
387 if (ERROR_MORE_DATA == er)
388 {
389 ++cch;
390 hr = StrAlloc(psczValue, cch);
391 ExitOnFailure(hr, "Failed to reallocate string for product info.");
392
393 er = vpfnMsiGetProductInfoW(wzProductCode, wzProperty, *psczValue, &cch);
394 }
395 ExitOnWin32Error(er, hr, "Failed to get product info.");
396
397LExit:
398 return hr;
399}
400
401
402extern "C" HRESULT DAPI WiuGetProductInfoEx(
403 __in_z LPCWSTR wzProductCode,
404 __in_z_opt LPCWSTR wzUserSid,
405 __in MSIINSTALLCONTEXT dwContext,
406 __in_z LPCWSTR wzProperty,
407 __out LPWSTR* psczValue
408 )
409{
410 HRESULT hr = S_OK;
411 UINT er = ERROR_SUCCESS;
412 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
413
414 if (!vpfnMsiGetProductInfoExW)
415 {
416 hr = WiuGetProductInfo(wzProductCode, wzProperty, psczValue);
417 ExitOnFailure(hr, "Failed to get product info when extended info was not available.");
418
419 ExitFunction();
420 }
421
422 hr = StrAlloc(psczValue, cch);
423 ExitOnFailure(hr, "Failed to allocate string for extended product info.");
424
425 er = vpfnMsiGetProductInfoExW(wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
426 if (ERROR_MORE_DATA == er)
427 {
428 ++cch;
429 hr = StrAlloc(psczValue, cch);
430 ExitOnFailure(hr, "Failed to reallocate string for extended product info.");
431
432 er = vpfnMsiGetProductInfoExW(wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
433 }
434 ExitOnWin32Error(er, hr, "Failed to get extended product info.");
435
436LExit:
437 return hr;
438}
439
440
441extern "C" HRESULT DAPI WiuGetProductProperty(
442 __in MSIHANDLE hProduct,
443 __in_z LPCWSTR wzProperty,
444 __out LPWSTR* psczValue
445 )
446{
447 HRESULT hr = S_OK;
448 UINT er = ERROR_SUCCESS;
449 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
450
451 hr = StrAlloc(psczValue, cch);
452 ExitOnFailure(hr, "Failed to allocate string for product property.");
453
454 er = ::MsiGetProductPropertyW(hProduct, wzProperty, *psczValue, &cch);
455 if (ERROR_MORE_DATA == er)
456 {
457 ++cch;
458 hr = StrAlloc(psczValue, cch);
459 ExitOnFailure(hr, "Failed to reallocate string for product property.");
460
461 er = ::MsiGetProductPropertyW(hProduct, wzProperty, *psczValue, &cch);
462 }
463 ExitOnWin32Error(er, hr, "Failed to get product property.");
464
465LExit:
466 return hr;
467}
468
469
470extern "C" HRESULT DAPI WiuGetPatchInfoEx(
471 __in_z LPCWSTR wzPatchCode,
472 __in_z LPCWSTR wzProductCode,
473 __in_z_opt LPCWSTR wzUserSid,
474 __in MSIINSTALLCONTEXT dwContext,
475 __in_z LPCWSTR wzProperty,
476 __out LPWSTR* psczValue
477 )
478{
479 HRESULT hr = S_OK;
480 UINT er = ERROR_SUCCESS;
481 DWORD cch = WIU_GOOD_ENOUGH_PROPERTY_LENGTH;
482
483 if (!vpfnMsiGetPatchInfoExW)
484 {
485 ExitFunction1(hr = E_NOTIMPL);
486 }
487
488 hr = StrAlloc(psczValue, cch);
489 ExitOnFailure(hr, "Failed to allocate string for extended patch info.");
490
491 er = vpfnMsiGetPatchInfoExW(wzPatchCode, wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
492 if (ERROR_MORE_DATA == er)
493 {
494 ++cch;
495 hr = StrAlloc(psczValue, cch);
496 ExitOnFailure(hr, "Failed to reallocate string for extended patch info.");
497
498 er = vpfnMsiGetPatchInfoExW(wzPatchCode, wzProductCode, wzUserSid, dwContext, wzProperty, *psczValue, &cch);
499 }
500 ExitOnWin32Error(er, hr, "Failed to get extended patch info.");
501
502LExit:
503 return hr;
504}
505
506
507extern "C" HRESULT DAPI WiuDeterminePatchSequence(
508 __in_z LPCWSTR wzProductCode,
509 __in_z_opt LPCWSTR wzUserSid,
510 __in MSIINSTALLCONTEXT context,
511 __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
512 __in DWORD cPatchInfo
513 )
514{
515 HRESULT hr = S_OK;
516 DWORD er = ERROR_SUCCESS;
517
518 if (!vpfnMsiDeterminePatchSequenceW)
519 {
520 ExitFunction1(hr = E_NOTIMPL);
521 }
522
523 er = vpfnMsiDeterminePatchSequenceW(wzProductCode, wzUserSid, context, cPatchInfo, pPatchInfo);
524 ExitOnWin32Error(er, hr, "Failed to determine patch sequence for product code.");
525
526LExit:
527 return hr;
528}
529
530
531extern "C" HRESULT DAPI WiuDetermineApplicablePatches(
532 __in_z LPCWSTR wzProductPackagePath,
533 __in PMSIPATCHSEQUENCEINFOW pPatchInfo,
534 __in DWORD cPatchInfo
535 )
536{
537 HRESULT hr = S_OK;
538 DWORD er = ERROR_SUCCESS;
539
540 if (!vpfnMsiDetermineApplicablePatchesW)
541 {
542 ExitFunction1(hr = E_NOTIMPL);
543 }
544
545 er = vpfnMsiDetermineApplicablePatchesW(wzProductPackagePath, cPatchInfo, pPatchInfo);
546 ExitOnWin32Error(er, hr, "Failed to determine applicable patches for product package.");
547
548LExit:
549 return hr;
550}
551
552
553extern "C" HRESULT DAPI WiuEnumProducts(
554 __in DWORD iProductIndex,
555 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
556 )
557{
558 HRESULT hr = S_OK;
559 DWORD er = ERROR_SUCCESS;
560
561 er = vpfnMsiEnumProductsW(iProductIndex, wzProductCode);
562 if (ERROR_NO_MORE_ITEMS == er)
563 {
564 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
565 }
566 ExitOnWin32Error(er, hr, "Failed to enumerate products.");
567
568LExit:
569 return hr;
570}
571
572
573extern "C" HRESULT DAPI WiuEnumProductsEx(
574 __in_z_opt LPCWSTR wzProductCode,
575 __in_z_opt LPCWSTR wzUserSid,
576 __in DWORD dwContext,
577 __in DWORD dwIndex,
578 __out_opt WCHAR wzInstalledProductCode[39],
579 __out_opt MSIINSTALLCONTEXT *pdwInstalledContext,
580 __out_opt LPWSTR wzSid,
581 __inout_opt LPDWORD pcchSid
582 )
583{
584 HRESULT hr = S_OK;
585 DWORD er = ERROR_SUCCESS;
586
587 if (!vpfnMsiEnumProductsExW)
588 {
589 ExitFunction1(hr = E_NOTIMPL);
590 }
591
592 er = vpfnMsiEnumProductsExW(wzProductCode, wzUserSid, dwContext, dwIndex, wzInstalledProductCode, pdwInstalledContext, wzSid, pcchSid);
593 if (ERROR_NO_MORE_ITEMS == er)
594 {
595 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
596 }
597 ExitOnWin32Error(er, hr, "Failed to enumerate products.");
598
599LExit:
600 return hr;
601}
602
603
604extern "C" HRESULT DAPI WiuEnumRelatedProducts(
605 __in_z LPCWSTR wzUpgradeCode,
606 __in DWORD iProductIndex,
607 __out_ecount(MAX_GUID_CHARS + 1) LPWSTR wzProductCode
608 )
609{
610 HRESULT hr = S_OK;
611 DWORD er = ERROR_SUCCESS;
612
613 er = vpfnMsiEnumRelatedProductsW(wzUpgradeCode, 0, iProductIndex, wzProductCode);
614 if (ERROR_NO_MORE_ITEMS == er)
615 {
616 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
617 }
618 ExitOnWin32Error(er, hr, "Failed to enumerate related products for updgrade code: %ls", wzUpgradeCode);
619
620LExit:
621 return hr;
622}
623
624/********************************************************************
625 WiuEnumRelatedProductCodes - Returns an array of related products for a given upgrade code.
626
627 Parameters:
628 wzUpgradeCode - The upgrade code that will be used to find the related products.
629 prgsczProductCodes - Pointer to the array that will contain the product codes.
630 pcRelatedProducts - Returns the count of the number of related products found.
631 fReturnHighestVersionOnly - When set to "TRUE", will only return the product code of the highest version found.
632********************************************************************/
633extern "C" HRESULT DAPI WiuEnumRelatedProductCodes(
634 __in_z LPCWSTR wzUpgradeCode,
635 __deref_out_ecount_opt(pcRelatedProducts) LPWSTR** prgsczProductCodes,
636 __out DWORD* pcRelatedProducts,
637 __in BOOL fReturnHighestVersionOnly
638 )
639{
640 HRESULT hr = S_OK;
641 WCHAR wzCurrentProductCode[MAX_GUID_CHARS + 1] = { };
642 LPWSTR sczInstalledVersion = NULL;
643 DWORD64 qwCurrentVersion = 0;
644 DWORD64 qwHighestVersion = 0;
645
646 // make sure we start at zero
647 *pcRelatedProducts = 0;
648
649 for (DWORD i = 0; ; ++i)
650 {
651 hr = WiuEnumRelatedProducts(wzUpgradeCode, i, wzCurrentProductCode);
652
653 if (E_NOMOREITEMS == hr)
654 {
655 hr = S_OK;
656 break;
657 }
658 ExitOnFailure(hr, "Failed to enumerate related products for upgrade code: %ls", wzUpgradeCode);
659
660 if (fReturnHighestVersionOnly)
661 {
662 // get the version
663 hr = WiuGetProductInfo(wzCurrentProductCode, L"VersionString", &sczInstalledVersion);
664 ExitOnFailure(hr, "Failed to get version for product code: %ls", wzCurrentProductCode);
665
666 hr = FileVersionFromStringEx(sczInstalledVersion, 0, &qwCurrentVersion);
667 ExitOnFailure(hr, "Failed to convert version: %ls to DWORD64 for product code: %ls", sczInstalledVersion, wzCurrentProductCode);
668
669 // if this is the first product found then it is the highest version (for now)
670 if (0 == *pcRelatedProducts)
671 {
672 qwHighestVersion = qwCurrentVersion;
673 }
674 else
675 {
676 // if this is the highest version encountered so far then overwrite
677 // the first item in the array (there will never be more than one item)
678 if (qwCurrentVersion > qwHighestVersion)
679 {
680 qwHighestVersion = qwCurrentVersion;
681
682 hr = StrAllocString(prgsczProductCodes[0], wzCurrentProductCode, 0);
683 ExitOnFailure(hr, "Failed to update array with higher versioned product code.");
684 }
685
686 // continue here as we don't want anything else added to the list
687 continue;
688 }
689 }
690
691 hr = StrArrayAllocString(prgsczProductCodes, (LPUINT)(pcRelatedProducts), wzCurrentProductCode, 0);
692 ExitOnFailure(hr, "Failed to add product code to array.");
693 }
694
695LExit:
696 ReleaseStr(sczInstalledVersion);
697 return hr;
698}
699
700
701extern "C" HRESULT DAPI WiuEnableLog(
702 __in DWORD dwLogMode,
703 __in_z LPCWSTR wzLogFile,
704 __in DWORD dwLogAttributes
705 )
706{
707 HRESULT hr = S_OK;
708 DWORD er = ERROR_SUCCESS;
709
710 er = vpfnMsiEnableLogW(dwLogMode, wzLogFile, dwLogAttributes);
711 ExitOnWin32Error(er, hr, "Failed to enable MSI internal logging.");
712
713LExit:
714 return hr;
715}
716
717
718extern "C" HRESULT DAPI WiuInitializeExternalUI(
719 __in PFN_MSIEXECUTEMESSAGEHANDLER pfnMessageHandler,
720 __in INSTALLUILEVEL internalUILevel,
721 __in HWND hwndParent,
722 __in LPVOID pvContext,
723 __in BOOL fRollback,
724 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
725 )
726{
727 HRESULT hr = S_OK;
728 DWORD er = ERROR_SUCCESS;
729
730 DWORD dwMessageFilter = INSTALLLOGMODE_INITIALIZE | INSTALLLOGMODE_TERMINATE |
731 INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING |
732 INSTALLLOGMODE_RESOLVESOURCE | INSTALLLOGMODE_OUTOFDISKSPACE |
733 INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA | INSTALLLOGMODE_COMMONDATA|
734 INSTALLLOGMODE_PROGRESS | INSTALLLOGMODE_FILESINUSE;
735
736 if (MAKEDWORD(0, 4) <= vdwMsiDllMajorMinor)
737 {
738 dwMessageFilter |= INSTALLLOGMODE_RMFILESINUSE;
739 }
740
741 memset(pExecuteContext, 0, sizeof(WIU_MSI_EXECUTE_CONTEXT));
742 pExecuteContext->fRollback = fRollback;
743 pExecuteContext->pfnMessageHandler = pfnMessageHandler;
744 pExecuteContext->pvContext = pvContext;
745
746 // Wire the internal and external UI handler.
747 pExecuteContext->previousInstallUILevel = vpfnMsiSetInternalUI(internalUILevel, &hwndParent);
748 pExecuteContext->hwndPreviousParentWindow = hwndParent;
749
750 // If the external UI record is available (MSI version >= 3.1) use it but fall back to the standard external
751 // UI handler if necesary.
752 if (vpfnMsiSetExternalUIRecord)
753 {
754 er = vpfnMsiSetExternalUIRecord(InstallEngineRecordCallback, dwMessageFilter, pExecuteContext, &pExecuteContext->pfnPreviousExternalUIRecord);
755 ExitOnWin32Error(er, hr, "Failed to wire up external UI record handler.");
756 pExecuteContext->fSetPreviousExternalUIRecord = TRUE;
757 }
758 else
759 {
760 pExecuteContext->pfnPreviousExternalUI = vpfnMsiSetExternalUIW(InstallEngineCallback, dwMessageFilter, pExecuteContext);
761 pExecuteContext->fSetPreviousExternalUI = TRUE;
762 }
763
764LExit:
765 return hr;
766}
767
768
769extern "C" void DAPI WiuUninitializeExternalUI(
770 __in WIU_MSI_EXECUTE_CONTEXT* pExecuteContext
771 )
772{
773 if (INSTALLUILEVEL_NOCHANGE != pExecuteContext->previousInstallUILevel)
774 {
775 pExecuteContext->previousInstallUILevel = vpfnMsiSetInternalUI(pExecuteContext->previousInstallUILevel, &pExecuteContext->hwndPreviousParentWindow);
776 }
777
778 if (pExecuteContext->fSetPreviousExternalUI) // unset the UI handler
779 {
780 vpfnMsiSetExternalUIW(pExecuteContext->pfnPreviousExternalUI, 0, NULL);
781 }
782
783 if (pExecuteContext->fSetPreviousExternalUIRecord) // unset the UI record handler
784 {
785 vpfnMsiSetExternalUIRecord(pExecuteContext->pfnPreviousExternalUIRecord, 0, NULL, NULL);
786 }
787
788 memset(pExecuteContext, 0, sizeof(WIU_MSI_EXECUTE_CONTEXT));
789}
790
791
792extern "C" HRESULT DAPI WiuConfigureProductEx(
793 __in_z LPCWSTR wzProduct,
794 __in int iInstallLevel,
795 __in INSTALLSTATE eInstallState,
796 __in_z LPCWSTR wzCommandLine,
797 __out WIU_RESTART* pRestart
798 )
799{
800 HRESULT hr = S_OK;
801 DWORD er = ERROR_SUCCESS;
802
803 er = vpfnMsiConfigureProductExW(wzProduct, iInstallLevel, eInstallState, wzCommandLine);
804 er = CheckForRestartErrorCode(er, pRestart);
805 ExitOnWin32Error(er, hr, "Failed to configure product: %ls", wzProduct);
806
807LExit:
808 return hr;
809}
810
811
812extern "C" HRESULT DAPI WiuInstallProduct(
813 __in_z LPCWSTR wzPackagePath,
814 __in_z LPCWSTR wzCommandLine,
815 __out WIU_RESTART* pRestart
816 )
817{
818 HRESULT hr = S_OK;
819 DWORD er = ERROR_SUCCESS;
820
821 er = vpfnMsiInstallProductW(wzPackagePath, wzCommandLine);
822 er = CheckForRestartErrorCode(er, pRestart);
823 ExitOnWin32Error(er, hr, "Failed to install product: %ls", wzPackagePath);
824
825LExit:
826 return hr;
827}
828
829
830extern "C" HRESULT DAPI WiuRemovePatches(
831 __in_z LPCWSTR wzPatchList,
832 __in_z LPCWSTR wzProductCode,
833 __in_z LPCWSTR wzPropertyList,
834 __out WIU_RESTART* pRestart
835 )
836{
837 HRESULT hr = S_OK;
838 DWORD er = ERROR_SUCCESS;
839
840 er = vpfnMsiRemovePatchesW(wzPatchList, wzProductCode, INSTALLTYPE_SINGLE_INSTANCE, wzPropertyList);
841 er = CheckForRestartErrorCode(er, pRestart);
842 ExitOnWin32Error(er, hr, "Failed to remove patches.");
843
844LExit:
845 return hr;
846}
847
848
849extern "C" HRESULT DAPI WiuSourceListAddSourceEx(
850 __in_z LPCWSTR wzProductCodeOrPatchCode,
851 __in_z_opt LPCWSTR wzUserSid,
852 __in MSIINSTALLCONTEXT dwContext,
853 __in DWORD dwCode,
854 __in_z LPCWSTR wzSource,
855 __in_opt DWORD dwIndex
856 )
857{
858 HRESULT hr = S_OK;
859 DWORD er = ERROR_SUCCESS;
860
861 er = vpfnMsiSourceListAddSourceExW(wzProductCodeOrPatchCode, wzUserSid, dwContext, MSISOURCETYPE_NETWORK | dwCode, wzSource, dwIndex);
862 ExitOnWin32Error(er, hr, "Failed to add source.");
863
864LExit:
865 return hr;
866}
867
868
869
870static DWORD CheckForRestartErrorCode(
871 __in DWORD dwErrorCode,
872 __out WIU_RESTART* pRestart
873 )
874{
875 switch (dwErrorCode)
876 {
877 case ERROR_SUCCESS_REBOOT_REQUIRED:
878 case ERROR_SUCCESS_RESTART_REQUIRED:
879 *pRestart = WIU_RESTART_REQUIRED;
880 dwErrorCode = ERROR_SUCCESS;
881 break;
882
883 case ERROR_SUCCESS_REBOOT_INITIATED:
884 case ERROR_INSTALL_SUSPEND:
885 *pRestart = WIU_RESTART_INITIATED;
886 dwErrorCode = ERROR_SUCCESS;
887 break;
888 }
889
890 return dwErrorCode;
891}
892
893static INT CALLBACK InstallEngineCallback(
894 __in LPVOID pvContext,
895 __in UINT uiMessage,
896 __in_z_opt LPCWSTR wzMessage
897 )
898{
899 INT nResult = IDNOACTION;
900 WIU_MSI_EXECUTE_CONTEXT* pContext = (WIU_MSI_EXECUTE_CONTEXT*)pvContext;
901 INSTALLMESSAGE mt = static_cast<INSTALLMESSAGE>(0xFF000000 & uiMessage);
902 UINT uiFlags = 0x00FFFFFF & uiMessage;
903
904 if (wzMessage)
905 {
906 if (INSTALLMESSAGE_PROGRESS == mt)
907 {
908 nResult = HandleInstallProgress(pContext, wzMessage, NULL);
909 }
910 else
911 {
912 nResult = HandleInstallMessage(pContext, mt, uiFlags, wzMessage, NULL);
913 }
914 }
915
916 return nResult;
917}
918
919static INT CALLBACK InstallEngineRecordCallback(
920 __in LPVOID pvContext,
921 __in UINT uiMessage,
922 __in_opt MSIHANDLE hRecord
923 )
924{
925 INT nResult = IDNOACTION;
926 HRESULT hr = S_OK;
927 WIU_MSI_EXECUTE_CONTEXT* pContext = (WIU_MSI_EXECUTE_CONTEXT*)pvContext;
928
929 INSTALLMESSAGE mt = static_cast<INSTALLMESSAGE>(0xFF000000 & uiMessage);
930 UINT uiFlags = 0x00FFFFFF & uiMessage;
931 LPWSTR sczMessage = NULL;
932 DWORD cchMessage = 0;
933
934 if (hRecord)
935 {
936 if (INSTALLMESSAGE_PROGRESS == mt)
937 {
938 nResult = HandleInstallProgress(pContext, NULL, hRecord);
939 }
940 else
941 {
942 // create formated message string
943#pragma prefast(push)
944#pragma prefast(disable:6298) // docs explicitly say this is a valid option for getting the buffer size
945 DWORD er = ::MsiFormatRecordW(NULL, hRecord, L"", &cchMessage);
946#pragma prefast(pop)
947 if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
948 {
949 hr = StrAlloc(&sczMessage, ++cchMessage);
950 }
951 else
952 {
953 hr = HRESULT_FROM_WIN32(er);
954 }
955 ExitOnFailure(hr, "Failed to allocate string for formated message.");
956
957 er = ::MsiFormatRecordW(NULL, hRecord, sczMessage, &cchMessage);
958 ExitOnWin32Error(er, hr, "Failed to format message record.");
959
960 // Pass to handler including both the formated message and the original record.
961 nResult = HandleInstallMessage(pContext, mt, uiFlags, sczMessage, hRecord);
962 }
963 }
964
965LExit:
966 ReleaseStr(sczMessage);
967 return nResult;
968}
969
970static INT HandleInstallMessage(
971 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
972 __in INSTALLMESSAGE mt,
973 __in UINT uiFlags,
974 __in_z LPCWSTR wzMessage,
975 __in_opt MSIHANDLE hRecord
976 )
977{
978 INT nResult = IDNOACTION;
979
980Trace(REPORT_STANDARD, "MSI install[%x]: %ls", pContext->dwCurrentProgressIndex, wzMessage);
981
982 // Handle the message.
983 switch (mt)
984 {
985 case INSTALLMESSAGE_INITIALIZE: // this message is received prior to internal UI initialization, no string data
986 ResetProgress(pContext);
987 break;
988
989 case INSTALLMESSAGE_TERMINATE: // sent after UI termination, no string data
990 break;
991
992 case INSTALLMESSAGE_ACTIONSTART:
993 if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex && pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData)
994 {
995 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
996 }
997
998 nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
999 break;
1000
1001 case INSTALLMESSAGE_ACTIONDATA:
1002 if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex && pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData)
1003 {
1004 if (pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward)
1005 {
1006 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted += pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep;
1007 }
1008 else // rollback.
1009 {
1010 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted -= pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep;
1011 }
1012
1013 nResult = SendProgressUpdate(pContext);
1014 }
1015 else
1016 {
1017 nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
1018 }
1019 break;
1020
1021 case INSTALLMESSAGE_OUTOFDISKSPACE: __fallthrough;
1022 case INSTALLMESSAGE_FATALEXIT: __fallthrough;
1023 case INSTALLMESSAGE_ERROR:
1024 nResult = SendErrorMessage(pContext, uiFlags, wzMessage, hRecord);
1025 break;
1026
1027 case INSTALLMESSAGE_FILESINUSE:
1028 case INSTALLMESSAGE_RMFILESINUSE:
1029 nResult = SendFilesInUseMessage(pContext, hRecord, INSTALLMESSAGE_RMFILESINUSE == mt);
1030 break;
1031
1032/*
1033#if 0
1034 case INSTALLMESSAGE_COMMONDATA:
1035 if (L'1' == wzMessage[0] && L':' == wzMessage[1] && L' ' == wzMessage[2])
1036 {
1037 if (L'0' == wzMessage[3])
1038 {
1039 // TODO: handle the language common data message.
1040 lres = IDOK;
1041 return lres;
1042 }
1043 else if (L'1' == wzMessage[3])
1044 {
1045 // TODO: really handle sending the caption.
1046 lres = ::SendSuxMessage(pInstallContext->pSetupUXInformation, SRM_EXEC_SET_CAPTION, uiFlags, reinterpret_cast<LPARAM>(wzMessage + 3));
1047 return lres;
1048 }
1049 else if (L'2' == wzMessage[3])
1050 {
1051 // TODO: really handle sending the cancel button status.
1052 lres = ::SendSuxMessage(pInstallContext->pSetupUXInformation, SRM_EXEC_SET_CANCEL, uiFlags, reinterpret_cast<LPARAM>(wzMessage + 3));
1053 return lres;
1054 }
1055 }
1056 break;
1057#endif
1058*/
1059
1060 //case INSTALLMESSAGE_WARNING:
1061 //case INSTALLMESSAGE_USER:
1062 //case INSTALLMESSAGE_INFO:
1063 //case INSTALLMESSAGE_SHOWDIALOG: // sent prior to display of authored dialog or wizard
1064 default:
1065 nResult = SendMsiMessage(pContext, mt, uiFlags, wzMessage, hRecord);
1066 break;
1067 }
1068
1069 // Always return "no action" (0) for resolve source messages.
1070 return (INSTALLMESSAGE_RESOLVESOURCE == mt) ? IDNOACTION : nResult;
1071}
1072
1073static INT HandleInstallProgress(
1074 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1075 __in_z_opt LPCWSTR wzMessage,
1076 __in_opt MSIHANDLE hRecord
1077 )
1078{
1079 HRESULT hr = S_OK;
1080 INT nResult = IDNOACTION;
1081 INT iFields[4] = { };
1082 INT cFields = 0;
1083 LPCWSTR pwz = NULL;
1084 DWORD cch = 0;
1085
1086 // get field values
1087 if (hRecord)
1088 {
1089 cFields = ::MsiRecordGetFieldCount(hRecord);
1090 cFields = min(cFields, countof(iFields)); // avoid buffer overrun if there are more fields than our buffer can hold
1091 for (INT i = 0; i < cFields; ++i)
1092 {
1093 iFields[i] = ::MsiRecordGetInteger(hRecord, i + 1);
1094 }
1095 }
1096 else
1097 {
1098 Assert(wzMessage);
1099
1100 // parse message string
1101 pwz = wzMessage;
1102 while (cFields < 4)
1103 {
1104 // check if we have the start of a valid part
1105 if ((L'1' + cFields) != pwz[0] || L':' != pwz[1] || L' ' != pwz[2])
1106 {
1107 break;
1108 }
1109 pwz += 3;
1110
1111 // find character count of number
1112 cch = 0;
1113 while (pwz[cch] && L' ' != pwz[cch])
1114 {
1115 ++cch;
1116 }
1117
1118 // parse number
1119 hr = StrStringToInt32(pwz, cch, &iFields[cFields]);
1120 ExitOnFailure(hr, "Failed to parse MSI message part.");
1121
1122 // increment field count
1123 ++cFields;
1124 }
1125 }
1126
1127#ifdef _DEBUG
1128 WCHAR wz[256];
1129 ::StringCchPrintfW(wz, countof(wz), L"1: %d 2: %d 3: %d 4: %d", iFields[0], iFields[1], iFields[2], iFields[3]);
1130 Trace(REPORT_STANDARD, "MSI progress[%x]: %ls", pContext->dwCurrentProgressIndex, wz);
1131#endif
1132
1133 // Verify that we have the enough field values.
1134 if (1 > cFields)
1135 {
1136 ExitFunction(); // unknown message, bail
1137 }
1138
1139 // Handle based on message type.
1140 switch (iFields[0])
1141 {
1142 case 0: // master progress reset
1143 if (4 > cFields)
1144 {
1145 Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
1146 ExitFunction();
1147 }
1148 //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - MASTER RESET - %d, %d, %d", iFields[1], iFields[2], iFields[3]);
1149
1150 // Update the index into progress array.
1151 if (WIU_MSI_PROGRESS_INVALID == pContext->dwCurrentProgressIndex)
1152 {
1153 pContext->dwCurrentProgressIndex = 0;
1154 }
1155 else if (pContext->dwCurrentProgressIndex + 1 < countof(pContext->rgMsiProgress))
1156 {
1157 ++pContext->dwCurrentProgressIndex;
1158 }
1159 else
1160 {
1161 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
1162 ExitOnRootFailure(hr, "Insufficient space to hold progress information.");
1163 }
1164
1165 // we only care about the first stage after script execution has started
1166 //if (!pEngineInfo->fMsiProgressScriptInProgress && 1 != iFields[3])
1167 //{
1168 // pEngineInfo->fMsiProgressFinished = TRUE;
1169 //}
1170
1171 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal = iFields[1];
1172 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted = 0 == iFields[2] ? 0 : iFields[1]; // if forward start at 0, if backwards start at max
1173 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward = (0 == iFields[2]);
1174 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
1175 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fScriptInProgress = (1 == iFields[3]);
1176
1177 if (0 == pContext->dwCurrentProgressIndex)
1178 {
1179 // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
1180 // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress
1181 // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this
1182 // "close" and deal with the rest.
1183 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal += 50;
1184 }
1185 break;
1186
1187 case 1: // action info.
1188 if (3 > cFields)
1189 {
1190 Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
1191 ExitFunction();
1192 }
1193 //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - ACTION INFO - %d, %d, %d", iFields[1], iFields[2], iFields[3]);
1194
1195 if (0 == iFields[2])
1196 {
1197 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = FALSE;
1198 }
1199 else
1200 {
1201 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fEnableActionData = TRUE;
1202 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwStep = iFields[1];
1203 }
1204 break;
1205
1206 case 2: // progress report.
1207 if (2 > cFields)
1208 {
1209 Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - Invalid field count %d, '%ls'", cFields, wzMessage);
1210 break;
1211 }
1212
1213 //Trace(REPORT_STANDARD, "INSTALLMESSAGE_PROGRESS - PROGRESS REPORT - %d, %d, %d", iFields[1], iFields[2], iFields[3]);
1214
1215 if (WIU_MSI_PROGRESS_INVALID == pContext->dwCurrentProgressIndex)
1216 {
1217 break;
1218 }
1219 else if (0 == pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal)
1220 {
1221 break;
1222 }
1223
1224 // Update progress.
1225 if (pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].fMoveForward)
1226 {
1227 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted += iFields[1];
1228 }
1229 else // rollback.
1230 {
1231 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted -= iFields[1];
1232 }
1233 break;
1234
1235 case 3: // extend the progress bar.
1236 pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal += iFields[1];
1237 break;
1238
1239 default:
1240 ExitFunction(); // unknown message, bail
1241 }
1242
1243 // If we have a valid progress index, send an update.
1244 if (WIU_MSI_PROGRESS_INVALID != pContext->dwCurrentProgressIndex)
1245 {
1246 nResult = SendProgressUpdate(pContext);
1247 }
1248
1249LExit:
1250 return nResult;
1251}
1252
1253static INT SendMsiMessage(
1254 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1255 __in INSTALLMESSAGE mt,
1256 __in UINT uiFlags,
1257 __in_z LPCWSTR wzMessage,
1258 __in_opt MSIHANDLE hRecord
1259 )
1260{
1261 INT nResult = IDNOACTION;
1262 WIU_MSI_EXECUTE_MESSAGE message = { };
1263 LPWSTR* rgsczData = NULL;
1264 DWORD cData = 0;
1265
1266 InitializeMessageData(hRecord, &rgsczData, &cData);
1267
1268 message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_MESSAGE;
1269 message.dwAllowedResults = uiFlags;
1270 message.cData = cData;
1271 message.rgwzData = (LPCWSTR*)rgsczData;
1272 message.msiMessage.mt = mt;
1273 message.msiMessage.wzMessage = wzMessage;
1274 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1275
1276 UninitializeMessageData(rgsczData, cData);
1277 return nResult;
1278}
1279
1280static INT SendErrorMessage(
1281 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1282 __in UINT uiFlags,
1283 __in_z LPCWSTR wzMessage,
1284 __in_opt MSIHANDLE hRecord
1285 )
1286{
1287 INT nResult = IDNOACTION;
1288 WIU_MSI_EXECUTE_MESSAGE message = { };
1289 DWORD dwErrorCode = 0;
1290 LPWSTR* rgsczData = NULL;
1291 DWORD cData = 0;
1292
1293 if (hRecord)
1294 {
1295 dwErrorCode = ::MsiRecordGetInteger(hRecord, 1);
1296
1297 // Set the recommendation if it's a known error code.
1298 switch (dwErrorCode)
1299 {
1300 case 1605: // continue with install even if there isn't enough room for rollback.
1301 nResult = IDIGNORE;
1302 break;
1303
1304 case 1704: // rollback suspended installs so our install can continue.
1305 nResult = IDOK;
1306 break;
1307 }
1308 }
1309
1310 InitializeMessageData(hRecord, &rgsczData, &cData);
1311
1312 message.type = WIU_MSI_EXECUTE_MESSAGE_ERROR;
1313 message.dwAllowedResults = uiFlags;
1314 message.nResultRecommendation = nResult;
1315 message.cData = cData;
1316 message.rgwzData = (LPCWSTR*)rgsczData;
1317 message.error.dwErrorCode = dwErrorCode;
1318 message.error.wzMessage = wzMessage;
1319 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1320
1321 UninitializeMessageData(rgsczData, cData);
1322 return nResult;
1323}
1324
1325static INT SendFilesInUseMessage(
1326 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1327 __in_opt MSIHANDLE hRecord,
1328 __in BOOL /*fRestartManagerRequest*/
1329 )
1330{
1331 INT nResult = IDNOACTION;
1332 WIU_MSI_EXECUTE_MESSAGE message = { };
1333 LPWSTR* rgsczData = NULL;
1334 DWORD cData = 0;
1335
1336 InitializeMessageData(hRecord, &rgsczData, &cData);
1337
1338 message.type = WIU_MSI_EXECUTE_MESSAGE_MSI_FILES_IN_USE;
1339 message.dwAllowedResults = WIU_MB_OKIGNORECANCELRETRY;
1340 message.cData = cData;
1341 message.rgwzData = (LPCWSTR*)rgsczData;
1342 message.msiFilesInUse.cFiles = message.cData; // point the files in use information to the message record information.
1343 message.msiFilesInUse.rgwzFiles = message.rgwzData;
1344 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1345
1346 UninitializeMessageData(rgsczData, cData);
1347 return nResult;
1348}
1349
1350static INT SendProgressUpdate(
1351 __in WIU_MSI_EXECUTE_CONTEXT* pContext
1352 )
1353{
1354 int nResult = IDNOACTION;
1355 DWORD dwPercentage = 0; // number representing 0 - 100%
1356 WIU_MSI_EXECUTE_MESSAGE message = { };
1357
1358 //DWORD dwMsiProgressTotal = pEngineInfo->dwMsiProgressTotal;
1359 //DWORD dwMsiProgressComplete = pEngineInfo->dwMsiProgressComplete; //min(dwMsiProgressTotal, pEngineInfo->dwMsiProgressComplete);
1360 //double dProgressGauge = 0;
1361 //double dProgressStageTotal = (double)pEngineInfo->qwProgressStageTotal;
1362
1363 // Calculate progress for the phases of Windows Installer.
1364 // TODO: handle upgrade progress which would add another phase.
1365 dwPercentage += CalculatePhaseProgress(pContext, 0, 15);
1366 dwPercentage += CalculatePhaseProgress(pContext, 1, 80);
1367 dwPercentage += CalculatePhaseProgress(pContext, 2, 5);
1368 dwPercentage = min(dwPercentage, 100); // ensure the percentage never goes over 100%.
1369
1370 if (pContext->fRollback)
1371 {
1372 dwPercentage = 100 - dwPercentage;
1373 }
1374
1375 //if (qwTotal) // avoid "divide by zero" if the MSI range is blank.
1376 //{
1377 // // calculate gauge.
1378 // double dProgressGauge = static_cast<double>(qwCompleted) / static_cast<double>(qwTotal);
1379 // dProgressGauge = (1.0 / (1.0 + exp(3.7 - dProgressGauge * 7.5)) - 0.024127021417669196) / 0.975872978582330804;
1380 // qwCompleted = (DWORD)(dProgressGauge * qwTotal);
1381
1382 // // calculate progress within range
1383 // //qwProgressComplete = (DWORD64)(dwMsiProgressComplete * (dProgressStageTotal / dwMsiProgressTotal));
1384 // //qwProgressComplete = min(qwProgressComplete, pEngineInfo->qwProgressStageTotal);
1385 //}
1386
1387#ifdef _DEBUG
1388 DWORD64 qwCompleted = pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwCompleted;
1389 DWORD64 qwTotal = pContext->rgMsiProgress[pContext->dwCurrentProgressIndex].dwTotal;
1390 Trace(REPORT_STANDARD, "MSI progress: %I64u/%I64u (%u%%)", qwCompleted, qwTotal, dwPercentage);
1391 //AssertSz(qwCompleted <= qwTotal, "Completed progress is larger than total progress.");
1392#endif
1393
1394 message.type = WIU_MSI_EXECUTE_MESSAGE_PROGRESS;
1395 message.dwAllowedResults = MB_OKCANCEL;
1396 message.progress.dwPercentage = dwPercentage;
1397 nResult = pContext->pfnMessageHandler(&message, pContext->pvContext);
1398
1399 return nResult;
1400}
1401
1402static void ResetProgress(
1403 __in WIU_MSI_EXECUTE_CONTEXT* pContext
1404 )
1405{
1406 memset(pContext->rgMsiProgress, 0, sizeof(pContext->rgMsiProgress));
1407 pContext->dwCurrentProgressIndex = WIU_MSI_PROGRESS_INVALID;
1408}
1409
1410static DWORD CalculatePhaseProgress(
1411 __in WIU_MSI_EXECUTE_CONTEXT* pContext,
1412 __in DWORD dwProgressIndex,
1413 __in DWORD dwWeightPercentage
1414 )
1415{
1416 DWORD dwPhasePercentage = 0;
1417
1418 // If we've already passed this progress index, return the maximum percentage possible (the weight)
1419 if (dwProgressIndex < pContext->dwCurrentProgressIndex)
1420 {
1421 dwPhasePercentage = dwWeightPercentage;
1422 }
1423 else if (dwProgressIndex == pContext->dwCurrentProgressIndex) // have to do the math for the current progress.
1424 {
1425 WIU_MSI_PROGRESS* pProgress = pContext->rgMsiProgress + dwProgressIndex;
1426 if (pProgress->dwTotal)
1427 {
1428 DWORD64 dw64Completed = pProgress->dwCompleted;
1429 dwPhasePercentage = static_cast<DWORD>(dw64Completed * dwWeightPercentage / pProgress->dwTotal);
1430 }
1431 }
1432 // else we're not there yet so it has to be zero.
1433
1434 return dwPhasePercentage;
1435}
1436
1437void InitializeMessageData(
1438 __in_opt MSIHANDLE hRecord,
1439 __deref_out_ecount(*pcData) LPWSTR** prgsczData,
1440 __out DWORD* pcData
1441 )
1442{
1443 DWORD cData = 0;
1444 LPWSTR* rgsczData = NULL;
1445
1446 // If we have a record based message, try to get the extra data.
1447 if (hRecord)
1448 {
1449 cData = ::MsiRecordGetFieldCount(hRecord);
1450 if (cData)
1451 {
1452 rgsczData = (LPWSTR*)MemAlloc(sizeof(LPWSTR*) * cData, TRUE);
1453 }
1454
1455 for (DWORD i = 0; rgsczData && i < cData; ++i)
1456 {
1457 DWORD cch = 0;
1458
1459 // get string from record
1460#pragma prefast(push)
1461#pragma prefast(disable:6298)
1462 DWORD er = ::MsiRecordGetStringW(hRecord, i + 1, L"", &cch);
1463#pragma prefast(pop)
1464 if (ERROR_MORE_DATA == er)
1465 {
1466 HRESULT hr = StrAlloc(&rgsczData[i], ++cch);
1467 if (SUCCEEDED(hr))
1468 {
1469 er = ::MsiRecordGetStringW(hRecord, i + 1, rgsczData[i], &cch);
1470 }
1471 }
1472 }
1473 }
1474
1475 *prgsczData = rgsczData;
1476 *pcData = cData;
1477}
1478
1479void UninitializeMessageData(
1480 __in LPWSTR* rgsczData,
1481 __in DWORD cData
1482 )
1483{
1484 // Clean up if there was any data allocated.
1485 if (rgsczData)
1486 {
1487 for (DWORD i = 0; i < cData; ++i)
1488 {
1489 ReleaseStr(rgsczData[i]);
1490 }
1491
1492 MemFree(rgsczData);
1493 }
1494}