@joebigelow / wix-1 / commits / 7728e34e

Use elevated engine to make the restart request when it is available.

Fixes 6145

Sean Hall committed Sep 2, 2022 at 16:12 UTC 7728e34e48a4fdb710ecc92dd8dca833bff3993f
8 files changed +240 -133
src/burn/engine/core.cpp
+42
@@ -2028,6 +2028,48 @@ extern "C" HRESULT DAPI CoreWaitForProcCompletion(
2028 return vpfnProcWaitForCompletion(hProcess, dwTimeout, pdwReturnCode);
2029 }
2030
2031 +extern "C" HRESULT DAPI CoreCloseElevatedLoggingThread(
2032 + __in BURN_ENGINE_STATE* pEngineState
2033 + )
2034 +{
2035 + HRESULT hr = S_OK;
2036 +
2037 + if (INVALID_HANDLE_VALUE == pEngineState->elevatedLoggingContext.hThread)
2038 + {
2039 + ExitFunction();
2040 + }
2041 +
2042 + if (!::SetEvent(pEngineState->elevatedLoggingContext.hFinishedEvent))
2043 + {
2044 + ExitWithLastError(hr, "Failed to set log finished event.");
2045 + }
2046 +
2047 + hr = AppWaitForSingleObject(pEngineState->elevatedLoggingContext.hThread, 5 * 60 * 1000); // TODO: is 5 minutes good?
2048 + ExitOnFailure(hr, "Failed to wait for elevated logging thread.");
2049 +
2050 +LExit:
2051 + return hr;
2052 +}
2053 +
2054 +extern "C" HRESULT DAPI CoreWaitForUnelevatedLoggingThread(
2055 + __in HANDLE hUnelevatedLoggingThread
2056 + )
2057 +{
2058 + HRESULT hr = S_OK;
2059 +
2060 + if (INVALID_HANDLE_VALUE == hUnelevatedLoggingThread)
2061 + {
2062 + ExitFunction();
2063 + }
2064 +
2065 + // Give the thread 15 seconds to exit.
2066 + hr = AppWaitForSingleObject(hUnelevatedLoggingThread, 15 * 1000);
2067 + ExitOnFailure(hr, "Failed to wait for unelevated logging thread.");
2068 +
2069 +LExit:
2070 + return hr;
2071 +}
2072 +
2073 // internal helper functions
2074
2075 static HRESULT AppendEscapedArgumentToCommandLine(
src/burn/engine/core.h
+17
@@ -123,6 +123,16 @@ typedef struct _BURN_ENGINE_COMMAND
123 LPWSTR sczLogFile;
124 } BURN_ENGINE_COMMAND;
125
126 +typedef struct _BURN_REDIRECTED_LOGGING_CONTEXT
127 +{
128 + CRITICAL_SECTION csBuffer;
129 + LPSTR sczBuffer;
130 + HANDLE hPipe;
131 + HANDLE hLogEvent;
132 + HANDLE hFinishedEvent;
133 + HANDLE hThread;
134 +} BURN_REDIRECTED_LOGGING_CONTEXT;
135 +
136 typedef struct _BURN_ENGINE_STATE
137 {
138 // UX flow control
@@ -164,6 +174,7 @@ typedef struct _BURN_ENGINE_STATE
174
175 BURN_PLAN plan;
176
177 + BURN_REDIRECTED_LOGGING_CONTEXT elevatedLoggingContext;
178 HANDLE hUnelevatedLoggingThread;
179
180 LPWSTR sczBundleEngineWorkingPath;
@@ -336,6 +347,12 @@ HRESULT DAPI CoreWaitForProcCompletion(
347 __in DWORD dwTimeout,
348 __out_opt DWORD* pdwReturnCode
349 );
350 +HRESULT DAPI CoreCloseElevatedLoggingThread(
351 + __in BURN_ENGINE_STATE* pEngineState
352 + );
353 +HRESULT DAPI CoreWaitForUnelevatedLoggingThread(
354 + __in HANDLE hUnelevatedLoggingThread
355 + );
356
357 #if defined(__cplusplus)
358 }
src/burn/engine/engine.cpp
+52 -100
@@ -3,15 +3,6 @@
3 #include "precomp.h"
4
5
6 -typedef struct _REDIRECTED_LOGGING_CONTEXT
7 -{
8 - CRITICAL_SECTION csBuffer;
9 - LPSTR sczBuffer;
10 - HANDLE hPipe;
11 - HANDLE hLogEvent;
12 - HANDLE hFinishedEvent;
13 -} REDIRECTED_LOGGING_CONTEXT;
14 -
6 // constants
7
8 const DWORD RESTART_RETRIES = 10;
@@ -65,13 +56,6 @@ static HRESULT LogStringOverPipe(
56 static DWORD WINAPI ElevatedLoggingThreadProc(
57 __in LPVOID lpThreadParameter
58 );
68 -static HRESULT WaitForElevatedLoggingThread(
69 - __in REDIRECTED_LOGGING_CONTEXT* pContext,
70 - __in HANDLE hLoggingThread
71 - );
72 -static HRESULT WaitForUnelevatedLoggingThread(
73 - __in HANDLE hUnelevatedLoggingThread
74 - );
59 static HRESULT Restart(
60 __in BURN_ENGINE_STATE* pEngineState
61 );
@@ -308,10 +292,6 @@ LExit:
292 {
293 LogId(REPORT_STANDARD, MSG_EXITING_RUN_ONCE, FAILED(hr) ? (int)hr : *pdwExitCode);
294 }
311 - else if (fRunElevated)
312 - {
313 - LogId(REPORT_STANDARD, MSG_EXITING_ELEVATED, FAILED(hr) ? (int)hr : *pdwExitCode);
314 - }
295
296 if (fLogInitialized)
297 {
@@ -320,7 +300,12 @@ LExit:
300 LogFlush();
301 }
302
323 - if (engineState.fRestart)
303 + // end per-machine process if running
304 + if (!fRunElevated && INVALID_HANDLE_VALUE != engineState.companionConnection.hPipe)
305 + {
306 + PipeTerminateChildProcess(&engineState.companionConnection, *pdwExitCode, engineState.fRestart);
307 + }
308 + else if (engineState.fRestart)
309 {
310 LogId(REPORT_STANDARD, MSG_RESTARTING);
311
@@ -334,6 +319,30 @@ LExit:
319 // If the message window is still around, close it.
320 UiCloseMessageWindow(&engineState);
321
322 + // If the logging thread is still around, close it.
323 + if (!fRunElevated)
324 + {
325 + CoreWaitForUnelevatedLoggingThread(engineState.hUnelevatedLoggingThread);
326 + }
327 + else if (fRunElevated)
328 + {
329 + CoreCloseElevatedLoggingThread(&engineState);
330 +
331 + // We're done talking to the child so always reset logging now.
332 + LogRedirect(NULL, NULL);
333 +
334 + // If there was a log message left, try to log it locally.
335 + if (engineState.elevatedLoggingContext.sczBuffer)
336 + {
337 + LogStringWorkRaw(engineState.elevatedLoggingContext.sczBuffer);
338 +
339 + ReleaseStr(engineState.elevatedLoggingContext.sczBuffer);
340 + }
341 +
342 + // Log the exit code here to make sure it gets in the elevated log.
343 + LogId(REPORT_STANDARD, MSG_EXITING_ELEVATED, FAILED(hr) ? (int)hr : *pdwExitCode);
344 + }
345 +
346 UninitializeEngineState(&engineState);
347
348 if (fXmlInitialized)
@@ -387,7 +396,12 @@ static HRESULT InitializeEngineState(
396 HANDLE hSectionFile = hEngineFile;
397 HANDLE hSourceEngineFile = INVALID_HANDLE_VALUE;
398
399 + pEngineState->hUnelevatedLoggingThread = INVALID_HANDLE_VALUE;
400 + pEngineState->elevatedLoggingContext.hPipe = INVALID_HANDLE_VALUE;
401 + pEngineState->elevatedLoggingContext.hThread = INVALID_HANDLE_VALUE;
402 +
403 ::InitializeCriticalSection(&pEngineState->csRestartState);
404 + ::InitializeCriticalSection(&pEngineState->elevatedLoggingContext.csBuffer);
405
406 pEngineState->internalCommand.automaticUpdates = BURN_AU_PAUSE_ACTION_IFELEVATED;
407 ::InitializeCriticalSection(&pEngineState->userExperience.csEngineActive);
@@ -423,6 +437,12 @@ static void UninitializeEngineState(
437 ReleaseMem(pEngineState->internalCommand.rgSecretArgs);
438 ReleaseMem(pEngineState->internalCommand.rgUnknownArgs);
439
440 + ReleaseFileHandle(pEngineState->hUnelevatedLoggingThread);
441 + ReleaseFileHandle(pEngineState->elevatedLoggingContext.hThread);
442 + ::DeleteCriticalSection(&pEngineState->elevatedLoggingContext.csBuffer);
443 + ReleaseHandle(pEngineState->elevatedLoggingContext.hLogEvent);
444 + ReleaseHandle(pEngineState->elevatedLoggingContext.hFinishedEvent);
445 +
446 PipeConnectionUninitialize(&pEngineState->embeddedConnection);
447 PipeConnectionUninitialize(&pEngineState->companionConnection);
448 ReleaseStr(pEngineState->sczBundleEngineWorkingPath)
@@ -643,14 +663,6 @@ LExit:
663
664 VariablesDump(&pEngineState->variables);
665
646 - // end per-machine process if running
647 - if (INVALID_HANDLE_VALUE != pEngineState->companionConnection.hPipe)
648 - {
649 - PipeTerminateChildProcess(&pEngineState->companionConnection, pEngineState->userExperience.dwExitCode, FALSE);
650 -
651 - WaitForUnelevatedLoggingThread(pEngineState->hUnelevatedLoggingThread);
652 - }
653 -
666 // If the splash screen is still around, close it.
667 if (::IsWindow(pEngineState->command.hwndSplashScreen))
668 {
@@ -671,9 +683,7 @@ static HRESULT RunElevated(
683 {
684 HRESULT hr = S_OK;
685 HANDLE hLock = NULL;
674 - HANDLE hLoggingThread = NULL;
675 - REDIRECTED_LOGGING_CONTEXT loggingContext = { };
676 - BOOL fDeleteLoggingCs = FALSE;
686 + BURN_REDIRECTED_LOGGING_CONTEXT* pLoggingContext = &pEngineState->elevatedLoggingContext;
687
688 // Initialize logging.
689 hr = LoggingOpen(&pEngineState->log, &pEngineState->internalCommand, &pEngineState->command, &pEngineState->variables, pEngineState->registration.sczDisplayName);
@@ -685,21 +695,18 @@ static HRESULT RunElevated(
695
696 // Set up the context for the logging thread then
697 // override logging to write over the pipe.
688 - ::InitializeCriticalSection(&loggingContext.csBuffer);
689 - fDeleteLoggingCs = TRUE;
690 -
691 - loggingContext.hLogEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL);
692 - ExitOnNullWithLastError(loggingContext.hLogEvent, hr, "Failed to create log event for logging thread.");
698 + pLoggingContext->hLogEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL);
699 + ExitOnNullWithLastError(pLoggingContext->hLogEvent, hr, "Failed to create log event for logging thread.");
700
694 - loggingContext.hFinishedEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL);
695 - ExitOnNullWithLastError(loggingContext.hFinishedEvent, hr, "Failed to create finished event for logging thread.");
701 + pLoggingContext->hFinishedEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL);
702 + ExitOnNullWithLastError(pLoggingContext->hFinishedEvent, hr, "Failed to create finished event for logging thread.");
703
697 - loggingContext.hPipe = pEngineState->companionConnection.hLoggingPipe;
704 + pLoggingContext->hPipe = pEngineState->companionConnection.hLoggingPipe;
705
699 - hLoggingThread = ::CreateThread(NULL, 0, ElevatedLoggingThreadProc, &loggingContext, 0, NULL);
700 - ExitOnNullWithLastError(hLoggingThread, hr, "Failed to create elevated logging thread.");
706 + pLoggingContext->hThread = ::CreateThread(NULL, 0, ElevatedLoggingThreadProc, pLoggingContext, 0, NULL);
707 + ExitOnNullWithLastError(pLoggingContext->hThread, hr, "Failed to create elevated logging thread.");
708
702 - LogRedirect(RedirectLoggingOverPipe, &loggingContext);
709 + LogRedirect(RedirectLoggingOverPipe, pLoggingContext);
710
711 if (!pEngineState->internalCommand.fInitiallyElevated)
712 {
@@ -716,29 +723,7 @@ static HRESULT RunElevated(
723 hr = ElevationChildPumpMessages(pEngineState->companionConnection.hPipe, pEngineState->companionConnection.hCachePipe, &pEngineState->approvedExes, &pEngineState->cache, &pEngineState->containers, &pEngineState->packages, &pEngineState->payloads, &pEngineState->variables, &pEngineState->registration, &pEngineState->userExperience, &hLock, &pEngineState->userExperience.dwExitCode, &pEngineState->fRestart, &pEngineState->plan.fApplying);
724 ExitOnFailure(hr, "Failed to pump messages from parent process.");
725
719 - WaitForElevatedLoggingThread(&loggingContext, hLoggingThread);
720 -
726 LExit:
722 - ReleaseHandle(hLoggingThread);
723 -
724 - LogRedirect(NULL, NULL); // we're done talking to the child so always reset logging now.
725 -
726 - if (fDeleteLoggingCs)
727 - {
728 - ::DeleteCriticalSection(&loggingContext.csBuffer);
729 - }
730 -
731 - ReleaseHandle(loggingContext.hLogEvent);
732 - ReleaseHandle(loggingContext.hFinishedEvent);
733 -
734 - // If there was a log message left, try to log it locally.
735 - if (loggingContext.sczBuffer)
736 - {
737 - LogStringWorkRaw(loggingContext.sczBuffer);
738 -
739 - ReleaseStr(loggingContext.sczBuffer);
740 - }
741 -
727 if (hLock)
728 {
729 ::ReleaseMutex(hLock);
@@ -936,7 +921,7 @@ static HRESULT DAPI RedirectLoggingOverPipe(
921 )
922 {
923 HRESULT hr = S_OK;
939 - REDIRECTED_LOGGING_CONTEXT* pContext = static_cast<REDIRECTED_LOGGING_CONTEXT*>(pvContext);
924 + BURN_REDIRECTED_LOGGING_CONTEXT* pContext = static_cast<BURN_REDIRECTED_LOGGING_CONTEXT*>(pvContext);
925
926 ::EnterCriticalSection(&pContext->csBuffer);
927
@@ -986,7 +971,7 @@ static DWORD WINAPI ElevatedLoggingThreadProc(
971 {
972 HRESULT hr = S_OK;
973 DWORD dwLastError = ERROR_SUCCESS;
989 - REDIRECTED_LOGGING_CONTEXT* pContext = static_cast<REDIRECTED_LOGGING_CONTEXT*>(lpThreadParameter);
974 + BURN_REDIRECTED_LOGGING_CONTEXT* pContext = static_cast<BURN_REDIRECTED_LOGGING_CONTEXT*>(lpThreadParameter);
975 DWORD dwSignaledIndex = 0;
976 LPSTR sczBuffer = NULL;
977 BURN_PIPE_RESULT result = { };
@@ -1079,39 +1064,6 @@ LExit:
1064 return (DWORD)hr;
1065 }
1066
1082 -static HRESULT WaitForElevatedLoggingThread(
1083 - __in REDIRECTED_LOGGING_CONTEXT* pContext,
1084 - __in HANDLE hLoggingThread
1085 - )
1086 -{
1087 - HRESULT hr = S_OK;
1088 -
1089 - if (!::SetEvent(pContext->hFinishedEvent))
1090 - {
1091 - ExitWithLastError(hr, "Failed to set log finished event.");
1092 - }
1093 -
1094 - hr = AppWaitForSingleObject(hLoggingThread, 5 * 60 * 1000); // TODO: is 5 minutes good?
1095 - ExitOnFailure(hr, "Failed to wait for elevated logging thread.");
1096 -
1097 -LExit:
1098 - return hr;
1099 -}
1100 -
1101 -static HRESULT WaitForUnelevatedLoggingThread(
1102 - __in HANDLE hUnelevatedLoggingThread
1103 - )
1104 -{
1105 - HRESULT hr = S_OK;
1106 -
1107 - // Give the thread 15 seconds to exit.
1108 - hr = AppWaitForSingleObject(hUnelevatedLoggingThread, 15 * 1000);
1109 - ExitOnFailure(hr, "Failed to wait for unelevated logging thread.");
1110 -
1111 -LExit:
1112 - return hr;
1113 -}
1114 -
1067 static HRESULT Restart(
1068 __in BURN_ENGINE_STATE* pEngineState
1069 )
src/burn/engine/uithread.cpp
+10
@@ -222,6 +222,16 @@ static LRESULT CALLBACK WndProc(
222 ::Sleep(250);
223 }
224
225 + // If this is the per-machine process then close the logging pipe with the parent process.
226 + if (pInfo->fElevatedEngine)
227 + {
228 + CoreCloseElevatedLoggingThread(pInfo->pEngineState);
229 + }
230 + else
231 + {
232 + CoreWaitForUnelevatedLoggingThread(pInfo->pEngineState->hUnelevatedLoggingThread);
233 + }
234 +
235 LogStringWorkRaw("=======================================\r\n");
236
237 // Close the log to try to make sure everything is flushed to disk.
src/test/burn/TestData/Manual/BafThmutilTesting/BafThmUtilTesting.cpp
-33
@@ -89,39 +89,6 @@ public: // IBAFunctions
89 return hr;
90 }
91
92 - virtual STDMETHODIMP WndProc(
93 - __in HWND hWnd,
94 - __in UINT uMsg,
95 - __in WPARAM /*wParam*/,
96 - __in LPARAM lParam,
97 - __inout BOOL* pfProcessed,
98 - __inout LRESULT* plResult
99 - )
100 - {
101 - switch (uMsg)
102 - {
103 - case WM_QUERYENDSESSION:
104 - if (BOOTSTRAPPER_DISPLAY_FULL <= m_command.display)
105 - {
106 - DWORD dwEndSession = static_cast<DWORD>(lParam);
107 - if (ENDSESSION_CRITICAL & dwEndSession)
108 - {
109 - // Return false to get the WM_ENDSESSION message so that critical shutdowns can be delayed.
110 - *plResult = FALSE;
111 - *pfProcessed = TRUE;
112 - }
113 - }
114 - break;
115 - case WM_ENDSESSION:
116 - if (BOOTSTRAPPER_DISPLAY_FULL <= m_command.display)
117 - {
118 - ::MessageBoxW(hWnd, L"WM_ENDSESSION", L"BAFunctions WndProc", MB_OK);
119 - }
120 - break;
121 - }
122 - return S_OK;
123 - }
124 -
92 public: //IBootstrapperApplication
93 virtual STDMETHODIMP OnExecuteBegin(
94 __in DWORD /*cExecutingPackages*/,
src/test/burn/TestData/Manual/BundleA/BundleA.wixproj
+4
@@ -11,8 +11,12 @@
11 <ItemGroup>
12 <ProjectReference Include="..\BafThmUtilTesting\BafThmUtilTesting.vcxproj" Properties="Platform=x86" ReferenceOutputAssembly="false" />
13 <ProjectReference Include="..\PackageA\PackageA.wixproj" />
14 + <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
15 </ItemGroup>
16 <ItemGroup>
17 <PackageReference Include="WixToolset.Bal.wixext" />
18 </ItemGroup>
19 + <Target Name="CopyManualInstructions" AfterTargets="AfterBuild">
20 + <Copy SourceFiles="ManualTests.txt" DestinationFiles="$(OutputPath)ManualTests.txt" />
21 + </Target>
22 </Project>
\ No newline at end of file
src/test/burn/TestData/Manual/BundleA/BundleA.wxs
+5
@@ -11,7 +11,12 @@
11 <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)">
12 <MsiProperty Name="FORCERESTARTCA" Value="[FORCERESTARTCA]" />
13 </MsiPackage>
14 + <ExePackage Id="ExeA" Cache="remove" PerMachine="yes"
15 + DetectCondition="" Permanent="yes" InstallArguments="/ec [EXEEXITCODE]">
16 + <PayloadGroupRef Id="TestExePayloads" />
17 + </ExePackage>
18 </PackageGroup>
19 + <Variable Name="EXEEXITCODE" bal:Overridable="yes" Value="0" />
20 <Variable Name="FORCERESTARTCA" bal:Overridable="yes" />
21 </Fragment>
22 </Wix>
src/test/burn/TestData/Manual/BundleA/ManualTests.txt new
+110
@@ -0,0 +1,110 @@
1 +VariousWixstdbaAndBAFunctionsAndThmutilFunctionality
2 +1. Run BundleA.exe unelevated.
3 +2. Click Test.
4 +3. Verify that the BafThmUtilTestingTheme window comes up and click Start.
5 +4. There are several pages. Follow the instructions on each page before clicking Next.
6 +5. Click ThemeClose to close the BafThmUtilTestingTheme window.
7 +6. Click the Options button.
8 +7. Click the Browse button.
9 +8. Type in "C:\Program Files (x86)\mytest".
10 +9. Click the Select Folder button.
11 +10. Verify the Install location textbox was populated with "C:\Program Files (x86)\mytest".
12 +11. Click OK.
13 +12. Verify the caption of the window is "~Manual - BundleA Setup" and the text is "~Manual - BundleA".
14 +13. Verify the Install button has the UAC shield and then click it (accept elevation).
15 +14. Click OK on the OnExecuteBegin message box.
16 +15. After it finishes, verify the window text is "Installation Successfully Completed".
17 +16. Click Close.
18 +17. Verify in the bundle log that the variable InstallFolder was set to "C:\Program Files (x86)\mytest".
19 +18. Run BundleA.exe again.
20 +19. Click Uninstall (accept elevation).
21 +20. Click OK on the OnExecuteBegin message box.
22 +21. After it finishes, verify the window text is "Uninstallation Successfully Completed".
23 +22. Click Close.
24 +
25 +CanBlockShutdownDuringApply
26 +1. Run BundleA.exe unelevated.
27 +2. Click Install (accept elevation).
28 +3. Verify the OnExecuteBegin message box comes up with the message "Shutdown requests should be denied right now." and leave it up.
29 +4. Use the Windows UI to try to restart the machine.
30 +5. On current versions of Windows, full screen UI will display that the bundle is blocking restart. Click Cancel to stop the restart.
31 +6. Click OK on the message box from step 3 to allow installation to continue.
32 +7. Verify that the installation was successful and then close the bundle.
33 +8. Look in the bundle log and verify that the restart request didn't cause any errors, and that it logged messages that look like:
34 +
35 +i400: Received system request to shut down the process: allowed: No, elevated: No, critical: No, logoff: No, close app: No
36 +i400: Received system request to shut down the process: allowed: No, elevated: Yes, critical: No, logoff: No, close app: No
37 +i401: Received result of system request to shut down the process: closing: No, elevated: No, critical: No, logoff: No, close app: No
38 +i401: Received result of system request to shut down the process: closing: No, elevated: Yes, critical: No, logoff: No, close app: No
39 +
40 +(9. Uninstall the bundle)
41 +
42 +CanLogRestartFailure
43 +
44 + Note: this test requires special setup. Change the User Rights Assignment such that no one has the shutdown privilege. Logout and log back in after making changes to make them take effect.
45 + (mmc.exe -> Group Policy Object Editor -> Local Computer Policy -> Computer Configuration -> Windows Settings -> Security Settings -> Local Policies -> User Rights Assignment -> Shut down the system)
46 +
47 +1. Run BundleA.exe elevated with the command line EXEEXITCODE=3010.
48 +2. Click Install.
49 +3. Click OK on the OnExecuteBegin message box.
50 +4. Click Restart.
51 +5. Look in the bundle log and verify that the restart request failure was logged with messages that look like:
52 +
53 +w005: Restarting computer...
54 +e000: Error 0x80070005: Failed to schedule restart.
55 +e023: The restart request failed, error: 0x80070005. The machine will need to be manually restarted.
56 +
57 +(5. Uninstall the bundle)
58 +
59 +CanGracefullyCutApplyShortInResponseToCriticalShutdown
60 +
61 + Note: Requires different User Rights Assignment settings from CanLogRestartFailure - Administrators need to have the shutdown privilege.
62 +
63 +1. Run BundleA.exe FORCERESTARTCA=1.
64 +2. Click Install (accept elevation).
65 +3. Click OK on the OnExecuteBegin message box.
66 +4. Verify the machine automatically restarts with no chance to cancel it (because the MSI forces a restart).
67 +5. Login to the machine.
68 +6. Verify that the bundle automatically started running again.
69 +7. Click OK on the OnExecuteBegin message box.
70 +8. Verify that the installation was successful and then close the bundle.
71 +9. Look in the bundle log and verify that the restart request didn't cause any errors, and that it logged messages that look like:
72 +
73 +i301: Applying execute package: PackageA, action: Install, path: C:\ProgramData\Package Cache\{1C6F49C0-B4BA-438D-871D-2B5E91C0EED5}v1.0.0.0\PackageA.msi, arguments: ' FORCERESTARTCA="1" ARPSYSTEMCOMPONENT="1" MSIFASTINSTALL="7" BURNMSIINSTALL=1 REINSTALLMODE="muso" REBOOT=ReallySuppress'
74 +...
75 +i400: Received system request to shut down the process: allowed: No, elevated: No, critical: Yes, logoff: No, close app: No
76 +i400: Received system request to shut down the process: allowed: No, elevated: Yes, critical: Yes, logoff: No, close app: No
77 +i401: Received result of system request to shut down the process: closing: Yes, elevated: No, critical: Yes, logoff: No, close app: No
78 +i401: Received result of system request to shut down the process: closing: Yes, elevated: Yes, critical: Yes, logoff: No, close app: No
79 +...
80 +i319: Applied execute package: PackageA, result: 0x0, restart: Initiated
81 +...
82 +i420: Resuming automatic updates.
83 +i421: Resumed automatic updates.
84 +=======================================
85 +i001: Burn x86 v4.0.0.525, Windows v10.0 x64 (Build 19041: Service Pack 0), path: C:\ProgramData\Package Cache\{30D63455-CD23-4AC3-81CC-4695434F848A}\BundleA.exe
86 +
87 +(10. Uninstall the bundle)
88 +
89 +CanRestartFromUnelevatedPerMachineBundleWithoutShutdownPrivilege
90 +
91 + Note: Requires different User Rights Assignment settings from CanLogRestartFailure - Only Administrators should have the shutdown privilege. Users should not have it.
92 +
93 +1. Run BundleA.exe unelevated with the command line EXEEXITCODE=3010.
94 +2. Click Install.
95 +3. Verify that the UAC prompt came up and accept elevation.
96 +4. Click OK on the OnExecuteBegin message box.
97 +5. Click Restart.
98 +6. The machine should restart.
99 +7. Login to the machine.
100 +8. Verify that the bundle did not automatically start running.
101 +9. Look in the bundle log and verify that the restart request didn't cause any errors, and that it logged messages that look like (the process id for w005 must match the elevated i400 and i401):
102 +
103 +[0DDC:0448]w005: Restarting computer...
104 +[1228:18CC]i400: Received system request to shut down the process: allowed: Yes, elevated: No, critical: No, logoff: No, close app: No
105 +[1228:18CC]i401: Received result of system request to shut down the process: closing: Yes, elevated: No, critical: No, logoff: No, close app: No
106 +[0DDC:0954]i400: Received system request to shut down the process: allowed: Yes, elevated: Yes, critical: No, logoff: No, close app: No
107 +[0DDC:0954]i401: Received result of system request to shut down the process: closing: Yes, elevated: Yes, critical: No, logoff: No, close app: No
108 +=======================================
109 +
110 +(10. Uninstall the bundle)