master
cpp 330 lines 12.6 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WslInstall.cpp
8
9 Abstract:
10
11 This file contains implementations for installing WSL distributions
12
13 --*/
14
15 #include "precomp.h"
16 #include "WslInstall.h"
17 #include "registry.hpp"
18 #include "wslutil.h"
19 #include "Distribution.h"
20 #include "HandleConsoleProgressBar.h"
21 #include "svccomm.hpp"
22 #include "WmiService.h"
23
24 extern HINSTANCE g_dllInstance;
25
26 constexpr LPCWSTR c_optionalFeatureInstallStatus = L"InstallStatus";
27
28 using wsl::shared::Localization;
29 using namespace wsl::windows::common::distribution;
30 using namespace wsl::windows::common::wslutil;
31
32 namespace {
33 void EnforceFileHash(HANDLE file, const std::wstring& expectedHash)
34 {
35 wsl::windows::common::ExecutionContext context(wsl::windows::common::VerifyChecksum);
36
37 const auto fileHash = wsl::windows::common::wslutil::HashFile(file, CALG_SHA_256);
38
39 THROW_LAST_ERROR_IF(SetFilePointer(file, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER);
40 if (fileHash != wsl::windows::common::string::HexToBytes(expectedHash))
41 {
42 THROW_HR_WITH_USER_ERROR(
43 TRUST_E_BAD_DIGEST,
44 wsl::shared::Localization::MessageHashMismatch(
45 expectedHash.c_str(), wsl::windows::common::string::BytesToHex(fileHash).c_str()));
46 }
47 }
48
49 std::vector<std::wstring> GetInstalledOptionalComponents()
50 {
51 // Query the list of optional components that have already been installed.
52 const auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
53 auto [key, error] = wsl::windows::common::registry::OpenKeyNoThrow(lxssKey.get(), c_optionalFeatureInstallStatus, KEY_READ);
54 std::vector<std::wstring> installedComponents;
55 if (key)
56 {
57 const auto components = wsl::windows::common::registry::ReadString(key.get(), nullptr, nullptr, L"");
58 installedComponents = wsl::shared::string::Split(components, L',');
59 }
60
61 return installedComponents;
62 }
63
64 }; // namespace
65
66 HRESULT WslInstall::InstallDistribution(
67 _Out_ InstallResult& installResult,
68 _In_ const std::optional<std::wstring>& distributionName,
69 _In_ const std::optional<ULONG>& version,
70 _In_ bool launchAfterInstall,
71 _In_ bool useGitHub,
72 _In_ bool legacy,
73 _In_ bool fixedVhd,
74 _In_ const std::optional<std::wstring>& localName,
75 _In_ const std::optional<std::wstring>& location,
76 _In_ const std::optional<uint64_t>& vhdSize)
77 try
78 {
79 wsl::windows::common::ExecutionContext context(wsl::windows::common::InstallDistro);
80
81 try
82 {
83 const auto distributions = wsl::windows::common::distribution::GetAvailable();
84
85 if (distributionName.has_value())
86 {
87 installResult.Distribution = LookupByName(distributions, distributionName->c_str(), legacy);
88 }
89 else
90 {
91 if (legacy)
92 {
93 THROW_HR_IF(
94 E_UNEXPECTED, !distributions.Manifest.Distributions.has_value() || distributions.Manifest.Distributions->empty());
95 installResult.Distribution = (*distributions.Manifest.Distributions)[0];
96 }
97 else
98 {
99 if (distributions.OverrideManifest.has_value() && distributions.OverrideManifest->Default.has_value())
100 {
101 installResult.Distribution = LookupByName(distributions, distributions.OverrideManifest->Default->c_str(), false);
102 }
103 else
104 {
105 if (!distributions.Manifest.Default.has_value())
106 {
107 THROW_HR_WITH_USER_ERROR(E_UNEXPECTED, wsl::shared::Localization::MessageNoInstallDefault());
108 }
109
110 installResult.Distribution = LookupByName(distributions, distributions.Manifest.Default->c_str(), false);
111 }
112 }
113 }
114
115 if (const auto* distro = std::get_if<ModernDistributionVersion>(&*installResult.Distribution))
116 {
117 std::tie(installResult.Name, installResult.Id) =
118 InstallModernDistribution(*distro, version, localName, location, vhdSize, fixedVhd);
119
120 installResult.InstalledViaGitHub = true;
121 }
122 else if (const auto* distro = std::get_if<Distribution>(&*installResult.Distribution))
123 {
124 std::list<std::pair<bool, LPCWSTR>> unsupportedArguments = {
125 {localName.has_value(), WSL_INSTALL_ARG_NAME_LONG},
126 {location.has_value(), WSL_INSTALL_ARG_LOCATION_LONG},
127 {vhdSize.has_value(), WSL_INSTALL_ARG_VHD_SIZE},
128 {fixedVhd, WSL_INSTALL_ARG_FIXED_VHD}};
129
130 for (const auto& [condition, argument] : unsupportedArguments)
131 {
132 if (condition)
133 {
134 THROW_HR_WITH_USER_ERROR(WSL_E_INVALID_USAGE, Localization::MessageNotSupportedOnLegacyDistros(argument).c_str());
135 }
136 }
137
138 installResult.Alreadyinstalled = wsl::windows::common::distribution::IsInstalled(*distro, useGitHub);
139 if (!installResult.Alreadyinstalled)
140 {
141 EMIT_USER_WARNING(Localization::MessageUsingLegacyDistribution());
142 if (version.has_value())
143 {
144 THROW_HR_WITH_USER_ERROR(WSL_E_INVALID_USAGE, Localization::MessageLegacyDistributionVersionArgNotSupported());
145 }
146
147 // If downloading from the store fails, attempt to download from GitHub.
148 if (!useGitHub)
149 {
150 auto hr =
151 wil::ResultFromException([&]() { wsl::windows::common::distribution::LegacyInstallViaStore(*distro); });
152 if (FAILED(hr))
153 {
154 useGitHub = true;
155 auto errorString = wsl::windows::common::wslutil::GetErrorString(hr);
156 wsl::windows::common::wslutil::PrintMessage(
157 Localization::MessageDistroStoreInstallFailed(distro->Name.c_str(), errorString.c_str()), stdout);
158 }
159 }
160
161 if (useGitHub)
162 {
163 wsl::windows::common::distribution::LegacyInstallViaGithub(*distro);
164 }
165 }
166
167 installResult.Name = distro->FriendlyName;
168 installResult.InstalledViaGitHub = useGitHub;
169 }
170 else
171 {
172 THROW_HR(E_UNEXPECTED);
173 }
174 }
175 catch (...)
176 {
177 // Rethrowing via WIL is required for the error context to be properly set
178 // in case a winrt exception was thrown.
179 THROW_HR(wil::ResultFromCaughtException());
180 }
181
182 return S_OK;
183 }
184 CATCH_RETURN()
185
186 std::pair<bool, std::vector<std::wstring>> WslInstall::CheckForMissingOptionalComponents(_In_ bool requireWslOptionalComponent)
187 {
188 // Include the WSL optional component if it was requested, or if the OS is not Windows 11 or later.
189 std::vector<std::wstring> missingComponents;
190 requireWslOptionalComponent |= !wsl::windows::common::helpers::IsWindows11OrAbove();
191 if (requireWslOptionalComponent && !wsl::windows::common::helpers::IsServicePresent(L"lxssmanager"))
192 {
193 missingComponents.emplace_back(c_optionalFeatureNameWsl);
194 }
195
196 if (!wsl::windows::common::wslutil::IsVirtualMachinePlatformInstalled())
197 {
198 missingComponents.emplace_back(c_optionalFeatureNameVmp);
199 }
200
201 // If any required components are not present, a reboot is required.
202 bool rebootRequired = !missingComponents.empty();
203
204 // Query the list of optional components that have already been installed.
205 const auto installedComponents = GetInstalledOptionalComponents();
206 for (const auto& component : installedComponents)
207 {
208 std::erase(missingComponents, component);
209 }
210
211 return {rebootRequired, std::move(missingComponents)};
212 }
213
214 DWORD WslInstall::InstallOptionalComponent(LPCWSTR component, bool consoleOutput)
215 {
216 std::wstring systemDirectory;
217 THROW_IF_FAILED(wil::GetSystemDirectoryW(systemDirectory));
218
219 const auto dismPath = std::filesystem::path(std::move(systemDirectory)) / L"dism.exe";
220
221 auto commandLine = std::format(L"{} /Online /NoRestart /enable-feature /featurename:{}", dismPath.native(), component);
222
223 wsl::windows::common::SubProcess process(nullptr, commandLine.c_str());
224 if (!consoleOutput)
225 {
226 process.SetFlags(CREATE_NEW_CONSOLE);
227 process.SetShowWindow(SW_HIDE);
228 }
229
230 return process.Run();
231 }
232
233 void WslInstall::InstallOptionalComponents(const std::vector<std::wstring>& components)
234 {
235 for (const auto& component : components)
236 {
237 wsl::windows::common::wslutil::PrintMessage(Localization::MessageInstallingWindowsComponent(component));
238
239 const auto exitCode = InstallOptionalComponent(component.c_str(), true);
240 if (exitCode != 0 && exitCode != ERROR_SUCCESS_REBOOT_REQUIRED)
241 {
242 THROW_HR_WITH_USER_ERROR(WSL_E_INSTALL_COMPONENT_FAILED, Localization::MessageOptionalComponentInstallFailed(component, exitCode));
243 }
244 }
245
246 // Update the list of optional components that have been installed.
247 auto installedComponents = GetInstalledOptionalComponents();
248 installedComponents.insert(installedComponents.end(), components.begin(), components.end());
249 const auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
250 const auto key =
251 wsl::windows::common::registry::CreateKey(lxssKey.get(), c_optionalFeatureInstallStatus, KEY_ALL_ACCESS, nullptr, REG_OPTION_VOLATILE);
252 wsl::windows::common::registry::WriteString(key.get(), nullptr, nullptr, wsl::shared::string::Join(installedComponents, L',').c_str());
253 }
254
255 bool WslInstall::IsOptionalComponentInstalled(LPCWSTR component)
256 {
257 constexpr int32_t c_enabled = 1;
258
259 wsl::core::WmiService service(L"ROOT\\CIMV2");
260 wsl::core::WmiEnumerate optionalFeatures(service);
261 const auto query = std::format(L"SELECT InstallState FROM Win32_OptionalFeature WHERE Name = '{}'", component);
262 const auto& results = optionalFeatures.query(query.c_str());
263 const auto result = results.begin();
264 THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), result == results.end(), "Optional component not found: %ls", component);
265
266 int32_t installState{};
267 THROW_HR_IF_MSG(E_UNEXPECTED, !result->get(L"InstallState", &installState), "Invalid optional component state: %ls", component);
268 return installState == c_enabled;
269 }
270
271 std::pair<std::wstring, GUID> WslInstall::InstallModernDistribution(
272 const ModernDistributionVersion& distribution,
273 const std::optional<ULONG>& version,
274 const std::optional<std::wstring>& name,
275 const std::optional<std::wstring>& location,
276 const std::optional<uint64_t>& vhdSize,
277 const bool fixedVhd)
278 {
279 wsl::windows::common::SvcComm service;
280
281 // Fail early if the distributions name is already in use.
282 auto result = wil::ResultFromException([&]() {
283 service.GetDistributionId(name.has_value() ? name->c_str() : distribution.Name.c_str(), LXSS_GET_DISTRO_ID_LIST_ALL);
284 });
285
286 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), SUCCEEDED(result));
287 LOG_HR_IF(result, result != WSL_E_DISTRO_NOT_FOUND);
288
289 const auto downloadInfo = wsl::shared::Arm64 ? distribution.Arm64Url : distribution.Amd64Url;
290 THROW_HR_IF(E_UNEXPECTED, !downloadInfo.has_value());
291
292 std::wstring installPath;
293 bool fileDownloaded{};
294 auto deleteFile = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
295 if (fileDownloaded)
296 {
297 THROW_IF_WIN32_BOOL_FALSE(DeleteFileW(installPath.c_str()));
298 }
299 });
300
301 if (auto localFile = wsl::windows::common::filesystem::TryGetPathFromFileUrl(downloadInfo->Url))
302 {
303 installPath = std::move(localFile.value());
304 }
305 else
306 {
307 PrintMessage(Localization::MessageDownloading(distribution.FriendlyName.c_str()), stdout);
308 installPath = DownloadFile(downloadInfo->Url, distribution.Name + L".wsl");
309 fileDownloaded = true;
310 }
311
312 PrintMessage(Localization::MessageInstalling(distribution.FriendlyName.c_str()), stdout);
313
314 wil::unique_handle file{CreateFile(installPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)};
315 THROW_LAST_ERROR_IF(!file);
316
317 EnforceFileHash(file.get(), downloadInfo->Sha256);
318
319 wsl::windows::common::HandleConsoleProgressBar progressBar(file.get(), Localization::MessageImportProgress());
320
321 auto [id, installedName] = service.RegisterDistribution(
322 name.has_value() ? name->c_str() : distribution.Name.c_str(),
323 version.value_or(LXSS_WSL_VERSION_DEFAULT),
324 file.get(),
325 location.has_value() ? location->c_str() : nullptr,
326 fixedVhd ? LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD : 0,
327 vhdSize);
328
329 return {installedName.get(), id};
330 }