/*++ Copyright (c) Microsoft. All rights reserved. Module Name: WslClient.cpp Abstract: This file contains the logic for WSL client entry points. --*/ #include "precomp.h" #include "WslInstall.h" #include "HandleConsoleProgressBar.h" #include "Distribution.h" #include "CommandLine.h" #include #include "wslaservice.h" #include "WSLAApi.h" #include "WSLAProcessLauncher.h" #include "WslCoreFilesystem.h" #define BASH_PATH L"/bin/bash" using winrt::Windows::Foundation::Uri; using winrt::Windows::Management::Deployment::DeploymentOptions; using wsl::shared::Localization; using wsl::windows::common::ClientExecutionContext; using wsl::windows::common::Context; using wsl::windows::common::WSLAProcessLauncher; using namespace wsl::windows::common; using namespace wsl::shared; using namespace wsl::windows::common::distribution; using wsl::windows::common::ProcessFlags; static bool g_promptBeforeExit = false; namespace { enum Entrypoint { Bash, Wsl, Wslconfig, Wslg }; struct LaunchProcessOptions { std::wstring CurrentWorkingDirectory; std::optional DistroGuid; std::wstring Username; ULONG LaunchFlags = LXSS_LAUNCH_FLAG_ENABLE_INTEROP | LXSS_LAUNCH_FLAG_TRANSLATE_ENVIRONMENT; }; struct ListOptions { bool verbose; bool quiet; bool running; bool all; bool online; }; struct ShellExecOptions { std::optional UseShell; std::optional Login; bool DefaultUseShell = true; bool DefaultLogin = false; bool IsLogin() const { return Login.value_or(DefaultLogin); } bool IsUseShell() const { return UseShell.value_or(DefaultUseShell); } void SetExecMode() { UseShell = false; Login = false; } void ParseShellOptionArg(std::wstring_view Argument) { if (Argument == WSL_SHELL_OPTION_ARG_LOGIN_OPTION) { UseShell = true; Login = true; } else if (Argument == WSL_SHELL_OPTION_ARG_NOSHELL_OPTION) { SetExecMode(); } else if (Argument == WSL_SHELL_OPTION_ARG_STANDARD_OPTION) { UseShell = true; Login = false; } else { THROW_HR(E_INVALIDARG); } } }; bool IsInteractiveConsole() { const HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD mode{}; return GetFileType(stdinHandle) == FILE_TYPE_CHAR && GetConsoleMode(stdinHandle, &mode); } void PromptForKeyPress() { if (IsInteractiveConsole()) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessagePressAnyKeyToExit()); LOG_IF_WIN32_BOOL_FALSE(FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE))); _getch(); } } // Forward function declarations. bool InstallPrerequisites(_In_ bool installWslOptionalComponent); int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options); int ListDistributionsHelper(_In_ ListOptions options); LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine); DWORD ParseVersionString(_In_ const std::wstring_view& versionString); int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe); int Version(); template struct WslVersion { T& value; int operator()(LPCWSTR Input) const { if (Input == nullptr) { return -1; } value = ParseVersionString(Input); return 1; } }; // Function definitions. int BashMain(_In_ std::wstring_view commandLine) { // Call the MSI package if we're in an MSIX context if (wsl::windows::common::wslutil::IsRunningInMsix()) { return wsl::windows::common::wslutil::CallMsiPackage(); } const auto options = ParseLegacyArguments(commandLine); // If the command line is empty, construct the arguments in the following // format to launch bash as a login shell: // // filename = /bin/bash // argv[0] = -bash // // N.B. This is the same logic that login uses to launch the shell. // // For non-empty command lines, construct the arguments in the following // format: // // filename = /bin/bash // argv[0] = /bin/bash // argv[1] = -c // argv[2] = /bin/bash -c "commandLine" // // N.B. The arguments are set up this way to leave /bin/bash in charge of // all argument parsing. int argc = 1; LPCWSTR argv[3]; std::wstring arguments; LPCWSTR filename; if (commandLine.empty()) { argv[0] = L"-bash"; filename = BASH_PATH; } else { argc = RTL_NUMBER_OF(argv); arguments = BASH_PATH L" "; arguments.append(commandLine); argv[0] = BASH_PATH; argv[1] = L"-c"; argv[2] = arguments.c_str(); filename = argv[0]; } return LaunchProcess(filename, argc, argv, options); } void ChangeDirectory(_In_ std::wstring_view argument, _Inout_ LaunchProcessOptions& options) { std::wstring directory(wsl::windows::common::string::StripQuotes(argument)); THROW_HR_IF(E_INVALIDARG, directory.empty()); // There are two supported directory arguments: // 1. Any path that begins with a '/' or `~` is assumed to be a Linux path. // If the path does not exist an error is logged to /dev/kmsg. // 2. Everything else is assumed to be a valid absolute Windows path. if ((directory[0] == L'/') || (directory[0] == L'~')) { options.CurrentWorkingDirectory = std::move(directory); } else { THROW_HR_IF(E_INVALIDARG, !std::filesystem::path(directory).is_absolute()); THROW_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(directory.c_str())); } } int ExportDistribution(_In_ std::wstring_view commandLine) { ULONG flags = 0; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); std::filesystem::path filePath; LPCWSTR name{}; auto parseFormat = [&flags](LPCWSTR Value) { if (Value == nullptr) { return -1; } if (wsl::shared::string::IsEqual(L"tar.gz", Value)) { WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_GZIP); } else if (wsl::shared::string::IsEqual(L"tar.xz", Value)) { WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_XZIP); } else if (wsl::shared::string::IsEqual(L"vhd", Value)) { WI_SetFlag(flags, LXSS_EXPORT_DISTRO_FLAGS_VHD); } else if (!wsl::shared::string::IsEqual(L"tar", Value)) { THROW_HR(E_INVALIDARG); } return 1; }; parser.AddPositionalArgument(name, 0); parser.AddPositionalArgument(filePath, 1); parser.AddArgument(SetFlag(flags), WSL_EXPORT_ARG_VHD_OPTION); parser.AddArgument(parseFormat, WSL_EXPORT_ARG_FORMAT_OPTION); parser.Parse(); THROW_HR_IF( WSL_E_INVALID_USAGE, filePath.empty() || (WI_IsFlagSet(flags, LXSS_EXPORT_DISTRO_FLAGS_GZIP) && WI_IsFlagSet(flags, LXSS_EXPORT_DISTRO_FLAGS_VHD))); // Determine if the target is stdout, or an on-disk file. wil::unique_hfile file; HANDLE fileHandle; if (filePath.wstring() == WSL_EXPORT_ARG_STDOUT) { fileHandle = GetStdHandle(STD_OUTPUT_HANDLE); } else { file.reset(CreateFileW( filePath.c_str(), GENERIC_WRITE, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); THROW_LAST_ERROR_IF(!file); fileHandle = file.get(); } // Delete the target if export was unsuccessful. auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { if (file) { LOG_IF_WIN32_BOOL_FALSE(DeleteFileW(filePath.c_str())); } }); // Export the distribution. wsl::windows::common::SvcComm service; const GUID distroId = service.GetDistributionId(name); { using wsl::windows::common::HandleConsoleProgressBar; HandleConsoleProgressBar exportProgress(fileHandle, Localization::MessageExportProgress(), HandleConsoleProgressBar::Format::FileSize); THROW_IF_FAILED(service.ExportDistribution(&distroId, fileHandle, flags)); } if (file) { wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); } cleanup.release(); return 0; } int ImportDistribution(_In_ std::wstring_view commandLine) { ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); LPCWSTR name{}; std::optional installPath{}; std::filesystem::path filePath; ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE; DWORD version = LXSS_WSL_VERSION_DEFAULT; parser.AddPositionalArgument(name, 0); parser.AddPositionalArgument(AbsolutePath(installPath), 1); parser.AddPositionalArgument(filePath, 2); parser.AddArgument(WslVersion(version), WSL_IMPORT_ARG_VERSION); parser.AddArgument(SetFlag{flags}, WSL_IMPORT_ARG_VHD); parser.Parse(); if (name == nullptr || !installPath.has_value() || filePath.empty()) { THROW_HR(E_INVALIDARG); } // Ensure that the install path exists. bool directoryCreated = true; if (!CreateDirectoryW(installPath->c_str(), nullptr)) { if (GetLastError() == ERROR_ALREADY_EXISTS) { directoryCreated = false; } else { THROW_LAST_ERROR_MSG("CreateDirectoryW"); } } auto directory_cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [directoryCreated, &installPath]() { if (directoryCreated) { LOG_IF_WIN32_BOOL_FALSE(RemoveDirectory(installPath->c_str())); } }); // Determine if the source of the tar file is stdin, or an on-disk file. wil::unique_hfile file; HANDLE fileHandle; if (filePath.wstring() == WSL_IMPORT_ARG_STDIN) { fileHandle = GetStdHandle(STD_INPUT_HANDLE); } else { if (WI_IsFlagClear(flags, LXSS_IMPORT_DISTRO_FLAGS_VHD)) { // Fail if expecting a tar, but the file name has the .vhd or .vhdx extension. if (wsl::windows::common::wslutil::IsVhdFile(filePath)) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessagePassVhdFlag()); return -1; } } file.reset(CreateFileW( filePath.c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); THROW_LAST_ERROR_IF(!file); fileHandle = file.get(); } // Register the distribution. { wsl::windows::common::HandleConsoleProgressBar progressBar(fileHandle, Localization::MessageImportProgress()); wsl::windows::common::SvcComm service; service.RegisterDistribution(name, version, fileHandle, installPath->c_str(), flags); } directory_cleanup.release(); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int ImportDistributionInplace(_In_ std::wstring_view commandLine) { // Parse the command line. int argc = 0; const wil::unique_hlocal_ptr argv{CommandLineToArgvW(std::wstring(commandLine).c_str(), &argc)}; THROW_LAST_ERROR_IF(!argv); THROW_HR_IF(WSL_E_INVALID_USAGE, argc != 2); const auto name(argv[0]); const auto filePath = wsl::windows::common::filesystem::GetFullPath(argv[1]); wsl::windows::common::SvcComm service; service.ImportDistributionInplace(name, filePath.c_str()); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int LaunchElevated(_In_ LPCWSTR commandLine) { wsl::windows::common::wslutil::PrintMessage( wsl::windows::common::wslutil::GetSystemErrorString(HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED))); // Add the attach parent process argument to the command line and shell execute an elevated version of wsl.exe. std::wstring arguments; arguments += WSL_PARENT_CONSOLE_ARG L" "; arguments += std::to_wstring(GetCurrentProcessId()); arguments += L" "; arguments += commandLine; const auto path = wil::GetModuleFileNameW(wil::GetModuleInstanceHandle()); SHELLEXECUTEINFOW execInfo{}; execInfo.cbSize = sizeof(execInfo); execInfo.fMask = (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE | SEE_MASK_FLAG_NO_UI); execInfo.lpFile = path.c_str(); execInfo.lpVerb = L"runas"; execInfo.nShow = SW_HIDE; execInfo.lpParameters = arguments.c_str(); THROW_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&execInfo)); const wil::unique_handle process{execInfo.hProcess}; // Get the process exit code. WI_VERIFY(WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0); DWORD exitCode; THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(process.get(), &exitCode)); return static_cast(exitCode); } int Install(_In_ std::wstring_view commandLine) { // Parse options. std::optional distroArgument; std::optional fromFile; std::optional name; std::optional location; std::optional version; std::optional vhdSize; bool fixedVhd = false; bool installWslOptionalComponent = false; bool noLaunchAfterInstall = false; bool noDistribution = false; bool legacy = false; bool webDownload = IsWindowsServer(); ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddPositionalArgument(distroArgument, 0); parser.AddArgument(distroArgument, WSL_INSTALL_ARG_DIST_OPTION_LONG, WSL_INSTALL_ARG_DIST_OPTION); parser.AddArgument(noLaunchAfterInstall, WSL_INSTALL_ARG_NO_LAUNCH_OPTION_LONG, WSL_INSTALL_ARG_NO_LAUNCH_OPTION); parser.AddArgument(webDownload, WSL_INSTALL_ARG_WEB_DOWNLOAD_LONG); parser.AddArgument(noDistribution, WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION); parser.AddArgument(installWslOptionalComponent, WSL_INSTALL_ARG_ENABLE_WSL1_LONG); parser.AddArgument(NoOp{}, WSL_INSTALL_ARG_PRERELEASE_LONG); // Unused but handled because argument may be present when invoked from inbox. parser.AddArgument(fromFile, WSL_INSTALL_ARG_FROM_FILE_LONG, WSL_INSTALL_ARG_FROM_FILE_OPTION); parser.AddArgument(name, WSL_INSTALL_ARG_NAME_LONG); parser.AddArgument(AbsolutePath(location), WSL_INSTALL_ARG_LOCATION_LONG, WSL_INSTALL_ARG_LOCATION_OPTION); parser.AddArgument(legacy, WSL_INSTALL_ARG_LEGACY_LONG); parser.AddArgument(WslVersion(version), WSL_INSTALL_ARG_VERSION); parser.AddArgument(g_promptBeforeExit, WSL_INSTALL_ARG_PROMPT_BEFORE_EXIT_OPTION); parser.AddArgument(SizeString(vhdSize), WSL_INSTALL_ARG_VHD_SIZE); parser.AddArgument(fixedVhd, WSL_INSTALL_ARG_FIXED_VHD); parser.Parse(); if (noDistribution && distroArgument.has_value()) { THROW_HR_WITH_USER_ERROR( E_INVALIDARG, Localization::MessageArgumentsNotValidTogether(WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION, WSL_INSTALL_ARG_DIST_OPTION_LONG)); } if (fixedVhd && !vhdSize.has_value()) { THROW_HR_WITH_USER_ERROR(E_INVALIDARG, Localization::MessageArgumentNotValidWithout(WSL_INSTALL_ARG_FIXED_VHD, WSL_INSTALL_ARG_VHD_SIZE)); } // A distribution to be installed can be specified in three ways: // wsl.exe --install --distribution Ubuntu // wsl.exe --install Ubuntu // wsl.exe --install // // N.B. The legacy method (specifying --distribution) is no longer documented, // but is still supported to avoid breaking existing scripts. if (fromFile.has_value()) { if (distroArgument.has_value()) { THROW_HR_WITH_USER_ERROR( E_INVALIDARG, Localization::MessageArgumentsNotValidTogether(WSL_INSTALL_ARG_FROM_FILE_LONG, WSL_INSTALL_ARG_DIST_OPTION_LONG)); } wil::unique_hfile diskFile; HANDLE file{}; if (fromFile.value() == WSL_IMPORT_ARG_STDIN) { file = GetStdHandle(STD_INPUT_HANDLE); fromFile = L""; } else { diskFile.reset(CreateFileW( fromFile->c_str(), GENERIC_READ, (FILE_SHARE_READ | FILE_SHARE_DELETE), nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); THROW_LAST_ERROR_IF(!diskFile); file = diskFile.get(); } wsl::windows::common::wslutil::PrintMessage(Localization::MessageInstalling(fromFile->c_str())); wsl::windows::common::HandleConsoleProgressBar progressBar(file, Localization::MessageImportProgress()); SvcComm service; auto [id, installedName] = service.RegisterDistribution( name.has_value() ? name->c_str() : nullptr, version.value_or(LXSS_WSL_VERSION_DEFAULT), file, location.has_value() ? location->c_str() : nullptr, fixedVhd ? LXSS_IMPORT_DISTRO_FLAGS_FIXED_VHD : 0, vhdSize); wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installedName.get()), stdout); if (!noLaunchAfterInstall) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageLaunchingDistro(installedName.get()), stdout); LaunchProcessOptions options{}; options.DistroGuid = id; return LaunchProcess(nullptr, 0, nullptr, options); } return 0; } bool rebootRequired = InstallPrerequisites(installWslOptionalComponent); noLaunchAfterInstall |= rebootRequired; // Install a distribution only if no reboot is required, or if we're on the --legacy path (to maintain old behavior). const Distribution* legacyDistro = nullptr; WslInstall::InstallResult installResult{}; if (!noDistribution && (legacy || !rebootRequired)) { auto result = WslInstall::InstallDistribution( installResult, distroArgument, version, !noLaunchAfterInstall, webDownload, legacy, fixedVhd, name, location, vhdSize); std::optional flavor; if (installResult.Distribution.has_value()) { if (const auto* distro = std::get_if(&*installResult.Distribution)) { flavor = distro->Name; } else { legacyDistro = std::get_if(&*installResult.Distribution); WI_ASSERT(legacyDistro != nullptr); flavor = legacyDistro->Name; } } // Logs when a specific distribution is installed, and whether that was successful. Used to report distro usage to distro maintainers WSL_LOG_TELEMETRY( "InstallDistribution", PDT_ProductAndServiceUsage, TraceLoggingValue(result, "result"), TraceLoggingValue(legacyDistro == nullptr, "modern"), TraceLoggingValue(flavor.value_or(L"").c_str(), "flavor")); THROW_IF_FAILED(result); } if (rebootRequired) { wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED); } else if (noDistribution) { wsl::windows::common::wslutil::PrintSystemError(NO_ERROR); } else { if (!installResult.Alreadyinstalled) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageDistributionInstalled(installResult.Name)); } if (!noLaunchAfterInstall) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageLaunchingDistro(installResult.Name), stdout); if (legacyDistro != nullptr) { wsl::windows::common::distribution::Launch(*legacyDistro, installResult.InstalledViaGitHub, !installResult.Alreadyinstalled); } else { LaunchProcessOptions options{}; options.DistroGuid = installResult.Id.value(); return LaunchProcess(nullptr, 0, nullptr, options); } } } return 0; } bool InstallPrerequisites(_In_ bool installWslOptionalComponent) { const auto [rebootRequired, missingComponents] = WslInstall::CheckForMissingOptionalComponents(installWslOptionalComponent); if (missingComponents.empty()) { return rebootRequired; } // Install any optional components that have not yet been installed. const auto token = wil::open_current_access_token(); if (!wsl::windows::common::security::IsTokenElevated(token.get())) { const auto elevatedCommand = std::format( L"{} {} {}", WSL_INSTALL_ARG, WSL_INSTALL_ARG_NO_DISTRIBUTION_OPTION, installWslOptionalComponent ? WSL_INSTALL_ARG_ENABLE_WSL1_LONG : L""); const auto exitCode = LaunchElevated(elevatedCommand.c_str()); if (exitCode != 0) { return exitCode; } } else { WslInstall::InstallOptionalComponents(missingComponents); } return rebootRequired; } int LaunchProcess(_In_opt_ LPCWSTR filename, _In_ int argc, _In_reads_(argc) LPCWSTR argv[], _In_ const LaunchProcessOptions& options) { // Create an instance of the specified distribution. // // N.B. If creating the instance fails because the file system needs to // be upgraded, the appropriate message is displayed before // re-attempting the create while allowing the upgrade. This is // only done if running in interactive mode. const LPCGUID distribution = options.DistroGuid.has_value() ? &options.DistroGuid.value() : nullptr; wsl::windows::common::SvcComm service; if (argc == 0) { ClientExecutionContext context; const auto result = service.CreateInstanceNoThrow(distribution, 0, context.OutError()); if (FAILED(result)) { if (result == WSL_E_FS_UPGRADE_NEEDED) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageFsUpgradeNeeded(), stderr); } else { THROW_HR(result); } } } const int exitCode = service.LaunchProcess( distribution, filename, argc, argv, options.LaunchFlags, options.Username.empty() ? nullptr : options.Username.c_str(), options.CurrentWorkingDirectory.empty() ? nullptr : options.CurrentWorkingDirectory.c_str()); THROW_HR_IF(WSL_E_USER_NOT_FOUND, (exitCode == LX_INIT_USER_NOT_FOUND)); THROW_HR_IF(WSL_E_TTY_LIMIT, (exitCode == LX_INIT_TTY_LIMIT)); return exitCode; } int ListDistributions(_In_ std::wstring_view commandLine) { ListOptions options{}; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddArgument(options.all, WSL_LIST_ARG_ALL_OPTION); parser.AddArgument(options.running, WSL_LIST_ARG_RUNNING_OPTION); parser.AddArgument(options.quiet, WSL_LIST_ARG_QUIET_OPTION_LONG, WSL_LIST_ARG_QUIET_OPTION); parser.AddArgument(options.verbose, WSL_LIST_ARG_VERBOSE_OPTION_LONG, WSL_LIST_ARG_VERBOSE_OPTION); parser.AddArgument(options.online, WSL_LIST_ARG_ONLINE_OPTION_LONG, WSL_LIST_ARG_ONLINE_OPTION); parser.Parse(); return ListDistributionsHelper(options); } int ListDistributionsHelper(_In_ ListOptions options) { // Handle invalid options. THROW_HR_IF( WSL_E_INVALID_USAGE, ((options.quiet && options.verbose) || (options.all && options.running)) || ((options.verbose || options.all) && options.online)); // Query all registered distributions and sort the list so the default // (if present) is first. wsl::windows::common::SvcComm service; auto distros = service.EnumerateDistributions(); std::sort(distros.begin(), distros.end(), [](const auto& Left, const auto&) { return (WI_IsFlagSet(Left.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT)); }); if (options.verbose) { THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, distros.empty()); // Determine max length of a distro name and construct the format string. size_t maxLength = wcslen(WSL_LIST_HEADER_NAME); std::for_each(distros.begin(), distros.end(), [&](const auto& entry) { const size_t length = wcslen(entry.DistroName); if (length > maxLength) { maxLength = length; } }); std::wstring formatString(L"%s %-"); formatString += std::to_wstring(maxLength + 4); formatString += L"s%-16s%s\n"; // Print distribution information. wprintf(formatString.c_str(), L" ", WSL_LIST_HEADER_NAME, WSL_LIST_HEADER_STATE, WSL_LIST_HEADER_VERSION); std::for_each(distros.begin(), distros.end(), [&](const auto& entry) { const LPCWSTR defaultDistro = WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT) ? L"*" : L" "; const std::wstring version(std::to_wstring(entry.Version)); auto state = L"Stopped"; switch (entry.State) { case LxssDistributionStateRunning: state = L"Running"; break; case LxssDistributionStateInstalling: state = L"Installing"; break; case LxssDistributionStateUninstalling: state = L"Uninstalling"; break; case LxssDistributionStateConverting: state = L"Converting"; break; case LxssDistributionStateExporting: state = L"Exporting"; break; default: break; } wprintf(formatString.c_str(), defaultDistro, entry.DistroName, state, version.c_str()); }); } else if (!options.online) { if (options.running) { std::erase_if(distros, [&](const auto& entry) { return (entry.State != LxssDistributionStateRunning); }); if ((!options.quiet) && (distros.empty())) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageNoRunningDistro()); return -1; } } if (!options.all) { std::erase_if(distros, [&](const auto& entry) { return ( (entry.State == LxssDistributionStateInstalling) || (entry.State == LxssDistributionStateUninstalling) || (entry.State == LxssDistributionStateConverting) || (entry.State == LxssDistributionStateExporting)); }); } if (!options.quiet) { THROW_HR_IF(WSL_E_DEFAULT_DISTRO_NOT_FOUND, distros.empty()); wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageRegisteredDistrosHeader()); } std::for_each(distros.begin(), distros.end(), [&](const auto& entry) { if ((!options.quiet) && WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT)) { wsl::windows::common::wslutil::PrintMessage(Localization::MessagePrintDistroDefault(entry.DistroName), stdout); } else { wprintf(L"%s\n", entry.DistroName); } }); } else { std::vector> names; size_t maxLength = wcslen(WSL_LIST_HEADER_NAME); auto appendIfNotPresent = [&](const std::wstring& name, const std::wstring& friendlyName) { auto pred = [&name](const auto& e) { return e.first == name; }; if (std::find_if(names.begin(), names.end(), pred) == names.end()) { names.emplace_back(name, friendlyName); if (name.size() > maxLength) { maxLength = name.size(); } } }; auto readNames = [&](const DistributionList& distributions) { if (distributions.ModernDistributions.has_value()) { for (const auto& [name, versions] : *distributions.ModernDistributions) { for (auto i = 0; i < versions.size(); i++) { if (!options.all && i > 3) { break; // Only show 3 entries per distro unless --all is passed. } appendIfNotPresent(versions[i].Name, versions[i].FriendlyName); } } } if (distributions.Distributions.has_value()) { for (const auto& e : *distributions.Distributions) { appendIfNotPresent(e.Name, e.FriendlyName); } } }; const auto manifest = wsl::windows::common::distribution::GetAvailable(); if (manifest.OverrideManifest.has_value()) { readNames(*manifest.OverrideManifest); } readNames(manifest.Manifest); std::wstring formatString(L"%-"); formatString += std::to_wstring(maxLength + 4); formatString += L"s%s\n"; wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDistributionListOnline(WSL_INSTALL_ARG)); wprintf(formatString.c_str(), WSL_LIST_HEADER_NAME, WSL_LIST_HEADER_FRIENDLY_NAME); std::for_each(names.begin(), names.end(), [&](const auto& entry) { wprintf(formatString.c_str(), entry.first.c_str(), entry.second.c_str()); }); } return 0; } int Manage(_In_ std::wstring_view commandLine) { LPCWSTR distribution{}; std::optional sparse; std::optional move; std::optional defaultUser; std::optional resize; bool allowUnsafe = false; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME, 0); parser.AddPositionalArgument(distribution, 0); parser.AddArgument(ParsedBool(sparse), WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG, WSL_MANAGE_ARG_SET_SPARSE_OPTION); parser.AddArgument(AbsolutePath(move), WSL_MANAGE_ARG_MOVE_OPTION_LONG, WSL_MANAGE_ARG_MOVE_OPTION); parser.AddArgument(defaultUser, WSL_MANAGE_ARG_SET_DEFAULT_USER_OPTION_LONG); parser.AddArgument(SizeString(resize), WSL_MANAGE_ARG_RESIZE_OPTION_LONG, WSL_MANAGE_ARG_RESIZE_OPTION); parser.AddArgument(allowUnsafe, WSL_MANAGE_ARG_ALLOW_UNSAFE); parser.Parse(); THROW_HR_IF(WSL_E_INVALID_USAGE, distribution == nullptr); wsl::windows::common::SvcComm service; auto distroGuid = service.GetDistributionId(distribution); if (sparse.has_value() + move.has_value() + defaultUser.has_value() + resize.has_value() != 1) { THROW_HR(WSL_E_INVALID_USAGE); } if (sparse) { SetSparse(distroGuid, sparse.value(), allowUnsafe); } else if (move) { service.MoveDistribution(distroGuid, move->c_str()); } else if (defaultUser) { auto wslExe = wil::GetModuleFileNameW(wil::GetModuleInstanceHandle()); auto commandLine = std::format( L"\"{}\" {} -u root /usr/bin/id -u -- '{}'", wslExe, wsl::shared::string::GuidToString(distroGuid), defaultUser.value()); wsl::windows::common::SubProcess process{wslExe.c_str(), commandLine.c_str()}; auto result = process.RunAndCaptureOutput(INFINITE, GetStdHandle(STD_ERROR_HANDLE)); if (result.ExitCode != 0) { return result.ExitCode; } while (!result.Stdout.empty() && (result.Stdout.back() == '\r' || result.Stdout.back() == '\n')) { result.Stdout.pop_back(); } wchar_t* endPtr{}; auto newUid = std::wcstoul(result.Stdout.c_str(), &endPtr, 10); THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), endPtr != result.Stdout.c_str() + result.Stdout.size()); service.ConfigureDistribution(&distroGuid, newUid, LXSS_DISTRO_FLAGS_UNCHANGED); } else if (resize) { THROW_IF_FAILED(service.ResizeDistribution(&distroGuid, resize.value())); } wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int Mount(_In_ std::wstring_view commandLine) { bool vhd = false; bool bare = false; std::optional options; ULONG partition = 0; std::optional type; std::optional name; std::wstring disk; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddArgument(bare, WSL_MOUNT_ARG_BARE_OPTION_LONG); parser.AddArgument(vhd, WSL_MOUNT_ARG_VHD_OPTION_LONG); parser.AddArgument(options, WSL_MOUNT_ARG_OPTIONS_OPTION_LONG, WSL_MOUNT_ARG_OPTIONS_OPTION); parser.AddArgument(Integer(partition), WSL_MOUNT_ARG_PARTITION_OPTION_LONG, WSL_MOUNT_ARG_PARTITION_OPTION); parser.AddArgument(type, WSL_MOUNT_ARG_TYPE_OPTION_LONG, WSL_MOUNT_ARG_TYPE_OPTION); parser.AddArgument(name, WSL_MOUNT_ARG_NAME_OPTION_LONG, WSL_MOUNT_ARG_NAME_OPTION); parser.AddPositionalArgument(UnquotedPath(disk), 0); parser.Parse(); THROW_HR_IF(WSL_E_INVALID_USAGE, disk.empty()); ULONG flags = 0; if (vhd) { WI_SetFlag(flags, LXSS_ATTACH_MOUNT_FLAGS_VHD); disk = wsl::windows::common::filesystem::GetFullPath(disk.c_str()).wstring(); } else { WI_SetFlag(flags, LXSS_ATTACH_MOUNT_FLAGS_PASS_THROUGH); } // First attach the disk to the vm wsl::windows::common::SvcComm service; const auto result = service.AttachDisk(disk.c_str(), flags); if (FAILED(result)) { THROW_HR_IF(result, bare); // In the case of a non-bare mount, WSL_E_DISK_ALREADY_ATTACHED and LXSS_E_USER_VHD_ALREADY_ATTACHED are // ok to ignore because the user can mount more than one partition on the same disk // (so that disk might be already attached). THROW_HR_IF(result, result != WSL_E_DISK_ALREADY_ATTACHED && result != WSL_E_USER_VHD_ALREADY_ATTACHED); } // Perform the mount if (!bare) { const auto mountResult = service.MountDisk( disk.c_str(), flags, partition, name.has_value() ? name->c_str() : nullptr, type.has_value() ? type->c_str() : nullptr, options.has_value() ? options->c_str() : nullptr); if (mountResult.Result != 0) { wsl::windows::common::wslutil::PrintMessage( Localization::MessageDiskMountFailed(strerror(-mountResult.Result), WSL_UNMOUNT_ARG, disk), stdout); return 1; } else { wsl::windows::common::wslutil::PrintMessage( Localization::MessageDiskMounted(mountResult.MountName.get(), WSL_UNMOUNT_ARG, disk), stdout); } } else { wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); } return 0; } LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine) { // Strip the executable name. Because this has to be a legal file name, quoted parts cannot contain escaped quotes. BOOLEAN inQuotes = FALSE; while ((!commandLine.empty()) && ((inQuotes != FALSE) || (!LXSS_IS_WHITESPACE(commandLine[0])))) { if (commandLine[0] == L'"') { inQuotes = !inQuotes; } commandLine = commandLine.substr(1); } // Strip any leading whitespace. commandLine = wsl::windows::common::string::StripLeadingWhitespace(commandLine); // Check for a distribution GUID as the first parameter and strip it out if present. auto argument = wsl::windows::common::helpers::ParseArgument(commandLine); auto distroGuid = wsl::shared::string::ToGuid(argument); if (distroGuid.has_value()) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); } // Check for the home directory parameter and strip it out if present. std::wstring currentWorkingDirectory; argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument == WSL_CWD_HOME) { currentWorkingDirectory = WSL_CWD_HOME; commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); } return {std::move(currentWorkingDirectory), std::move(distroGuid)}; } DWORD ParseVersionString(_In_ const std::wstring_view& versionString) { DWORD version; const auto result = wil::ResultFromException([&]() { version = std::stoi(std::wstring(versionString)); }); THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_VERSION_PARSE_ERROR), (FAILED(result) || ((version != LXSS_WSL_VERSION_1) && (version != LXSS_WSL_VERSION_2)))); return version; } int SetDefaultDistribution(_In_ LPCWSTR distributionName) { wsl::windows::common::SvcComm service; const GUID distroGuid = service.GetDistributionId(distributionName); service.SetDefaultDistribution(&distroGuid); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int SetDefaultVersion(_In_ std::wstring_view commandLine) { const auto argument = wsl::windows::common::helpers::ParseArgument(commandLine); const auto version = ParseVersionString(argument); if (version == LXSS_WSL_VERSION_1) { THROW_HR_IF(WSL_E_WSL1_NOT_SUPPORTED, !wsl::windows::common::helpers::IsWslOptionalComponentPresent()); } else { WI_ASSERT(version == LXSS_WSL_VERSION_2); wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageVmModeConversionInfo()); } const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey(); wsl::windows::common::registry::WriteDword(lxssKey.get(), nullptr, LXSS_WSL_DEFAULT_VERSION, version); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int Shutdown(_In_ std::wstring_view commandLine) { bool force = false; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddArgument(force, WSL_SHUTDOWN_OPTION_FORCE); parser.Parse(); wsl::windows::common::SvcComm service; service.Shutdown(force); return 0; } int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe) { wsl::windows::common::SvcComm service; auto setProgress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageConversionStart()); THROW_IF_FAILED(service.SetSparse(&distroGuid, sparse, allowUnsafe)); return 0; } int SetVersion(_In_ std::wstring_view commandLine) { auto argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_SET_VERSION_ARG), stdout); return -1; } const std::wstring distributionName(argument); wsl::windows::common::SvcComm service; const auto distroGuid = service.GetDistributionId(distributionName.c_str()); commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); const auto version = ParseVersionString(argument); if (version == LXSS_WSL_VERSION_2) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageVmModeConversionInfo()); } auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageConversionStart(), true); const auto result = service.SetVersion(&distroGuid, version); progress.End(); THROW_IF_FAILED(result); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int Status() { // Print the default distro. wsl::windows::common::SvcComm service; const auto distros = service.EnumerateDistributions(); for (const auto& entry : distros) { if (WI_IsFlagSet(entry.Flags, LXSS_ENUMERATE_FLAGS_DEFAULT)) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageStatusDefaultDistro(entry.DistroName), stdout); break; } } // Print the default version. const DWORD version = wsl::windows::common::wslutil::GetDefaultVersion(); wsl::windows::common::wslutil::PrintMessage(Localization::MessageStatusDefaultVersion(version), stdout); // Print a message if the WSL optional component is not present for WSL1 support. if (!wsl::windows::common::helpers::IsWslOptionalComponentPresent()) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageWsl1NotSupported()); } // Print a message if the vmcompute service is present for WSL2 support. if (!wsl::windows::common::helpers::IsServicePresent(L"vmcompute")) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageEnableVirtualization()); } return 0; } int TerminateDistribution(_In_ LPCWSTR distributionName) { wsl::windows::common::SvcComm service; const GUID distroGuid = service.GetDistributionId(distributionName); service.TerminateInstance(&distroGuid); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int Unmount(_In_ const std::wstring& arg) { const auto* disk = arg.empty() ? nullptr : arg.c_str(); std::pair value; wsl::windows::common::SvcComm service; const HRESULT result = wil::ResultFromException([&] { value = service.DetachDisk(disk); }); // support relative paths in unmount // check is the result is the error code for "file not found" and the path is relative if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && PathIsRelative(disk)) { // retry dismounting with the absolute path const auto absoluteDisk = wsl::windows::common::filesystem::GetFullPath(filesystem::UnquotePath(disk).c_str()); value = service.DetachDisk(absoluteDisk.c_str()); } else if (FAILED(result)) { THROW_HR(result); } if (value.first != 0) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageDetachFailed(strerror(-value.first), WSL_SHUTDOWN_ARG), stdout); return -1; } wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int UnregisterDistribution(_In_ LPCWSTR distributionName) { auto progress = wsl::windows::common::ConsoleProgressIndicator(wsl::shared::Localization::MessageStatusUnregistering(), true); wsl::windows::common::SvcComm service; const GUID distroGuid = service.GetDistributionId(distributionName, LXSS_GET_DISTRO_ID_LIST_ALL); service.UnregisterDistribution(&distroGuid); progress.End(); wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS); return 0; } int UpdatePackage(std::wstring_view commandLine) { ExecutionContext context(wsl::windows::common::UpdatePackage); bool preRelease{}; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddArgument(preRelease, WSL_UPDATE_ARG_PRE_RELEASE_OPTION_LONG); // Options kept for compatibility with inbox WSL. parser.AddArgument(NoOp(), WSL_UPDATE_ARG_WEB_DOWNLOAD_OPTION_LONG); parser.AddArgument(NoOp(), WSL_UPDATE_ARG_CONFIRM_OPTION_LONG); parser.AddArgument(NoOp(), WSL_UPDATE_ARG_PROMPT_OPTION_LONG); parser.Parse(); return wsl::windows::common::wslutil::UpdatePackage(preRelease, false); } int Uninstall() { auto logFile = std::filesystem::temp_directory_path() / L"wsl-uninstall-logs.txt"; auto clearLogs = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile.c_str())); }); const auto exitCode = wsl::windows::common::wslutil::UninstallViaMsi(logFile.c_str(), &wsl::windows::common::wslutil::MsiMessageCallback); if (exitCode != 0) { clearLogs.release(); THROW_HR_WITH_USER_ERROR( HRESULT_FROM_WIN32(exitCode), wsl::shared::Localization::MessageUninstallFailed(exitCode) + L"\r\n" + wsl::shared::Localization::MessageSeeLogFile(logFile.c_str())); } return exitCode; } int Version() { // Query the Windows version. const auto windowsVersion = wsl::windows::common::helpers::GetWindowsVersionString(); wsl::windows::common::wslutil::PrintMessage( Localization::MessagePackageVersions( WSL_PACKAGE_VERSION, KERNEL_VERSION, WSLG_VERSION, MSRDC_VERSION, DIRECT3D_VERSION, DXCORE_VERSION, windowsVersion), stdout); if constexpr (!wsl::shared::OfficialBuild) { // Print additional information if running a debug build. wsl::windows::common::wslutil::PrintMessage(Localization::MessageBuildInfo(_MSC_VER, COMMIT_HASH, __TIME__ " " __DATE__), stdout); } return 0; } int WslconfigMain(_In_ int argc, _In_reads_(argc) LPWSTR* argv) { // Call the MSI package if we're in an MSIX context if (wsl::windows::common::wslutil::IsRunningInMsix()) { return wsl::windows::common::wslutil::CallMsiPackage(); } using wsl::shared::string::IsEqual; // Use exit code -1 on generic failures. This was the original exit code and shouldn't be changed, especially since wslconfig.exe is deprecated. int exitCode = -1; if ((argc >= 2) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_LIST, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_LIST_SHORT, true)))) { ListOptions options{}; for (int index = 2; index < argc; index += 1) { std::wstring_view argument = argv[index]; if (argument.empty()) { break; } if (IsEqual(argument, WSLCONFIG_COMMAND_LIST_ALL, true)) { options.all = true; } else if (IsEqual(argument, WSLCONFIG_COMMAND_LIST_RUNNING, true)) { options.running = true; } else { THROW_HR(WSL_E_INVALID_USAGE); } } exitCode = ListDistributionsHelper(options); } else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_SET_DEFAULT, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_SET_DEFAULT_SHORT, true)))) { exitCode = SetDefaultDistribution(argv[2]); } else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_TERMINATE, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_TERMINATE_SHORT, true)))) { exitCode = TerminateDistribution(argv[2]); } else if ((argc >= 3) && ((IsEqual(argv[1], WSLCONFIG_COMMAND_UNREGISTER_DISTRIBUTION, true)) || (IsEqual(argv[1], WSLCONFIG_COMMAND_UNREGISTER_DISTRIBUTION_SHORT, true)))) { exitCode = UnregisterDistribution(argv[2]); } else { THROW_HR(WSL_E_INVALID_USAGE); } return exitCode; } int WslgMain(_In_ std::wstring_view commandLine) { // N.B. There is no app execution alias for wslg, so it cannot run in an MSIX context. WI_ASSERT(!wsl::windows::common::wslutil::IsRunningInMsix()); auto options = ParseLegacyArguments(commandLine); // Parse additional arguments. std::wstring_view argument; ShellExecOptions shellExecOptions{}; wsl::windows::common::SvcComm service; for (;;) { argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { break; } if ((argument == WSL_DISTRO_ARG) || (argument == WSL_DISTRO_ARG_LONG)) { THROW_HR_IF(WSL_E_INVALID_USAGE, options.DistroGuid.has_value()); commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); THROW_HR_IF(WSL_E_INVALID_USAGE, argument.empty()); // Query the service for the distribution id. options.DistroGuid = service.GetDistributionId(std::wstring(argument).c_str()); } else if (argument == WSL_SHELL_OPTION_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); THROW_HR_IF(E_INVALIDARG, argument.empty()); shellExecOptions.ParseShellOptionArg(argument); } else if ((argument == WSL_USER_ARG) || (argument == WSL_USER_ARG_LONG)) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); THROW_HR_IF(WSL_E_INVALID_USAGE, argument.empty()); options.Username = argument; } else if (argument == WSL_CHANGE_DIRECTORY_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine, true); ChangeDirectory(argument, options); } else if (argument == WSL_STOP_PARSING_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); break; } else { THROW_HR_IF(WSL_E_INVALID_USAGE, ((argument.size() > 0) && (argument[0] == L'-'))); break; } commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); } // Launching a graphical application requires a non-empty command line. THROW_HR_IF(WSL_E_INVALID_USAGE, commandLine.empty()); std::vector arguments; const std::wstring commandLineString{commandLine}; wil::unique_hlocal_ptr execArguments{}; LPCWSTR filename{}; if (!shellExecOptions.IsUseShell()) { int argc; execArguments.reset(CommandLineToArgvW(commandLineString.c_str(), &argc)); THROW_HR_IF(E_INVALIDARG, (!execArguments || (argc == 0))); arguments.reserve(argc); arguments.insert(arguments.begin(), &execArguments.get()[0], &execArguments.get()[argc]); filename = arguments[0]; } else { arguments.push_back(commandLineString.c_str()); } // Graphical applications by default will use a login shell so that users can modify behavior. shellExecOptions.DefaultUseShell = true; shellExecOptions.DefaultLogin = shellExecOptions.IsUseShell(); if (shellExecOptions.IsLogin()) { // Launch via the user's default shell in login mode to parse files like /etc/profile. WI_SetFlag(options.LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN); } return LaunchProcess(filename, gsl::narrow_cast(arguments.size()), arguments.data(), options); } int RunDebugShell() { ExecutionContext context(Context::DebugShell); auto token = wil::open_current_access_token(); auto tokenInfo = wil::get_token_information(token.get()); auto pipePath = wsl::windows::common::wslutil::GetDebugShellPipeName(tokenInfo->User.Sid); wil::unique_hfile pipe{CreateFileW(pipePath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr)}; if (!pipe) { auto error = GetLastError(); if (error == ERROR_ACCESS_DENIED && !wsl::windows::common::security::IsTokenElevated(token.get())) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageAdministratorAccessRequiredForDebugShell()); return 1; } else if ( error == ERROR_FILE_NOT_FOUND && !wsl::windows::policies::IsFeatureAllowed(wsl::windows::policies::OpenPoliciesKey().get(), wsl::windows::policies::c_allowDebugShellUserSetting)) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDebugShellDisabled()); return 1; } else { THROW_WIN32(error); } } // agetty waits for a LF before printing the prompt, so write it immediately after the pipe is opened. // This is needed because without the '-w' flag, agetty doesn't wait and prints the shell prompt before // a pipe is connected, so it's lost. THROW_IF_WIN32_BOOL_FALSE(WriteFile(pipe.get(), "\n", 1, nullptr, nullptr)); // Create a thread to relay stdin to the pipe. auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); std::thread inputThread([&]() { wsl::windows::common::relay::StandardInputRelay(GetStdHandle(STD_INPUT_HANDLE), pipe.get(), []() {}, exitEvent.get()); }); auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { exitEvent.SetEvent(); inputThread.join(); }); // Relay the contents of the pipe to stdout. wsl::windows::common::relay::InterruptableRelay(pipe.get(), GetStdHandle(STD_OUTPUT_HANDLE)); // Print a message that the VM has exited and signal the input thread to exit. fputws(L"\n", stdout); THROW_HR(HCS_E_CONNECTION_CLOSED); } DEFINE_ENUM_FLAG_OPERATORS(WSLAFeatureFlags); // Temporary debugging tool for WSLA int WslaShell(_In_ std::wstring_view commandLine) { WSLA_SESSION_SETTINGS sessionSettings{}; sessionSettings.DisplayName = L"WSLAShell"; sessionSettings.CpuCount = 4; sessionSettings.MemoryMb = 4096; sessionSettings.NetworkingMode = WSLANetworkingModeNAT; sessionSettings.BootTimeoutMs = 30 * 1000; sessionSettings.MaximumStorageSizeMb = 4096; std::string shell = "/bin/sh"; std::string containerImage; bool help = false; std::wstring debugShell; std::wstring storagePath; std::wstring rootVhdOverride; std::string rootVhdTypeOverride; ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME); parser.AddArgument(rootVhdOverride, L"--vhd"); parser.AddArgument(Utf8String(shell), L"--shell"); parser.AddArgument( SetFlag(reinterpret_cast(sessionSettings.FeatureFlags)), L"--dns-tunneling"); parser.AddArgument(Integer(sessionSettings.MemoryMb), L"--memory"); parser.AddArgument(Integer(sessionSettings.CpuCount), L"--cpu"); parser.AddArgument(Utf8String(rootVhdTypeOverride), L"--fstype"); parser.AddArgument(storagePath, L"--storage"); parser.AddArgument(Integer(reinterpret_cast(sessionSettings.NetworkingMode)), L"--networking-mode"); parser.AddArgument(Utf8String(containerImage), L"--image"); parser.AddArgument(debugShell, L"--debug-shell"); parser.AddArgument(help, L"--help"); parser.Parse(); if (help) { const auto usage = std::format( LR"({} --wsla [--vhd ] [--shell ] [--memory ] [--cpu ] [--dns-tunneling] [--networking-mode ] [--fstype ] [--container-vhd ] [--help])", WSL_BINARY_NAME); wprintf(L"%ls\n", usage.c_str()); return 1; } switch (sessionSettings.NetworkingMode) { case WSLANetworkingMode::WSLANetworkingModeNone: case WSLANetworkingMode::WSLANetworkingModeNAT: case WSLANetworkingMode::WSLANetworkingModeVirtioProxy: break; default: THROW_HR(E_INVALIDARG); } wil::com_ptr userSession; THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLAUserSession), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&userSession))); wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); wil::com_ptr virtualMachine; wil::com_ptr session; if (!rootVhdOverride.empty()) { if (rootVhdTypeOverride.empty()) { wprintf(L"--fstype required when --vhd is passed\n"); return 1; } sessionSettings.RootVhdOverride = rootVhdOverride.c_str(); sessionSettings.RootVhdTypeOverride = rootVhdTypeOverride.c_str(); } if (!storagePath.empty()) { storagePath = std::filesystem::weakly_canonical(storagePath).wstring(); sessionSettings.StoragePath = storagePath.c_str(); } if (!debugShell.empty()) { THROW_IF_FAILED(userSession->OpenSessionByName(debugShell.c_str(), &session)); } else { THROW_IF_FAILED(userSession->CreateSession(&sessionSettings, &session)); THROW_IF_FAILED(session->GetVirtualMachine(&virtualMachine)); wsl::windows::common::security::ConfigureForCOMImpersonation(userSession.get()); } std::optional> container; std::optional process; // Get the terminal size. HANDLE Stdout = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE Stdin = GetStdHandle(STD_INPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFOEX Info{}; Info.cbSize = sizeof(Info); THROW_IF_WIN32_BOOL_FALSE(::GetConsoleScreenBufferInfoEx(Stdout, &Info)); wsl::windows::common::WSLAProcessLauncher launcher{shell, {shell}, {"TERM=xterm-256color"}, ProcessFlags::None}; launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}); launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}); launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}); launcher.SetTtySize(Info.srWindow.Bottom - Info.srWindow.Top + 1, Info.srWindow.Right - Info.srWindow.Left + 1); if (containerImage.empty()) { wsl::windows::common::WSLAProcessLauncher launcher{shell, {shell}, {"TERM=xterm-256color"}, ProcessFlags::None}; launcher.AddFd(WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}); launcher.AddFd(WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}); launcher.AddFd(WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}); process = launcher.Launch(*session); } else { std::vector fds{ WSLA_PROCESS_FD{.Fd = 0, .Type = WSLAFdTypeTerminalInput}, WSLA_PROCESS_FD{.Fd = 1, .Type = WSLAFdTypeTerminalOutput}, WSLA_PROCESS_FD{.Fd = 2, .Type = WSLAFdTypeTerminalControl}, }; WSLA_CONTAINER_OPTIONS containerOptions{}; containerOptions.Image = containerImage.c_str(); containerOptions.Name = "test-container"; containerOptions.InitProcessOptions.Fds = fds.data(); containerOptions.InitProcessOptions.FdsCount = static_cast(fds.size()); container.emplace(); THROW_IF_FAILED(session->CreateContainer(&containerOptions, &container.value())); wil::com_ptr initProcess; THROW_IF_FAILED((*container)->GetInitProcess(&initProcess)); process.emplace(std::move(initProcess), std::move(fds)); } // Configure console for interactive usage. { DWORD OutputMode{}; THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdout, &OutputMode)); WI_SetAllFlags(OutputMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN); THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(Stdout, OutputMode)); } { DWORD InputMode{}; THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdin, &InputMode)); WI_SetAllFlags(InputMode, (ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT)); WI_ClearAllFlags(InputMode, (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)); THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(Stdin, InputMode)); } THROW_LAST_ERROR_IF(!::SetConsoleOutputCP(CP_UTF8)); { // Create a thread to relay stdin to the pipe. auto exitEvent = wil::unique_event(wil::EventOptions::ManualReset); wsl::shared::SocketChannel controlChannel{ wil::unique_socket{(SOCKET)process->GetStdHandle(2).release()}, "TerminalControl", exitEvent.get()}; std::thread inputThread([&]() { auto updateTerminal = [&controlChannel, &Stdout]() { CONSOLE_SCREEN_BUFFER_INFOEX info{}; info.cbSize = sizeof(info); THROW_IF_WIN32_BOOL_FALSE(GetConsoleScreenBufferInfoEx(Stdout, &info)); WSLA_TERMINAL_CHANGED message{}; message.Columns = info.srWindow.Right - info.srWindow.Left + 1; message.Rows = info.srWindow.Bottom - info.srWindow.Top + 1; controlChannel.SendMessage(message); }; wsl::windows::common::relay::StandardInputRelay(Stdin, process->GetStdHandle(0).get(), updateTerminal, exitEvent.get()); }); auto joinThread = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { exitEvent.SetEvent(); inputThread.join(); }); // Relay the contents of the pipe to stdout. wsl::windows::common::relay::InterruptableRelay(process->GetStdHandle(1).get(), Stdout); } process->GetExitEvent().wait(); auto [code, signalled] = process->GetExitState(); wprintf(L"%hs exited with: %i%hs", shell.c_str(), code, signalled ? " (signalled)" : ""); return code; } int WslMain(_In_ std::wstring_view commandLine) { // Call the MSI package if we're in an MSIX context if (wsl::windows::common::wslutil::IsRunningInMsix()) { return wsl::windows::common::wslutil::CallMsiPackage(); } // Use exit code -1 so invokers of wsl.exe can distinguish between a Linux // process failure and a wsl.exe failure. The distro launcher sample depends // on this specific code. int exitCode = -1; // Parse the command line to determine if the legacy distro GUID or the '~' argument were specified. auto options = ParseLegacyArguments(commandLine); // Parse additional arguments. std::wstring_view argument; ShellExecOptions shellExecOptions{}; for (;;) { argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { break; } if (argument == WSL_DEBUG_SHELL_ARG_LONG) { return RunDebugShell(); } else if ((argument == WSL_DISTRO_ARG) || (argument == WSL_DISTRO_ARG_LONG)) { // Ensure the distribution has not already been set. if (options.DistroGuid.has_value()) { wsl::windows::common::wslutil::PrintMessage(wsl::shared::Localization::MessageDistroAlreadySet()); return exitCode; } commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_DISTRO_ARG_LONG), stdout); return exitCode; } // Query the service for the distribution id. wsl::windows::common::SvcComm service; options.DistroGuid = service.GetDistributionId(std::wstring(argument).c_str()); } else if (argument == WSL_CHANGE_DIRECTORY_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine, true); ChangeDirectory(argument, options); } else if (argument == WSL_DISTRIBUTION_ID_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_DISTRIBUTION_ID_ARG), stdout); return exitCode; } options.DistroGuid = wsl::shared::string::ToGuid(argument); THROW_HR_IF(E_INVALIDARG, !options.DistroGuid.has_value()); } else if ((argument == WSL_USER_ARG) || (argument == WSL_USER_ARG_LONG)) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_USER_ARG_LONG), stdout); return exitCode; } options.Username = argument; } else if (argument == WSL_UPDATE_ARG) { return UpdatePackage(commandLine); } else if (argument == WSL_HELP_ARG) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageWslUsage()); return exitCode; } else if (argument == WSL_STOP_PARSING_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); break; } else if ((argument == WSL_EXEC_ARG) || (argument == WSL_EXEC_ARG_LONG)) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); shellExecOptions.SetExecMode(); break; } else if (argument == WSL_SHELL_OPTION_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_SHELL_OPTION_ARG), stdout); return exitCode; } shellExecOptions.ParseShellOptionArg(argument); } else if (argument == WSL_EXPORT_ARG) { return ExportDistribution(commandLine); } else if (argument == WSL_IMPORT_ARG) { return ImportDistribution(commandLine); } else if (argument == WSL_IMPORT_INPLACE_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); return ImportDistributionInplace(commandLine); } else if ((argument == WSL_LIST_ARG) || (argument == WSL_LIST_ARG_LONG)) { return ListDistributions(commandLine); } else if ((argument == WSL_SET_DEFAULT_DISTRO_ARG) || (argument == WSL_SET_DEFAULT_DISTRO_ARG_LEGACY) || (argument == WSL_SET_DEFAULT_DISTRO_ARG_LONG)) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage( Localization::MessageRequiredParameterMissing(WSL_SET_DEFAULT_DISTRO_ARG_LONG), stdout); return exitCode; } return SetDefaultDistribution(std::wstring(argument).c_str()); } else if (argument == WSL_PARENT_CONSOLE_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_PARENT_CONSOLE_ARG), stdout); return exitCode; } const auto parentProcessId = std::stoi(std::wstring(argument)); FreeConsole(); THROW_IF_WIN32_BOOL_FALSE(AttachConsole(parentProcessId)); } else if ((argument == WSL_TERMINATE_ARG) || (argument == WSL_TERMINATE_ARG_LONG)) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_TERMINATE_ARG_LONG), stdout); return exitCode; } return TerminateDistribution(std::wstring(argument).c_str()); } else if (argument == WSL_UNREGISTER_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); argument = wsl::windows::common::helpers::ParseArgument(commandLine); if (argument.empty()) { wsl::windows::common::wslutil::PrintMessage(Localization::MessageRequiredParameterMissing(WSL_UNREGISTER_ARG), stdout); return exitCode; } return UnregisterDistribution(std::wstring(argument).c_str()); } else if (argument == WSL_SET_DEFAULT_VERSION_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); return SetDefaultVersion(commandLine); } else if (argument == WSL_SHUTDOWN_ARG) { return Shutdown(commandLine); } else if (argument == WSL_MANAGE_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); return Manage(commandLine); } else if (argument == WSL_SET_VERSION_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); return SetVersion(commandLine); } else if (argument == WSL_MOUNT_ARG) { return Mount(commandLine); } else if (argument == WSL_UNMOUNT_ARG) { commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); return Unmount(std::wstring(commandLine)); } else if (argument == WSL_INSTALL_ARG) { return Install(commandLine); } else if (argument == WSL_SYSTEM_DISTRO_ARG) { WI_SetFlag(options.LaunchFlags, LXSS_LAUNCH_FLAG_USE_SYSTEM_DISTRO); } else if (argument == WSL_STATUS_ARG) { return Status(); } else if ((argument == WSL_VERSION_ARG) || (argument == WSL_VERSION_ARG_LONG)) { return Version(); } else if (argument == WSL_UNINSTALL_ARG) { return Uninstall(); } else if (argument == L"--wsla") { return WslaShell(commandLine); } else { if ((argument.size() > 0) && (argument[0] == L'-')) { std::wstring InvalidArgument(argument); wsl::windows::common::wslutil::PrintMessage(Localization::MessageInvalidCommandLine(InvalidArgument, WSL_BINARY_NAME), stdout); return exitCode; } break; } commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument); } // There are three possible cases: // 1. Empty command line - Launch the default user's default shell. // 2. Exec mode - Call CommandLineToArgvW on the remaining command // line and pass it along to the create process call. // 3. Non-empty command line - The command is invoked through the // default user's default shell via '$SHELL -c commandLine'. int argc = 0; LPCWSTR* arguments{}; LPCWSTR argv[1]; std::wstring commandLineString{commandLine}; wil::unique_hlocal_ptr execArguments{}; LPCWSTR filename{}; if (!commandLine.empty()) { if (!shellExecOptions.IsUseShell()) { execArguments.reset(CommandLineToArgvW(commandLineString.c_str(), &argc)); THROW_HR_IF(E_INVALIDARG, (!execArguments || (argc == 0))); arguments = const_cast(execArguments.get()); filename = arguments[0]; } else { argv[0] = commandLineString.c_str(); arguments = argv; argc = RTL_NUMBER_OF(argv); } } else { THROW_HR_IF(E_INVALIDARG, !shellExecOptions.IsUseShell()); } shellExecOptions.DefaultLogin = shellExecOptions.IsUseShell() && commandLine.empty(); WI_SetFlagIf(options.LaunchFlags, LXSS_LAUNCH_FLAG_SHELL_LOGIN, shellExecOptions.IsLogin()); // Launch the process. return LaunchProcess(filename, argc, arguments, options); } } // namespace int wsl::windows::common::WslClient::Main(_In_ LPCWSTR commandLine) { wsl::windows::common::EnableContextualizedErrors(false); // Note WslTraceLoggingUninitialize() is a no-op if WslTraceLoggingInitialize was not called. auto cleanupTelemetry = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { WslTraceLoggingUninitialize(); }); std::optional context; auto entryPoint = Entrypoint::Wsl; DWORD exitCode; HRESULT result = S_OK; try { wsl::windows::common::wslutil::ConfigureCrt(); wsl::windows::common::wslutil::InitializeWil(); WslTraceLoggingInitialize(LxssTelemetryProvider, !wsl::shared::OfficialBuild); // Set CRT encoding. const char* encoding = getenv("WSL_UTF8"); if (encoding != nullptr && strcmp(encoding, "1") == 0) { wsl::windows::common::wslutil::SetCrtEncoding(_O_U8TEXT); } else { wsl::windows::common::wslutil::SetCrtEncoding(_O_U16TEXT); } // Initialize COM. auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED); wsl::windows::common::wslutil::CoInitializeSecurity(); auto cleanupWinrt = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { winrt::clear_factory_cache(); }); // Initialize winsock. WSADATA data; THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &data)); // Determine which entrypoint to use. int argc = 0; wil::unique_hlocal_ptr argv{CommandLineToArgvW(commandLine, &argc)}; THROW_HR_IF(E_INVALIDARG, (!argv || (argc == 0))); auto fileName = std::filesystem::path(argv[0]).stem().wstring(); std::transform(fileName.begin(), fileName.end(), fileName.begin(), tolower); FILE* warningsFile = nullptr; const char* disableWarnings = getenv("WSL_DISABLE_WARNINGS"); if (disableWarnings == nullptr || strcmp(disableWarnings, "1") != 0) { warningsFile = stderr; } if (fileName == L"bash") { entryPoint = Entrypoint::Bash; context.emplace(Context::Bash, warningsFile); exitCode = BashMain(commandLine); } else if (fileName == L"wslconfig") { entryPoint = Entrypoint::Wslconfig; context.emplace(Context::WslConfig, warningsFile); exitCode = WslconfigMain(argc, argv.get()); } else if (fileName == L"wslg") { entryPoint = Entrypoint::Wslg; context.emplace(Context::Wslg, warningsFile); exitCode = WslgMain(commandLine); } else { context.emplace(Context::Wsl, warningsFile); exitCode = WslMain(commandLine); } } catch (...) { // N.B. bash.exe historically has used 1 instead of -1 to indicate failure. exitCode = (entryPoint == Entrypoint::Bash) ? 1 : -1; result = wil::ResultFromCaughtException(); } // Print error messages for failures. if (FAILED(result)) { try { std::wstring errorString{}; if (context.has_value() && context->ReportedError().has_value()) { auto strings = wsl::windows::common::wslutil::ErrorToString(context->ReportedError().value()); // 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. if (context->ReportedError()->Code != WSL_E_DEFAULT_DISTRO_NOT_FOUND && context->ReportedError()->Code != WSL_E_INVALID_USAGE) { errorString = Localization::MessageErrorCode(strings.Message, strings.Code); } else { errorString = strings.Message.c_str(); } // Logs when an error is shown to the user, and what that error is WSL_LOG_TELEMETRY( "UserVisibleError", PDT_ProductAndServicePerformance, TraceLoggingLevel(WINEVENT_LEVEL_ERROR), TraceLoggingValue(strings.Code.c_str(), "ErrorCode")); } else { errorString = wsl::windows::common::wslutil::GetErrorString(result); } // For wslg.exe, attempt to print the error message to the parent console, if that fails display a messagebox. if ((entryPoint == Entrypoint::Wslg) && (!wsl::windows::common::helpers::TryAttachConsole())) { auto caption = wsl::shared::Localization::AppName(); LOG_LAST_ERROR_IF(MessageBoxW(nullptr, errorString.c_str(), caption.c_str(), (MB_OK | MB_ICONEXCLAMATION)) == 0); g_promptBeforeExit = false; } else { wsl::windows::common::wslutil::PrintMessage(errorString); // // If the app was launched via the start menu tile, prompt for input so the // message does not disappear. // TODO: This should be replaced with launching the WSL Settings app when that is created. // if (entryPoint == Entrypoint::Wsl && winrt::Windows::ApplicationModel::AppInstance::GetActivatedEventArgs() != nullptr) { g_promptBeforeExit = true; } } } CATCH_LOG() } if (g_promptBeforeExit) { g_promptBeforeExit = false; PromptForKeyPress(); } return exitCode; }