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 <benhill@ntdev.microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Ben Hillis
2026-04-06 15:17:38 -07:00
committed by GitHub
parent 11ae8b2322
commit 7ee07db18d
3 changed files with 36 additions and 10 deletions

View File

@@ -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(

View File

@@ -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<void(INSTALLMESSAGE, LPCWSTR)>& 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));

View File

@@ -79,7 +79,20 @@ std::pair<UINT, std::wstring> 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};
}