From 7ee07db18d8d831ec7a2e402fb15ef90cd2c92ca Mon Sep 17 00:00:00 2001 From: Ben Hillis Date: Mon, 6 Apr 2026 15:17:38 -0700 Subject: [PATCH] Suppress MSI-initiated reboots during Store updates (#40079) When the WSL MSIX package is updated via the Microsoft Store, the WslInstaller service automatically upgrades the MSI package by calling MsiInstallProduct. This call was made with INSTALLUILEVEL_NONE (silent install) but without setting the REBOOT=ReallySuppress property. Per Windows Installer documentation, when a silent install encounters files in use and REBOOT is not suppressed, the system reboots automatically without any user prompt. This could cause unexpected machine restarts after a Store update when WSL binaries (e.g. wslservice.exe) were in use during the upgrade. Every deployment script in the repo already passes /norestart to msiexec (deploy-to-host.ps1, deploy-to-vm.ps1, install-latest-wsl.ps1, test-setup.ps1), but the programmatic MsiInstallProduct path used by the WslInstaller service lacked the equivalent property. This change: - Always appends REBOOT=ReallySuppress to MsiInstallProduct arguments in UpgradeViaMsi, preventing Windows Installer from ever initiating a system restart during install/upgrade. - Switches UninstallViaMsi from MsiConfigureProduct to MsiConfigureProductEx so we can pass REBOOT=ReallySuppress during uninstall as well. - Propagates ERROR_SUCCESS_REBOOT_REQUIRED (3010) to callers instead of swallowing it. User-facing paths (wsl --update, wsl --uninstall) print a reboot-needed message to stderr. The background WslInstaller service silently treats 3010 as success since it has no console. Co-authored-by: Ben Hillis Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/windows/common/WslClient.cpp | 6 ++++- src/windows/common/install.cpp | 25 +++++++++++++------ src/windows/wslinstaller/exe/WslInstaller.cpp | 15 ++++++++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp index e1f3a0f1..5e9f952d 100644 --- a/src/windows/common/WslClient.cpp +++ b/src/windows/common/WslClient.cpp @@ -1277,7 +1277,11 @@ int Uninstall() const auto exitCode = wsl::windows::common::install::UninstallViaMsi(logFile.c_str(), &wsl::windows::common::install::MsiMessageCallback); - if (exitCode != 0) + if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED) + { + wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (exitCode != 0) { clearLogs.release(); THROW_HR_WITH_USER_ERROR( diff --git a/src/windows/common/install.cpp b/src/windows/common/install.cpp index 740e382d..a268f8bf 100644 --- a/src/windows/common/install.cpp +++ b/src/windows/common/install.cpp @@ -104,7 +104,11 @@ int UpdatePackageImpl(bool preRelease, bool repair) const auto exitCode = UpgradeViaMsi(downloadPath.c_str(), L"", logFile.c_str(), &MsiMessageCallback); - if (exitCode != 0) + if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED) + { + PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED); + } + else if (exitCode != 0) { clearLogs.release(); THROW_HR_WITH_USER_ERROR( @@ -358,15 +362,20 @@ int wsl::windows::common::install::UpdatePackage(bool PreRelease, bool Repair) UINT wsl::windows::common::install::UpgradeViaMsi( _In_ LPCWSTR PackageLocation, _In_opt_ LPCWSTR ExtraArgs, _In_opt_ LPCWSTR LogFile, _In_ const std::function& Callback) { - WriteInstallLog(std::format("Upgrading via MSI package: {}. Args: {}", PackageLocation, ExtraArgs != nullptr ? ExtraArgs : L"")); + // Always suppress MSI-initiated reboots. With INSTALLUILEVEL_NONE, Windows Installer + // will silently reboot the machine if files are in use and REBOOT is not suppressed. + std::wstring args = L"REBOOT=ReallySuppress"; + if (ExtraArgs != nullptr && *ExtraArgs != L'\0') + { + args = std::wstring(ExtraArgs) + L" " + args; + } + + WriteInstallLog(std::format("Upgrading via MSI package: {}. Args: {}", PackageLocation, args)); ConfigureMsiLogging(LogFile, Callback); - auto result = MsiInstallProduct(PackageLocation, ExtraArgs); - WSL_LOG( - "MsiInstallResult", - TraceLoggingValue(result, "result"), - TraceLoggingValue(ExtraArgs != nullptr ? ExtraArgs : L"", "ExtraArgs")); + auto result = MsiInstallProduct(PackageLocation, args.c_str()); + WSL_LOG("MsiInstallResult", TraceLoggingValue(result, "result"), TraceLoggingValue(args.c_str(), "ExtraArgs")); WriteInstallLog(std::format("MSI upgrade result: {}", result)); @@ -382,7 +391,7 @@ UINT wsl::windows::common::install::UninstallViaMsi(_In_opt_ LPCWSTR LogFile, _I ConfigureMsiLogging(LogFile, Callback); - auto result = MsiConfigureProduct(productCode.c_str(), 0, INSTALLSTATE_ABSENT); + auto result = MsiConfigureProductEx(productCode.c_str(), 0, INSTALLSTATE_ABSENT, L"REBOOT=ReallySuppress"); WSL_LOG("MsiUninstallResult", TraceLoggingValue(result, "result")); WriteInstallLog(std::format("MSI package uninstall result: {}", result)); diff --git a/src/windows/wslinstaller/exe/WslInstaller.cpp b/src/windows/wslinstaller/exe/WslInstaller.cpp index a4019e61..f370d649 100644 --- a/src/windows/wslinstaller/exe/WslInstaller.cpp +++ b/src/windows/wslinstaller/exe/WslInstaller.cpp @@ -79,7 +79,20 @@ std::pair InstallMsipackageImpl() auto result = wsl::windows::common::install::UpgradeViaMsi( GetMsiPackagePath().c_str(), L"SKIPMSIX=1", logFile.has_value() ? logFile->c_str() : nullptr, messageCallback); - WSL_LOG("MSIUpgradeResult", TraceLoggingValue(result, "result"), TraceLoggingValue(errors.c_str(), "errorMessage")); + // ERROR_SUCCESS_REBOOT_REQUIRED (3010) means the install succeeded but some files + // will be replaced on the next reboot. Treat as success since the service runs + // silently with no user-facing console. + const bool rebootRequired = (result == ERROR_SUCCESS_REBOOT_REQUIRED); + if (rebootRequired) + { + result = ERROR_SUCCESS; + } + + WSL_LOG( + "MSIUpgradeResult", + TraceLoggingValue(result, "result"), + TraceLoggingValue(rebootRequired, "rebootRequired"), + TraceLoggingValue(errors.c_str(), "errorMessage")); return {result, errors}; }