master
cpp 1,996 lines 70.9 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WslClient.cpp
8
9 Abstract:
10
11 This file contains the logic for WSL client entry points.
12
13 --*/
14
15 #include "precomp.h"
16 #include "install.h"
17 #include "WslInstall.h"
18 #include "HandleConsoleProgressBar.h"
19 #include "Distribution.h"
20 #include "CommandLine.h"
21 #include <conio.h>
22 #include "WslCoreFilesystem.h"
23
24 #define BASH_PATH L"/bin/bash"
25
26 using winrt::Windows::Foundation::Uri;
27 using winrt::Windows::Management::Deployment::DeploymentOptions;
28 using wsl::shared::Localization;
29 using wsl::windows::common::ClientExecutionContext;
30 using wsl::windows::common::Context;
31 using namespace wsl::windows::common;
32 using namespace wsl::shared;
33 using namespace wsl::windows::common::distribution;
34
35 static bool g_promptBeforeExit = false;
36
37 namespace {
38
39 enum Entrypoint
40 {
41 Bash,
42 Wsl,
43 Wslconfig,
44 Wslg
45 };
46
47 struct LaunchProcessOptions
48 {
49 std::wstring CurrentWorkingDirectory;
50 std::optional<GUID> DistroGuid;
51 std::wstring Username;
52 ULONG LaunchFlags = LXSS_LAUNCH_FLAG_ENABLE_INTEROP | LXSS_LAUNCH_FLAG_TRANSLATE_ENVIRONMENT;
53 };
54
55 struct ListOptions
56 {
57 bool verbose;
58 bool quiet;
59 bool running;
60 bool all;
61 bool online;
62 };
63
64 struct ShellExecOptions
65 {
66 std::optional<bool> UseShell;
67 std::optional<bool> Login;
68
69 bool DefaultUseShell = true;
70 bool DefaultLogin = false;
71
72 bool IsLogin() const
73 {
74 return Login.value_or(DefaultLogin);
75 }
76
77 bool IsUseShell() const
78 {
79 return UseShell.value_or(DefaultUseShell);
80 }
81
82 void SetExecMode()
83 {
84 UseShell = false;
85 Login = false;
86 }
87
88 void ParseShellOptionArg(std::wstring_view Argument)
89 {
90 if (Argument == WSL_SHELL_OPTION_ARG_LOGIN_OPTION)
91 {
92 UseShell = true;
93 Login = true;
94 }
95 else if (Argument == WSL_SHELL_OPTION_ARG_NOSHELL_OPTION)
96 {
97 SetExecMode();
98 }
99 else if (Argument == WSL_SHELL_OPTION_ARG_STANDARD_OPTION)
100 {
101 UseShell = true;
102 Login = false;
103 }
104 else
105 {
106 THROW_HR(E_INVALIDARG);
107 }
108 }
109 };
110
111 void PromptForKeyPressToExit()
112 {
113 if (wsl::windows::common::wslutil::IsInteractiveConsole())
114 {
115 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessagePressAnyKeyToExit());
116 LOG_IF_WIN32_BOOL_FALSE(FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)));
117 _getch();
118 }
119 }
120
121 // Forward function declarations.
122 bool InstallPrerequisites(_In_ bool installWslOptionalComponent);
123 int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options);
124 int ListDistributionsHelper(_In_ ListOptions options);
125 LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine);
126 DWORD ParseVersionString(_In_ const std::wstring_view& versionString);
127 int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe);
128 int Version();
129
130 template <typename T>
131 struct WslVersion
132 {
133 T& value;
134
135 int operator()(LPCWSTR Input) const
136 {
137 if (Input == nullptr)
138 {
139 return -1;
140 }
141
142 value = ParseVersionString(Input);
143 return 1;
144 }
145 };
146
147 // Function definitions.
148 int BashMain(_In_ std::wstring_view commandLine)
149 {
150 // Call the MSI package if we're in an MSIX context
151 if (wsl::windows::common::wslutil::IsRunningInMsix())
152 {
153 return wsl::windows::common::install::CallMsiPackage();
154 }
155
156 const auto options = ParseLegacyArguments(commandLine);
157
158 // If the command line is empty, construct the arguments in the following
159 // format to launch bash as a login shell:
160 //
161 // filename = /bin/bash
162 // argv[0] = -bash
163 //
164 // N.B. This is the same logic that login uses to launch the shell.
165 //
166 // For non-empty command lines, construct the arguments in the following
167 // format:
168 //
169 // filename = /bin/bash
170 // argv[0] = /bin/bash
171 // argv[1] = -c
172 // argv[2] = /bin/bash -c "commandLine"
173 //
174 // N.B. The arguments are set up this way to leave /bin/bash in charge of
175 // all argument parsing.
176 int argc = 1;
177 LPCWSTR argv[3];
178 std::wstring arguments;
179 LPCWSTR filename;
180 if (commandLine.empty())
181 {
182 argv[0] = L"-bash";
183 filename = BASH_PATH;
184 }
185 else
186 {
187 argc = RTL_NUMBER_OF(argv);
188 arguments = BASH_PATH L" ";
189 arguments.append(commandLine);
190 argv[0] = BASH_PATH;
191 argv[1] = L"-c";
192 argv[2] = arguments.c_str();
193 filename = argv[0];
194 }
195
196 return LaunchProcess(filename, argc, argv, options);
197 }
198
199 void ChangeDirectory(_In_ std::wstring_view argument, _Inout_ LaunchProcessOptions& options)
200 {
201 std::wstring directory(wsl::windows::common::string::StripQuotes(argument));
202 THROW_HR_IF(E_INVALIDARG, directory.empty());
203
204 // There are two supported directory arguments:
205 // 1. Any path that begins with a '/' or `~` is assumed to be a Linux path.
206 // If the path does not exist an error is logged to /dev/kmsg.
207 // 2. Everything else is assumed to be a valid absolute Windows path.
208 if ((directory[0] == L'/') || (directory[0] == L'~'))
209 {
210 options.CurrentWorkingDirectory = std::move(directory);
211 }
212 else
213 {
214 THROW_HR_IF(E_INVALIDARG, !std::filesystem::path(directory).is_absolute());
215
216 THROW_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(directory.c_str()));
217 }
218 }
219
220 int ExportDistribution(_In_ std::wstring_view commandLine)
221 {
222 ULONG flags = 0;
223 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
224 std::filesystem::path filePath;
225 LPCWSTR name{};
226 int tarFormatSet = 0;
227
228 auto parseFormat = [&flags, &tarFormatSet](LPCWSTR Value) {
229 if (Value == nullptr)
230 {
231 return -1;
232 }
233
234 if (wsl::shared::string::IsEqual(L"tar.gz", Value))
235 {
236 WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_GZIP);
237 }
238 else if (wsl::shared::string::IsEqual(L"tar.xz", Value))
239 {
240 WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_XZIP);
241 }
242 else if (wsl::shared::string::IsEqual(L"vhd", Value))
243 {
244 WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_VHD);
245 }
246 else if (wsl::shared::string::IsEqual(L"tar", Value))
247 {
248 tarFormatSet = 1;
249 }
250 else
251 {
252 THROW_HR(E_INVALIDARG);
253 }
254
255 return 1;
256 };
257
258 parser.AddPositionalArgument(name, 0);
259 parser.AddPositionalArgument(filePath, 1);
260 parser.AddArgument(SetFlag<LXSS_EXPORT_DISTRO_FLAGS_VHD, ULONG>(flags), WSL_EXPORT_ARG_VHD_OPTION);
261 parser.AddArgument(parseFormat, WSL_EXPORT_ARG_FORMAT_OPTION);
262 parser.Parse();
263
264 constexpr ULONG c_exportFormatFlags = LXSS_EXPORT_DISTRO_FLAGS_VHD | LXSS_EXPORT_DISTRO_FLAGS_GZIP | LXSS_EXPORT_DISTRO_FLAGS_XZIP;
265 THROW_HR_IF(WSL_E_INVALID_USAGE, filePath.empty() || std::popcount(flags & c_exportFormatFlags) + tarFormatSet > 1);
266
267 // Determine if the target is stdout, or an on-disk file.
268 wil::unique_hfile file;
269 HANDLE fileHandle;
270 if (filePath.wstring() == WSL_EXPORT_ARG_STDOUT)
271 {
272 fileHandle = GetStdHandle(STD_OUTPUT_HANDLE);
273 }
274 else
275 {
276 file.reset(CreateFileW(
277 filePath.c_str(), GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
278
279 THROW_LAST_ERROR_IF(!file);
280
281 fileHandle = file.get();
282 }
283
284 // Delete the target if export was unsuccessful.
285 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
286 if (file)
287 {
288 LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(filePath.c_str()));
289 }
290 });
291
292 // Export the distribution.
293 wsl::windows::common::SvcComm service;
294 const GUID distroId = service.GetDistributionId(name);
295
296 {
297 using wsl::windows::common::HandleConsoleProgressBar;
298
299 HandleConsoleProgressBar exportProgress(fileHandle, Localization::MessageExportProgress(), HandleConsoleProgressBar::Format::FileSize);
300 THROW_IF_FAILED(service.ExportDistribution(&distroId, fileHandle, flags));
301 }
302
303 if (file)
304 {
305 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
306 }
307
308 cleanup.release();
309 return 0;
310 }
311
312 int ImportDistribution(_In_ std::wstring_view commandLine)
313 {
314 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
315 LPCWSTR name{};
316 std::optional<std::wstring> installPath{};
317 std::filesystem::path filePath;
318 ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE;
319 DWORD version = LXSS_WSL_VERSION_DEFAULT;
320
321 parser.AddPositionalArgument(name, 0);
322 parser.AddPositionalArgument(AbsolutePath(installPath), 1);
323 parser.AddPositionalArgument(filePath, 2);
324 parser.AddArgument(WslVersion(version), WSL_IMPORT_ARG_VERSION);
325 parser.AddArgument(SetFlag<LXSS_IMPORT_DISTRO_FLAGS_VHD, ULONG>{flags}, WSL_IMPORT_ARG_VHD);
326
327 parser.Parse();
328
329 if (name == nullptr || !installPath.has_value() || filePath.empty())
330 {
331 THROW_HR(E_INVALIDARG);
332 }
333
334 // Ensure that the install path exists.
335 bool directoryCreated = true;
336 if (!CreateDirectoryW(installPath->c_str(), nullptr))
337 {
338 if (GetLastError() == ERROR_ALREADY_EXISTS)
339 {
340 directoryCreated = false;
341 }
342 else
343 {
344 THROW_LAST_ERROR_MSG("CreateDirectoryW");
345 }
346 }
347
348 auto directory_cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [directoryCreated, &installPath]() {
349 if (directoryCreated)
350 {
351 LOG_IF_WIN32_BOOL_FALSE(RemoveDirectory(installPath->c_str()));
352 }
353 });
354
355 // Determine if the source of the tar file is stdin, or an on-disk file.
356 wil::unique_hfile file;
357 HANDLE fileHandle;
358 if (filePath.wstring() == WSL_IMPORT_ARG_STDIN)
359 {
360 fileHandle = GetStdHandle(STD_INPUT_HANDLE);
361 }
362 else
363 {
364 if (WI_IsFlagClear(flags, LXSS_IMPORT_DISTRO_FLAGS_VHD))
365 {
366 // Fail if expecting a tar, but the file name has the .vhd or .vhdx extension.
367 if (wsl::windows::common::wslutil::IsVhdFile(filePath))
368 {
369 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessagePassVhdFlag());
370 return -1;
371 }
372 }
373
374 file.reset(CreateFileW(
375 filePath.c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
376
377 THROW_LAST_ERROR_IF(!file);
378
379 fileHandle = file.get();
380 }
381
382 // Register the distribution.
383 {
384 wsl::windows::common::HandleConsoleProgressBar progressBar(fileHandle, Localization::MessageImportProgress());
385 wsl::windows::common::SvcComm service;
386 service.RegisterDistribution(name, version, fileHandle, installPath->c_str(), flags);
387 }
388
389 directory_cleanup.release();
390 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
391 return 0;
392 }
393
394 int ImportDistributionInplace(_In_ std::wstring_view commandLine)
395 {
396 // Parse the command line.
397 int argc = 0;
398 const wil::unique_hlocal_ptr<LPWSTR[]> argv{CommandLineToArgvW(std::wstring(commandLine).c_str(), &argc)};
399 THROW_LAST_ERROR_IF(!argv);
400
401 THROW_HR_IF(WSL_E_INVALID_USAGE, argc != 2);
402
403 const auto name(argv[0]);
404 const auto filePath = wsl::windows::common::filesystem::GetFullPath(argv[1]);
405
406 wsl::windows::common::SvcComm service;
407 service.ImportDistributionInplace(name, filePath.c_str());
408 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
409 return 0;
410 }
411
412 int LaunchElevated(_In_ LPCWSTR commandLine)
413 {
414 wsl::windows::common::wslutil::PrintMessage(
415 wsl::windows::common::wslutil::GetSystemErrorString(HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED)));
416
417 // Add the attach parent process argument to the command line and shell execute an elevated version of wsl.exe.
418 std::wstring arguments;
419 arguments += WSL_PARENT_CONSOLE_ARG L" ";
420 arguments += std::to_wstring(GetCurrentProcessId());
421 arguments += L" ";
422 arguments += commandLine;
423
424 const auto path = wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle());
425 SHELLEXECUTEINFOW execInfo{};
426 execInfo.cbSize = sizeof(execInfo);
427 execInfo.fMask = (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_NO_UI);
428 execInfo.lpFile = path.c_str();
429 execInfo.lpVerb = L"runas";
430 execInfo.nShow = SW_HIDE;
431 execInfo.lpParameters = arguments.c_str();
432 THROW_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&execInfo));
433 const wil::unique_handle process{execInfo.hProcess};
434
435 // Get the process exit code.
436 WI_VERIFY(WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0);
437
438 DWORD exitCode;
439 THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(process.get(), &exitCode));
440 return static_cast<int>(exitCode);
441 }
442
443 int Install(_In_ std::wstring_view commandLine)
444 {
445
446 // Parse options.
447 std::optional<std::wstring> distroArgument;
448 std::optional<std::wstring> fromFile;
449 std::optional<std::wstring> name;
450 std::optional<std::filesystem::path> location;
451 std::optional<ULONG> version;
452 std::optional<uint64_t> vhdSize;
453 bool fixedVhd = false;
454 bool installWslOptionalComponent = false;
455 bool noLaunchAfterInstall = false;
456 bool noDistribution = false;
457 bool legacy = false;
458 bool webDownload = IsWindowsServer();
459
460 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
461 parser.AddPositionalArgument(distroArgument, 0);
462 parser.AddArgument(distroArgument, WSL_INSTALL_ARG_DIST_OPTION_LONG, WSL_INSTALL_ARG_DIST_OPTION);
463 parser.AddArgument(noLaunchAfterInstall, WSL_INSTALL_ARG_NO_LAUNCH_OPTION_LONG, WSL_INSTALL_ARG_NO_LAUNCH_OPTION);
464 parser.AddArgument(webDownload, WSL_INSTALL_ARG_WEB_DOWNLOAD_LONG);
465 parser.AddArgument(noDistribution, WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION);
466 parser.AddArgument(installWslOptionalComponent, WSL_INSTALL_ARG_ENABLE_WSL1_LONG);
467 parser.AddArgument(NoOp{}, WSL_INSTALL_ARG_PRERELEASE_LONG); // Unused but handled because argument may be present when invoked from inbox.
468 parser.AddArgument(fromFile, WSL_INSTALL_ARG_FROM_FILE_LONG, WSL_INSTALL_ARG_FROM_FILE_OPTION);
469 parser.AddArgument(name, WSL_INSTALL_ARG_NAME_LONG);
470 parser.AddArgument(AbsolutePath(location), WSL_INSTALL_ARG_LOCATION_LONG, WSL_INSTALL_ARG_LOCATION_OPTION);
471 parser.AddArgument(legacy, WSL_INSTALL_ARG_LEGACY_LONG);
472 parser.AddArgument(WslVersion(version), WSL_INSTALL_ARG_VERSION);
473 parser.AddArgument(g_promptBeforeExit, WSL_INSTALL_ARG_PROMPT_BEFORE_EXIT_OPTION);
474 parser.AddArgument(SizeString(vhdSize), WSL_INSTALL_ARG_VHD_SIZE);
475 parser.AddArgument(fixedVhd, WSL_INSTALL_ARG_FIXED_VHD);
476
477 parser.Parse();
478
479 if (noDistribution && distroArgument.has_value())
480 {
481 THROW_HR_WITH_USER_ERROR(
482 E_INVALIDARG, Localization::MessageArgumentsNotValidTogether(WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION, WSL_INSTALL_ARG_DIST_OPTION_LONG));
483 }
484
485 if (fixedVhd && !vhdSize.has_value())
486 {
487 THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::MessageArgumentNotValidWithout(WSL_INSTALL_ARG_FIXED_VHD, WSL_INSTALL_ARG_VHD_SIZE));
488 }
489
490 // A distribution to be installed can be specified in three ways:
491 // wsl.exe --install --distribution Ubuntu
492 // wsl.exe --install Ubuntu
493 // wsl.exe --install
494 //
495 // N.B. The legacy method (specifying --distribution) is no longer documented,
496 // but is still supported to avoid breaking existing scripts.
497 if (fromFile.has_value())
498 {
499 if (distroArgument.has_value())
500 {
501 THROW_HR_WITH_USER_ERROR(
502 E_INVALIDARG, Localization::MessageArgumentsNotValidTogether(WSL_INSTALL_ARG_FROM_FILE_LONG, WSL_INSTALL_ARG_DIST_OPTION_LONG));
503 }
504
505 wil::unique_hfile diskFile;
506 HANDLE file{};
507 if (fromFile.value() == WSL_IMPORT_ARG_STDIN)
508 {
509 file = GetStdHandle(STD_INPUT_HANDLE);
510 fromFile = L"<stdin>";
511 }
512 else
513 {
514 diskFile.reset(CreateFileW(
515 fromFile->c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
516
517 THROW_LAST_ERROR_IF(!diskFile);
518
519 file = diskFile.get();
520 }
521
522 wsl::windows::common::wslutil::PrintMessage(Localization::MessageInstalling(fromFile->c_str()));
523 wsl::windows::common::HandleConsoleProgressBar progressBar(file, Localization::MessageImportProgress());
524
525 SvcComm service;
526 auto [id, installedName] = service.RegisterDistribution(
527 name.has_value() ? name->c_str() : nullptr,
528 version.value_or(LXSS_WSL_VERSION_DEFAULT),
529 file,
530 location.has_value() ? location->c_str() : nullptr,
531 fixedVhd ? LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD : 0,
532 vhdSize);
533
534 wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installedName.get()), stdout);
535
536 if (!noLaunchAfterInstall)
537 {
538 wsl::windows::common::wslutil::PrintMessage(Localization::MessageLaunchingDistro(installedName.get()), stdout);
539
540 LaunchProcessOptions options{};
541 options.DistroGuid = id;
542 return LaunchProcess(nullptr, 0, nullptr, options);
543 }
544
545 return 0;
546 }
547
548 bool rebootRequired = InstallPrerequisites(installWslOptionalComponent);
549 noLaunchAfterInstall |= rebootRequired;
550
551 // Install a distribution only if no reboot is required, or if we're on the --legacy path (to maintain old behavior).
552 const Distribution* legacyDistro = nullptr;
553
554 WslInstall::InstallResult installResult{};
555 if (!noDistribution && (legacy || !rebootRequired))
556 {
557 auto result = WslInstall::InstallDistribution(
558 installResult, distroArgument, version, !noLaunchAfterInstall, webDownload, legacy, fixedVhd, name, location, vhdSize);
559
560 std::optional<std::wstring> flavor;
561 if (installResult.Distribution.has_value())
562 {
563 if (const auto* distro = std::get_if<ModernDistributionVersion>(&*installResult.Distribution))
564 {
565 flavor = distro->Name;
566 }
567 else
568 {
569 legacyDistro = std::get_if<Distribution>(&*installResult.Distribution);
570 WI_ASSERT(legacyDistro != nullptr);
571
572 flavor = legacyDistro->Name;
573 }
574 }
575
576 // Logs when a specific distribution is installed, and whether that was successful. Used to report distro usage to distro maintainers
577 WSL_LOG_TELEMETRY(
578 "InstallDistribution",
579 PDT_ProductAndServiceUsage,
580 TraceLoggingValue(result, "result"),
581 TraceLoggingValue(legacyDistro == nullptr, "modern"),
582 TraceLoggingValue(flavor.value_or(L"<none>").c_str(), "flavor"));
583
584 THROW_IF_FAILED(result);
585 }
586
587 if (rebootRequired)
588 {
589 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED);
590 }
591 else if (noDistribution)
592 {
593 wsl::windows::common::wslutil::PrintSystemError(NO_ERROR);
594 }
595 else
596 {
597 if (!installResult.Alreadyinstalled)
598 {
599 wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installResult.Name));
600 }
601
602 if (!noLaunchAfterInstall)
603 {
604 wsl::windows::common::wslutil::PrintMessage(Localization::MessageLaunchingDistro(installResult.Name), stdout);
605
606 if (legacyDistro != nullptr)
607 {
608 wsl::windows::common::distribution::Launch(*legacyDistro, installResult.InstalledViaGitHub, !installResult.Alreadyinstalled);
609 }
610 else
611 {
612 LaunchProcessOptions options{};
613 options.DistroGuid = installResult.Id.value();
614
615 return LaunchProcess(nullptr, 0, nullptr, options);
616 }
617 }
618 }
619
620 return 0;
621 }
622
623 bool InstallPrerequisites(_In_ bool installWslOptionalComponent)
624 {
625 const auto [rebootRequired, missingComponents] = WslInstall::CheckForMissingOptionalComponents(installWslOptionalComponent);
626 if (missingComponents.empty())
627 {
628 return rebootRequired;
629 }
630
631 // Install any optional components that have not yet been installed.
632 const auto token = wil::open_current_access_token();
633 if (!wsl::windows::common::security::IsTokenElevated(token.get()))
634 {
635 const auto elevatedCommand = std::format(
636 L"{} {} {}", WSL_INSTALL_ARG, WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION, installWslOptionalComponent ? WSL_INSTALL_ARG_ENABLE_WSL1_LONG : L"");
637
638 const auto exitCode = LaunchElevated(elevatedCommand.c_str());
639 if (exitCode != 0)
640 {
641 THROW_HR_WITH_USER_ERROR(
642 WSL_E_INSTALL_COMPONENT_FAILED,
643 Localization::MessageOptionalComponentInstallFailed(wsl::shared::string::Join(missingComponents, L','), exitCode));
644 }
645 }
646 else
647 {
648 WslInstall::InstallOptionalComponents(missingComponents);
649 }
650
651 return rebootRequired;
652 }
653
654 int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options)
655 {
656 // Create an instance of the specified distribution.
657 //
658 // N.B. If creating the instance fails because the file system needs to
659 // be upgraded, the appropriate message is displayed before
660 // re-attempting the create while allowing the upgrade. This is
661 // only done if running in interactive mode.
662 const LPCGUID distribution = options.DistroGuid.has_value() ? &options.DistroGuid.value() : nullptr;
663 wsl::windows::common::SvcComm service;
664 if (argc == 0)
665 {
666 ClientExecutionContext context;
667 const auto result = service.CreateInstanceNoThrow(distribution, 0, context.OutError());
668 if (FAILED(result))
669 {
670 if (result == WSL_E_FS_UPGRADE_NEEDED)
671 {
672 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageFsUpgradeNeeded(), stderr);
673 }
674 else
675 {
676 THROW_HR(result);
677 }
678 }
679 }
680
681 const int exitCode = service.LaunchProcess(
682 distribution,
683 filename,
684 argc,
685 argv,
686 options.LaunchFlags,
687 options.Username.empty() ? nullptr : options.Username.c_str(),
688 options.CurrentWorkingDirectory.empty() ? nullptr : options.CurrentWorkingDirectory.c_str());
689
690 THROW_HR_IF(WSL_E_USER_NOT_FOUND, (exitCode == LX_INIT_USER_NOT_FOUND));
691 THROW_HR_IF(WSL_E_TTY_LIMIT, (exitCode == LX_INIT_TTY_LIMIT));
692
693 return exitCode;
694 }
695
696 int ListDistributions(_In_ std::wstring_view commandLine)
697 {
698 ListOptions options{};
699 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
700 parser.AddArgument(options.all, WSL_LIST_ARG_ALL_OPTION);
701 parser.AddArgument(options.running, WSL_LIST_ARG_RUNNING_OPTION);
702 parser.AddArgument(options.quiet, WSL_LIST_ARG_QUIET_OPTION_LONG, WSL_LIST_ARG_QUIET_OPTION);
703 parser.AddArgument(options.verbose, WSL_LIST_ARG_VERBOSE_OPTION_LONG, WSL_LIST_ARG_VERBOSE_OPTION);
704 parser.AddArgument(options.online, WSL_LIST_ARG_ONLINE_OPTION_LONG, WSL_LIST_ARG_ONLINE_OPTION);
705
706 parser.Parse();
707
708 return ListDistributionsHelper(options);
709 }
710
711 int ListDistributionsHelper(_In_ ListOptions options)
712 {
713 // Handle invalid options.
714 THROW_HR_IF(
715 WSL_E_INVALID_USAGE,
716 ((options.quiet && options.verbose) || (options.all && options.running)) || ((options.verbose || options.all) && options.online));
717
718 // Query all registered distributions and sort the list so the default
719 // (if present) is first.
720 wsl::windows::common::SvcComm service;
721 auto distros = service.EnumerateDistributions();
722 std::sort(distros.begin(), distros.end(), [](const auto& Left, const auto&) {
723 return (WI_IsFlagSet(Left.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT));
724 });
725
726 if (options.verbose)
727 {
728 THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, distros.empty());
729
730 // Determine max length of a distro name and construct the format string.
731 size_t maxLength = wcslen(WSL_LIST_HEADER_NAME);
732 std::for_each(distros.begin(), distros.end(), [&](const auto& entry) {
733 const size_t length = wcslen(entry.DistroName);
734 if (length > maxLength)
735 {
736 maxLength = length;
737 }
738 });
739
740 std::wstring formatString(L"%s %-");
741 formatString += std::to_wstring(maxLength + 4);
742 formatString += L"s%-16s%s\n";
743
744 // Print distribution information.
745 wprintf(formatString.c_str(), L" ", WSL_LIST_HEADER_NAME, WSL_LIST_HEADER_STATE, WSL_LIST_HEADER_VERSION);
746 std::for_each(distros.begin(), distros.end(), [&](const auto& entry) {
747 const LPCWSTR defaultDistro = WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT) ? L"*" : L" ";
748 const std::wstring version(std::to_wstring(entry.Version));
749 auto state = L"Stopped";
750 switch (entry.State)
751 {
752 case LxssDistributionStateRunning:
753 state = L"Running";
754 break;
755
756 case LxssDistributionStateInstalling:
757 state = L"Installing";
758 break;
759
760 case LxssDistributionStateUninstalling:
761 state = L"Uninstalling";
762 break;
763
764 case LxssDistributionStateConverting:
765 state = L"Converting";
766 break;
767
768 case LxssDistributionStateExporting:
769 state = L"Exporting";
770 break;
771
772 case LxssDistributionStateCompacting:
773 state = L"Compacting";
774 break;
775
776 default:
777 break;
778 }
779
780 wprintf(formatString.c_str(), defaultDistro, entry.DistroName, state, version.c_str());
781 });
782 }
783 else if (!options.online)
784 {
785 if (options.running)
786 {
787 std::erase_if(distros, [&](const auto& entry) { return (entry.State != LxssDistributionStateRunning); });
788
789 if ((!options.quiet) && (distros.empty()))
790 {
791 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageNoRunningDistro());
792 return -1;
793 }
794 }
795
796 if (!options.all)
797 {
798 std::erase_if(distros, [&](const auto& entry) {
799 return (
800 (entry.State == LxssDistributionStateInstalling) || (entry.State == LxssDistributionStateUninstalling) ||
801 (entry.State == LxssDistributionStateConverting) || (entry.State == LxssDistributionStateExporting) ||
802 (entry.State == LxssDistributionStateCompacting));
803 });
804 }
805
806 if (!options.quiet)
807 {
808 THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, distros.empty());
809
810 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageRegisteredDistrosHeader());
811 }
812
813 std::for_each(distros.begin(), distros.end(), [&](const auto& entry) {
814 if ((!options.quiet) && WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT))
815 {
816 wsl::windows::common::wslutil::PrintMessage(Localization::MessagePrintDistroDefault(entry.DistroName), stdout);
817 }
818 else
819 {
820 wprintf(L"%s\n", entry.DistroName);
821 }
822 });
823 }
824 else
825 {
826 std::vector<std::pair<std::wstring, std::wstring>> names;
827
828 size_t maxLength = wcslen(WSL_LIST_HEADER_NAME);
829
830 auto appendIfNotPresent = [&](const std::wstring& name, const std::wstring& friendlyName) {
831 auto pred = [&name](const auto& e) { return e.first == name; };
832
833 if (std::find_if(names.begin(), names.end(), pred) == names.end())
834 {
835 names.emplace_back(name, friendlyName);
836
837 if (name.size() > maxLength)
838 {
839 maxLength = name.size();
840 }
841 }
842 };
843
844 auto readNames = [&](const DistributionList& distributions) {
845 if (distributions.ModernDistributions.has_value())
846 {
847 for (const auto& [name, versions] : *distributions.ModernDistributions)
848 {
849 for (auto i = 0; i < versions.size(); i++)
850 {
851 if (!options.all && i > 3)
852 {
853 break; // Only show 3 entries per distro unless --all is passed.
854 }
855
856 appendIfNotPresent(versions[i].Name, versions[i].FriendlyName);
857 }
858 }
859 }
860
861 if (distributions.Distributions.has_value())
862 {
863 for (const auto& e : *distributions.Distributions)
864 {
865 appendIfNotPresent(e.Name, e.FriendlyName);
866 }
867 }
868 };
869
870 const auto manifest = wsl::windows::common::distribution::GetAvailable();
871 if (manifest.OverrideManifest.has_value())
872 {
873 readNames(*manifest.OverrideManifest);
874 }
875
876 readNames(manifest.Manifest);
877
878 std::wstring formatString(L"%-");
879 formatString += std::to_wstring(maxLength + 4);
880 formatString += L"s%s\n";
881
882 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDistributionListOnline(WSL_INSTALL_ARG));
883 wprintf(formatString.c_str(), WSL_LIST_HEADER_NAME, WSL_LIST_HEADER_FRIENDLY_NAME);
884 std::for_each(names.begin(), names.end(), [&](const auto& entry) {
885 wprintf(formatString.c_str(), entry.first.c_str(), entry.second.c_str());
886 });
887 }
888
889 return 0;
890 }
891
892 int Manage(_In_ std::wstring_view commandLine)
893 {
894 LPCWSTR distribution{};
895 std::optional<bool> sparse;
896 std::optional<std::wstring> move;
897 std::optional<std::wstring> defaultUser;
898 std::optional<uint64_t> resize;
899 bool compact = false;
900 bool allowUnsafe = false;
901
902 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME, 0);
903 parser.AddPositionalArgument(distribution, 0);
904 parser.AddArgument(ParsedBool(sparse), WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG, WSL_MANAGE_ARG_SET_SPARSE_OPTION);
905 parser.AddArgument(AbsolutePath(move), WSL_MANAGE_ARG_MOVE_OPTION_LONG, WSL_MANAGE_ARG_MOVE_OPTION);
906 parser.AddArgument(defaultUser, WSL_MANAGE_ARG_SET_DEFAULT_USER_OPTION_LONG);
907 parser.AddArgument(SizeString(resize), WSL_MANAGE_ARG_RESIZE_OPTION_LONG, WSL_MANAGE_ARG_RESIZE_OPTION);
908 parser.AddArgument(compact, WSL_MANAGE_ARG_COMPACT_OPTION_LONG);
909 parser.AddArgument(allowUnsafe, WSL_MANAGE_ARG_ALLOW_UNSAFE);
910 parser.Parse();
911
912 THROW_HR_IF(WSL_E_INVALID_USAGE, distribution == nullptr);
913
914 wsl::windows::common::SvcComm service;
915 auto distroGuid = service.GetDistributionId(distribution);
916
917 if (sparse.has_value() + move.has_value() + defaultUser.has_value() + resize.has_value() + compact != 1)
918 {
919 THROW_HR(WSL_E_INVALID_USAGE);
920 }
921
922 if (sparse)
923 {
924 SetSparse(distroGuid, sparse.value(), allowUnsafe);
925 }
926 else if (move)
927 {
928 service.MoveDistribution(distroGuid, move->c_str());
929 }
930 else if (defaultUser)
931 {
932 auto wslExe = wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle());
933 const auto distroGuidString = wsl::shared::string::GuidToString<wchar_t>(distroGuid);
934 const std::array<std::wstring_view, 9> arguments{
935 wslExe, distroGuidString, WSL_USER_ARG, L"root", WSL_EXEC_ARG, L"/usr/bin/id", L"-u", L"--", defaultUser.value()};
936 const auto commandLine = wil::ArgvToCommandLine(arguments);
937
938 wsl::windows::common::SubProcess process{wslExe.c_str(), commandLine.c_str()};
939
940 auto result = process.RunAndCaptureOutput(INFINITE, GetStdHandle(STD_ERROR_HANDLE));
941 if (result.ExitCode != 0)
942 {
943 return result.ExitCode;
944 }
945
946 while (!result.Stdout.empty() && (result.Stdout.back() == '\r' || result.Stdout.back() == '\n'))
947 {
948 result.Stdout.pop_back();
949 }
950
951 wchar_t* endPtr{};
952 auto newUid = std::wcstoul(result.Stdout.c_str(), &endPtr, 10);
953
954 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), endPtr != result.Stdout.c_str() + result.Stdout.size());
955
956 service.ConfigureDistribution(&distroGuid, newUid, LXSS_DISTRO_FLAGS_UNCHANGED);
957 }
958 else if (resize)
959 {
960 THROW_IF_FAILED(service.ResizeDistribution(&distroGuid, resize.value()));
961 }
962 else if (compact)
963 {
964 auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageCompactionStart(), true);
965 const auto result = service.CompactDistribution(&distroGuid);
966 progress.End();
967 THROW_IF_FAILED(result);
968 }
969
970 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
971 return 0;
972 }
973
974 int Mount(_In_ std::wstring_view commandLine)
975 {
976 bool vhd = false;
977 bool bare = false;
978 std::optional<std::wstring> options;
979 ULONG partition = 0;
980 std::optional<std::wstring> type;
981 std::optional<std::wstring> name;
982 std::wstring disk;
983
984 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
985 parser.AddArgument(bare, WSL_MOUNT_ARG_BARE_OPTION_LONG);
986 parser.AddArgument(vhd, WSL_MOUNT_ARG_VHD_OPTION_LONG);
987 parser.AddArgument(options, WSL_MOUNT_ARG_OPTIONS_OPTION_LONG, WSL_MOUNT_ARG_OPTIONS_OPTION);
988 parser.AddArgument(Integer(partition), WSL_MOUNT_ARG_PARTITION_OPTION_LONG, WSL_MOUNT_ARG_PARTITION_OPTION);
989 parser.AddArgument(type, WSL_MOUNT_ARG_TYPE_OPTION_LONG, WSL_MOUNT_ARG_TYPE_OPTION);
990 parser.AddArgument(name, WSL_MOUNT_ARG_NAME_OPTION_LONG, WSL_MOUNT_ARG_NAME_OPTION);
991 parser.AddPositionalArgument(UnquotedPath(disk), 0);
992 parser.Parse();
993
994 THROW_HR_IF(WSL_E_INVALID_USAGE, disk.empty());
995
996 ULONG flags = 0;
997 if (vhd)
998 {
999 WI_SetFlag(flags, LXSS_ATTACH_MOUNT_FLAGS_VHD);
1000 disk = wsl::windows::common::filesystem::GetFullPath(disk.c_str()).wstring();
1001 }
1002 else
1003 {
1004 WI_SetFlag(flags, LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH);
1005 }
1006
1007 // First attach the disk to the vm
1008 wsl::windows::common::SvcComm service;
1009 const auto result = service.AttachDisk(disk.c_str(), flags);
1010 if (FAILED(result))
1011 {
1012 THROW_HR_IF(result, bare);
1013
1014 // In the case of a non-bare mount, WSL_E_DISK_ALREADY_ATTACHED and LXSS_E_USER_VHD_ALREADY_ATTACHED are
1015 // ok to ignore because the user can mount more than one partition on the same disk
1016 // (so that disk might be already attached).
1017 THROW_HR_IF(result, result != WSL_E_DISK_ALREADY_ATTACHED && result != WSL_E_USER_VHD_ALREADY_ATTACHED);
1018 }
1019
1020 // Perform the mount
1021 if (!bare)
1022 {
1023 const auto mountResult = service.MountDisk(
1024 disk.c_str(),
1025 flags,
1026 partition,
1027 name.has_value() ? name->c_str() : nullptr,
1028 type.has_value() ? type->c_str() : nullptr,
1029 options.has_value() ? options->c_str() : nullptr);
1030
1031 if (mountResult.Result != 0)
1032 {
1033 wsl::windows::common::wslutil::PrintMessage(
1034 Localization::MessageDiskMountFailed(strerror(-mountResult.Result), WSL_UNMOUNT_ARG, disk), stdout);
1035 return 1;
1036 }
1037 else
1038 {
1039 wsl::windows::common::wslutil::PrintMessage(
1040 Localization::MessageDiskMounted(mountResult.MountName.get(), WSL_UNMOUNT_ARG, disk), stdout);
1041 }
1042 }
1043 else
1044 {
1045 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1046 }
1047
1048 return 0;
1049 }
1050
1051 LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine)
1052 {
1053 // Strip the executable name. Because this has to be a legal file name, quoted parts cannot contain escaped quotes.
1054 BOOLEAN inQuotes = FALSE;
1055 while ((!commandLine.empty()) && ((inQuotes != FALSE) || (!LXSS_IS_WHITESPACE(commandLine[0]))))
1056 {
1057 if (commandLine[0] == L'"')
1058 {
1059 inQuotes = !inQuotes;
1060 }
1061
1062 commandLine = commandLine.substr(1);
1063 }
1064
1065 // Strip any leading whitespace.
1066 commandLine = wsl::windows::common::string::StripLeadingWhitespace(commandLine);
1067
1068 // Check for a distribution GUID as the first parameter and strip it out if present.
1069 auto argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1070 auto distroGuid = wsl::shared::string::ToGuid(argument);
1071 if (distroGuid.has_value())
1072 {
1073 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1074 }
1075
1076 // Check for the home directory parameter and strip it out if present.
1077 std::wstring currentWorkingDirectory;
1078 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1079 if (argument == WSL_CWD_HOME)
1080 {
1081 currentWorkingDirectory = WSL_CWD_HOME;
1082 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1083 }
1084
1085 return {std::move(currentWorkingDirectory), std::move(distroGuid)};
1086 }
1087
1088 DWORD
1089 ParseVersionString(_In_ const std::wstring_view& versionString)
1090 {
1091 DWORD version;
1092 const auto result = wil::ResultFromException([&]() { version = std::stoi(std::wstring(versionString)); });
1093 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_VERSION_PARSE_ERROR), (FAILED(result) || ((version != LXSS_WSL_VERSION_1) && (version != LXSS_WSL_VERSION_2))));
1094
1095 return version;
1096 }
1097
1098 int SetDefaultDistribution(_In_ LPCWSTR distributionName)
1099 {
1100 wsl::windows::common::SvcComm service;
1101 const GUID distroGuid = service.GetDistributionId(distributionName);
1102 service.SetDefaultDistribution(&distroGuid);
1103 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1104 return 0;
1105 }
1106
1107 int SetDefaultVersion(_In_ std::wstring_view commandLine)
1108 {
1109 const auto argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1110 const auto version = ParseVersionString(argument);
1111 if (version == LXSS_WSL_VERSION_1)
1112 {
1113 THROW_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !wsl::windows::common::helpers::IsWslOptionalComponentPresent());
1114 }
1115 else
1116 {
1117 WI_ASSERT(version == LXSS_WSL_VERSION_2);
1118
1119 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageVmModeConversionInfo());
1120 }
1121
1122 const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
1123 wsl::windows::common::registry::WriteDword(lxssKey.get(), nullptr, LXSS_WSL_DEFAULT_VERSION, version);
1124 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1125 return 0;
1126 }
1127
1128 int Shutdown(_In_ std::wstring_view commandLine)
1129 {
1130 bool force = false;
1131 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
1132 parser.AddArgument(force, WSL_SHUTDOWN_OPTION_FORCE);
1133
1134 parser.Parse();
1135
1136 wsl::windows::common::SvcComm service;
1137 service.Shutdown(force);
1138
1139 return 0;
1140 }
1141
1142 int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe)
1143 {
1144 wsl::windows::common::SvcComm service;
1145
1146 auto setProgress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageConversionStart());
1147 THROW_IF_FAILED(service.SetSparse(&distroGuid, sparse, allowUnsafe));
1148
1149 return 0;
1150 }
1151
1152 int SetVersion(_In_ std::wstring_view commandLine)
1153 {
1154 auto argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1155 if (argument.empty())
1156 {
1157 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_SET_VERSION_ARG), stdout);
1158 return -1;
1159 }
1160
1161 const std::wstring distributionName(argument);
1162 wsl::windows::common::SvcComm service;
1163 const auto distroGuid = service.GetDistributionId(distributionName.c_str());
1164
1165 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1166 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1167 const auto version = ParseVersionString(argument);
1168 if (version == LXSS_WSL_VERSION_2)
1169 {
1170 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageVmModeConversionInfo());
1171 }
1172
1173 auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageConversionStart(), true);
1174 const auto result = service.SetVersion(&distroGuid, version);
1175 progress.End();
1176 THROW_IF_FAILED(result);
1177
1178 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1179 return 0;
1180 }
1181
1182 int Status()
1183 {
1184 // Print the default distro.
1185 wsl::windows::common::SvcComm service;
1186 const auto distros = service.EnumerateDistributions();
1187 for (const auto& entry : distros)
1188 {
1189 if (WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT))
1190 {
1191 wsl::windows::common::wslutil::PrintMessage(Localization::MessageStatusDefaultDistro(entry.DistroName), stdout);
1192 break;
1193 }
1194 }
1195
1196 // Print the default version.
1197 const DWORD version = wsl::windows::common::wslutil::GetDefaultVersion();
1198 wsl::windows::common::wslutil::PrintMessage(Localization::MessageStatusDefaultVersion(version), stdout);
1199
1200 // Print a message if the WSL optional component is not present for WSL1 support.
1201 if (!wsl::windows::common::helpers::IsWslOptionalComponentPresent())
1202 {
1203 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageWsl1NotSupported());
1204 }
1205
1206 // Print a message if the vmcompute service is present for WSL2 support.
1207 if (!wsl::windows::common::helpers::IsServicePresent(L"vmcompute"))
1208 {
1209 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageEnableVirtualization());
1210 }
1211
1212 return 0;
1213 }
1214
1215 int TerminateDistribution(_In_ LPCWSTR distributionName)
1216 {
1217 wsl::windows::common::SvcComm service;
1218 const GUID distroGuid = service.GetDistributionId(distributionName);
1219 service.TerminateInstance(&distroGuid);
1220 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1221 return 0;
1222 }
1223
1224 int Unmount(_In_ const std::wstring& arg)
1225 {
1226 const auto* disk = arg.empty() ? nullptr : arg.c_str();
1227
1228 std::pair<int, int> value;
1229 wsl::windows::common::SvcComm service;
1230 const HRESULT result = wil::ResultFromException([&] { value = service.DetachDisk(disk); });
1231
1232 // Retry with the normalized path to handle relative paths and \\?\ prefix mismatches.
1233 if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
1234 {
1235 // retry dismounting with the absolute path
1236 const auto absoluteDisk = wsl::windows::common::filesystem::GetFullPath(filesystem::UnquotePath(disk).c_str());
1237 value = service.DetachDisk(absoluteDisk.c_str());
1238 }
1239 else if (FAILED(result))
1240 {
1241 THROW_HR(result);
1242 }
1243
1244 if (value.first != 0)
1245 {
1246 wsl::windows::common::wslutil::PrintMessage(Localization::MessageDetachFailed(strerror(-value.first), WSL_SHUTDOWN_ARG), stdout);
1247 return -1;
1248 }
1249
1250 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1251 return 0;
1252 }
1253
1254 int UnregisterDistribution(_In_ LPCWSTR distributionName)
1255 {
1256 auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageStatusUnregistering(), true);
1257 wsl::windows::common::SvcComm service;
1258 const GUID distroGuid = service.GetDistributionId(distributionName, LXSS_GET_DISTRO_ID_LIST_ALL);
1259 service.UnregisterDistribution(&distroGuid);
1260 progress.End();
1261 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
1262 return 0;
1263 }
1264
1265 int UpdatePackage(std::wstring_view commandLine)
1266 {
1267 ExecutionContext context(wsl::windows::common::UpdatePackage);
1268
1269 bool preRelease{};
1270 ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
1271 parser.AddArgument(preRelease, WSL_UPDATE_ARG_PRE_RELEASE_OPTION_LONG);
1272
1273 // Options kept for compatibility with inbox WSL.
1274 parser.AddArgument(NoOp(), WSL_UPDATE_ARG_WEB_DOWNLOAD_OPTION_LONG);
1275 parser.AddArgument(NoOp(), WSL_UPDATE_ARG_CONFIRM_OPTION_LONG);
1276 parser.AddArgument(NoOp(), WSL_UPDATE_ARG_PROMPT_OPTION_LONG);
1277 parser.Parse();
1278
1279 return wsl::windows::common::install::UpdatePackage(preRelease, false);
1280 }
1281
1282 int Uninstall()
1283 {
1284 auto logFile = std::filesystem::temp_directory_path() / L"wsl-uninstall-logs.txt";
1285 auto clearLogs =
1286 wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile.c_str())); });
1287
1288 const auto exitCode = wsl::windows::common::install::UninstallViaMsi(logFile.c_str(), &wsl::windows::common::install::MsiMessageCallback);
1289
1290 if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED)
1291 {
1292 wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED);
1293 }
1294 else if (exitCode != 0)
1295 {
1296 clearLogs.release();
1297 THROW_HR_WITH_USER_ERROR(
1298 HRESULT_FROM_WIN32(exitCode),
1299 wsl::shared::Localization::MessageUninstallFailed(exitCode) + L"\r\n" +
1300 wsl::shared::Localization::MessageSeeLogFile(logFile.c_str()));
1301 }
1302
1303 return exitCode;
1304 }
1305
1306 int Version()
1307 {
1308 // Query the Windows version.
1309 const auto windowsVersion = wsl::windows::common::helpers::GetWindowsVersionString();
1310 wsl::windows::common::wslutil::PrintMessage(
1311 Localization::MessagePackageVersions(
1312 WSL_PACKAGE_VERSION, KERNEL_VERSION, WSLG_VERSION, MSRDC_VERSION, DIRECT3D_VERSION, DXCORE_VERSION, windowsVersion),
1313 stdout);
1314
1315 if constexpr (!wsl::shared::OfficialBuild)
1316 {
1317 // Print additional information if running a debug build.
1318 wsl::windows::common::wslutil::PrintMessage(Localization::MessageBuildInfo(_MSC_VER, COMMIT_HASH, __TIME__ " " __DATE__), stdout);
1319 }
1320
1321 return 0;
1322 }
1323
1324 int WslconfigMain(_In_ int argc, _In_reads_(argc) LPWSTR* argv)
1325 {
1326 // Call the MSI package if we're in an MSIX context
1327 if (wsl::windows::common::wslutil::IsRunningInMsix())
1328 {
1329 return wsl::windows::common::install::CallMsiPackage();
1330 }
1331
1332 using wsl::shared::string::IsEqual;
1333
1334 // Use exit code -1 on generic failures. This was the original exit code and shouldn't be changed, especially since wslconfig.exe is deprecated.
1335 int exitCode = -1;
1336 if ((argc >= 2) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_LIST, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_LIST_SHORT, true))))
1337 {
1338 ListOptions options{};
1339 for (int index = 2; index < argc; index += 1)
1340 {
1341 std::wstring_view argument = argv[index];
1342 if (argument.empty())
1343 {
1344 break;
1345 }
1346 if (IsEqual(argument, WSLCONFIG_COMMAND_LIST_ALL, true))
1347 {
1348 options.all = true;
1349 }
1350 else if (IsEqual(argument, WSLCONFIG_COMMAND_LIST_RUNNING, true))
1351 {
1352 options.running = true;
1353 }
1354 else
1355 {
1356 THROW_HR(WSL_E_INVALID_USAGE);
1357 }
1358 }
1359
1360 exitCode = ListDistributionsHelper(options);
1361 }
1362 else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_SET_DEFAULT, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_SET_DEFAULT_SHORT, true))))
1363 {
1364 exitCode = SetDefaultDistribution(argv[2]);
1365 }
1366 else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_TERMINATE, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_TERMINATE_SHORT, true))))
1367 {
1368 exitCode = TerminateDistribution(argv[2]);
1369 }
1370 else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_UNREGISTER_DISTRIBUTION, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_UNREGISTER_DISTRIBUTION_SHORT, true))))
1371 {
1372 exitCode = UnregisterDistribution(argv[2]);
1373 }
1374 else
1375 {
1376 THROW_HR(WSL_E_INVALID_USAGE);
1377 }
1378
1379 return exitCode;
1380 }
1381
1382 int WslgMain(_In_ std::wstring_view commandLine)
1383 {
1384 // N.B. There is no app execution alias for wslg, so it cannot run in an MSIX context.
1385 WI_ASSERT(!wsl::windows::common::wslutil::IsRunningInMsix());
1386
1387 auto options = ParseLegacyArguments(commandLine);
1388
1389 // Parse additional arguments.
1390 std::wstring_view argument;
1391 ShellExecOptions shellExecOptions{};
1392 wsl::windows::common::SvcComm service;
1393 for (;;)
1394 {
1395 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1396 if (argument.empty())
1397 {
1398 break;
1399 }
1400
1401 if ((argument == WSL_DISTRO_ARG) || (argument == WSL_DISTRO_ARG_LONG))
1402 {
1403 THROW_HR_IF(WSL_E_INVALID_USAGE, options.DistroGuid.has_value());
1404
1405 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1406 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1407 THROW_HR_IF(WSL_E_INVALID_USAGE, argument.empty());
1408
1409 // Query the service for the distribution id.
1410 options.DistroGuid = service.GetDistributionId(std::wstring(argument).c_str());
1411 }
1412 else if (argument == WSL_SHELL_OPTION_ARG)
1413 {
1414 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1415 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1416 THROW_HR_IF(E_INVALIDARG, argument.empty());
1417
1418 shellExecOptions.ParseShellOptionArg(argument);
1419 }
1420 else if ((argument == WSL_USER_ARG) || (argument == WSL_USER_ARG_LONG))
1421 {
1422 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1423 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1424 THROW_HR_IF(WSL_E_INVALID_USAGE, argument.empty());
1425
1426 options.Username = argument;
1427 }
1428 else if (argument == WSL_CHANGE_DIRECTORY_ARG)
1429 {
1430 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1431 argument = wsl::windows::common::helpers::ParseArgument(commandLine, true);
1432 ChangeDirectory(argument, options);
1433 }
1434 else if (argument == WSL_STOP_PARSING_ARG)
1435 {
1436 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1437 break;
1438 }
1439 else
1440 {
1441 THROW_HR_IF(WSL_E_INVALID_USAGE, ((argument.size() > 0) && (argument[0] == L'-')));
1442
1443 break;
1444 }
1445
1446 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1447 }
1448
1449 // Launching a graphical application requires a non-empty command line.
1450 THROW_HR_IF(WSL_E_INVALID_USAGE, commandLine.empty());
1451
1452 std::vector<const wchar_t*> arguments;
1453 const std::wstring commandLineString{commandLine};
1454 wil::unique_hlocal_ptr<LPWSTR[]> execArguments{};
1455 LPCWSTR filename{};
1456 if (!shellExecOptions.IsUseShell())
1457 {
1458 int argc;
1459 execArguments.reset(CommandLineToArgvW(commandLineString.c_str(), &argc));
1460 THROW_HR_IF(E_INVALIDARG, (!execArguments || (argc == 0)));
1461
1462 arguments.reserve(argc);
1463 arguments.insert(arguments.begin(), &execArguments.get()[0], &execArguments.get()[argc]);
1464 filename = arguments[0];
1465 }
1466 else
1467 {
1468 arguments.push_back(commandLineString.c_str());
1469 }
1470
1471 // Graphical applications by default will use a login shell so that users can modify behavior.
1472 shellExecOptions.DefaultUseShell = true;
1473 shellExecOptions.DefaultLogin = shellExecOptions.IsUseShell();
1474 if (shellExecOptions.IsLogin())
1475 {
1476 // Launch via the user's default shell in login mode to parse files like /etc/profile.
1477 WI_SetFlag(options.LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN);
1478 }
1479
1480 return LaunchProcess(filename, gsl::narrow_cast<int>(arguments.size()), arguments.data(), options);
1481 }
1482
1483 int RunDebugShell()
1484 {
1485 ExecutionContext context(Context::DebugShell);
1486
1487 auto token = wil::open_current_access_token();
1488 auto tokenInfo = wil::get_token_information<TOKEN_USER>(token.get());
1489 auto pipePath = wsl::windows::common::wslutil::GetDebugShellPipeName(tokenInfo->User.Sid);
1490 wil::unique_hfile pipe{CreateFileW(pipePath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr)};
1491
1492 if (!pipe)
1493 {
1494 auto error = GetLastError();
1495 if (error == ERROR_ACCESS_DENIED && !wsl::windows::common::security::IsTokenElevated(token.get()))
1496 {
1497 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageAdministratorAccessRequiredForDebugShell());
1498 return 1;
1499 }
1500 else if (
1501 error == ERROR_FILE_NOT_FOUND &&
1502 !wsl::windows::policies::IsFeatureAllowed(wsl::windows::policies::OpenPoliciesKey().get(), wsl::windows::policies::c_allowDebugShellUserSetting))
1503 {
1504 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDebugShellDisabled());
1505 return 1;
1506 }
1507 else
1508 {
1509 THROW_WIN32(error);
1510 }
1511 }
1512
1513 // agetty waits for a LF before printing the prompt, so write it immediately after the pipe is opened.
1514 // This is needed because without the '-w' flag, agetty doesn't wait and prints the shell prompt before
1515 // a pipe is connected, so it's lost.
1516 THROW_IF_WIN32_BOOL_FALSE(WriteFile(pipe.get(), "\n", 1, nullptr, nullptr));
1517
1518 // Create a thread to relay stdin to the pipe.
1519 wsl::windows::common::ConsoleState console;
1520 console.SetInteractiveMode();
1521 auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset);
1522 std::thread inputThread([&]() {
1523 wsl::windows::common::relay::StandardInputRelay(GetStdHandle(STD_INPUT_HANDLE), pipe.get(), []() {}, exitEvent.get());
1524 });
1525
1526 auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
1527 exitEvent.SetEvent();
1528 inputThread.join();
1529 });
1530
1531 // Relay the contents of the pipe to stdout.
1532 wsl::windows::common::relay::InterruptableRelay(pipe.get(), GetStdHandle(STD_OUTPUT_HANDLE));
1533
1534 // Print a message that the VM has exited and signal the input thread to exit.
1535 fputws(L"\n", stdout);
1536 THROW_HR(HCS_E_CONNECTION_CLOSED);
1537 }
1538
1539 int WslMain(_In_ std::wstring_view commandLine)
1540 {
1541 // Call the MSI package if we're in an MSIX context
1542 if (wsl::windows::common::wslutil::IsRunningInMsix())
1543 {
1544 const bool launchedViaAppActivation = winrt::Windows::ApplicationModel::AppInstance::GetActivatedEventArgs() != nullptr;
1545 const auto exitCode = wsl::windows::common::install::CallMsiPackage();
1546 if (launchedViaAppActivation && exitCode == -1)
1547 {
1548 g_promptBeforeExit = true;
1549 }
1550
1551 return exitCode;
1552 }
1553
1554 // Use exit code -1 so invokers of wsl.exe can distinguish between a Linux
1555 // process failure and a wsl.exe failure. The distro launcher sample depends
1556 // on this specific code.
1557 int exitCode = -1;
1558
1559 // Parse the command line to determine if the legacy distro GUID or the '~' argument were specified.
1560 auto options = ParseLegacyArguments(commandLine);
1561
1562 // Parse additional arguments.
1563 std::wstring_view argument;
1564 ShellExecOptions shellExecOptions{};
1565 for (;;)
1566 {
1567 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1568 if (argument.empty())
1569 {
1570 break;
1571 }
1572
1573 if (argument == WSL_DEBUG_SHELL_ARG_LONG)
1574 {
1575 return RunDebugShell();
1576 }
1577 else if ((argument == WSL_DISTRO_ARG) || (argument == WSL_DISTRO_ARG_LONG))
1578 {
1579 // Ensure the distribution has not already been set.
1580 if (options.DistroGuid.has_value())
1581 {
1582 wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDistroAlreadySet());
1583 return exitCode;
1584 }
1585
1586 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1587 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1588 if (argument.empty())
1589 {
1590 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_DISTRO_ARG_LONG), stdout);
1591 return exitCode;
1592 }
1593
1594 // Query the service for the distribution id.
1595 wsl::windows::common::SvcComm service;
1596 options.DistroGuid = service.GetDistributionId(std::wstring(argument).c_str());
1597 }
1598 else if (argument == WSL_CHANGE_DIRECTORY_ARG)
1599 {
1600 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1601 argument = wsl::windows::common::helpers::ParseArgument(commandLine, true);
1602 ChangeDirectory(argument, options);
1603 }
1604 else if (argument == WSL_DISTRIBUTION_ID_ARG)
1605 {
1606 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1607 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1608
1609 if (argument.empty())
1610 {
1611 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_DISTRIBUTION_ID_ARG), stdout);
1612 return exitCode;
1613 }
1614
1615 options.DistroGuid = wsl::shared::string::ToGuid(argument);
1616 THROW_HR_IF(E_INVALIDARG, !options.DistroGuid.has_value());
1617 }
1618 else if ((argument == WSL_USER_ARG) || (argument == WSL_USER_ARG_LONG))
1619 {
1620 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1621 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1622 if (argument.empty())
1623 {
1624 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_USER_ARG_LONG), stdout);
1625 return exitCode;
1626 }
1627
1628 options.Username = argument;
1629 }
1630 else if (argument == WSL_UPDATE_ARG)
1631 {
1632 return UpdatePackage(commandLine);
1633 }
1634 else if (argument == WSL_HELP_ARG)
1635 {
1636 wsl::windows::common::wslutil::PrintMessage(Localization::MessageWslUsage());
1637 return exitCode;
1638 }
1639 else if (argument == WSL_STOP_PARSING_ARG)
1640 {
1641 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1642 break;
1643 }
1644 else if ((argument == WSL_EXEC_ARG) || (argument == WSL_EXEC_ARG_LONG))
1645 {
1646 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1647 shellExecOptions.SetExecMode();
1648 break;
1649 }
1650 else if (argument == WSL_SHELL_OPTION_ARG)
1651 {
1652 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1653 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1654 if (argument.empty())
1655 {
1656 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_SHELL_OPTION_ARG), stdout);
1657 return exitCode;
1658 }
1659
1660 shellExecOptions.ParseShellOptionArg(argument);
1661 }
1662 else if (argument == WSL_EXPORT_ARG)
1663 {
1664 return ExportDistribution(commandLine);
1665 }
1666 else if (argument == WSL_IMPORT_ARG)
1667 {
1668 return ImportDistribution(commandLine);
1669 }
1670 else if (argument == WSL_IMPORT_INPLACE_ARG)
1671 {
1672 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1673 return ImportDistributionInplace(commandLine);
1674 }
1675 else if ((argument == WSL_LIST_ARG) || (argument == WSL_LIST_ARG_LONG))
1676 {
1677 return ListDistributions(commandLine);
1678 }
1679 else if ((argument == WSL_SET_DEFAULT_DISTRO_ARG) || (argument == WSL_SET_DEFAULT_DISTRO_ARG_LEGACY) || (argument == WSL_SET_DEFAULT_DISTRO_ARG_LONG))
1680 {
1681 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1682 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1683 if (argument.empty())
1684 {
1685 wsl::windows::common::wslutil::PrintMessage(
1686 Localization::MessageRequiredParameterMissing(WSL_SET_DEFAULT_DISTRO_ARG_LONG), stdout);
1687 return exitCode;
1688 }
1689
1690 return SetDefaultDistribution(std::wstring(argument).c_str());
1691 }
1692 else if (argument == WSL_PARENT_CONSOLE_ARG)
1693 {
1694 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1695 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1696 if (argument.empty())
1697 {
1698 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_PARENT_CONSOLE_ARG), stdout);
1699 return exitCode;
1700 }
1701
1702 const auto parentProcessId = std::stoi(std::wstring(argument));
1703
1704 FreeConsole();
1705 THROW_IF_WIN32_BOOL_FALSE(AttachConsole(parentProcessId));
1706 }
1707 else if ((argument == WSL_TERMINATE_ARG) || (argument == WSL_TERMINATE_ARG_LONG))
1708 {
1709 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1710 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1711 if (argument.empty())
1712 {
1713 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_TERMINATE_ARG_LONG), stdout);
1714 return exitCode;
1715 }
1716
1717 return TerminateDistribution(std::wstring(argument).c_str());
1718 }
1719 else if (argument == WSL_UNREGISTER_ARG)
1720 {
1721 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1722 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1723 if (argument.empty())
1724 {
1725 wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_UNREGISTER_ARG), stdout);
1726 return exitCode;
1727 }
1728
1729 return UnregisterDistribution(std::wstring(argument).c_str());
1730 }
1731 else if (argument == WSL_SET_DEFAULT_VERSION_ARG)
1732 {
1733 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1734 return SetDefaultVersion(commandLine);
1735 }
1736 else if (argument == WSL_SHUTDOWN_ARG)
1737 {
1738 return Shutdown(commandLine);
1739 }
1740 else if (argument == WSL_MANAGE_ARG)
1741 {
1742 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1743 return Manage(commandLine);
1744 }
1745 else if (argument == WSL_SET_VERSION_ARG)
1746 {
1747 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1748 return SetVersion(commandLine);
1749 }
1750 else if (argument == WSL_MOUNT_ARG)
1751 {
1752 return Mount(commandLine);
1753 }
1754 else if (argument == WSL_UNMOUNT_ARG)
1755 {
1756 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1757 return Unmount(std::wstring(commandLine));
1758 }
1759 else if (argument == WSL_INSTALL_ARG)
1760 {
1761 return Install(commandLine);
1762 }
1763 else if (argument == WSL_SYSTEM_DISTRO_ARG)
1764 {
1765 WI_SetFlag(options.LaunchFlags, LXSS_LAUNCH_FLAG_USE_SYSTEM_DISTRO);
1766 }
1767 else if (argument == WSL_STATUS_ARG)
1768 {
1769 return Status();
1770 }
1771 else if ((argument == WSL_VERSION_ARG) || (argument == WSL_VERSION_ARG_LONG))
1772 {
1773 return Version();
1774 }
1775 else if (argument == WSL_UNINSTALL_ARG)
1776 {
1777 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1778 argument = wsl::windows::common::helpers::ParseArgument(commandLine);
1779 if (!argument.empty())
1780 {
1781 wsl::windows::common::wslutil::PrintMessage(
1782 Localization::MessageUninstallNoArguments(WSL_UNINSTALL_ARG, WSL_UNREGISTER_ARG), stdout);
1783 return exitCode;
1784 }
1785
1786 return Uninstall();
1787 }
1788 else
1789 {
1790 if ((argument.size() > 0) && (argument[0] == L'-'))
1791 {
1792 std::wstring InvalidArgument(argument);
1793 wsl::windows::common::wslutil::PrintMessage(Localization::MessageInvalidCommandLine(InvalidArgument, WSL_BINARY_NAME), stdout);
1794 return exitCode;
1795 }
1796
1797 break;
1798 }
1799
1800 commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
1801 }
1802
1803 // There are three possible cases:
1804 // 1. Empty command line - Launch the default user's default shell.
1805 // 2. Exec mode - Call CommandLineToArgvW on the remaining command
1806 // line and pass it along to the create process call.
1807 // 3. Non-empty command line - The command is invoked through the
1808 // default user's default shell via '$SHELL -c commandLine'.
1809 int argc = 0;
1810 LPCWSTR* arguments{};
1811 LPCWSTR argv[1];
1812 std::wstring commandLineString{commandLine};
1813 wil::unique_hlocal_ptr<LPWSTR[]> execArguments{};
1814 LPCWSTR filename{};
1815 if (!commandLine.empty())
1816 {
1817 if (!shellExecOptions.IsUseShell())
1818 {
1819 execArguments.reset(CommandLineToArgvW(commandLineString.c_str(), &argc));
1820 THROW_HR_IF(E_INVALIDARG, (!execArguments || (argc == 0)));
1821
1822 arguments = const_cast<LPCWSTR*>(execArguments.get());
1823 filename = arguments[0];
1824 }
1825 else
1826 {
1827 argv[0] = commandLineString.c_str();
1828 arguments = argv;
1829 argc = RTL_NUMBER_OF(argv);
1830 }
1831 }
1832 else
1833 {
1834 THROW_HR_IF(E_INVALIDARG, !shellExecOptions.IsUseShell());
1835 }
1836
1837 shellExecOptions.DefaultLogin = shellExecOptions.IsUseShell() && commandLine.empty();
1838 WI_SetFlagIf(options.LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN, shellExecOptions.IsLogin());
1839
1840 // Launch the process.
1841 return LaunchProcess(filename, argc, arguments, options);
1842 }
1843
1844 } // namespace
1845
1846 int wsl::windows::common::WslClient::Main(_In_ LPCWSTR commandLine)
1847 {
1848 wsl::windows::common::EnableContextualizedErrors(false);
1849
1850 // Note WslTraceLoggingUninitialize() is a no-op if WslTraceLoggingInitialize was not called.
1851 auto cleanupTelemetry = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslTraceLoggingUninitialize(); });
1852
1853 std::optional<wsl::windows::common::ExecutionContext> context;
1854 auto entryPoint = Entrypoint::Wsl;
1855 DWORD exitCode;
1856 HRESULT result = S_OK;
1857 try
1858 {
1859 wsl::windows::common::wslutil::ConfigureCrt();
1860 wsl::windows::common::wslutil::InitializeWil();
1861 WslTraceLoggingInitialize(LxssTelemetryProvider, !wsl::shared::OfficialBuild);
1862
1863 // Set CRT encoding.
1864 const char* encoding = getenv("WSL_UTF8");
1865 if (encoding != nullptr && strcmp(encoding, "1") == 0)
1866 {
1867 wsl::windows::common::wslutil::SetCrtEncoding(_O_U8TEXT);
1868 }
1869 else
1870 {
1871 wsl::windows::common::wslutil::SetCrtEncoding(_O_U16TEXT);
1872 }
1873
1874 // Initialize COM.
1875 auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED);
1876 wsl::windows::common::wslutil::CoInitializeSecurity();
1877
1878 auto cleanupWinrt = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { winrt::clear_factory_cache(); });
1879
1880 // Initialize winsock.
1881 WSADATA data;
1882 THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data));
1883
1884 // Determine which entrypoint to use.
1885 int argc = 0;
1886 wil::unique_hlocal_ptr<LPWSTR[]> argv{CommandLineToArgvW(commandLine, &argc)};
1887 THROW_HR_IF(E_INVALIDARG, (!argv || (argc == 0)));
1888
1889 auto fileName = std::filesystem::path(argv[0]).stem().wstring();
1890 std::transform(fileName.begin(), fileName.end(), fileName.begin(), tolower);
1891
1892 FILE* warningsFile = nullptr;
1893 const char* disableWarnings = getenv("WSL_DISABLE_WARNINGS");
1894 if (disableWarnings == nullptr || strcmp(disableWarnings, "1") != 0)
1895 {
1896 warningsFile = stderr;
1897 }
1898
1899 if (fileName == L"bash")
1900 {
1901 entryPoint = Entrypoint::Bash;
1902 context.emplace(Context::Bash, warningsFile);
1903 exitCode = BashMain(commandLine);
1904 }
1905 else if (fileName == L"wslconfig")
1906 {
1907 entryPoint = Entrypoint::Wslconfig;
1908 context.emplace(Context::WslConfig, warningsFile);
1909 exitCode = WslconfigMain(argc, argv.get());
1910 }
1911 else if (fileName == L"wslg")
1912 {
1913 entryPoint = Entrypoint::Wslg;
1914 context.emplace(Context::Wslg, warningsFile);
1915 exitCode = WslgMain(commandLine);
1916 }
1917 else
1918 {
1919 context.emplace(Context::Wsl, warningsFile);
1920 exitCode = WslMain(commandLine);
1921 }
1922 }
1923 catch (...)
1924 {
1925 // N.B. bash.exe historically has used 1 instead of -1 to indicate failure.
1926 exitCode = (entryPoint == Entrypoint::Bash) ? 1 : -1;
1927 result = wil::ResultFromCaughtException();
1928 }
1929
1930 // Print error messages for failures.
1931 if (FAILED(result))
1932 {
1933 try
1934 {
1935 std::wstring errorString{};
1936 if (context.has_value() && context->ReportedError().has_value())
1937 {
1938 auto strings = wsl::windows::common::wslutil::ErrorToString(context->ReportedError().value());
1939
1940 // Don't print the error code for WSL_E_DEFAULT_DISTRO_NOT_FOUND and WSL_E_INVALID_USAGE to make the error message easier to read.
1941 if (context->ReportedError()->Code != WSL_E_DEFAULT_DISTRO_NOT_FOUND && context->ReportedError()->Code != WSL_E_INVALID_USAGE)
1942 {
1943 errorString = Localization::MessageErrorCode(strings.Message, strings.Code);
1944 }
1945 else
1946 {
1947 errorString = strings.Message.c_str();
1948 }
1949
1950 // Logs when an error is shown to the user, and what that error is
1951 WSL_LOG_TELEMETRY(
1952 "UserVisibleError",
1953 PDT_ProductAndServicePerformance,
1954 TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
1955 TraceLoggingValue(strings.Code.c_str(), "ErrorCode"));
1956 }
1957 else
1958 {
1959 errorString = wsl::windows::common::wslutil::GetErrorString(result);
1960 }
1961
1962 // For wslg.exe, attempt to print the error message to the parent console, if that fails display a messagebox.
1963 if ((entryPoint == Entrypoint::Wslg) && (!wsl::windows::common::helpers::TryAttachConsole()))
1964 {
1965 auto caption = wsl::shared::Localization::AppName();
1966 LOG_LAST_ERROR_IF(MessageBoxW(nullptr, errorString.c_str(), caption.c_str(), (MB_OK | MB_ICONEXCLAMATION)) == 0);
1967
1968 g_promptBeforeExit = false;
1969 }
1970 else
1971 {
1972 wsl::windows::common::wslutil::PrintMessage(errorString);
1973
1974 //
1975 // If the app was launched via the start menu tile, prompt for input so the
1976 // message does not disappear.
1977 // TODO: This should be replaced with launching the WSL Settings app when that is created.
1978 //
1979
1980 if (entryPoint == Entrypoint::Wsl && winrt::Windows::ApplicationModel::AppInstance::GetActivatedEventArgs() != nullptr)
1981 {
1982 g_promptBeforeExit = true;
1983 }
1984 }
1985 }
1986 CATCH_LOG()
1987 }
1988
1989 if (g_promptBeforeExit)
1990 {
1991 g_promptBeforeExit = false;
1992 PromptForKeyPressToExit();
1993 }
1994
1995 return exitCode;
1996 }