@joebigelow / wix-1 / commits / fb54576f

Give BA process id and option to wait for cancelled process to exit.

Sean Hall committed Mar 18, 2022 at 20:15 UTC fb54576f1d05e82ba47cd718c4c4f8b3bad624c9
31 files changed +716 -124
src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h
+27
@@ -224,6 +224,7 @@ enum BOOTSTRAPPER_APPLICATION_MESSAGE
224 BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE,
225 BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE,
226 BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE,
227 + BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL,
228 };
229
230 enum BOOTSTRAPPER_APPLYCOMPLETE_ACTION
@@ -282,6 +283,18 @@ enum BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION
283 BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_SUSPEND,
284 };
285
286 +enum BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION
287 +{
288 + // Instructs the engine to stop waiting for the process to exit.
289 + // The package is immediately considered to have failed with ERROR_INSTALL_USEREXIT.
290 + // The engine will never rollback the package.
291 + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_ABANDON,
292 + // Instructs the engine to wait for the process to exit.
293 + // Once the process has exited, the package is considered to have failed with ERROR_INSTALL_USEREXIT.
294 + // This allows the engine to rollback the package if necessary.
295 + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_WAIT,
296 +};
297 +
298 enum BOOTSTRAPPER_SHUTDOWN_ACTION
299 {
300 BOOTSTRAPPER_SHUTDOWN_ACTION_NONE,
@@ -997,6 +1010,20 @@ struct BA_ONEXECUTEPATCHTARGET_RESULTS
1010 BOOL fCancel;
1011 };
1012
1013 +struct BA_ONEXECUTEPROCESSCANCEL_ARGS
1014 +{
1015 + DWORD cbSize;
1016 + LPCWSTR wzPackageId;
1017 + DWORD dwProcessId;
1018 + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation;
1019 +};
1020 +
1021 +struct BA_ONEXECUTEPROCESSCANCEL_RESULTS
1022 +{
1023 + DWORD cbSize;
1024 + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action;
1025 +};
1026 +
1027 struct BA_ONEXECUTEPROGRESS_ARGS
1028 {
1029 DWORD cbSize;
src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs
+25
@@ -280,6 +280,9 @@ namespace WixToolset.Mba.Core
280 /// <inheritdoc/>
281 public event EventHandler<PlanRestoreRelatedBundleEventArgs> PlanRestoreRelatedBundle;
282
283 + /// <inheritdoc/>
284 + public event EventHandler<ExecuteProcessCancelEventArgs> ExecuteProcessCancel;
285 +
286 /// <summary>
287 /// Entry point that is called when the bootstrapper application is ready to run.
288 /// </summary>
@@ -1369,6 +1372,19 @@ namespace WixToolset.Mba.Core
1372 }
1373 }
1374
1375 + /// <summary>
1376 + /// Called by the engine, raises the <see cref="ExecuteProcessCancel"/> event.
1377 + /// </summary>
1378 + /// <param name="args">Additional arguments for this event.</param>
1379 + protected virtual void OnExecuteProcessCancel(ExecuteProcessCancelEventArgs args)
1380 + {
1381 + EventHandler<ExecuteProcessCancelEventArgs> handler = this.ExecuteProcessCancel;
1382 + if (null != handler)
1383 + {
1384 + handler(this, args);
1385 + }
1386 + }
1387 +
1388 #region IBootstrapperApplication Members
1389
1390 int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults, IntPtr pvContext)
@@ -2119,6 +2135,15 @@ namespace WixToolset.Mba.Core
2135 return args.HResult;
2136 }
2137
2138 + int IBootstrapperApplication.OnExecuteProcessCancel(string wzPackageId, int processId, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, ref BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION pAction)
2139 + {
2140 + ExecuteProcessCancelEventArgs args = new ExecuteProcessCancelEventArgs(wzPackageId, processId, recommendation, pAction);
2141 + this.OnExecuteProcessCancel(args);
2142 +
2143 + pAction = args.Action;
2144 + return args.HResult;
2145 + }
2146 +
2147 #endregion
2148 }
2149 }
src/api/burn/WixToolset.Mba.Core/EventArgs.cs
+36
@@ -2488,4 +2488,40 @@ namespace WixToolset.Mba.Core
2488 /// </summary>
2489 public RequestState State { get; set; }
2490 }
2491 +
2492 + /// <summary>
2493 + /// Event arguments for <see cref="IDefaultBootstrapperApplication.ExecuteProcessCancel"/>
2494 + /// </summary>
2495 + [Serializable]
2496 + public class ExecuteProcessCancelEventArgs : HResultEventArgs
2497 + {
2498 + /// <summary />
2499 + public ExecuteProcessCancelEventArgs(string packageId, int processId, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action)
2500 + {
2501 + this.PackageId = packageId;
2502 + this.ProcessId = processId;
2503 + this.Recommendation = recommendation;
2504 + this.Action = action;
2505 + }
2506 +
2507 + /// <summary>
2508 + /// Gets the identity of the package.
2509 + /// </summary>
2510 + public string PackageId { get; private set; }
2511 +
2512 + /// <summary>
2513 + /// Gets the process id.
2514 + /// </summary>
2515 + public int ProcessId { get; private set; }
2516 +
2517 + /// <summary>
2518 + /// Gets the recommended action from the engine.
2519 + /// </summary>
2520 + public BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION Recommendation { get; private set; }
2521 +
2522 + /// <summary>
2523 + /// Gets or sets the action to be performed. This is passed back to the engine.
2524 + /// </summary>
2525 + public BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION Action { get; set; }
2526 + }
2527 }
src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs
+32
@@ -1170,6 +1170,18 @@ namespace WixToolset.Mba.Core
1170 [MarshalAs(UnmanagedType.I4)] int hrRecommended,
1171 [MarshalAs(UnmanagedType.I4)] ref int hrStatus
1172 );
1173 +
1174 + /// <summary>
1175 + /// See <see cref="IDefaultBootstrapperApplication.ExecuteProcessCancel"/>.
1176 + /// </summary>
1177 + [PreserveSig]
1178 + [return: MarshalAs(UnmanagedType.I4)]
1179 + int OnExecuteProcessCancel(
1180 + [MarshalAs(UnmanagedType.LPWStr)] string wzPackageId,
1181 + int processId,
1182 + [MarshalAs(UnmanagedType.I4)] BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation,
1183 + [MarshalAs(UnmanagedType.I4)] ref BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION pAction
1184 + );
1185 }
1186
1187 /// <summary>
@@ -1906,6 +1918,26 @@ namespace WixToolset.Mba.Core
1918 Suspend,
1919 }
1920
1921 + /// <summary>
1922 + /// The available actions for <see cref="IDefaultBootstrapperApplication.ExecuteProcessCancel"/>.
1923 + /// </summary>
1924 + public enum BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION
1925 + {
1926 + /// <summary>
1927 + /// Instructs the engine to stop waiting for the process to exit.
1928 + /// The package is immediately considered to have failed with ERROR_INSTALL_USEREXIT.
1929 + /// The engine will never rollback the package.
1930 + /// </summary>
1931 + Abandon,
1932 +
1933 + /// <summary>
1934 + /// Instructs the engine to wait for the process to exit.
1935 + /// Once the process has exited, the package is considered to have failed with ERROR_INSTALL_USEREXIT.
1936 + /// This allows the engine to rollback the package if necessary.
1937 + /// </summary>
1938 + Wait,
1939 + }
1940 +
1941 /// <summary>
1942 /// The result of evaluating a condition from a package.
1943 /// </summary>
src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs
+5
@@ -243,6 +243,11 @@ namespace WixToolset.Mba.Core
243 /// </summary>
244 event EventHandler<ExecutePackageCompleteEventArgs> ExecutePackageComplete;
245
246 + /// <summary>
247 + /// Fired when a package that spawned a process is cancelled.
248 + /// </summary>
249 + event EventHandler<ExecuteProcessCancelEventArgs> ExecuteProcessCancel;
250 +
251 /// <summary>
252 /// Fired when the engine executes one or more patches targeting a product.
253 /// </summary>
src/api/burn/balutil/inc/BAFunctions.h
+1
@@ -91,6 +91,7 @@ enum BA_FUNCTIONS_MESSAGE
91 BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE,
92 BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE,
93 BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE,
94 + BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL = BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL,
95
96 BA_FUNCTIONS_MESSAGE_ONTHEMELOADED = 1024,
97 BA_FUNCTIONS_MESSAGE_WNDPROC,
src/api/burn/balutil/inc/BalBaseBAFunctions.h
+10
@@ -877,6 +877,16 @@ public: // IBootstrapperApplication
877 return S_OK;
878 }
879
880 + virtual STDMETHODIMP OnExecuteProcessCancel(
881 + __in_z LPCWSTR /*wzPackageId*/,
882 + __in DWORD /*dwProcessId*/,
883 + __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION /*recommendation*/,
884 + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* /*pAction*/
885 + )
886 + {
887 + return S_OK;
888 + }
889 +
890 public: // IBAFunctions
891 virtual STDMETHODIMP OnPlan(
892 )
src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h
+1
@@ -162,6 +162,7 @@ static HRESULT WINAPI BalBaseBAFunctionsProc(
162 case BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE:
163 case BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE:
164 case BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE:
165 + case BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL:
166 hr = BalBaseBootstrapperApplicationProc((BOOTSTRAPPER_APPLICATION_MESSAGE)message, pvArgs, pvResults, pvContext);
167 break;
168 case BA_FUNCTIONS_MESSAGE_ONTHEMELOADED:
src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h
+10
@@ -1077,6 +1077,16 @@ public: // IBootstrapperApplication
1077 return S_OK;
1078 }
1079
1080 + virtual STDMETHODIMP OnExecuteProcessCancel(
1081 + __in_z LPCWSTR /*wzPackageId*/,
1082 + __in DWORD /*dwProcessId*/,
1083 + __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION /*recommendation*/,
1084 + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* /*pAction*/
1085 + )
1086 + {
1087 + return S_OK;
1088 + }
1089 +
1090 public: //CBalBaseBootstrapperApplication
1091 virtual STDMETHODIMP Initialize(
1092 __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs
src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h
+12
@@ -486,6 +486,15 @@ static HRESULT BalBaseBAProcOnExecutePackageComplete(
486 return pBA->OnExecutePackageComplete(pArgs->wzPackageId, pArgs->hrStatus, pArgs->restart, pArgs->recommendation, &pResults->action);
487 }
488
489 +static HRESULT BalBaseBAProcOnExecuteProcessCancel(
490 + __in IBootstrapperApplication* pBA,
491 + __in BA_ONEXECUTEPROCESSCANCEL_ARGS* pArgs,
492 + __inout BA_ONEXECUTEPROCESSCANCEL_RESULTS* pResults
493 + )
494 +{
495 + return pBA->OnExecuteProcessCancel(pArgs->wzPackageId, pArgs->dwProcessId, pArgs->recommendation, &pResults->action);
496 +}
497 +
498 static HRESULT BalBaseBAProcOnExecuteComplete(
499 __in IBootstrapperApplication* pBA,
500 __in BA_ONEXECUTECOMPLETE_ARGS* pArgs,
@@ -1012,6 +1021,9 @@ static HRESULT WINAPI BalBaseBootstrapperApplicationProc(
1021 case BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE:
1022 hr = BalBaseBAProcOnApplyDowngrade(pBA, reinterpret_cast<BA_ONAPPLYDOWNGRADE_ARGS*>(pvArgs), reinterpret_cast<BA_ONAPPLYDOWNGRADE_RESULTS*>(pvResults));
1023 break;
1024 + case BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL:
1025 + hr = BalBaseBAProcOnExecuteProcessCancel(pBA, reinterpret_cast<BA_ONEXECUTEPROCESSCANCEL_ARGS*>(pvArgs), reinterpret_cast<BA_ONEXECUTEPROCESSCANCEL_RESULTS*>(pvResults));
1026 + break;
1027 }
1028 }
1029
src/api/burn/balutil/inc/IBootstrapperApplication.h
+9
@@ -714,4 +714,13 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A
714 __in HRESULT hrRecommended,
715 __inout HRESULT* phrStatus
716 ) = 0;
717 +
718 + // OnExecuteProcessCancel - called when a package that spawned a process is cancelled.
719 + //
720 + STDMETHOD(OnExecuteProcessCancel)(
721 + __in_z LPCWSTR wzPackageId,
722 + __in DWORD dwProcessId,
723 + __in BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation,
724 + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction
725 + ) = 0;
726 };
src/burn/engine/apply.cpp
+8
@@ -3326,6 +3326,14 @@ static int GenericExecuteMessageHandler(
3326 }
3327 break;
3328
3329 + case GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL:
3330 + {
3331 + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action = BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_ABANDON;
3332 + UserExperienceOnExecuteProcessCancel(pContext->pUX, pContext->wzExecutingPackageId, pMessage->processCancel.dwProcessId, &action); // ignore return value.
3333 + nResult = BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION_WAIT == action ? IDRETRY : IDIGNORE;
3334 + }
3335 + break;
3336 +
3337 case GENERIC_EXECUTE_MESSAGE_ERROR:
3338 UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->wzExecutingPackageId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, 0, NULL, &nResult); // ignore return value.
3339 break;
src/burn/engine/apply.h
+5
@@ -13,6 +13,7 @@ enum GENERIC_EXECUTE_MESSAGE_TYPE
13 GENERIC_EXECUTE_MESSAGE_ERROR,
14 GENERIC_EXECUTE_MESSAGE_PROGRESS,
15 GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE,
16 + GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL,
17 };
18
19 typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA
@@ -43,6 +44,10 @@ typedef struct _GENERIC_EXECUTE_MESSAGE
44 DWORD cFiles;
45 LPCWSTR* rgwzFiles;
46 } filesInUse;
47 + struct
48 + {
49 + DWORD dwProcessId;
50 + } processCancel;
51 };
52 } GENERIC_EXECUTE_MESSAGE;
53
src/burn/engine/bundlepackageengine.cpp
+3 -25
@@ -251,7 +251,6 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
251 )
252 {
253 HRESULT hr = S_OK;
254 - int nResult = IDNOACTION;
254 LPCWSTR wzArguments = NULL;
255 LPWSTR sczArguments = NULL;
256 LPWSTR sczArgumentsFormatted = NULL;
@@ -420,31 +419,10 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
419 hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode);
420 ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath);
421 }
423 - else // create and wait for the executable process while sending fake progress to allow cancel.
422 + else
423 {
425 - // Make the cache location of the executable the current directory to help those executables
426 - // that expect stuff to be relative to them.
427 - si.cb = sizeof(si);
428 - if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi))
429 - {
430 - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath);
431 - }
432 -
433 - do
434 - {
435 - message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS;
436 - message.dwUIHint = MB_OKCANCEL;
437 - message.progress.dwPercentage = 50;
438 - nResult = pfnGenericMessageHandler(&message, pvContext);
439 - hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE);
440 - ExitOnRootFailure(hr, "Bootstrapper application aborted during BUNDLE progress.");
441 -
442 - hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode);
443 - if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr)
444 - {
445 - ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath);
446 - }
447 - } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr);
424 + hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczExecutablePath, sczCommand, sczCachedDirectory, &dwExitCode);
425 + ExitOnFailure(hr, "Failed to run BUNDLE process");
426 }
427
428 hr = ExeEngineHandleExitCode(pPackage->Bundle.rgExitCodes, pPackage->Bundle.cExitCodes, dwExitCode, pRestart);
src/burn/engine/elevation.cpp
+16 -1
@@ -43,6 +43,7 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE
43 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE,
44 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS,
45 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS,
46 + BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL,
47 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR,
48 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE,
49 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE,
@@ -1812,7 +1813,14 @@ static HRESULT ProcessGenericExecuteMessages(
1813
1814 // read message parameters
1815 hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.progress.dwPercentage);
1815 - ExitOnFailure(hr, "Failed to progress.");
1816 + ExitOnFailure(hr, "Failed to read progress.");
1817 + break;
1818 +
1819 + case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL:
1820 + message.type = GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL;
1821 +
1822 + hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.processCancel.dwProcessId);
1823 + ExitOnFailure(hr, "Failed to read processId.");
1824 break;
1825
1826 case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR:
@@ -3450,6 +3458,13 @@ static int GenericExecuteMessageHandler(
3458 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS;
3459 break;
3460
3461 + case GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL:
3462 + hr = BuffWriteNumber(&pbData, &cbData, pMessage->processCancel.dwProcessId);
3463 + ExitOnFailure(hr, "Failed to write progress percentage to message buffer.");
3464 +
3465 + dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL;
3466 + break;
3467 +
3468 case GENERIC_EXECUTE_MESSAGE_ERROR:
3469 // serialize message data
3470 hr = BuffWriteNumber(&pbData, &cbData, pMessage->error.dwErrorCode);
src/burn/engine/engine.mc
+7
@@ -933,6 +933,13 @@ Language=English
933 Could not create system restore point, error: 0x%1!x!. Continuing...
934 .
935
936 +MessageId=364
937 +Severity=Success
938 +SymbolicName=MSG_EXECUTE_PROCESS_DELAYED_CANCEL_REQUESTED
939 +Language=English
940 +Bootstrapper application requested delayed cancel during package process progress, id: %1!ls!. Waiting...
941 +.
942 +
943 MessageId=370
944 Severity=Success
945 SymbolicName=MSG_SESSION_BEGIN
src/burn/engine/exeengine.cpp
+96 -36
@@ -317,7 +317,6 @@ extern "C" HRESULT ExeEngineExecutePackage(
317 )
318 {
319 HRESULT hr = S_OK;
320 - int nResult = IDNOACTION;
320 LPCWSTR wzArguments = NULL;
321 LPWSTR sczArguments = NULL;
322 LPWSTR sczArgumentsFormatted = NULL;
@@ -327,10 +326,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
326 LPWSTR sczCommand = NULL;
327 LPWSTR sczCommandObfuscated = NULL;
328 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
330 - STARTUPINFOW si = { };
331 - PROCESS_INFORMATION pi = { };
329 DWORD dwExitCode = 0;
333 - GENERIC_EXECUTE_MESSAGE message = { };
330 BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage;
331 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload;
332
@@ -442,37 +438,10 @@ extern "C" HRESULT ExeEngineExecutePackage(
438 hr = NetFxRunChainer(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode);
439 ExitOnFailure(hr, "Failed to run netfx chainer: %ls", sczExecutablePath);
440 }
445 - else // create and wait for the executable process while sending fake progress to allow cancel.
441 + else
442 {
447 - // Make the cache location of the executable the current directory to help those executables
448 - // that expect stuff to be relative to them.
449 - si.cb = sizeof(si);
450 - if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi))
451 - {
452 - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath);
453 - }
454 -
455 - if (pPackage->Exe.fFireAndForget)
456 - {
457 - ::WaitForInputIdle(pi.hProcess, 5000);
458 - ExitFunction();
459 - }
460 -
461 - do
462 - {
463 - message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS;
464 - message.dwUIHint = MB_OKCANCEL;
465 - message.progress.dwPercentage = 50;
466 - nResult = pfnGenericMessageHandler(&message, pvContext);
467 - hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE);
468 - ExitOnRootFailure(hr, "Bootstrapper application aborted during EXE progress.");
469 -
470 - hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode);
471 - if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr)
472 - {
473 - ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath);
474 - }
475 - } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr);
443 + hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczExecutablePath, sczCommand, sczCachedDirectory, &dwExitCode);
444 + ExitOnFailure(hr, "Failed to run EXE process");
445 }
446
447 hr = ExeEngineHandleExitCode(pPackage->Exe.rgExitCodes, pPackage->Exe.cExitCodes, dwExitCode, pRestart);
@@ -487,8 +456,6 @@ LExit:
456 StrSecureZeroFreeString(sczCommand);
457 ReleaseStr(sczCommandObfuscated);
458
490 - ReleaseHandle(pi.hThread);
491 - ReleaseHandle(pi.hProcess);
459 ReleaseFileHandle(hExecutableFile);
460
461 // Best effort to clear the execute package cache folder and action variables.
@@ -498,6 +465,99 @@ LExit:
465 return hr;
466 }
467
468 +extern "C" HRESULT ExeEngineRunProcess(
469 + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
470 + __in LPVOID pvContext,
471 + __in BURN_PACKAGE* pPackage,
472 + __in_z LPCWSTR wzExecutablePath,
473 + __in_z LPWSTR wzCommand,
474 + __in_z_opt LPCWSTR wzCachedDirectory,
475 + __inout DWORD* pdwExitCode
476 + )
477 +{
478 + HRESULT hr = S_OK;
479 + STARTUPINFOW si = { };
480 + PROCESS_INFORMATION pi = { };
481 + GENERIC_EXECUTE_MESSAGE message = { };
482 + int nResult = IDNOACTION;
483 + DWORD dwProcessId = 0;
484 + BOOL fDelayedCancel = FALSE;
485 + BOOL fFireAndForget = BURN_PACKAGE_TYPE_EXE == pPackage->type && pPackage->Exe.fFireAndForget;
486 + BOOL fInheritHandles = BURN_PACKAGE_TYPE_BUNDLE == pPackage->type;
487 +
488 + // Make the cache location of the executable the current directory to help those executables
489 + // that expect stuff to be relative to them.
490 + si.cb = sizeof(si);
491 + if (!::CreateProcessW(wzExecutablePath, wzCommand, NULL, NULL, fInheritHandles, CREATE_NO_WINDOW, NULL, wzCachedDirectory, &si, &pi))
492 + {
493 + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", wzExecutablePath);
494 + }
495 +
496 + if (fFireAndForget)
497 + {
498 + ::WaitForInputIdle(pi.hProcess, 5000);
499 + ExitFunction();
500 + }
501 +
502 + dwProcessId = ::GetProcessId(pi.hProcess);
503 +
504 + // Wait for the executable process while sending fake progress to allow cancel.
505 + do
506 + {
507 + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS;
508 + message.dwUIHint = MB_OKCANCEL;
509 + message.progress.dwPercentage = 50;
510 + nResult = pfnGenericMessageHandler(&message, pvContext);
511 +
512 + if (IDCANCEL == nResult)
513 + {
514 + memset(&message, 0, sizeof(message));
515 + message.type = GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL;
516 + message.dwUIHint = MB_ABORTRETRYIGNORE;
517 + message.processCancel.dwProcessId = dwProcessId;
518 + nResult = pfnGenericMessageHandler(&message, pvContext);
519 +
520 + if (IDIGNORE == nResult) // abandon
521 + {
522 + nResult = IDCANCEL;
523 + fDelayedCancel = FALSE;
524 + }
525 + //else if (IDABORT == nResult) // kill
526 + else // wait
527 + {
528 + if (!fDelayedCancel)
529 + {
530 + fDelayedCancel = TRUE;
531 +
532 + LogId(REPORT_STANDARD, MSG_EXECUTE_PROCESS_DELAYED_CANCEL_REQUESTED, pPackage->sczId);
533 + }
534 +
535 + nResult = IDNOACTION;
536 + }
537 + }
538 +
539 + hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE);
540 + ExitOnRootFailure(hr, "Bootstrapper application aborted during package process progress.");
541 +
542 + hr = ProcWaitForCompletion(pi.hProcess, 500, pdwExitCode);
543 + if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr)
544 + {
545 + ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", wzExecutablePath);
546 + }
547 + } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr);
548 +
549 + if (fDelayedCancel)
550 + {
551 + ExitWithRootFailure(hr, HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT), "Bootstrapper application cancelled during package process progress, exit code: 0x%x", *pdwExitCode);
552 + }
553 +
554 +LExit:
555 + ReleaseHandle(pi.hThread);
556 + ReleaseHandle(pi.hProcess);
557 +
558 + return hr;
559 +}
560 +
561 extern "C" void ExeEngineUpdateInstallRegistrationState(
562 __in BURN_EXECUTE_ACTION* pAction,
563 __in HRESULT hrExecute
src/burn/engine/exeengine.h
+9
@@ -42,6 +42,15 @@ HRESULT ExeEngineExecutePackage(
42 __in LPVOID pvContext,
43 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
44 );
45 +HRESULT ExeEngineRunProcess(
46 + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler,
47 + __in LPVOID pvContext,
48 + __in BURN_PACKAGE* pPackage,
49 + __in_z LPCWSTR wzExecutablePath,
50 + __in_z LPWSTR wzCommand,
51 + __in_z_opt LPCWSTR wzCachedDirectory,
52 + __inout DWORD* pdwExitCode
53 + );
54 void ExeEngineUpdateInstallRegistrationState(
55 __in BURN_EXECUTE_ACTION* pAction,
56 __in HRESULT hrExecute
src/burn/engine/msuengine.cpp
+2 -30
@@ -264,7 +264,6 @@ extern "C" HRESULT MsuEngineExecutePackage(
264 )
265 {
266 HRESULT hr = S_OK;
267 - int nResult = IDNOACTION;
267 LPWSTR sczCachedDirectory = NULL;
268 LPWSTR sczMsuPath = NULL;
269 LPWSTR sczWindowsPath = NULL;
@@ -350,35 +349,8 @@ extern "C" HRESULT MsuEngineExecutePackage(
349 hr = EnsureWUServiceEnabled(fStopWusaService, &schWu, &fWuWasDisabled);
350 ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package.");
351
353 - // create process
354 - si.cb = sizeof(si);
355 - if (!::CreateProcessW(sczWusaPath, sczCommand, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
356 - {
357 - ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczWusaPath);
358 - }
359 -
360 - do
361 - {
362 - message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS;
363 - message.dwUIHint = MB_OKCANCEL;
364 - message.progress.dwPercentage = 50;
365 - nResult = pfnGenericMessageHandler(&message, pvContext);
366 - hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE);
367 - ExitOnRootFailure(hr, "Bootstrapper application aborted during MSU progress.");
368 -
369 - // wait for process to terminate
370 - hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode);
371 - if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr)
372 - {
373 - ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczWusaPath);
374 - }
375 - } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr);
376 -
377 - // get process exit code
378 - if (!::GetExitCodeProcess(pi.hProcess, &dwExitCode))
379 - {
380 - ExitWithLastError(hr, "Failed to get process exit code.");
381 - }
352 + hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczWusaPath, sczCommand, NULL, &dwExitCode);
353 + ExitOnFailure(hr, "Failed to run MSU process");
354
355 // We'll normalize the restart required error code from wusa.exe just in case. Most likely
356 // that on reboot we'll actually get WU_S_REBOOT_REQUIRED.
src/burn/engine/userexperience.cpp
+29 -1
@@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad(
104 args.pCommand = pCommand;
105 args.pfnBootstrapperEngineProc = EngineForApplicationProc;
106 args.pvBootstrapperEngineProcContext = pEngineContext;
107 - args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 14, 0);
107 + args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 17, 0);
108
109 results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS);
110
@@ -1701,6 +1701,34 @@ LExit:
1701 return hr;
1702 }
1703
1704 +BAAPI UserExperienceOnExecuteProcessCancel(
1705 + __in BURN_USER_EXPERIENCE* pUserExperience,
1706 + __in_z LPCWSTR wzPackageId,
1707 + __in DWORD dwProcessId,
1708 + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction
1709 + )
1710 +{
1711 + HRESULT hr = S_OK;
1712 + BA_ONEXECUTEPROCESSCANCEL_ARGS args = { };
1713 + BA_ONEXECUTEPROCESSCANCEL_RESULTS results = { };
1714 +
1715 + args.cbSize = sizeof(args);
1716 + args.wzPackageId = wzPackageId;
1717 + args.dwProcessId = dwProcessId;
1718 + args.recommendation = *pAction;
1719 +
1720 + results.cbSize = sizeof(results);
1721 + results.action = *pAction;
1722 +
1723 + hr = SendBAMessage(pUserExperience, BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL, &args, &results);
1724 + ExitOnFailure(hr, "BA OnExecuteProcessCancel failed.");
1725 +
1726 + *pAction = results.action;
1727 +
1728 +LExit:
1729 + return hr;
1730 +}
1731 +
1732 EXTERN_C BAAPI UserExperienceOnExecuteProgress(
1733 __in BURN_USER_EXPERIENCE* pUserExperience,
1734 __in_z LPCWSTR wzPackageId,
src/burn/engine/userexperience.h
+6
@@ -398,6 +398,12 @@ BAAPI UserExperienceOnExecutePatchTarget(
398 __in_z LPCWSTR wzPackageId,
399 __in_z LPCWSTR wzTargetProductCode
400 );
401 +BAAPI UserExperienceOnExecuteProcessCancel(
402 + __in BURN_USER_EXPERIENCE* pUserExperience,
403 + __in_z LPCWSTR wzPackageId,
404 + __in DWORD dwProcessId,
405 + __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction
406 + );
407 BAAPI UserExperienceOnExecuteProgress(
408 __in BURN_USER_EXPERIENCE* pUserExperience,
409 __in_z LPCWSTR wzPackageId,
src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj
+2
@@ -80,7 +80,9 @@
80 <ItemGroup>
81 <None Include="TestData\CacheTest\CacheSignatureTest.File" CopyToOutputDirectory="PreserveNewest" />
82 <None Include="TestData\PlanTest\BasicFunctionality_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
83 + <None Include="TestData\PlanTest\Failure_BundleD_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
84 <None Include="TestData\PlanTest\MsiTransaction_BundleAv1_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
85 + <None Include="TestData\PlanTest\MsuPackageFixture_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
86 <None Include="TestData\PlanTest\Slipstream_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
87 <None Include="TestData\PlanTest\Slipstream_BundleA_modified_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
88 </ItemGroup>
src/burn/test/BurnUnitTest/PlanTest.cpp
+200
@@ -10,7 +10,9 @@ static HRESULT WINAPI PlanTestBAProc(
10 );
11
12 static LPCWSTR wzMsiTransactionManifestFileName = L"MsiTransaction_BundleAv1_manifest.xml";
13 +static LPCWSTR wzSingleExeManifestFileName = L"Failure_BundleD_manifest.xml";
14 static LPCWSTR wzSingleMsiManifestFileName = L"BasicFunctionality_BundleA_manifest.xml";
15 +static LPCWSTR wzSingleMsuManifestFileName = L"MsuPackageFixture_manifest.xml";
16 static LPCWSTR wzSlipstreamManifestFileName = L"Slipstream_BundleA_manifest.xml";
17 static LPCWSTR wzSlipstreamModifiedManifestFileName = L"Slipstream_BundleA_modified_manifest.xml";
18
@@ -658,6 +660,97 @@ namespace Bootstrapper
660 ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT);
661 }
662
663 + [Fact]
664 + void SingleExeInstallTest()
665 + {
666 + HRESULT hr = S_OK;
667 + BURN_ENGINE_STATE engineState = { };
668 + BURN_ENGINE_STATE* pEngineState = &engineState;
669 + BURN_PLAN* pPlan = &engineState.plan;
670 +
671 + InitializeEngineStateForCorePlan(wzSingleExeManifestFileName, pEngineState);
672 + DetectAttachedContainerAsAttached(pEngineState);
673 + DetectPermanentPackagesAsPresentAndCached(pEngineState);
674 +
675 + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL);
676 + NativeAssert::Succeeded(hr, "CorePlan failed");
677 +
678 + Assert::Equal<DWORD>(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action);
679 + NativeAssert::StringEqual(L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", pPlan->wzBundleId);
680 + NativeAssert::StringEqual(L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", pPlan->wzBundleProviderKey);
681 + Assert::Equal<BOOL>(FALSE, pPlan->fEnabledForwardCompatibleBundle);
682 + Assert::Equal<BOOL>(TRUE, pPlan->fPerMachine);
683 + Assert::Equal<BOOL>(TRUE, pPlan->fCanAffectMachineState);
684 + Assert::Equal<BOOL>(FALSE, pPlan->fDisableRollback);
685 + Assert::Equal<BOOL>(FALSE, pPlan->fDisallowRemoval);
686 + Assert::Equal<BOOL>(FALSE, pPlan->fDowngrade);
687 +
688 + BOOL fRollback = FALSE;
689 + DWORD dwIndex = 0;
690 + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}");
691 + Assert::Equal(dwIndex, pPlan->cRegistrationActions);
692 +
693 + fRollback = TRUE;
694 + dwIndex = 0;
695 + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}");
696 + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions);
697 +
698 + fRollback = FALSE;
699 + dwIndex = 0;
700 + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1);
701 + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"ExeA");
702 + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++);
703 + Assert::Equal(dwIndex, pPlan->cCacheActions);
704 +
705 + fRollback = TRUE;
706 + dwIndex = 0;
707 + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions);
708 +
709 + Assert::Equal(1463267ull, pPlan->qwEstimatedSize);
710 + Assert::Equal(119695ull, pPlan->qwCacheSizeTotal);
711 +
712 + fRollback = FALSE;
713 + dwIndex = 0;
714 + DWORD dwExecuteCheckpointId = 2;
715 + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
716 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
717 + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"ExeA");
718 + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"ExeA", BOOTSTRAPPER_ACTION_STATE_INSTALL);
719 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
720 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
721 + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
722 + Assert::Equal(dwIndex, pPlan->cExecuteActions);
723 +
724 + fRollback = TRUE;
725 + dwIndex = 0;
726 + dwExecuteCheckpointId = 2;
727 + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
728 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
729 + ValidateExecuteExePackage(pPlan, fRollback, dwIndex++, L"ExeA", BOOTSTRAPPER_ACTION_STATE_UNINSTALL);
730 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
731 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
732 + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
733 + Assert::Equal(dwIndex, pPlan->cRollbackActions);
734 +
735 + Assert::Equal(1ul, pPlan->cExecutePackagesTotal);
736 + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal);
737 +
738 + dwIndex = 0;
739 + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions);
740 +
741 + dwIndex = 0;
742 + ValidateCleanAction(pPlan, dwIndex++, L"NetFx48Web");
743 + ValidateCleanAction(pPlan, dwIndex++, L"ExeA");
744 + Assert::Equal(dwIndex, pPlan->cCleanActions);
745 +
746 + UINT uIndex = 0;
747 + ValidatePlannedProvider(pPlan, uIndex++, L"{9C184683-04FB-49AD-9D79-65101BDC3EE3}", NULL);
748 + Assert::Equal(uIndex, pPlan->cPlannedProviders);
749 +
750 + Assert::Equal(2ul, pEngineState->packages.cPackages);
751 + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[1], L"ExeA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT);
752 + }
753 +
754 [Fact]
755 void SingleMsiCacheTest()
756 {
@@ -1532,6 +1625,98 @@ namespace Bootstrapper
1625 ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"PackageA", BURN_PACKAGE_REGISTRATION_STATE_ABSENT, BURN_PACKAGE_REGISTRATION_STATE_ABSENT);
1626 }
1627
1628 + [Fact]
1629 + void SingleMsuInstallTest()
1630 + {
1631 + HRESULT hr = S_OK;
1632 + BURN_ENGINE_STATE engineState = { };
1633 + BURN_ENGINE_STATE* pEngineState = &engineState;
1634 + BURN_PLAN* pPlan = &engineState.plan;
1635 +
1636 + InitializeEngineStateForCorePlan(wzSingleMsuManifestFileName, pEngineState);
1637 + DetectAttachedContainerAsAttached(pEngineState);
1638 + DetectPackagesAsAbsent(pEngineState);
1639 +
1640 + hr = CorePlan(pEngineState, BOOTSTRAPPER_ACTION_INSTALL);
1641 + NativeAssert::Succeeded(hr, "CorePlan failed");
1642 +
1643 + Assert::Equal<DWORD>(BOOTSTRAPPER_ACTION_INSTALL, pPlan->action);
1644 + NativeAssert::StringEqual(L"{06077C60-DC46-4F4A-8D3C-05F869187191}", pPlan->wzBundleId);
1645 + NativeAssert::StringEqual(L"{06077C60-DC46-4F4A-8D3C-05F869187191}", pPlan->wzBundleProviderKey);
1646 + Assert::Equal<BOOL>(FALSE, pPlan->fEnabledForwardCompatibleBundle);
1647 + Assert::Equal<BOOL>(TRUE, pPlan->fPerMachine);
1648 + Assert::Equal<BOOL>(TRUE, pPlan->fCanAffectMachineState);
1649 + Assert::Equal<BOOL>(FALSE, pPlan->fDisableRollback);
1650 + Assert::Equal<BOOL>(FALSE, pPlan->fDisallowRemoval);
1651 + Assert::Equal<BOOL>(FALSE, pPlan->fDowngrade);
1652 +
1653 + BOOL fRollback = FALSE;
1654 + DWORD dwIndex = 0;
1655 + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, TRUE, L"{06077C60-DC46-4F4A-8D3C-05F869187191}", L"{06077C60-DC46-4F4A-8D3C-05F869187191}");
1656 + Assert::Equal(dwIndex, pPlan->cRegistrationActions);
1657 +
1658 + fRollback = TRUE;
1659 + dwIndex = 0;
1660 + ValidateDependentRegistrationAction(pPlan, fRollback, dwIndex++, FALSE, L"{06077C60-DC46-4F4A-8D3C-05F869187191}", L"{06077C60-DC46-4F4A-8D3C-05F869187191}");
1661 + Assert::Equal(dwIndex, pPlan->cRollbackRegistrationActions);
1662 +
1663 + fRollback = FALSE;
1664 + dwIndex = 0;
1665 + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1);
1666 + ValidateCachePackage(pPlan, fRollback, dwIndex++, L"test.msu");
1667 + ValidateCacheSignalSyncpoint(pPlan, fRollback, dwIndex++);
1668 + Assert::Equal(dwIndex, pPlan->cCacheActions);
1669 +
1670 + fRollback = TRUE;
1671 + dwIndex = 0;
1672 + ValidateCacheRollbackPackage(pPlan, fRollback, dwIndex++, L"test.msu");
1673 + ValidateCacheCheckpoint(pPlan, fRollback, dwIndex++, 1);
1674 + Assert::Equal(dwIndex, pPlan->cRollbackCacheActions);
1675 +
1676 + Assert::Equal(56ull, pPlan->qwEstimatedSize);
1677 + Assert::Equal(140ull, pPlan->qwCacheSizeTotal);
1678 +
1679 + fRollback = FALSE;
1680 + dwIndex = 0;
1681 + DWORD dwExecuteCheckpointId = 2;
1682 + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
1683 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1684 + ValidateExecuteWaitCachePackage(pPlan, fRollback, dwIndex++, L"test.msu");
1685 + ValidateExecuteMsuPackage(pPlan, fRollback, dwIndex++, L"test.msu", BOOTSTRAPPER_ACTION_STATE_INSTALL);
1686 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1687 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1688 + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
1689 + Assert::Equal(dwIndex, pPlan->cExecuteActions);
1690 +
1691 + fRollback = TRUE;
1692 + dwIndex = 0;
1693 + dwExecuteCheckpointId = 2;
1694 + ValidateExecuteRollbackBoundaryStart(pPlan, fRollback, dwIndex++, L"WixDefaultBoundary", TRUE, FALSE);
1695 + ValidateExecuteUncachePackage(pPlan, fRollback, dwIndex++, L"test.msu");
1696 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1697 + ValidateExecuteMsuPackage(pPlan, fRollback, dwIndex++, L"test.msu", BOOTSTRAPPER_ACTION_STATE_UNINSTALL);
1698 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1699 + ValidateExecuteCheckpoint(pPlan, fRollback, dwIndex++, dwExecuteCheckpointId++);
1700 + ValidateExecuteRollbackBoundaryEnd(pPlan, fRollback, dwIndex++);
1701 + Assert::Equal(dwIndex, pPlan->cRollbackActions);
1702 +
1703 + Assert::Equal(1ul, pPlan->cExecutePackagesTotal);
1704 + Assert::Equal(2ul, pPlan->cOverallProgressTicksTotal);
1705 +
1706 + dwIndex = 0;
1707 + Assert::Equal(dwIndex, pPlan->cRestoreRelatedBundleActions);
1708 +
1709 + dwIndex = 0;
1710 + Assert::Equal(dwIndex, pPlan->cCleanActions);
1711 +
1712 + UINT uIndex = 0;
1713 + ValidatePlannedProvider(pPlan, uIndex++, L"{06077C60-DC46-4F4A-8D3C-05F869187191}", NULL);
1714 + Assert::Equal(uIndex, pPlan->cPlannedProviders);
1715 +
1716 + Assert::Equal(1ul, pEngineState->packages.cPackages);
1717 + ValidateNonPermanentPackageExpectedStates(&pEngineState->packages.rgPackages[0], L"test.msu", BURN_PACKAGE_REGISTRATION_STATE_PRESENT, BURN_PACKAGE_REGISTRATION_STATE_PRESENT);
1718 + }
1719 +
1720 [Fact]
1721 void SlipstreamInstallTest()
1722 {
@@ -2571,6 +2756,21 @@ namespace Bootstrapper
2756 NativeAssert::StringEqual(wzPackageId, pOrderedPatch->pPackage->sczId);
2757 }
2758
2759 + void ValidateExecuteMsuPackage(
2760 + __in BURN_PLAN* pPlan,
2761 + __in BOOL fRollback,
2762 + __in DWORD dwIndex,
2763 + __in LPCWSTR wzPackageId,
2764 + __in BOOTSTRAPPER_ACTION_STATE action
2765 + )
2766 + {
2767 + BURN_EXECUTE_ACTION* pAction = ValidateExecuteActionExists(pPlan, fRollback, dwIndex);
2768 + Assert::Equal<DWORD>(BURN_EXECUTE_ACTION_TYPE_MSU_PACKAGE, pAction->type);
2769 + NativeAssert::StringEqual(wzPackageId, pAction->msuPackage.pPackage->sczId);
2770 + Assert::Equal<DWORD>(action, pAction->msuPackage.action);
2771 + Assert::Equal<BOOL>(FALSE, pAction->fDeleted);
2772 + }
2773 +
2774 void ValidateExecutePackageDependency(
2775 __in BURN_PLAN* pPlan,
2776 __in BOOL fRollback,
src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml new
+1
@@ -0,0 +1 @@
1 +<?xml version="1.0" encoding="utf-8"?><BurnManifest xmlns="http://wixtoolset.org/schemas/v4/2008/Burn"><Log PathVariable="WixBundleLog" Prefix="~FailureTests_BundleD" Extension=".log" /><RelatedBundle Id="{3C1A4842-81AC-4C90-8B35-A5E18F034C8D}" Action="Upgrade" /><Variable Id="TestGroupName" Value="FailureTests" Type="string" Hidden="no" Persisted="no" /><Variable Id="WixBundleInProgressName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSource" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSourceFolder" Hidden="no" Persisted="yes" /><Variable Id="WixBundleLastUsedSource" Hidden="no" Persisted="yes" /><RegistrySearch Id="NETFRAMEWORK45" Variable="NETFRAMEWORK45" Root="HKLM" Key="SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" Value="Release" Type="value" VariableType="string" /><RegistrySearch Id="wrsQ7JTGqvaQuDYjfHJoyjxtkLlR6c" Variable="ExeA_Version" Root="HKLM" Key="Software\WiX\Tests\FailureTests\ExeA" Value="Version" Type="value" VariableType="string" /><UX><Payload Id="WixManagedBootstrapperApplicationHost" FilePath="mbahost.dll" SourcePath="u30" /><Payload Id="payO60IVK4ATGzPpMz3rwVbUWl6DyU" FilePath="WixToolset.Mba.Host.config" SourcePath="u0" /><Payload Id="payxj4zDAKL2NVlz4ohp0GvwFHepyI" FilePath="TestBA.dll" SourcePath="u1" /><Payload Id="pay1hOSAUC8_D633cD2TXpIXCL30OU" FilePath="mbanative.dll" SourcePath="u2" /><Payload Id="payujy6Izl_BlUNfHt2eI.ADfjYAv4" FilePath="WixToolset.Mba.Core.dll" SourcePath="u3" /><Payload Id="payR4EbR4OTDZpPEycWaSSM_gZRBWM" FilePath="mbapreq.thm" SourcePath="u4" /><Payload Id="paylVCy2Ecl8pHPdJTCQZryUG4T9us" FilePath="mbapreq.png" SourcePath="u5" /><Payload Id="payTaG4B_lob1aLcKFaOqSSG3MPMpU" FilePath="mbapreq.wxl" SourcePath="u6" /><Payload Id="payZwIGuiezVTitZOoZKxyh2DdRSGs" FilePath="1028\mbapreq.wxl" SourcePath="u7" /><Payload Id="pay.herBWX.LlOh8jLsx24aWdunV_0" FilePath="1029\mbapreq.wxl" SourcePath="u8" /><Payload Id="pay8DkMszYsoxxdgX14huLDMYXylQg" FilePath="1030\mbapreq.wxl" SourcePath="u9" /><Payload Id="payPaHpoTeOdkW.TK99IDwktNLhTAg" FilePath="1031\mbapreq.wxl" SourcePath="u10" /><Payload Id="pay45AtAzterLTMzZgdxxtuYvaiXwU" FilePath="1032\mbapreq.wxl" SourcePath="u11" /><Payload Id="payA2VEKIqhePyNIEmr14eyH3JoVLc" FilePath="1035\mbapreq.wxl" SourcePath="u12" /><Payload Id="payvre23ObscjzhcaFIifUAkXMdPa8" FilePath="1036\mbapreq.wxl" SourcePath="u13" /><Payload Id="paytxUV3vuBbG2c.a9c.d_sZX2x6wA" FilePath="1038\mbapreq.wxl" SourcePath="u14" /><Payload Id="payYvMWRK9xelo5.sQn7jRkJIaBp9A" FilePath="1040\mbapreq.wxl" SourcePath="u15" /><Payload Id="pay68KKSApyQimbA25t6kSbqhdeH10" FilePath="1041\mbapreq.wxl" SourcePath="u16" /><Payload Id="paypiqxaHpYZqx.9eDVjQrj1igLbRY" FilePath="1042\mbapreq.wxl" SourcePath="u17" /><Payload Id="payTO0YwZzxKpbqdrBVUcVRTu3BFe8" FilePath="1043\mbapreq.wxl" SourcePath="u18" /><Payload Id="payIXg2ldBJukRzhqWolJVOEbTmF34" FilePath="1044\mbapreq.wxl" SourcePath="u19" /><Payload Id="payOHIZbSkIvrpwKkkXI173tv3u3B4" FilePath="1045\mbapreq.wxl" SourcePath="u20" /><Payload Id="payQRQ_UZl_R2UtV0xDXB2yeH2bg3E" FilePath="1046\mbapreq.wxl" SourcePath="u21" /><Payload Id="payhrejLLBfc1i27iN._QPhQ4K337I" FilePath="1049\mbapreq.wxl" SourcePath="u22" /><Payload Id="payqEzaDNzxB68vGp29jgDcCos6dvg" FilePath="1051\mbapreq.wxl" SourcePath="u23" /><Payload Id="paydz8Vk8xSTyYohgGXTSIxWGXL5.Q" FilePath="1053\mbapreq.wxl" SourcePath="u24" /><Payload Id="pay0HRUZTlbC3taSOffJBsEj92Br8Y" FilePath="1055\mbapreq.wxl" SourcePath="u25" /><Payload Id="payIvUOkc_EMH7laMFehefNolV8hZo" FilePath="1060\mbapreq.wxl" SourcePath="u26" /><Payload Id="payLFhOb.rHuk4sW5CYAPMShG0NjGI" FilePath="2052\mbapreq.wxl" SourcePath="u27" /><Payload Id="payqIKCmERK7Nhxx_nNXvRxdKqKDbI" FilePath="2070\mbapreq.wxl" SourcePath="u28" /><Payload Id="payqeWUzIVaEqjuRXN0z8ECC3Y4tCc" FilePath="3082\mbapreq.wxl" SourcePath="u29" /><Payload Id="paylfeHEjJSSTnNzY9QMZM2Ye3Ipy4" FilePath="mbapreq.dll" SourcePath="u31" /><Payload Id="payDPxs6uy8nbky.R7zhir2RRAfc.c" FilePath="WixToolset.Mba.Host.dll" SourcePath="u32" /><Payload Id="uxTxMXPVMXwQrPTMIGa5WGt93w0Ns" FilePath="BootstrapperApplicationData.xml" SourcePath="u33" /><Payload Id="uxYRbgitOs0K878jn5L_z7LdJ21KI" FilePath="BundleExtensionData.xml" SourcePath="u34" /></UX><Container Id="WixAttachedContainer" FileSize="24029" Hash="03F9C95A2ADA5563D3D937C0161F22A76E12F2F0AF2AA6BE567292D0AB122E2C42990E97CA9C1EE9A5F43A571B01C4ED7A3EA5759A6836AC8BFD959D7FFDCB18" FilePath="BundleD.exe" AttachedIndex="1" Attached="yes" Primary="yes" /><Payload Id="NetFx48Web" FilePath="redist\ndp48-web.exe" FileSize="1439328" CertificateRootPublicKeyIdentifier="F49F9B33E25E33CCA0BFB15A62B7C29FFAB3880B" CertificateRootThumbprint="ABDCA79AF9DD48A0EA702AD45260B3C03093FB4B" DownloadUrl="https://go.microsoft.com/fwlink/?LinkId=2085155" Packaging="external" SourcePath="redist\ndp48-web.exe" /><Payload Id="TestExe.exe" FilePath="TestExe.exe" FileSize="23552" Hash="4344604ECBA4DFE5DE7C680CB1AA5BD6FAA29BF95CE07740F02878C2BB1EF6DE6432944A0DB79B034D1C6F68CF80842EEE442EA8A551816E52D3F68901C50AB9" Packaging="embedded" SourcePath="a0" Container="WixAttachedContainer" /><Payload Id="paygJp32KbpyjbVEQFNbl5_izmhdZw" FilePath="TestExe.exe.config" FileSize="387" Hash="8C819A9E835F3921FA80C5C783AB0C42DDAADF0C0F2BEF8630EA122ABCB9DC8EAF0B14E061C46B37C92F55114BB09A8D5B1B613947A76A648953F2C63C0ACA63" Packaging="embedded" SourcePath="a1" Container="WixAttachedContainer" /><RollbackBoundary Id="WixDefaultBoundary" Vital="yes" Transaction="no" /><Registration Id="{9C184683-04FB-49AD-9D79-65101BDC3EE3}" ExecutableName="BundleD.exe" PerMachine="yes" Tag="" Version="1.0.0.0" ProviderKey="{9C184683-04FB-49AD-9D79-65101BDC3EE3}"><Arp Register="yes" DisplayName="~FailureTests - BundleD" DisplayVersion="1.0.0.0" /></Registration><Chain><ExePackage Id="NetFx48Web" Cache="remove" CacheId="642721C60D52051C7F3434D8710FE3406A7CFE10B2B39E90EA847719ED1697D7C614F2DF44AD50412B1DF8C98DD78FDC57CA1D047D28C81AC158092E5FB18040" InstallSize="1439328" Size="1439328" PerMachine="yes" Permanent="yes" Vital="yes" RollbackBoundaryForward="WixDefaultBoundary" LogPathVariable="NetFx48WebLog" RollbackLogPathVariable="WixBundleRollbackLog_NetFx48Web" DetectCondition="NETFRAMEWORK45 &gt;= 528040" InstallArguments="/q /norestart /log &quot;[NetFx48WebLog].html&quot;" UninstallArguments="" Uninstallable="no" RepairArguments="" Repairable="no" Protocol="netfx4"><PayloadRef Id="NetFx48Web" /></ExePackage><ExePackage Id="ExeA" Cache="remove" CacheId="4344604ECBA4DFE5DE7C680CB1AA5BD6FAA29BF95CE07740F02878C2BB1EF6DE6432944A0DB79B034D1C6F68CF80842EEE442EA8A551816E52D3F68901C50AB9" InstallSize="23939" Size="23939" PerMachine="yes" Permanent="no" Vital="yes" RollbackBoundaryBackward="WixDefaultBoundary" LogPathVariable="WixBundleLog_ExeA" RollbackLogPathVariable="WixBundleRollbackLog_ExeA" DetectCondition="ExeA_Version AND ExeA_Version &gt;= v1.0.0.0" InstallArguments="/s 5000 /regw &quot;HKLM\Software\WiX\Tests\FailureTests\ExeA,Version,String,1.0.0.0&quot;" UninstallArguments="/regd &quot;HKLM\Software\WiX\Tests\FailureTests\ExeA,Version&quot;" Uninstallable="yes" RepairArguments="/regw &quot;HKLM\Software\WiX\Tests\FailureTests\ExeA,Version,String,1.0.0.0&quot;" Repairable="yes"><PayloadRef Id="TestExe.exe" /><PayloadRef Id="paygJp32KbpyjbVEQFNbl5_izmhdZw" /></ExePackage></Chain><CommandLine Variables="upperCase" /></BurnManifest>
\ No newline at end of file
src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml new
+1
@@ -0,0 +1 @@
1 +<?xml version="1.0" encoding="utf-8"?><BurnManifest xmlns="http://wixtoolset.org/schemas/v4/2008/Burn"><Log PathVariable="WixBundleLog" Prefix="BurnBundle" Extension="log" /><RelatedBundle Id="{B94478B1-E1F3-4700-9CE8-6AA090854AEC}" Action="Upgrade" /><Variable Id="WixBundleInProgressName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleName" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSource" Hidden="no" Persisted="yes" /><Variable Id="WixBundleOriginalSourceFolder" Hidden="no" Persisted="yes" /><Variable Id="WixBundleLastUsedSource" Hidden="no" Persisted="yes" /><UX><Payload Id="payaQenPi7_8hq6T._EXtBW0NvR7gA" FilePath="fakeba.dll" SourcePath="u0" /><Payload Id="uxTxMXPVMXwQrPTMIGa5WGt93w0Ns" FilePath="BootstrapperApplicationData.xml" SourcePath="u1" /><Payload Id="uxYRbgitOs0K878jn5L_z7LdJ21KI" FilePath="BundleExtensionData.xml" SourcePath="u2" /></UX><Container Id="WixAttachedContainer" FileSize="119" Hash="06D28293FD57CD231E125EF9C82418A488928A98832A6937A77A3283A17A5C37F8D619C51759319A57E8F8A948FA73E8C5814185A0114130F3213AB268073555" FilePath="test.exe" AttachedIndex="1" Attached="yes" Primary="yes" /><Payload Id="test.msu" FilePath="test.msu" FileSize="28" Hash="B040F02D2F90E04E9AFBDC91C00CEB5DF97D48E205D96DC0A44E10AF8870794DAE62CA70224F12BE9112AA730BBE470CA81FB5617AAC690E832F3F84510E92BA" Packaging="embedded" SourcePath="a0" Container="WixAttachedContainer" /><RollbackBoundary Id="WixDefaultBoundary" Vital="yes" Transaction="no" /><Registration Id="{06077C60-DC46-4F4A-8D3C-05F869187191}" ExecutableName="test.exe" PerMachine="yes" Tag="" Version="1.0.0.0" ProviderKey="{06077C60-DC46-4F4A-8D3C-05F869187191}"><Arp Register="yes" DisplayName="BurnBundle" DisplayVersion="1.0.0.0" Publisher="Example Corporation" /></Registration><Chain><MsuPackage Id="test.msu" Cache="keep" CacheId="B040F02D2F90E04E9AFBDC91C00CEB5DF97D48E205D96DC0A44E10AF8870794DAE62CA70224F12BE9112AA730BBE470CA81FB5617AAC690E832F3F84510E92BA" InstallSize="28" Size="28" PerMachine="yes" Permanent="no" Vital="yes" RollbackBoundaryForward="WixDefaultBoundary" RollbackBoundaryBackward="WixDefaultBoundary" DetectCondition="DetectedTheMsu" KB="xyz"><PayloadRef Id="test.msu" /></MsuPackage></Chain><CommandLine Variables="upperCase" /></BurnManifest>
\ No newline at end of file
src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp
+8
@@ -2170,6 +2170,14 @@ private: // privates
2170 m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE, pArgs, pResults, m_pvBAFunctionsProcContext);
2171 }
2172
2173 + void OnExecuteProcessCancelFallback(
2174 + __in BA_ONEXECUTEPROCESSCANCEL_ARGS* pArgs,
2175 + __inout BA_ONEXECUTEPROCESSCANCEL_RESULTS* pResults
2176 + )
2177 + {
2178 + m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL, pArgs, pResults, m_pvBAFunctionsProcContext);
2179 + }
2180 +
2181
2182 public: //CBalBaseBootstrapperApplication
2183 virtual STDMETHODIMP Initialize(
src/test/burn/TestBA/TestBA.cs
+60 -31
@@ -21,7 +21,7 @@ namespace WixToolset.Test.BA
21 private Form dummyWindow;
22 private IntPtr windowHandle;
23 private LaunchAction action;
24 - private ManualResetEvent wait;
24 + private readonly ManualResetEvent wait;
25 private int result;
26
27 private string updateBundlePath;
@@ -397,6 +397,35 @@ namespace WixToolset.Test.BA
397 }
398 }
399
400 + protected override void OnExecutePackageComplete(ExecutePackageCompleteEventArgs args)
401 + {
402 + bool logTestRegistryValue;
403 + string recordTestRegistryValue = this.ReadPackageAction(args.PackageId, "RecordTestRegistryValue");
404 + if (!String.IsNullOrEmpty(recordTestRegistryValue) && Boolean.TryParse(recordTestRegistryValue, out logTestRegistryValue) && logTestRegistryValue)
405 + {
406 + var value = this.ReadTestRegistryValue(args.PackageId);
407 + this.Log("TestRegistryValue: {0}, Version, '{1}'", args.PackageId, value);
408 + }
409 + }
410 +
411 + protected override void OnExecuteProcessCancel(ExecuteProcessCancelEventArgs args)
412 + {
413 + BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action;
414 + string actionValue = this.ReadPackageAction(args.PackageId, "ProcessCancelAction");
415 + if (actionValue != null && TryParseEnum<BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION>(actionValue, out action))
416 + {
417 + args.Action = action;
418 + }
419 +
420 + if (args.Action == BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION.Abandon)
421 + {
422 + // Give time to the process to start before its files are deleted.
423 + Thread.Sleep(2000);
424 + }
425 +
426 + this.Log("OnExecuteProcessCancel({0})", args.Action);
427 + }
428 +
429 protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args)
430 {
431 this.Log("OnExecuteFilesInUse() - package: {0}, source: {1}, retries remaining: {2}, data: {3}", args.PackageId, args.Source, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray()));
@@ -488,43 +517,34 @@ namespace WixToolset.Test.BA
517 private void TestVariables()
518 {
519 // First make sure we can check and get standard variables of each type.
520 + if (this.Engine.ContainsVariable("WindowsFolder"))
521 {
492 - string value = null;
493 - if (this.Engine.ContainsVariable("WindowsFolder"))
494 - {
495 - value = this.Engine.GetVariableString("WindowsFolder");
496 - this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a string variable: WindowsFolder");
497 - }
498 - else
499 - {
500 - throw new Exception("Engine did not define a standard variable: WindowsFolder");
501 - }
522 + string value = this.Engine.GetVariableString("WindowsFolder");
523 + this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a string variable: WindowsFolder '{0}'", value));
524 + }
525 + else
526 + {
527 + throw new Exception("Engine did not define a standard variable: WindowsFolder");
528 }
529
530 + if (this.Engine.ContainsVariable("NTProductType"))
531 {
505 - long value = 0;
506 - if (this.Engine.ContainsVariable("NTProductType"))
507 - {
508 - value = this.Engine.GetVariableNumeric("NTProductType");
509 - this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a numeric variable: NTProductType");
510 - }
511 - else
512 - {
513 - throw new Exception("Engine did not define a standard variable: NTProductType");
514 - }
532 + long value = this.Engine.GetVariableNumeric("NTProductType");
533 + this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a numeric variable: NTProductType '{0}'", value));
534 + }
535 + else
536 + {
537 + throw new Exception("Engine did not define a standard variable: NTProductType");
538 }
539
540 + if (this.Engine.ContainsVariable("VersionMsi"))
541 {
518 - string value = null;
519 - if (this.Engine.ContainsVariable("VersionMsi"))
520 - {
521 - value = this.Engine.GetVariableVersion("VersionMsi");
522 - this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a version variable: VersionMsi");
523 - }
524 - else
525 - {
526 - throw new Exception("Engine did not define a standard variable: VersionMsi");
527 - }
542 + string value = this.Engine.GetVariableVersion("VersionMsi");
543 + this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a version variable: VersionMsi '{0}'", value));
544 + }
545 + else
546 + {
547 + throw new Exception("Engine did not define a standard variable: VersionMsi");
548 }
549
550 // Now validate that Contians returns false for non-existant variables of each type.
@@ -596,6 +616,15 @@ namespace WixToolset.Test.BA
616 }
617 }
618
619 + private string ReadTestRegistryValue(string name)
620 + {
621 + string testName = this.Engine.GetVariableString("TestGroupName");
622 + using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\{0}\{1}", testName, name)))
623 + {
624 + return testKey == null ? null : testKey.GetValue("Version") as string;
625 + }
626 + }
627 +
628 private static bool TryParseEnum<T>(string value, out T t)
629 {
630 try
src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj new
+19
@@ -0,0 +1,19 @@
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 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <OutputType>Bundle</OutputType>
5 + <UpgradeCode>{3C1A4842-81AC-4C90-8B35-A5E18F034C8D}</UpgradeCode>
6 + <Version>1.0.0.0</Version>
7 + </PropertyGroup>
8 + <ItemGroup>
9 + <Compile Include="..\..\Templates\Bundle.wxs" Link="Bundle.wxs" />
10 + </ItemGroup>
11 + <ItemGroup>
12 + <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
13 + </ItemGroup>
14 + <ItemGroup>
15 + <PackageReference Include="WixToolset.Bal.wixext" />
16 + <PackageReference Include="WixToolset.NetFx.wixext" />
17 + <PackageReference Include="WixToolset.Util.wixext" />
18 + </ItemGroup>
19 +</Project>
\ No newline at end of file
src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs new
+19
@@ -0,0 +1,19 @@
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 +<?define TestExeRegistryKey = Software\WiX\Tests\$(var.TestGroupName)\ExeA?>
4 +
5 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
6 + <Fragment>
7 + <util:RegistrySearch Root="HKLM" Key="$(var.TestExeRegistryKey)" Value="Version" Variable="ExeA_Version" />
8 +
9 + <PackageGroup Id="BundlePackages">
10 + <ExePackage Id="ExeA" Cache="remove" PerMachine="yes"
11 + DetectCondition="ExeA_Version AND ExeA_Version &gt;= v$(var.Version)"
12 + InstallArguments="/s 5000 /regw &quot;HKLM\$(var.TestExeRegistryKey),Version,String,$(var.Version)&quot;"
13 + RepairArguments="/regw &quot;HKLM\$(var.TestExeRegistryKey),Version,String,$(var.Version)&quot;"
14 + UninstallArguments="/regd &quot;HKLM\$(var.TestExeRegistryKey),Version&quot;">
15 + <PayloadGroupRef Id="TestExePayloads" />
16 + </ExePackage>
17 + </PackageGroup>
18 + </Fragment>
19 +</Wix>
src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs
+42
@@ -2,7 +2,9 @@
2
3 namespace WixToolsetTest.BurnE2E
4 {
5 + using System.Threading;
6 using WixTestTools;
7 + using WixToolset.Mba.Core;
8 using Xunit;
9 using Xunit.Abstractions;
10
@@ -10,6 +12,46 @@ namespace WixToolsetTest.BurnE2E
12 {
13 public FailureTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
14
15 + [Fact]
16 + public void CanCancelExePackageAndAbandonIt()
17 + {
18 + var bundleD = this.CreateBundleInstaller("BundleD");
19 + var testBAController = this.CreateTestBAController();
20 +
21 + // Cancel package ExeA after it starts.
22 + testBAController.SetPackageCancelExecuteAtProgress("ExeA", 1);
23 + testBAController.SetPackageRecordTestRegistryValue("ExeA");
24 +
25 + var logPath = bundleD.Install((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_USEREXIT);
26 + bundleD.VerifyUnregisteredAndRemovedFromPackageCache();
27 +
28 + Assert.True(LogVerifier.MessageInLogFile(logPath, "TestRegistryValue: ExeA, Version, ''"));
29 +
30 + // Make sure ExeA finishes running.
31 + Thread.Sleep(3000);
32 +
33 + bundleD.VerifyExeTestRegistryValue("ExeA", "1.0.0.0");
34 + }
35 +
36 + [Fact]
37 + public void CanCancelExePackageAndWaitUntilItCompletes()
38 + {
39 + var bundleD = this.CreateBundleInstaller("BundleD");
40 + var testBAController = this.CreateTestBAController();
41 +
42 + // Cancel package ExeA after it starts.
43 + testBAController.SetPackageCancelExecuteAtProgress("ExeA", 1);
44 + testBAController.SetPackageProcessCancelAction("ExeA", BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION.Wait);
45 + testBAController.SetPackageRecordTestRegistryValue("ExeA");
46 +
47 + var logPath = bundleD.Install((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_USEREXIT);
48 + bundleD.VerifyUnregisteredAndRemovedFromPackageCache();
49 +
50 + Assert.True(LogVerifier.MessageInLogFile(logPath, "TestRegistryValue: ExeA, Version, '1.0.0.0'"));
51 +
52 + bundleD.VerifyExeTestRegistryValue("ExeA", "1.0.0.0");
53 + }
54 +
55 [Fact]
56 public void CanCancelMsiPackageVeryEarly()
57 {
src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs
+15
@@ -147,6 +147,21 @@ namespace WixToolsetTest.BurnE2E
147 this.SetPackageState(packageId, String.Concat(featureId, "Requested"), state.ToString());
148 }
149
150 + /// <summary>
151 + /// Requests the BA to log the test registry value for the specified package.
152 + /// </summary>
153 + /// <param name="packageId"></param>
154 + /// <param name="value"></param>
155 + public void SetPackageRecordTestRegistryValue(string packageId, string value = "true")
156 + {
157 + this.SetPackageState(packageId, "RecordTestRegistryValue", value);
158 + }
159 +
160 + public void SetPackageProcessCancelAction(string packageId, BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action)
161 + {
162 + this.SetPackageState(packageId, "ProcessCancelAction", action.ToString());
163 + }
164 +
165 /// <summary>
166 /// Sets the number of times to re-run the Detect phase.
167 /// </summary>