From da73f0ede30f01c3740686f96e7b591e5a62e266 Mon Sep 17 00:00:00 2001 From: Ben Hillis Date: Thu, 2 Apr 2026 08:59:19 -0700 Subject: [PATCH] Suppress MSI-initiated reboots during Store updates 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: 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 e1f3a0f1f..5e9f952d7 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 740e382d2..a268f8bf6 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 a4019e612..f370d6498 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}; }