diff options
author | Mike Kaganski <mike.kaganski@collabora.com> | 2019-04-21 22:39:43 +0300 |
---|---|---|
committer | Mike Kaganski <mike.kaganski@collabora.com> | 2019-04-22 04:22:57 +0200 |
commit | bb6906b66d7ceac8465b43d7e2b1f05976d43600 (patch) | |
tree | c70cbe9fe5f15fbcd04cadb8adfa06f1cf7fcd4a | |
parent | 91f22f62aa6568aed206e303fcd8eb99fa1b545f (diff) |
tdf#124794: Wait for WU service stopped before launching wusa.exe
It seems that wusa.exe would fail with error code 0x80080005
if launched too soon after WU service was sent a stop control
(which was added in tdf#123832).
Change-Id: I470a8a8e933e8a0cd6208c095ed63c1761b3b434
Reviewed-on: https://gerrit.libreoffice.org/71045
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
-rw-r--r-- | setup_native/source/win32/customactions/inst_msu/inst_msu.cxx | 136 |
1 files changed, 96 insertions, 40 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 ebcb96c5a4da..6ce517f2f863 100644 --- a/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx +++ b/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx @@ -141,14 +141,14 @@ void ShowWarning(MSIHANDLE hInst, const std::wstring& sKBNo, const char* sMessag } // Set custom action description visible in progress dialog -void SetStatusText(MSIHANDLE hInst, const std::wstring& sKBNo) +void SetStatusText(MSIHANDLE hInst, const std::wstring& actName, const std::wstring& actDesc) { PMSIHANDLE hRec = MsiCreateRecord(3); // For INSTALLMESSAGE_ACTIONSTART, record's Field 0 must be null - std::wstring s(sKBNo + L" installation"); - MsiRecordSetStringW(hRec, 1, s.c_str()); // Field 1: Action name - must be non-null - s = L"Installing " + sKBNo; - MsiRecordSetStringW(hRec, 2, s.c_str()); // Field 2: Action description - displayed in dialog + // Field 1: Action name - must be non-null + MsiRecordSetStringW(hRec, 1, actName.c_str()); + // Field 2: Action description - displayed in dialog + MsiRecordSetStringW(hRec, 2, actDesc.c_str()); // Let Field 3 stay null - no action template MsiProcessMessage(hInst, INSTALLMESSAGE_ACTIONSTART, hRec); } @@ -199,6 +199,46 @@ bool IsWow64Process() #endif } +// This class uses MsiProcessMessage to check for user input: it returns IDCANCEL when user cancels +// installation. It throws a special exception, to be intercepted in main action function to return +// corresponding exit code. +class UserInputChecker +{ +public: + class eUserCancelled + { + }; + + UserInputChecker(MSIHANDLE hInstall) + : m_hInstall(hInstall) + , m_hProgressRec(MsiCreateRecord(3)) + { + // Use explicit progress messages + MsiRecordSetInteger(m_hProgressRec, 1, 1); + MsiRecordSetInteger(m_hProgressRec, 2, 1); + MsiRecordSetInteger(m_hProgressRec, 3, 0); + int nResult = MsiProcessMessage(m_hInstall, INSTALLMESSAGE_PROGRESS, m_hProgressRec); + if (nResult == IDCANCEL) + throw eUserCancelled(); + // Prepare the record to following progress update calls + MsiRecordSetInteger(m_hProgressRec, 1, 2); + MsiRecordSetInteger(m_hProgressRec, 2, 0); // step by 0 - don't move progress + MsiRecordSetInteger(m_hProgressRec, 3, 0); + } + + void ThrowIfUserCancelled() + { + // Check if user has cancelled + int nResult = MsiProcessMessage(m_hInstall, INSTALLMESSAGE_PROGRESS, m_hProgressRec); + if (nResult == IDCANCEL) + throw eUserCancelled(); + } + +private: + MSIHANDLE m_hInstall; + PMSIHANDLE m_hProgressRec; +}; + // 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. @@ -218,7 +258,7 @@ public: if (mhService) { EnsureServiceEnabled(mhInstall, mhService.get(), false); - StopService(mhInstall, mhService.get()); + StopService(mhInstall, mhService.get(), false); } } catch (std::exception& e) @@ -246,8 +286,9 @@ private: // 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/). + // tdf#124794: Wait for service to stop. if (nCurrentStatus == SERVICE_RUNNING) - StopService(hInstall, hService.get()); + StopService(hInstall, hService.get(), true); if (nCurrentStatus == SERVICE_RUNNING || !EnsureServiceEnabled(hInstall, hService.get(), true)) @@ -268,9 +309,8 @@ private: DWORD nCbRequired = 0; if (!QueryServiceConfigW(hService, nullptr, 0, &nCbRequired)) { - DWORD nError = GetLastError(); - if (nError != ERROR_INSUFFICIENT_BUFFER) - ThrowLastError("QueryServiceConfigW"); + if (DWORD nError = GetLastError(); nError != ERROR_INSUFFICIENT_BUFFER) + ThrowWin32Error("QueryServiceConfigW", nError); } std::unique_ptr<char[]> pBuf(new char[nCbRequired]); LPQUERY_SERVICE_CONFIGW pConfig = reinterpret_cast<LPQUERY_SERVICE_CONFIGW>(pBuf.get()); @@ -344,7 +384,7 @@ private: return aServiceStatus.dwCurrentState; } - static void StopService(MSIHANDLE hInstall, SC_HANDLE hService) + static void StopService(MSIHANDLE hInstall, SC_HANDLE hService, bool bWait) { try { @@ -352,12 +392,34 @@ private: { 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 + ThrowLastError("ControlService"); + WriteLog(hInstall, + "Successfully sent SERVICE_CONTROL_STOP code to Windows Update service"); + if (aServiceStatus.dwCurrentState != SERVICE_STOPPED && bWait) + { + // Let user cancel too long wait + UserInputChecker aInputChecker(hInstall); + // aServiceStatus.dwWaitHint is unreasonably high for Windows Update (30000), + // so don't use it, but simply poll service status each second + for (int nWait = 0; nWait < 30; ++nWait) // arbitrary limit of 30 s + { + for (int i = 0; i < 2; ++i) // check user input twice a second + { + Sleep(500); + aInputChecker.ThrowIfUserCancelled(); + } + + if (!QueryServiceStatus(hService, &aServiceStatus)) + ThrowLastError("QueryServiceStatus"); + + if (aServiceStatus.dwCurrentState == SERVICE_STOPPED) + break; + } + } + if (aServiceStatus.dwCurrentState == SERVICE_STOPPED) + WriteLog(hInstall, "Successfully stopped Windows Update service"); + else if (bWait) + WriteLog(hInstall, "Wait for Windows Update stop timed out - proceeding"); } else WriteLog(hInstall, "Windows Update service is not running"); @@ -490,12 +552,16 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall) sKBNo = std::wstring(sCustomActionData, sBinaryName - sCustomActionData); ++sBinaryName; auto aDeleteFileGuard(Guard(sBinaryName)); - SetStatusText(hInstall, sKBNo); + + SetStatusText(hInstall, L"WU service state check", + L"Checking Windows Update service state"); // 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); + SetStatusText(hInstall, sKBNo + L" installation", L"Installing " + sKBNo); + const bool bWow64Process = IsWow64Process(); WriteLog(hInstall, "Is Wow64 Process:", bWow64Process ? "YES" : "NO"); std::wstring sWUSAPath = bWow64Process ? GetKnownFolder(FOLDERID_Windows) + L"\\SysNative" @@ -519,33 +585,16 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall) { // 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 wusa.exe in some circumstances to wait really long (indefinitely?), we check + // for user input here. + UserInputChecker aInputChecker(hInstall); 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; - } + aInputChecker.ThrowIfUserCancelled(); else ThrowWin32Error("WaitForSingleObject", nWaitResult); } @@ -570,6 +619,10 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall) ThrowHResult("Execution of wusa.exe", hr); } } + catch (const UserInputChecker::eUserCancelled&) + { + return ERROR_INSTALL_USEREXIT; + } catch (std::exception& e) { WriteLog(hInstall, e.what()); @@ -596,7 +649,10 @@ extern "C" __declspec(dllexport) UINT __stdcall CleanupMSU(MSIHANDLE hInstall) WriteLog(hInstall, "Got CustomActionData value:", sBinaryName); if (!DeleteFileW(sBinaryName)) - ThrowLastError("DeleteFileW"); + { + if (DWORD nError = GetLastError(); nError != ERROR_FILE_NOT_FOUND) + ThrowWin32Error("DeleteFileW", nError); + } WriteLog(hInstall, "File successfully removed"); } catch (std::exception& e) |