master
cpp 218 lines 6.24 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WslInstaller.cpp
8
9 Abstract:
10
11 This file contains the implementation of the WslInstaller class.
12
13 --*/
14
15 #include "precomp.h"
16 #include "install.h"
17 #include "WslInstaller.h"
18
19 extern wil::unique_event g_stopEvent;
20
21 std::wstring GetMsiPackagePath()
22 {
23 #ifdef WSL_DEV_THIN_MSI_PACKAGE
24
25 static_assert(!wsl::shared::OfficialBuild);
26
27 return wsl::windows::common::filesystem::GetCanonicalPath(WSL_DEV_THIN_MSI_PACKAGE).wstring();
28
29 #endif
30
31 return (wsl::windows::common::wslutil::GetBasePath() / L"wsl.msi").wstring();
32 }
33
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 {
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.
53 return UpgradeLogInfo{wsl::windows::common::filesystem::GetCanonicalPath(path), true};
54 }
55 catch (...)
56 {
57 LOG_CAUGHT_EXCEPTION();
58 return {};
59 }
60
61 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)
78 {
79 case INSTALLMESSAGE_ERROR:
80 case INSTALLMESSAGE_FATALEXIT:
81 case INSTALLMESSAGE_WARNING:
82 case INSTALLMESSAGE_FILESINUSE:
83 case INSTALLMESSAGE_OUTOFDISKSPACE:
84 if (!errors.empty())
85 {
86 errors += L"\n";
87 }
88
89 errors += message;
90 break;
91
92 default:
93 break;
94 }
95 };
96
97 auto result = wsl::windows::common::install::UpgradeViaMsi(
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
102 // silently with no user-facing console.
103 const bool rebootRequired = (result == ERROR_SUCCESS_REBOOT_REQUIRED);
104 if (rebootRequired)
105 {
106 result = ERROR_SUCCESS;
107 }
108
109 WSL_LOG(
110 "MSIUpgradeResult",
111 TraceLoggingValue(result, "result"),
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
123 DWORD WINAPI InstallMsiPackage(LPVOID Context)
124 {
125 auto* installContext = reinterpret_cast<InstallContext*>(Context);
126
127 try
128 {
129 std::tie(installContext->ExitCode, installContext->Errors) = InstallMsipackageImpl();
130 }
131 catch (...)
132 {
133 LOG_CAUGHT_EXCEPTION();
134 installContext->Result = wil::ResultFromCaughtException();
135 return 0;
136 }
137
138 installContext->Result = S_OK;
139 return 0;
140 }
141
142 std::pair<bool, std::wstring> IsUpdateNeeded()
143 {
144 try
145 {
146 const auto key = wsl::windows::common::registry::OpenLxssMachineKey();
147
148 const auto installedVersion = wsl::windows::common::registry::ReadString(key.get(), L"MSI", L"Version", L"");
149
150 WSL_LOG(
151 "DetectedInstalledVersion",
152 TraceLoggingLevel(WINEVENT_LEVEL_INFO),
153 TraceLoggingValue(installedVersion.c_str(), "InstalledVersion"));
154
155 return std::make_pair(
156 installedVersion.empty() || wsl::windows::common::wslutil::ParseWslPackageVersion(installedVersion) < wsl::shared::PackageVersion,
157 installedVersion);
158 }
159 catch (...)
160 {
161 LOG_CAUGHT_EXCEPTION();
162
163 return std::make_pair(false, L"");
164 }
165 }
166
167 std::shared_ptr<InstallContext> LaunchInstall()
168 {
169 static wil::srwlock mutex;
170 static std::weak_ptr<InstallContext> weak_context;
171
172 auto lock = mutex.lock_exclusive();
173
174 auto [updateNeeded, existingVersion] = IsUpdateNeeded();
175 if (!updateNeeded)
176 {
177 return {};
178 }
179
180 wsl::windows::common::install::WriteInstallLog(std::format("Starting upgrade via WslInstaller. Previous version: {}", existingVersion));
181
182 // Return an existing install if any
183 if (auto ptr = weak_context.lock(); ptr != nullptr)
184 {
185 return ptr;
186 }
187
188 // Else launch a new install
189 auto context = std::make_shared<InstallContext>();
190 weak_context = std::weak_ptr<InstallContext>(context);
191
192 context->Thread = wil::unique_handle{CreateThread(nullptr, 0, &InstallMsiPackage, context.get(), 0, nullptr)};
193
194 return context;
195 }
196
197 HRESULT WslInstaller::Install(UINT* ExitCode, LPWSTR* Errors)
198 try
199 {
200 const auto context = LaunchInstall();
201 if (!context)
202 {
203 // This block can be reached if the installation completed after the client looked up the MSI package.
204 // In this case don't attempt to install and return success so the client looks up the MSI package again.
205
206 *ExitCode = 0;
207 *Errors = wil::make_unique_string<wil::unique_cotaskmem_string>(L"").release();
208 return S_OK;
209 }
210
211 THROW_LAST_ERROR_IF(WaitForSingleObject(context->Thread.get(), INFINITE) != WAIT_OBJECT_0);
212
213 *ExitCode = context->ExitCode;
214 *Errors = wil::make_unique_string<wil::unique_cotaskmem_string>(context->Errors.c_str()).release();
215
216 return context->Result;
217 }
218 CATCH_RETURN()