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