diagnostics: collect MSI verbose install logs in diagnostic bundle (#40218)

* diagnostics: collect MSI verbose install log in diagnostic bundle The MSI verbose log (wsl-install-logs.txt) is generated during MSI upgrades via MsiEnableLog but was not collected by the diagnostic script. Additionally, the Store/winget upgrade path (WslInstaller) did not write an MSI log at all when no registry override was set. Changes: - WslInstaller.cpp: default to %TEMP%\wsl-install-logs.txt when UpgradeLogFile registry key is empty, with delete-on-success / preserve-on-failure (same pattern as wsl --update in install.cpp) - collect-wsl-logs.ps1: collect %TEMP%\wsl-install-logs.txt Now all MSI upgrade paths write to the same log location: - wsl --update: already writes here (install.cpp) - Store/winget: now also writes here (WslInstaller.cpp) - Both: delete on success, preserve on failure Tested: installed MSI with file lock contention, collected log contains Warning 1946 detail for diagnosis. Refs: microsoft/WSL#13469, microsoft/WSL#11276, microsoft/WSL#12759 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: handle reboot-required and service temp path - Treat ERROR_SUCCESS_REBOOT_REQUIRED (3010) as success when deciding whether to preserve MSI logs (delete-on-success, keep-on-failure). - Collect logs from both user temp and system temp (WslInstaller service runs as SYSTEM, so its temp_directory_path resolves to %WINDIR%\Temp). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback: respect UpgradeLogFile registry, fix collector - Add UpgradeLogInfo struct with fromRegistry flag to skip log deletion when the UpgradeLogFile registry value is explicitly set (per OneBlue) - Remove duplicate system temp copy in collector (per OneBlue) - Fix forward-slash inconsistency in collector (per ptrivedi) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Gordon Lam committed Apr 24, 2026 at 15:31 UTC 2c42f23a759c8ca435546dabcb7da602261dc247
2 files changed +31 -4
diagnostics/collect-wsl-logs.ps1
+4
@@ -177,8 +177,12 @@ if (Test-Path $wslconfig)
177 Copy-Item $wslconfig $folder | Out-Null
178 }
179
180 +# Collect high-level WSL install log (written by WriteInstallLog() in install.cpp)
181 Copy-Item "C:\Windows\temp\wsl-install-log.txt" $folder -ErrorAction ignore
182
183 +# Collect MSI verbose install log (preserved on failure by wsl --update or WslInstaller service).
184 +Copy-Item "$env:TEMP\wsl-install-logs.txt" $folder -ErrorAction ignore
185 +
186 get-appxpackage MicrosoftCorporationII.WindowsSubsystemforLinux -ErrorAction Ignore > $folder/appxpackage.txt
187 get-acl "C:\ProgramData\Microsoft\Windows\WindowsApps" -ErrorAction Ignore | Format-List > $folder/acl.txt
188 Get-WindowsOptionalFeature -Online > $folder/optional-components.txt
src/windows/wslinstaller/exe/WslInstaller.cpp
+27 -4
@@ -31,18 +31,26 @@ std::wstring GetMsiPackagePath()
31 return (wsl::windows::common::wslutil::GetBasePath() / L"wsl.msi").wstring();
32 }
33
34 -std::optional<std::wstring> GetUpgradeLogFileLocation()
34 +struct UpgradeLogInfo
35 +{
36 + std::wstring path;
37 + bool fromRegistry; // true when the path was explicitly configured via UpgradeLogFile registry value
38 +};
39 +
40 +std::optional<UpgradeLogInfo> GetUpgradeLogFileLocation()
41 try
42 {
43 const auto key = wsl::windows::common::registry::OpenLxssMachineKey();
44 const auto path = wsl::windows::common::registry::ReadString(key.get(), L"MSI", L"UpgradeLogFile", L"");
45 if (path.empty())
46 {
41 - return {};
47 + // Default to the same path used by wsl --update so all MSI logs
48 + // are collected from one location by the diagnostic script.
49 + return UpgradeLogInfo{(std::filesystem::temp_directory_path() / L"wsl-install-logs.txt").wstring(), false};
50 }
51
52 // A canonical path is required because msiexec doesn't like symlinks.
45 - return std::filesystem::weakly_canonical(path);
53 + return UpgradeLogInfo{std::filesystem::weakly_canonical(path), true};
54 }
55 catch (...)
56 {
@@ -54,6 +62,16 @@ std::pair<UINT, std::wstring> InstallMsipackageImpl()
62 {
63 const auto logFile = GetUpgradeLogFileLocation();
64
65 + // Delete MSI log on success, preserve on failure for diagnostics (same as wsl --update).
66 + // When the UpgradeLogFile registry value is set, always keep the log — the registry key
67 + // is explicitly designed to retain MSI logs across installs.
68 + auto clearLogs = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() {
69 + if (logFile.has_value() && !logFile->fromRegistry)
70 + {
71 + LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile->path.c_str()));
72 + }
73 + });
74 +
75 std::wstring errors;
76 auto messageCallback = [&errors](INSTALLMESSAGE type, LPCWSTR message) {
77 switch (type)
@@ -77,7 +95,7 @@ std::pair<UINT, std::wstring> InstallMsipackageImpl()
95 };
96
97 auto result = wsl::windows::common::install::UpgradeViaMsi(
80 - GetMsiPackagePath().c_str(), L"SKIPMSIX=1", logFile.has_value() ? logFile->c_str() : nullptr, messageCallback);
98 + GetMsiPackagePath().c_str(), L"SKIPMSIX=1", logFile.has_value() ? logFile->path.c_str() : nullptr, messageCallback);
99
100 // ERROR_SUCCESS_REBOOT_REQUIRED (3010) means the install succeeded but some files
101 // will be replaced on the next reboot. Treat as success since the service runs
@@ -94,6 +112,11 @@ std::pair<UINT, std::wstring> InstallMsipackageImpl()
112 TraceLoggingValue(rebootRequired, "rebootRequired"),
113 TraceLoggingValue(errors.c_str(), "errorMessage"));
114
115 + if (result != ERROR_SUCCESS && result != ERROR_SUCCESS_REBOOT_REQUIRED)
116 + {
117 + clearLogs.release();
118 + }
119 +
120 return {result, errors};
121 }
122