aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h27
-rw-r--r--src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs25
-rw-r--r--src/api/burn/WixToolset.Mba.Core/EventArgs.cs36
-rw-r--r--src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs32
-rw-r--r--src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs5
-rw-r--r--src/api/burn/balutil/inc/BAFunctions.h1
-rw-r--r--src/api/burn/balutil/inc/BalBaseBAFunctions.h10
-rw-r--r--src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h1
-rw-r--r--src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h10
-rw-r--r--src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h12
-rw-r--r--src/api/burn/balutil/inc/IBootstrapperApplication.h9
-rw-r--r--src/burn/engine/apply.cpp8
-rw-r--r--src/burn/engine/apply.h5
-rw-r--r--src/burn/engine/bundlepackageengine.cpp28
-rw-r--r--src/burn/engine/elevation.cpp17
-rw-r--r--src/burn/engine/engine.mc7
-rw-r--r--src/burn/engine/exeengine.cpp132
-rw-r--r--src/burn/engine/exeengine.h9
-rw-r--r--src/burn/engine/msuengine.cpp32
-rw-r--r--src/burn/engine/userexperience.cpp30
-rw-r--r--src/burn/engine/userexperience.h6
-rw-r--r--src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj2
-rw-r--r--src/burn/test/BurnUnitTest/PlanTest.cpp200
-rw-r--r--src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml1
-rw-r--r--src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml1
-rw-r--r--src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp8
-rw-r--r--src/test/burn/TestBA/TestBA.cs91
-rw-r--r--src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj19
-rw-r--r--src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs19
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs42
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs15
31 files changed, 716 insertions, 124 deletions
diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h
index 0b81b35a..df8cac76 100644
--- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h
+++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BootstrapperApplication.h
@@ -224,6 +224,7 @@ enum BOOTSTRAPPER_APPLICATION_MESSAGE
224 BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE, 224 BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE,
225 BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE, 225 BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE,
226 BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE, 226 BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE,
227 BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL,
227}; 228};
228 229
229enum BOOTSTRAPPER_APPLYCOMPLETE_ACTION 230enum BOOTSTRAPPER_APPLYCOMPLETE_ACTION
@@ -282,6 +283,18 @@ enum BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION
282 BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_SUSPEND, 283 BOOTSTRAPPER_EXECUTEPACKAGECOMPLETE_ACTION_SUSPEND,
283}; 284};
284 285
286enum 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
285enum BOOTSTRAPPER_SHUTDOWN_ACTION 298enum BOOTSTRAPPER_SHUTDOWN_ACTION
286{ 299{
287 BOOTSTRAPPER_SHUTDOWN_ACTION_NONE, 300 BOOTSTRAPPER_SHUTDOWN_ACTION_NONE,
@@ -997,6 +1010,20 @@ struct BA_ONEXECUTEPATCHTARGET_RESULTS
997 BOOL fCancel; 1010 BOOL fCancel;
998}; 1011};
999 1012
1013struct BA_ONEXECUTEPROCESSCANCEL_ARGS
1014{
1015 DWORD cbSize;
1016 LPCWSTR wzPackageId;
1017 DWORD dwProcessId;
1018 BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION recommendation;
1019};
1020
1021struct BA_ONEXECUTEPROCESSCANCEL_RESULTS
1022{
1023 DWORD cbSize;
1024 BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action;
1025};
1026
1000struct BA_ONEXECUTEPROGRESS_ARGS 1027struct BA_ONEXECUTEPROGRESS_ARGS
1001{ 1028{
1002 DWORD cbSize; 1029 DWORD cbSize;
diff --git a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs
index 8a2e0e93..5ed064fa 100644
--- a/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs
+++ b/src/api/burn/WixToolset.Mba.Core/BootstrapperApplication.cs
@@ -280,6 +280,9 @@ namespace WixToolset.Mba.Core
280 /// <inheritdoc/> 280 /// <inheritdoc/>
281 public event EventHandler<PlanRestoreRelatedBundleEventArgs> PlanRestoreRelatedBundle; 281 public event EventHandler<PlanRestoreRelatedBundleEventArgs> PlanRestoreRelatedBundle;
282 282
283 /// <inheritdoc/>
284 public event EventHandler<ExecuteProcessCancelEventArgs> ExecuteProcessCancel;
285
283 /// <summary> 286 /// <summary>
284 /// Entry point that is called when the bootstrapper application is ready to run. 287 /// Entry point that is called when the bootstrapper application is ready to run.
285 /// </summary> 288 /// </summary>
@@ -1369,6 +1372,19 @@ namespace WixToolset.Mba.Core
1369 } 1372 }
1370 } 1373 }
1371 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
1372 #region IBootstrapperApplication Members 1388 #region IBootstrapperApplication Members
1373 1389
1374 int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults, IntPtr pvContext) 1390 int IBootstrapperApplication.BAProc(int message, IntPtr pvArgs, IntPtr pvResults, IntPtr pvContext)
@@ -2119,6 +2135,15 @@ namespace WixToolset.Mba.Core
2119 return args.HResult; 2135 return args.HResult;
2120 } 2136 }
2121 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
2122 #endregion 2147 #endregion
2123 } 2148 }
2124} 2149}
diff --git a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs
index c93c2885..c2c73067 100644
--- a/src/api/burn/WixToolset.Mba.Core/EventArgs.cs
+++ b/src/api/burn/WixToolset.Mba.Core/EventArgs.cs
@@ -2488,4 +2488,40 @@ namespace WixToolset.Mba.Core
2488 /// </summary> 2488 /// </summary>
2489 public RequestState State { get; set; } 2489 public RequestState State { get; set; }
2490 } 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 }
2491} 2527}
diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs
index d4fe8320..1786eecd 100644
--- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs
+++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperApplication.cs
@@ -1170,6 +1170,18 @@ namespace WixToolset.Mba.Core
1170 [MarshalAs(UnmanagedType.I4)] int hrRecommended, 1170 [MarshalAs(UnmanagedType.I4)] int hrRecommended,
1171 [MarshalAs(UnmanagedType.I4)] ref int hrStatus 1171 [MarshalAs(UnmanagedType.I4)] ref int hrStatus
1172 ); 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 );
1173 } 1185 }
1174 1186
1175 /// <summary> 1187 /// <summary>
@@ -1907,6 +1919,26 @@ namespace WixToolset.Mba.Core
1907 } 1919 }
1908 1920
1909 /// <summary> 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>
1910 /// The result of evaluating a condition from a package. 1942 /// The result of evaluating a condition from a package.
1911 /// </summary> 1943 /// </summary>
1912 public enum BOOTSTRAPPER_PACKAGE_CONDITION_RESULT 1944 public enum BOOTSTRAPPER_PACKAGE_CONDITION_RESULT
diff --git a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs
index c9284b69..21d99b32 100644
--- a/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs
+++ b/src/api/burn/WixToolset.Mba.Core/IDefaultBootstrapperApplication.cs
@@ -244,6 +244,11 @@ namespace WixToolset.Mba.Core
244 event EventHandler<ExecutePackageCompleteEventArgs> ExecutePackageComplete; 244 event EventHandler<ExecutePackageCompleteEventArgs> ExecutePackageComplete;
245 245
246 /// <summary> 246 /// <summary>
247 /// Fired when a package that spawned a process is cancelled.
248 /// </summary>
249 event EventHandler<ExecuteProcessCancelEventArgs> ExecuteProcessCancel;
250
251 /// <summary>
247 /// Fired when the engine executes one or more patches targeting a product. 252 /// Fired when the engine executes one or more patches targeting a product.
248 /// </summary> 253 /// </summary>
249 event EventHandler<ExecutePatchTargetEventArgs> ExecutePatchTarget; 254 event EventHandler<ExecutePatchTargetEventArgs> ExecutePatchTarget;
diff --git a/src/api/burn/balutil/inc/BAFunctions.h b/src/api/burn/balutil/inc/BAFunctions.h
index 58c26166..158e65b5 100644
--- a/src/api/burn/balutil/inc/BAFunctions.h
+++ b/src/api/burn/balutil/inc/BAFunctions.h
@@ -91,6 +91,7 @@ enum BA_FUNCTIONS_MESSAGE
91 BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE, 91 BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRESTORERELATEDBUNDLE,
92 BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE, 92 BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONPLANRELATEDBUNDLETYPE,
93 BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE, 93 BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE = BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE,
94 BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL = BOOTSTRAPPER_APPLICATION_MESSAGE_ONEXECUTEPROCESSCANCEL,
94 95
95 BA_FUNCTIONS_MESSAGE_ONTHEMELOADED = 1024, 96 BA_FUNCTIONS_MESSAGE_ONTHEMELOADED = 1024,
96 BA_FUNCTIONS_MESSAGE_WNDPROC, 97 BA_FUNCTIONS_MESSAGE_WNDPROC,
diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctions.h b/src/api/burn/balutil/inc/BalBaseBAFunctions.h
index fe5c99ba..614d4bcf 100644
--- a/src/api/burn/balutil/inc/BalBaseBAFunctions.h
+++ b/src/api/burn/balutil/inc/BalBaseBAFunctions.h
@@ -877,6 +877,16 @@ public: // IBootstrapperApplication
877 return S_OK; 877 return S_OK;
878 } 878 }
879 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
880public: // IBAFunctions 890public: // IBAFunctions
881 virtual STDMETHODIMP OnPlan( 891 virtual STDMETHODIMP OnPlan(
882 ) 892 )
diff --git a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h
index 100e5c30..b96a180c 100644
--- a/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h
+++ b/src/api/burn/balutil/inc/BalBaseBAFunctionsProc.h
@@ -162,6 +162,7 @@ static HRESULT WINAPI BalBaseBAFunctionsProc(
162 case BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE: 162 case BA_FUNCTIONS_MESSAGE_ONPLANRESTORERELATEDBUNDLE:
163 case BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE: 163 case BA_FUNCTIONS_MESSAGE_ONPLANRELATEDBUNDLETYPE:
164 case BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE: 164 case BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE:
165 case BA_FUNCTIONS_MESSAGE_ONEXECUTEPROCESSCANCEL:
165 hr = BalBaseBootstrapperApplicationProc((BOOTSTRAPPER_APPLICATION_MESSAGE)message, pvArgs, pvResults, pvContext); 166 hr = BalBaseBootstrapperApplicationProc((BOOTSTRAPPER_APPLICATION_MESSAGE)message, pvArgs, pvResults, pvContext);
166 break; 167 break;
167 case BA_FUNCTIONS_MESSAGE_ONTHEMELOADED: 168 case BA_FUNCTIONS_MESSAGE_ONTHEMELOADED:
diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h
index fd06a83f..25570ffd 100644
--- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h
+++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplication.h
@@ -1077,6 +1077,16 @@ public: // IBootstrapperApplication
1077 return S_OK; 1077 return S_OK;
1078 } 1078 }
1079 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
1080public: //CBalBaseBootstrapperApplication 1090public: //CBalBaseBootstrapperApplication
1081 virtual STDMETHODIMP Initialize( 1091 virtual STDMETHODIMP Initialize(
1082 __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs 1092 __in const BOOTSTRAPPER_CREATE_ARGS* pCreateArgs
diff --git a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h
index 4e413e4e..b196d183 100644
--- a/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h
+++ b/src/api/burn/balutil/inc/BalBaseBootstrapperApplicationProc.h
@@ -486,6 +486,15 @@ static HRESULT BalBaseBAProcOnExecutePackageComplete(
486 return pBA->OnExecutePackageComplete(pArgs->wzPackageId, pArgs->hrStatus, pArgs->restart, pArgs->recommendation, &pResults->action); 486 return pBA->OnExecutePackageComplete(pArgs->wzPackageId, pArgs->hrStatus, pArgs->restart, pArgs->recommendation, &pResults->action);
487} 487}
488 488
489static 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
489static HRESULT BalBaseBAProcOnExecuteComplete( 498static HRESULT BalBaseBAProcOnExecuteComplete(
490 __in IBootstrapperApplication* pBA, 499 __in IBootstrapperApplication* pBA,
491 __in BA_ONEXECUTECOMPLETE_ARGS* pArgs, 500 __in BA_ONEXECUTECOMPLETE_ARGS* pArgs,
@@ -1012,6 +1021,9 @@ static HRESULT WINAPI BalBaseBootstrapperApplicationProc(
1012 case BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE: 1021 case BOOTSTRAPPER_APPLICATION_MESSAGE_ONAPPLYDOWNGRADE:
1013 hr = BalBaseBAProcOnApplyDowngrade(pBA, reinterpret_cast<BA_ONAPPLYDOWNGRADE_ARGS*>(pvArgs), reinterpret_cast<BA_ONAPPLYDOWNGRADE_RESULTS*>(pvResults)); 1022 hr = BalBaseBAProcOnApplyDowngrade(pBA, reinterpret_cast<BA_ONAPPLYDOWNGRADE_ARGS*>(pvArgs), reinterpret_cast<BA_ONAPPLYDOWNGRADE_RESULTS*>(pvResults));
1014 break; 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;
1015 } 1027 }
1016 } 1028 }
1017 1029
diff --git a/src/api/burn/balutil/inc/IBootstrapperApplication.h b/src/api/burn/balutil/inc/IBootstrapperApplication.h
index c9cf3126..6174c290 100644
--- a/src/api/burn/balutil/inc/IBootstrapperApplication.h
+++ b/src/api/burn/balutil/inc/IBootstrapperApplication.h
@@ -714,4 +714,13 @@ DECLARE_INTERFACE_IID_(IBootstrapperApplication, IUnknown, "53C31D56-49C0-426B-A
714 __in HRESULT hrRecommended, 714 __in HRESULT hrRecommended,
715 __inout HRESULT* phrStatus 715 __inout HRESULT* phrStatus
716 ) = 0; 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;
717}; 726};
diff --git a/src/burn/engine/apply.cpp b/src/burn/engine/apply.cpp
index 3ad22e9b..73b5b396 100644
--- a/src/burn/engine/apply.cpp
+++ b/src/burn/engine/apply.cpp
@@ -3326,6 +3326,14 @@ static int GenericExecuteMessageHandler(
3326 } 3326 }
3327 break; 3327 break;
3328 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
3329 case GENERIC_EXECUTE_MESSAGE_ERROR: 3337 case GENERIC_EXECUTE_MESSAGE_ERROR:
3330 UserExperienceOnError(pContext->pUX, BOOTSTRAPPER_ERROR_TYPE_EXE_PACKAGE, pContext->wzExecutingPackageId, pMessage->error.dwErrorCode, pMessage->error.wzMessage, pMessage->dwUIHint, 0, NULL, &nResult); // ignore return value. 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.
3331 break; 3339 break;
diff --git a/src/burn/engine/apply.h b/src/burn/engine/apply.h
index 1717a71a..47f0ece6 100644
--- a/src/burn/engine/apply.h
+++ b/src/burn/engine/apply.h
@@ -13,6 +13,7 @@ enum GENERIC_EXECUTE_MESSAGE_TYPE
13 GENERIC_EXECUTE_MESSAGE_ERROR, 13 GENERIC_EXECUTE_MESSAGE_ERROR,
14 GENERIC_EXECUTE_MESSAGE_PROGRESS, 14 GENERIC_EXECUTE_MESSAGE_PROGRESS,
15 GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE, 15 GENERIC_EXECUTE_MESSAGE_NETFX_FILES_IN_USE,
16 GENERIC_EXECUTE_MESSAGE_PROCESS_CANCEL,
16}; 17};
17 18
18typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA 19typedef struct _APPLY_AUTHENTICATION_REQUIRED_DATA
@@ -43,6 +44,10 @@ typedef struct _GENERIC_EXECUTE_MESSAGE
43 DWORD cFiles; 44 DWORD cFiles;
44 LPCWSTR* rgwzFiles; 45 LPCWSTR* rgwzFiles;
45 } filesInUse; 46 } filesInUse;
47 struct
48 {
49 DWORD dwProcessId;
50 } processCancel;
46 }; 51 };
47} GENERIC_EXECUTE_MESSAGE; 52} GENERIC_EXECUTE_MESSAGE;
48 53
diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp
index 88a00f5e..0bee054f 100644
--- a/src/burn/engine/bundlepackageengine.cpp
+++ b/src/burn/engine/bundlepackageengine.cpp
@@ -251,7 +251,6 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
251 ) 251 )
252{ 252{
253 HRESULT hr = S_OK; 253 HRESULT hr = S_OK;
254 int nResult = IDNOACTION;
255 LPCWSTR wzArguments = NULL; 254 LPCWSTR wzArguments = NULL;
256 LPWSTR sczArguments = NULL; 255 LPWSTR sczArguments = NULL;
257 LPWSTR sczArgumentsFormatted = NULL; 256 LPWSTR sczArgumentsFormatted = NULL;
@@ -420,31 +419,10 @@ extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle(
420 hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); 419 hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode);
421 ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath); 420 ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath);
422 } 421 }
423 else // create and wait for the executable process while sending fake progress to allow cancel. 422 else
424 { 423 {
425 // Make the cache location of the executable the current directory to help those executables 424 hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczExecutablePath, sczCommand, sczCachedDirectory, &dwExitCode);
426 // that expect stuff to be relative to them. 425 ExitOnFailure(hr, "Failed to run BUNDLE process");
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);
448 } 426 }
449 427
450 hr = ExeEngineHandleExitCode(pPackage->Bundle.rgExitCodes, pPackage->Bundle.cExitCodes, dwExitCode, pRestart); 428 hr = ExeEngineHandleExitCode(pPackage->Bundle.rgExitCodes, pPackage->Bundle.cExitCodes, dwExitCode, pRestart);
diff --git a/src/burn/engine/elevation.cpp b/src/burn/engine/elevation.cpp
index 636d67ce..3c2872f1 100644
--- a/src/burn/engine/elevation.cpp
+++ b/src/burn/engine/elevation.cpp
@@ -43,6 +43,7 @@ typedef enum _BURN_ELEVATION_MESSAGE_TYPE
43 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE, 43 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_COMPLETE,
44 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS, 44 BURN_ELEVATION_MESSAGE_TYPE_BURN_CACHE_SUCCESS,
45 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS, 45 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS,
46 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROCESS_CANCEL,
46 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR, 47 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR,
47 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE, 48 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_MESSAGE,
48 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE, 49 BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_MSI_FILES_IN_USE,
@@ -1812,7 +1813,14 @@ static HRESULT ProcessGenericExecuteMessages(
1812 1813
1813 // read message parameters 1814 // read message parameters
1814 hr = BuffReadNumber((BYTE*)pMsg->pvData, pMsg->cbData, &iData, &message.progress.dwPercentage); 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.");
1816 break; 1824 break;
1817 1825
1818 case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR: 1826 case BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_ERROR:
@@ -3450,6 +3458,13 @@ static int GenericExecuteMessageHandler(
3450 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS; 3458 dwMessage = BURN_ELEVATION_MESSAGE_TYPE_EXECUTE_PROGRESS;
3451 break; 3459 break;
3452 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
3453 case GENERIC_EXECUTE_MESSAGE_ERROR: 3468 case GENERIC_EXECUTE_MESSAGE_ERROR:
3454 // serialize message data 3469 // serialize message data
3455 hr = BuffWriteNumber(&pbData, &cbData, pMessage->error.dwErrorCode); 3470 hr = BuffWriteNumber(&pbData, &cbData, pMessage->error.dwErrorCode);
diff --git a/src/burn/engine/engine.mc b/src/burn/engine/engine.mc
index 53e6b256..9e139661 100644
--- a/src/burn/engine/engine.mc
+++ b/src/burn/engine/engine.mc
@@ -933,6 +933,13 @@ Language=English
933Could not create system restore point, error: 0x%1!x!. Continuing... 933Could not create system restore point, error: 0x%1!x!. Continuing...
934. 934.
935 935
936MessageId=364
937Severity=Success
938SymbolicName=MSG_EXECUTE_PROCESS_DELAYED_CANCEL_REQUESTED
939Language=English
940Bootstrapper application requested delayed cancel during package process progress, id: %1!ls!. Waiting...
941.
942
936MessageId=370 943MessageId=370
937Severity=Success 944Severity=Success
938SymbolicName=MSG_SESSION_BEGIN 945SymbolicName=MSG_SESSION_BEGIN
diff --git a/src/burn/engine/exeengine.cpp b/src/burn/engine/exeengine.cpp
index c984f5a7..4c3c6fb0 100644
--- a/src/burn/engine/exeengine.cpp
+++ b/src/burn/engine/exeengine.cpp
@@ -317,7 +317,6 @@ extern "C" HRESULT ExeEngineExecutePackage(
317 ) 317 )
318{ 318{
319 HRESULT hr = S_OK; 319 HRESULT hr = S_OK;
320 int nResult = IDNOACTION;
321 LPCWSTR wzArguments = NULL; 320 LPCWSTR wzArguments = NULL;
322 LPWSTR sczArguments = NULL; 321 LPWSTR sczArguments = NULL;
323 LPWSTR sczArgumentsFormatted = NULL; 322 LPWSTR sczArgumentsFormatted = NULL;
@@ -327,10 +326,7 @@ extern "C" HRESULT ExeEngineExecutePackage(
327 LPWSTR sczCommand = NULL; 326 LPWSTR sczCommand = NULL;
328 LPWSTR sczCommandObfuscated = NULL; 327 LPWSTR sczCommandObfuscated = NULL;
329 HANDLE hExecutableFile = INVALID_HANDLE_VALUE; 328 HANDLE hExecutableFile = INVALID_HANDLE_VALUE;
330 STARTUPINFOW si = { };
331 PROCESS_INFORMATION pi = { };
332 DWORD dwExitCode = 0; 329 DWORD dwExitCode = 0;
333 GENERIC_EXECUTE_MESSAGE message = { };
334 BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage; 330 BURN_PACKAGE* pPackage = pExecuteAction->exePackage.pPackage;
335 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; 331 BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload;
336 332
@@ -442,37 +438,10 @@ extern "C" HRESULT ExeEngineExecutePackage(
442 hr = NetFxRunChainer(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); 438 hr = NetFxRunChainer(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode);
443 ExitOnFailure(hr, "Failed to run netfx chainer: %ls", sczExecutablePath); 439 ExitOnFailure(hr, "Failed to run netfx chainer: %ls", sczExecutablePath);
444 } 440 }
445 else // create and wait for the executable process while sending fake progress to allow cancel. 441 else
446 { 442 {
447 // Make the cache location of the executable the current directory to help those executables 443 hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczExecutablePath, sczCommand, sczCachedDirectory, &dwExitCode);
448 // that expect stuff to be relative to them. 444 ExitOnFailure(hr, "Failed to run EXE process");
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);
476 } 445 }
477 446
478 hr = ExeEngineHandleExitCode(pPackage->Exe.rgExitCodes, pPackage->Exe.cExitCodes, dwExitCode, pRestart); 447 hr = ExeEngineHandleExitCode(pPackage->Exe.rgExitCodes, pPackage->Exe.cExitCodes, dwExitCode, pRestart);
@@ -487,8 +456,6 @@ LExit:
487 StrSecureZeroFreeString(sczCommand); 456 StrSecureZeroFreeString(sczCommand);
488 ReleaseStr(sczCommandObfuscated); 457 ReleaseStr(sczCommandObfuscated);
489 458
490 ReleaseHandle(pi.hThread);
491 ReleaseHandle(pi.hProcess);
492 ReleaseFileHandle(hExecutableFile); 459 ReleaseFileHandle(hExecutableFile);
493 460
494 // Best effort to clear the execute package cache folder and action variables. 461 // Best effort to clear the execute package cache folder and action variables.
@@ -498,6 +465,99 @@ LExit:
498 return hr; 465 return hr;
499} 466}
500 467
468extern "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
554LExit:
555 ReleaseHandle(pi.hThread);
556 ReleaseHandle(pi.hProcess);
557
558 return hr;
559}
560
501extern "C" void ExeEngineUpdateInstallRegistrationState( 561extern "C" void ExeEngineUpdateInstallRegistrationState(
502 __in BURN_EXECUTE_ACTION* pAction, 562 __in BURN_EXECUTE_ACTION* pAction,
503 __in HRESULT hrExecute 563 __in HRESULT hrExecute
diff --git a/src/burn/engine/exeengine.h b/src/burn/engine/exeengine.h
index 743621b7..636988f1 100644
--- a/src/burn/engine/exeengine.h
+++ b/src/burn/engine/exeengine.h
@@ -42,6 +42,15 @@ HRESULT ExeEngineExecutePackage(
42 __in LPVOID pvContext, 42 __in LPVOID pvContext,
43 __out BOOTSTRAPPER_APPLY_RESTART* pRestart 43 __out BOOTSTRAPPER_APPLY_RESTART* pRestart
44 ); 44 );
45HRESULT 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 );
45void ExeEngineUpdateInstallRegistrationState( 54void ExeEngineUpdateInstallRegistrationState(
46 __in BURN_EXECUTE_ACTION* pAction, 55 __in BURN_EXECUTE_ACTION* pAction,
47 __in HRESULT hrExecute 56 __in HRESULT hrExecute
diff --git a/src/burn/engine/msuengine.cpp b/src/burn/engine/msuengine.cpp
index 091bbe62..2f1fb61c 100644
--- a/src/burn/engine/msuengine.cpp
+++ b/src/burn/engine/msuengine.cpp
@@ -264,7 +264,6 @@ extern "C" HRESULT MsuEngineExecutePackage(
264 ) 264 )
265{ 265{
266 HRESULT hr = S_OK; 266 HRESULT hr = S_OK;
267 int nResult = IDNOACTION;
268 LPWSTR sczCachedDirectory = NULL; 267 LPWSTR sczCachedDirectory = NULL;
269 LPWSTR sczMsuPath = NULL; 268 LPWSTR sczMsuPath = NULL;
270 LPWSTR sczWindowsPath = NULL; 269 LPWSTR sczWindowsPath = NULL;
@@ -350,35 +349,8 @@ extern "C" HRESULT MsuEngineExecutePackage(
350 hr = EnsureWUServiceEnabled(fStopWusaService, &schWu, &fWuWasDisabled); 349 hr = EnsureWUServiceEnabled(fStopWusaService, &schWu, &fWuWasDisabled);
351 ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package."); 350 ExitOnFailure(hr, "Failed to ensure WU service was enabled to install MSU package.");
352 351
353 // create process 352 hr = ExeEngineRunProcess(pfnGenericMessageHandler, pvContext, pPackage, sczWusaPath, sczCommand, NULL, &dwExitCode);
354 si.cb = sizeof(si); 353 ExitOnFailure(hr, "Failed to run MSU process");
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 }
382 354
383 // We'll normalize the restart required error code from wusa.exe just in case. Most likely 355 // We'll normalize the restart required error code from wusa.exe just in case. Most likely
384 // that on reboot we'll actually get WU_S_REBOOT_REQUIRED. 356 // that on reboot we'll actually get WU_S_REBOOT_REQUIRED.
diff --git a/src/burn/engine/userexperience.cpp b/src/burn/engine/userexperience.cpp
index 81ce8bb9..06f87363 100644
--- a/src/burn/engine/userexperience.cpp
+++ b/src/burn/engine/userexperience.cpp
@@ -104,7 +104,7 @@ extern "C" HRESULT UserExperienceLoad(
104 args.pCommand = pCommand; 104 args.pCommand = pCommand;
105 args.pfnBootstrapperEngineProc = EngineForApplicationProc; 105 args.pfnBootstrapperEngineProc = EngineForApplicationProc;
106 args.pvBootstrapperEngineProcContext = pEngineContext; 106 args.pvBootstrapperEngineProcContext = pEngineContext;
107 args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 14, 0); 107 args.qwEngineAPIVersion = MAKEQWORDVERSION(2022, 3, 17, 0);
108 108
109 results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS); 109 results.cbSize = sizeof(BOOTSTRAPPER_CREATE_RESULTS);
110 110
@@ -1701,6 +1701,34 @@ LExit:
1701 return hr; 1701 return hr;
1702} 1702}
1703 1703
1704BAAPI 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
1728LExit:
1729 return hr;
1730}
1731
1704EXTERN_C BAAPI UserExperienceOnExecuteProgress( 1732EXTERN_C BAAPI UserExperienceOnExecuteProgress(
1705 __in BURN_USER_EXPERIENCE* pUserExperience, 1733 __in BURN_USER_EXPERIENCE* pUserExperience,
1706 __in_z LPCWSTR wzPackageId, 1734 __in_z LPCWSTR wzPackageId,
diff --git a/src/burn/engine/userexperience.h b/src/burn/engine/userexperience.h
index 2f18acdd..de558ad5 100644
--- a/src/burn/engine/userexperience.h
+++ b/src/burn/engine/userexperience.h
@@ -398,6 +398,12 @@ BAAPI UserExperienceOnExecutePatchTarget(
398 __in_z LPCWSTR wzPackageId, 398 __in_z LPCWSTR wzPackageId,
399 __in_z LPCWSTR wzTargetProductCode 399 __in_z LPCWSTR wzTargetProductCode
400 ); 400 );
401BAAPI UserExperienceOnExecuteProcessCancel(
402 __in BURN_USER_EXPERIENCE* pUserExperience,
403 __in_z LPCWSTR wzPackageId,
404 __in DWORD dwProcessId,
405 __inout BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION* pAction
406 );
401BAAPI UserExperienceOnExecuteProgress( 407BAAPI UserExperienceOnExecuteProgress(
402 __in BURN_USER_EXPERIENCE* pUserExperience, 408 __in BURN_USER_EXPERIENCE* pUserExperience,
403 __in_z LPCWSTR wzPackageId, 409 __in_z LPCWSTR wzPackageId,
diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj
index 7375af86..35415dc3 100644
--- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj
+++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj
@@ -80,7 +80,9 @@
80 <ItemGroup> 80 <ItemGroup>
81 <None Include="TestData\CacheTest\CacheSignatureTest.File" CopyToOutputDirectory="PreserveNewest" /> 81 <None Include="TestData\CacheTest\CacheSignatureTest.File" CopyToOutputDirectory="PreserveNewest" />
82 <None Include="TestData\PlanTest\BasicFunctionality_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> 82 <None Include="TestData\PlanTest\BasicFunctionality_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
83 <None Include="TestData\PlanTest\Failure_BundleD_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
83 <None Include="TestData\PlanTest\MsiTransaction_BundleAv1_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" />
84 <None Include="TestData\PlanTest\Slipstream_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> 86 <None Include="TestData\PlanTest\Slipstream_BundleA_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
85 <None Include="TestData\PlanTest\Slipstream_BundleA_modified_manifest.xml" CopyToOutputDirectory="PreserveNewest" /> 87 <None Include="TestData\PlanTest\Slipstream_BundleA_modified_manifest.xml" CopyToOutputDirectory="PreserveNewest" />
86 </ItemGroup> 88 </ItemGroup>
diff --git a/src/burn/test/BurnUnitTest/PlanTest.cpp b/src/burn/test/BurnUnitTest/PlanTest.cpp
index 4d726fb4..770922b4 100644
--- a/src/burn/test/BurnUnitTest/PlanTest.cpp
+++ b/src/burn/test/BurnUnitTest/PlanTest.cpp
@@ -10,7 +10,9 @@ static HRESULT WINAPI PlanTestBAProc(
10 ); 10 );
11 11
12static LPCWSTR wzMsiTransactionManifestFileName = L"MsiTransaction_BundleAv1_manifest.xml"; 12static LPCWSTR wzMsiTransactionManifestFileName = L"MsiTransaction_BundleAv1_manifest.xml";
13static LPCWSTR wzSingleExeManifestFileName = L"Failure_BundleD_manifest.xml";
13static LPCWSTR wzSingleMsiManifestFileName = L"BasicFunctionality_BundleA_manifest.xml"; 14static LPCWSTR wzSingleMsiManifestFileName = L"BasicFunctionality_BundleA_manifest.xml";
15static LPCWSTR wzSingleMsuManifestFileName = L"MsuPackageFixture_manifest.xml";
14static LPCWSTR wzSlipstreamManifestFileName = L"Slipstream_BundleA_manifest.xml"; 16static LPCWSTR wzSlipstreamManifestFileName = L"Slipstream_BundleA_manifest.xml";
15static LPCWSTR wzSlipstreamModifiedManifestFileName = L"Slipstream_BundleA_modified_manifest.xml"; 17static LPCWSTR wzSlipstreamModifiedManifestFileName = L"Slipstream_BundleA_modified_manifest.xml";
16 18
@@ -659,6 +661,97 @@ namespace Bootstrapper
659 } 661 }
660 662
661 [Fact] 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]
662 void SingleMsiCacheTest() 755 void SingleMsiCacheTest()
663 { 756 {
664 HRESULT hr = S_OK; 757 HRESULT hr = S_OK;
@@ -1533,6 +1626,98 @@ namespace Bootstrapper
1533 } 1626 }
1534 1627
1535 [Fact] 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]
1536 void SlipstreamInstallTest() 1721 void SlipstreamInstallTest()
1537 { 1722 {
1538 HRESULT hr = S_OK; 1723 HRESULT hr = S_OK;
@@ -2571,6 +2756,21 @@ namespace Bootstrapper
2571 NativeAssert::StringEqual(wzPackageId, pOrderedPatch->pPackage->sczId); 2756 NativeAssert::StringEqual(wzPackageId, pOrderedPatch->pPackage->sczId);
2572 } 2757 }
2573 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
2574 void ValidateExecutePackageDependency( 2774 void ValidateExecutePackageDependency(
2575 __in BURN_PLAN* pPlan, 2775 __in BURN_PLAN* pPlan,
2576 __in BOOL fRollback, 2776 __in BOOL fRollback,
diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml
new file mode 100644
index 00000000..6afb0108
--- /dev/null
+++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/Failure_BundleD_manifest.xml
@@ -0,0 +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
diff --git a/src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml b/src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml
new file mode 100644
index 00000000..fb6afa88
--- /dev/null
+++ b/src/burn/test/BurnUnitTest/TestData/PlanTest/MsuPackageFixture_manifest.xml
@@ -0,0 +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
diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp
index 84e32867..444a917f 100644
--- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp
+++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp
@@ -2170,6 +2170,14 @@ private: // privates
2170 m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE, pArgs, pResults, m_pvBAFunctionsProcContext); 2170 m_pfnBAFunctionsProc(BA_FUNCTIONS_MESSAGE_ONAPPLYDOWNGRADE, pArgs, pResults, m_pvBAFunctionsProcContext);
2171 } 2171 }
2172 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
2173 2181
2174public: //CBalBaseBootstrapperApplication 2182public: //CBalBaseBootstrapperApplication
2175 virtual STDMETHODIMP Initialize( 2183 virtual STDMETHODIMP Initialize(
diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs
index 3688e028..c219ce9c 100644
--- a/src/test/burn/TestBA/TestBA.cs
+++ b/src/test/burn/TestBA/TestBA.cs
@@ -21,7 +21,7 @@ namespace WixToolset.Test.BA
21 private Form dummyWindow; 21 private Form dummyWindow;
22 private IntPtr windowHandle; 22 private IntPtr windowHandle;
23 private LaunchAction action; 23 private LaunchAction action;
24 private ManualResetEvent wait; 24 private readonly ManualResetEvent wait;
25 private int result; 25 private int result;
26 26
27 private string updateBundlePath; 27 private string updateBundlePath;
@@ -397,6 +397,35 @@ namespace WixToolset.Test.BA
397 } 397 }
398 } 398 }
399 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
400 protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args) 429 protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args)
401 { 430 {
402 this.Log("OnExecuteFilesInUse() - package: {0}, source: {1}, retries remaining: {2}, data: {3}", args.PackageId, args.Source, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray())); 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
488 private void TestVariables() 517 private void TestVariables()
489 { 518 {
490 // First make sure we can check and get standard variables of each type. 519 // First make sure we can check and get standard variables of each type.
520 if (this.Engine.ContainsVariable("WindowsFolder"))
491 { 521 {
492 string value = null; 522 string value = this.Engine.GetVariableString("WindowsFolder");
493 if (this.Engine.ContainsVariable("WindowsFolder")) 523 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a string variable: WindowsFolder '{0}'", value));
494 { 524 }
495 value = this.Engine.GetVariableString("WindowsFolder"); 525 else
496 this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a string variable: WindowsFolder"); 526 {
497 } 527 throw new Exception("Engine did not define a standard variable: WindowsFolder");
498 else
499 {
500 throw new Exception("Engine did not define a standard variable: WindowsFolder");
501 }
502 } 528 }
503 529
530 if (this.Engine.ContainsVariable("NTProductType"))
504 { 531 {
505 long value = 0; 532 long value = this.Engine.GetVariableNumeric("NTProductType");
506 if (this.Engine.ContainsVariable("NTProductType")) 533 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a numeric variable: NTProductType '{0}'", value));
507 { 534 }
508 value = this.Engine.GetVariableNumeric("NTProductType"); 535 else
509 this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a numeric variable: NTProductType"); 536 {
510 } 537 throw new Exception("Engine did not define a standard variable: NTProductType");
511 else
512 {
513 throw new Exception("Engine did not define a standard variable: NTProductType");
514 }
515 } 538 }
516 539
540 if (this.Engine.ContainsVariable("VersionMsi"))
517 { 541 {
518 string value = null; 542 string value = this.Engine.GetVariableVersion("VersionMsi");
519 if (this.Engine.ContainsVariable("VersionMsi")) 543 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a version variable: VersionMsi '{0}'", value));
520 { 544 }
521 value = this.Engine.GetVariableVersion("VersionMsi"); 545 else
522 this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a version variable: VersionMsi"); 546 {
523 } 547 throw new Exception("Engine did not define a standard variable: VersionMsi");
524 else
525 {
526 throw new Exception("Engine did not define a standard variable: VersionMsi");
527 }
528 } 548 }
529 549
530 // Now validate that Contians returns false for non-existant variables of each type. 550 // Now validate that Contians returns false for non-existant variables of each type.
@@ -596,6 +616,15 @@ namespace WixToolset.Test.BA
596 } 616 }
597 } 617 }
598 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
599 private static bool TryParseEnum<T>(string value, out T t) 628 private static bool TryParseEnum<T>(string value, out T t)
600 { 629 {
601 try 630 try
diff --git a/src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj
new file mode 100644
index 00000000..7b7408c6
--- /dev/null
+++ b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wixproj
@@ -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
diff --git a/src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs
new file mode 100644
index 00000000..ca70236d
--- /dev/null
+++ b/src/test/burn/TestData/FailureTests/BundleD/BundleD.wxs
@@ -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>
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs
index d8428a54..bbc0b387 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/FailureTests.cs
@@ -2,7 +2,9 @@
2 2
3namespace WixToolsetTest.BurnE2E 3namespace WixToolsetTest.BurnE2E
4{ 4{
5 using System.Threading;
5 using WixTestTools; 6 using WixTestTools;
7 using WixToolset.Mba.Core;
6 using Xunit; 8 using Xunit;
7 using Xunit.Abstractions; 9 using Xunit.Abstractions;
8 10
@@ -11,6 +13,46 @@ namespace WixToolsetTest.BurnE2E
11 public FailureTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } 13 public FailureTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
12 14
13 [Fact] 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]
14 public void CanCancelMsiPackageVeryEarly() 56 public void CanCancelMsiPackageVeryEarly()
15 { 57 {
16 var packageA = this.CreatePackageInstaller("PackageA"); 58 var packageA = this.CreatePackageInstaller("PackageA");
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs b/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs
index d2e8a1ca..fa553919 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/TestBAController.cs
@@ -148,6 +148,21 @@ namespace WixToolsetTest.BurnE2E
148 } 148 }
149 149
150 /// <summary> 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>
151 /// Sets the number of times to re-run the Detect phase. 166 /// Sets the number of times to re-run the Detect phase.
152 /// </summary> 167 /// </summary>
153 /// <param name="state">Number of times to run Detect (after the first, normal, Detect).</param> 168 /// <param name="state">Number of times to run Detect (after the first, normal, Detect).</param>