summaryrefslogtreecommitdiff
path: root/setup_native
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2019-03-21 00:32:43 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2019-03-22 23:56:58 +0100
commit601cc06187d20355810666547225549984b0caa4 (patch)
treed666125db87192dfec738702f53f4df6b209295d /setup_native
parentcd72a2dd6c65284a9a5cedff48766c94f9fffb94 (diff)
tdf#123832: Try to prevent/control hung wusa.exe while installing UCRT
Reportedly under some circumstances execution of wusa.exe may hang for very long time (available logs show more than 90 min). LO MSI installer waits for wusa.exe indefinitely in its deferred inst_msu custom action; during this wait, one can't abort the installation. There is an evidence [1] that stopping WU service prior to wusa launch might prevent this hang. The patch does two things: 1. It stops running WU service prior to wusa launch. 2. It adds a check for user input by executing MsiProcessMessage twice a second, and checking its return value, to allow early return in case user cancels the installation. This is a blind shot, since I cannot reproduce the problem myself. [1] https://superuser.com/questions/1044528/ Change-Id: I8bf4ce52b3e9d98a4f90af3abcc49ac63b6a0e40 Reviewed-on: https://gerrit.libreoffice.org/69487 Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com> Tested-by: Mike Kaganski <mike.kaganski@collabora.com>
Diffstat (limited to 'setup_native')
-rw-r--r--setup_native/source/win32/customactions/inst_msu/inst_msu.cxx101
1 files changed, 76 insertions, 25 deletions
diff --git a/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx b/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx
index 97394e76f3ce..ebcb96c5a4da 100644
--- a/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx
+++ b/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx
@@ -35,6 +35,14 @@ template <typename IntType> std::string Num2Dec(IntType n)
return sMsg.str();
}
+std::string Win32ErrorMessage(const char* sFunc, DWORD nWin32Error)
+{
+ std::stringstream sMsg;
+ sMsg << sFunc << " failed with Win32 error code " << Num2Hex(nWin32Error) << "!";
+
+ return sMsg.str();
+}
+
void ThrowHResult(const char* sFunc, HRESULT hr)
{
std::stringstream sMsg;
@@ -51,10 +59,7 @@ void CheckHResult(const char* sFunc, HRESULT hr)
void ThrowWin32Error(const char* sFunc, DWORD nWin32Error)
{
- std::stringstream sMsg;
- sMsg << sFunc << " failed with Win32 error code " << Num2Hex(nWin32Error) << "!";
-
- throw std::exception(sMsg.str().c_str());
+ throw std::exception(Win32ErrorMessage(sFunc, nWin32Error).c_str());
}
void ThrowLastError(const char* sFunc) { ThrowWin32Error(sFunc, GetLastError()); }
@@ -195,6 +200,8 @@ bool IsWow64Process()
}
// Checks if Windows Update service is disabled, and if it is, enables it temporarily.
+// Also stops the service if it's currently running, because it seems that wusa.exe
+// does not freeze when it starts the service itself.
class WUServiceEnabler
{
public:
@@ -235,7 +242,14 @@ private:
ThrowLastError("OpenServiceW");
WriteLog(hInstall, "Obtained WU service handle");
- if (ServiceStatus(hInstall, hService.get()) == SERVICE_RUNNING
+ const DWORD nCurrentStatus = ServiceStatus(hInstall, hService.get());
+ // Stop currently running service to prevent wusa.exe from hanging trying to detect if the
+ // update is applicable (sometimes this freezes it ~indefinitely; it seems that it doesn't
+ // happen if wusa.exe starts the service itself: https://superuser.com/questions/1044528/).
+ if (nCurrentStatus == SERVICE_RUNNING)
+ StopService(hInstall, hService.get());
+
+ if (nCurrentStatus == SERVICE_RUNNING
|| !EnsureServiceEnabled(hInstall, hService.get(), true))
{
// No need to restore anything back, since we didn't change config
@@ -332,17 +346,26 @@ private:
static void StopService(MSIHANDLE hInstall, SC_HANDLE hService)
{
- if (ServiceStatus(hInstall, hService) != SERVICE_STOPPED)
+ try
{
- SERVICE_STATUS aServiceStatus{};
- if (!ControlService(hService, SERVICE_CONTROL_STOP, &aServiceStatus))
- ThrowLastError("ControlService");
- WriteLog(hInstall,
- "Successfully sent SERVICE_CONTROL_STOP code to Windows Update service");
- // No need to wait for the service stopped
+ if (ServiceStatus(hInstall, hService) != SERVICE_STOPPED)
+ {
+ SERVICE_STATUS aServiceStatus{};
+ if (!ControlService(hService, SERVICE_CONTROL_STOP, &aServiceStatus))
+ WriteLog(hInstall, Win32ErrorMessage("ControlService", GetLastError()));
+ else
+ WriteLog(
+ hInstall,
+ "Successfully sent SERVICE_CONTROL_STOP code to Windows Update service");
+ // No need to wait for the service stopped
+ }
+ else
+ WriteLog(hInstall, "Windows Update service is not running");
+ }
+ catch (std::exception& e)
+ {
+ WriteLog(hInstall, e.what());
}
- else
- WriteLog(hInstall, "Windows Update service is not running");
}
MSIHANDLE mhInstall;
@@ -469,7 +492,8 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall)
auto aDeleteFileGuard(Guard(sBinaryName));
SetStatusText(hInstall, sKBNo);
- // In case the Windows Update service is disabled, we temporarily enable it here
+ // In case the Windows Update service is disabled, we temporarily enable it here. We also
+ // stop running WU service, to avoid wusa.exe freeze (see comment in EnableWUService).
WUServiceEnabler aWUServiceEnabler(hInstall);
const bool bWow64Process = IsWow64Process();
@@ -489,34 +513,61 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall)
if (!CreateProcessW(sWUSAPath.c_str(), const_cast<LPWSTR>(sWUSACmd.c_str()), nullptr,
nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
ThrowLastError("CreateProcessW");
+ CloseHandle(pi.hThread);
auto aCloseProcHandleGuard(Guard(pi.hProcess));
WriteLog(hInstall, "CreateProcessW succeeded");
- DWORD nWaitResult = WaitForSingleObject(pi.hProcess, INFINITE);
- if (nWaitResult != WAIT_OBJECT_0)
- ThrowWin32Error("WaitForSingleObject", nWaitResult);
+ {
+ // This block waits when the started wusa.exe process finishes. Since it's possible
+ // for wusa.exe in some circumstances to wait really long (indefinitely?), we use
+ // MsiProcessMessage to check for user input: it returns IDCANCEL when user cancels
+ // installation.
+ PMSIHANDLE hProgressRec = MsiCreateRecord(3);
+ // Use explicit progress messages
+ MsiRecordSetInteger(hProgressRec, 1, 1);
+ MsiRecordSetInteger(hProgressRec, 2, 1);
+ MsiRecordSetInteger(hProgressRec, 3, 0);
+ int nResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec);
+ if (nResult == IDCANCEL)
+ return ERROR_INSTALL_USEREXIT;
+ // Prepare the record to following progress update calls
+ MsiRecordSetInteger(hProgressRec, 1, 2);
+ MsiRecordSetInteger(hProgressRec, 2, 0); // step by 0 - don't move progress
+ MsiRecordSetInteger(hProgressRec, 3, 0);
+ for (;;)
+ {
+ DWORD nWaitResult = WaitForSingleObject(pi.hProcess, 500);
+ if (nWaitResult == WAIT_OBJECT_0)
+ break; // wusa.exe finished
+ else if (nWaitResult == WAIT_TIMEOUT)
+ {
+ // Check if user has cancelled
+ nResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec);
+ if (nResult == IDCANCEL)
+ return ERROR_INSTALL_USEREXIT;
+ }
+ else
+ ThrowWin32Error("WaitForSingleObject", nWaitResult);
+ }
+ }
DWORD nExitCode = 0;
if (!GetExitCodeProcess(pi.hProcess, &nExitCode))
ThrowLastError("GetExitCodeProcess");
- HRESULT hr = static_cast<HRESULT>(nExitCode);
- if (hr == HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED))
- hr = WU_S_REBOOT_REQUIRED;
-
- switch (hr)
+ switch (HRESULT hr = static_cast<HRESULT>(nExitCode))
{
case S_OK:
- case S_FALSE:
case WU_S_ALREADY_INSTALLED:
case WU_E_NOT_APPLICABLE: // Windows could lie us about its version, etc.
case ERROR_SUCCESS_REBOOT_REQUIRED:
+ case HRESULT_FROM_WIN32(ERROR_SUCCESS_REBOOT_REQUIRED):
case WU_S_REBOOT_REQUIRED:
WriteLog(hInstall, "wusa.exe succeeded with exit code", Num2Hex(nExitCode));
return ERROR_SUCCESS;
default:
- ThrowWin32Error("Execution of wusa.exe", nExitCode);
+ ThrowHResult("Execution of wusa.exe", hr);
}
}
catch (std::exception& e)