summaryrefslogtreecommitdiff
path: root/setup_native
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2019-04-21 22:39:43 +0300
committerMike Kaganski <mike.kaganski@collabora.com>2019-04-22 04:22:57 +0200
commitbb6906b66d7ceac8465b43d7e2b1f05976d43600 (patch)
treec70cbe9fe5f15fbcd04cadb8adfa06f1cf7fcd4a /setup_native
parent91f22f62aa6568aed206e303fcd8eb99fa1b545f (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>
Diffstat (limited to 'setup_native')
-rw-r--r--setup_native/source/win32/customactions/inst_msu/inst_msu.cxx136
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)