diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index 61526d673..82f88ce95 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -205,6 +205,34 @@ Install using 'wsl.exe {} <Distro>'.
Importing distribution
+
+ Portable distribution '{}' successfully mounted from '{}'.
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Failed to mount portable distribution from '{}'.
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Portable distribution '{}' successfully unmounted.
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Distribution '{}' is not a portable distribution.
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Portable distribution '{}' created successfully at '{}'.
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Failed to create portable distribution at '{}'.
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Portable path must be on removable media: {}
Use the --allow-fixed flag to allow portable distributions on fixed drives for development/testing.
+ {FixedPlaceholder="{}"}{Locked="--allow-fixed "}Command line arguments, file names and string inserts should not be translated
+
{} has been downloaded.
{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
diff --git a/src/windows/common/CMakeLists.txt b/src/windows/common/CMakeLists.txt
index 90c50d02f..0a98418a3 100644
--- a/src/windows/common/CMakeLists.txt
+++ b/src/windows/common/CMakeLists.txt
@@ -9,6 +9,7 @@ set(SOURCES
helpers.cpp
interop.cpp
ExecutionContext.cpp
+ PortableDistribution.cpp
socket.cpp
hvsocket.cpp
Localization.cpp
@@ -69,6 +70,7 @@ set(HEADERS
HandleConsoleProgressBar.h
interop.hpp
ExecutionContext.h
+ PortableDistribution.h
socket.hpp
hvsocket.hpp
LxssMessagePort.h
diff --git a/src/windows/common/PortableDistribution.cpp b/src/windows/common/PortableDistribution.cpp
new file mode 100644
index 000000000..b67b0ddc1
--- /dev/null
+++ b/src/windows/common/PortableDistribution.cpp
@@ -0,0 +1,455 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ PortableDistribution.cpp
+
+Abstract:
+
+ This file contains the implementation of portable WSL distribution functionality.
+
+--*/
+
+#include "precomp.h"
+#include "PortableDistribution.h"
+#include "svccomm.hpp"
+#include "registry.hpp"
+#include "filesystem.hpp"
+#include "string.hpp"
+#include "helpers.hpp"
+#include
+#include
+
+namespace wsl::windows::common::portable {
+
+constexpr inline auto c_portableMetadataFileName = L"wsl-portable.json";
+constexpr inline auto c_portableRegistryValue = L"PortableBasePath";
+constexpr inline auto c_portableFlagValue = L"IsPortable";
+constexpr inline auto c_portableTemporaryValue = L"IsTemporary";
+
+bool IsRemovableDrive(_In_ const std::filesystem::path& path, _In_ bool allowFixed)
+{
+ // Get the root path of the drive
+ auto rootPath = path.root_path();
+ if (rootPath.empty())
+ {
+ // If no root, try to get the absolute path
+ std::error_code ec;
+ auto absPath = std::filesystem::absolute(path, ec);
+ if (ec)
+ {
+ return false;
+ }
+ rootPath = absPath.root_path();
+ }
+
+ // For Volume GUID paths like \\?\Volume{UUID}\, extract the volume
+ auto pathStr = rootPath.wstring();
+ if (IsVolumeGuidPath(pathStr))
+ {
+ // Extract volume GUID and check its properties
+ // Volume GUID format: \\?\Volume{GUID}\
+ auto volumeStart = pathStr.find(L"Volume{");
+ if (volumeStart != std::wstring::npos)
+ {
+ auto volumeEnd = pathStr.find(L"}", volumeStart);
+ if (volumeEnd != std::wstring::npos)
+ {
+ // Extract the volume path (including the trailing backslash)
+ volumeEnd = pathStr.find(L"\\", volumeEnd);
+ if (volumeEnd != std::wstring::npos)
+ {
+ pathStr = pathStr.substr(0, volumeEnd + 1);
+ }
+ }
+ }
+ }
+
+ const UINT driveType = GetDriveTypeW(pathStr.c_str());
+
+ // Restrict to removable media by default for security
+ // Allow fixed drives only when explicitly requested (for development/testing)
+ if (driveType == DRIVE_REMOVABLE)
+ {
+ return true;
+ }
+
+ return allowFixed && (driveType == DRIVE_FIXED);
+}
+
+bool IsVolumeGuidPath(_In_ const std::wstring& path)
+{
+ // Check if path starts with \\?\Volume{ format
+ return path.find(L"\\\\?\\Volume{") == 0 || path.find(L"\\??\\Volume{") == 0;
+}
+
+std::filesystem::path NormalizePortablePath(
+ _In_ const std::filesystem::path& basePath,
+ _In_ const std::filesystem::path& targetPath)
+{
+ std::error_code ec;
+ auto relativePath = std::filesystem::relative(targetPath, basePath, ec);
+
+ if (!ec && !relativePath.empty())
+ {
+ return relativePath;
+ }
+
+ // If we can't make it relative, return absolute path
+ return std::filesystem::absolute(targetPath, ec);
+}
+
+PortableDistributionMetadata ReadPortableMetadata(_In_ const std::filesystem::path& metadataPath)
+{
+ std::ifstream file(metadataPath);
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !file.is_open());
+
+ nlohmann::json jsonData;
+ try
+ {
+ file >> jsonData;
+ }
+ catch (const nlohmann::json::exception& e)
+ {
+ THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), "Failed to parse portable metadata: %s", e.what());
+ }
+
+ PortableDistributionMetadata metadata;
+ try
+ {
+ metadata = jsonData.get();
+ }
+ catch (const nlohmann::json::exception& e)
+ {
+ THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), "Invalid portable metadata format: %s", e.what());
+ }
+
+ return metadata;
+}
+
+void WritePortableMetadata(_In_ const std::filesystem::path& metadataPath, _In_ const PortableDistributionMetadata& metadata)
+{
+ nlohmann::json jsonData = metadata;
+
+ std::ofstream file(metadataPath);
+ THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !file.is_open());
+
+ try
+ {
+ file << jsonData.dump(4); // Pretty print with 4-space indentation
+ }
+ catch (const std::exception& e)
+ {
+ THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_WRITE_FAULT), "Failed to write portable metadata: %s", e.what());
+ }
+}
+
+PortableMountResult MountPortableDistribution(
+ _In_ const std::filesystem::path& portablePath,
+ _In_opt_ LPCWSTR distroName,
+ _In_ bool temporary,
+ _In_ bool allowFixed)
+{
+ // Validate the portable path
+ ValidatePortablePath(portablePath, allowFixed);
+
+ // Look for metadata file
+ auto metadataPath = portablePath / c_portableMetadataFileName;
+
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND),
+ !std::filesystem::exists(metadataPath),
+ "Portable metadata file not found. Expected: %ls",
+ metadataPath.c_str());
+
+ // Read metadata
+ auto metadata = ReadPortableMetadata(metadataPath);
+
+ // Use provided name or fall back to metadata name
+ std::wstring actualName = distroName ? distroName : metadata.Name;
+
+ // Find the VHDX file
+ std::filesystem::path vhdxPath;
+ if (metadata.VhdxPath.has_value())
+ {
+ vhdxPath = portablePath / metadata.VhdxPath.value();
+ }
+ else
+ {
+ // Try to find a VHDX file in the directory
+ for (const auto& entry : std::filesystem::directory_iterator(portablePath))
+ {
+ if (entry.is_regular_file() && entry.path().extension() == L".vhdx")
+ {
+ vhdxPath = entry.path();
+ break;
+ }
+ }
+ }
+
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND),
+ vhdxPath.empty() || !std::filesystem::exists(vhdxPath),
+ "VHDX file not found in portable directory");
+
+ // Register the distribution
+ wsl::windows::common::SvcComm service;
+ ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_VHD | LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE;
+
+ // Import the distribution in-place
+ GUID distroGuid;
+ try
+ {
+ distroGuid = service.ImportDistributionInplace(actualName.c_str(), vhdxPath.c_str());
+ }
+ catch (...)
+ {
+ // If import fails, try to provide helpful error message
+ THROW_HR_MSG(
+ wil::ResultFromCaughtException(),
+ "Failed to mount portable distribution from %ls",
+ vhdxPath.c_str());
+ }
+
+ // Store portable metadata in registry
+ try
+ {
+ const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
+ const auto guidString = wsl::shared::string::GuidToString(distroGuid);
+ const wil::unique_hkey distroKey = wsl::windows::common::registry::OpenKey(lxssKey.get(), guidString.c_str(), false);
+
+ // Mark as portable
+ wsl::windows::common::registry::WriteDword(distroKey.get(), nullptr, c_portableFlagValue, 1);
+
+ // Store base path for cleanup
+ wsl::windows::common::registry::WriteString(
+ distroKey.get(),
+ nullptr,
+ c_portableRegistryValue,
+ portablePath.wstring().c_str());
+
+ // Track temporary flag for potential auto-cleanup on reboot/logout
+ if (temporary)
+ {
+ wsl::windows::common::registry::WriteDword(distroKey.get(), nullptr, c_portableTemporaryValue, 1);
+ }
+ }
+ catch (...)
+ {
+ // If we can't write registry, unregister and fail
+ try
+ {
+ service.UnregisterDistribution(&distroGuid);
+ }
+ catch (...) {}
+
+ throw;
+ }
+
+ PortableMountResult result;
+ result.DistroGuid = distroGuid;
+ result.DistroName = actualName;
+ result.VhdxPath = vhdxPath;
+ result.NewlyCreated = false;
+
+ return result;
+}
+
+void UnmountPortableDistribution(_In_ const GUID& distroGuid, _In_ bool removeRegistration)
+{
+ if (!IsPortableDistribution(distroGuid))
+ {
+ return;
+ }
+
+ wsl::windows::common::SvcComm service;
+
+ // Terminate any running instances
+ try
+ {
+ service.TerminateInstance(&distroGuid);
+ }
+ catch (...) {}
+
+ // Unregister the distribution
+ if (removeRegistration)
+ {
+ try
+ {
+ service.UnregisterDistribution(&distroGuid);
+ }
+ catch (...) {}
+
+ // Clean up registry entries
+ try
+ {
+ const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
+ const auto guidString = wsl::shared::string::GuidToString(distroGuid);
+ RegDeleteKeyW(lxssKey.get(), guidString.c_str());
+ }
+ catch (...) {}
+ }
+}
+
+bool IsPortableDistribution(_In_ const GUID& distroGuid)
+{
+ try
+ {
+ const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
+ const auto guidString = wsl::shared::string::GuidToString(distroGuid);
+ const wil::unique_hkey distroKey = wsl::windows::common::registry::OpenKey(lxssKey.get(), guidString.c_str(), true);
+
+ if (!distroKey)
+ {
+ return false;
+ }
+
+ DWORD isPortable = 0;
+ if (SUCCEEDED(wsl::windows::common::registry::ReadDword(distroKey.get(), nullptr, c_portableFlagValue, isPortable)))
+ {
+ return isPortable != 0;
+ }
+ }
+ catch (...) {}
+
+ return false;
+}
+
+std::optional GetPortableBasePath(_In_ const GUID& distroGuid)
+{
+ try
+ {
+ const wil::unique_hkey lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
+ const auto guidString = wsl::shared::string::GuidToString(distroGuid);
+ const wil::unique_hkey distroKey = wsl::windows::common::registry::OpenKey(lxssKey.get(), guidString.c_str(), true);
+
+ if (!distroKey)
+ {
+ return std::nullopt;
+ }
+
+ wil::unique_cotaskmem_string pathStr;
+ if (SUCCEEDED(wsl::windows::common::registry::ReadString(distroKey.get(), nullptr, c_portableRegistryValue, pathStr)))
+ {
+ return std::filesystem::path(pathStr.get());
+ }
+ }
+ catch (...) {}
+
+ return std::nullopt;
+}
+
+void ValidatePortablePath(_In_ const std::filesystem::path& path, _In_ bool allowFixed)
+{
+ // Check if path exists
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND),
+ !std::filesystem::exists(path),
+ "Portable path does not exist: %ls",
+ path.c_str());
+
+ // Check if it's a directory
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_DIRECTORY),
+ !std::filesystem::is_directory(path),
+ "Portable path must be a directory: %ls",
+ path.c_str());
+
+ // Check if it's on removable media (restrict to removable by default)
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
+ !IsRemovableDrive(path, allowFixed),
+ "Portable path must be on removable media: %ls. To allow fixed drives, use the --allow-fixed flag.",
+ path.c_str());
+}
+
+void CreatePortableDistribution(
+ _In_ const std::filesystem::path& portablePath,
+ _In_ LPCWSTR distroName,
+ _In_ const std::filesystem::path& sourceFile,
+ _In_ ULONG version,
+ _In_ ULONG flags,
+ _In_ bool allowFixed)
+{
+ // Validate portable path
+ ValidatePortablePath(portablePath, allowFixed);
+
+ // Check if metadata already exists
+ auto metadataPath = portablePath / c_portableMetadataFileName;
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_FILE_EXISTS),
+ std::filesystem::exists(metadataPath),
+ "Portable distribution already exists at: %ls",
+ portablePath.c_str());
+
+ // Determine target VHDX path
+ std::wstring vhdxFileName = distroName;
+ vhdxFileName += L".vhdx";
+ auto vhdxPath = portablePath / vhdxFileName;
+
+ // Import the distribution using WSL service
+ // We'll register it temporarily to create the VHDX, then manually clean up the registry
+ wsl::windows::common::SvcComm service;
+
+ wil::unique_hfile sourceFileHandle;
+ sourceFileHandle.reset(CreateFileW(
+ sourceFile.c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr));
+
+ THROW_LAST_ERROR_IF(!sourceFileHandle);
+
+ // Create the VHDX in the portable location
+ // The RegisterDistribution service handles tar extraction and VHDX creation
+ auto [guid, name] = service.RegisterDistribution(
+ distroName,
+ version,
+ sourceFileHandle.get(),
+ portablePath.c_str(),
+ flags | LXSS_IMPORT_DISTRO_FLAGS_VHD);
+
+ // Clean up the registry entry without deleting the VHDX file
+ // We do this manually to avoid the full UnregisterDistribution which would delete the VHDX
+ try
+ {
+ // Open the LXSS registry key
+ auto lxssKey = wsl::windows::common::registry::OpenLxssUserKey();
+
+ // Convert GUID to string for registry key name
+ auto guidStr = wsl::windows::common::string::GuidToString(guid);
+
+ // Delete only the distribution's registry key, leaving the VHDX intact
+ wsl::windows::common::registry::DeleteKey(lxssKey.get(), guidStr.c_str());
+
+ // Note: We intentionally do NOT call UnregisterDistribution here because
+ // it would delete the VHDX file we just created. Instead, we manually remove
+ // just the registry entry, leaving the VHDX for portable use.
+ }
+ catch (...)
+ {
+ // If cleanup fails, log but continue - the VHDX was created successfully
+ // The orphaned registry entry will be cleaned up if the user tries to use
+ // the distribution and it doesn't exist
+ LOG_CAUGHT_EXCEPTION();
+ }
+
+ // Create portable metadata
+ PortableDistributionMetadata metadata;
+ metadata.Name = distroName;
+ metadata.FriendlyName = distroName;
+ metadata.VhdxPath = vhdxFileName;
+ metadata.Version = version;
+ metadata.DefaultUid = 1000; // Standard default
+ metadata.IsPortable = true;
+
+ // Write metadata
+ WritePortableMetadata(metadataPath, metadata);
+}
+
+} // namespace wsl::windows::common::portable
diff --git a/src/windows/common/PortableDistribution.h b/src/windows/common/PortableDistribution.h
new file mode 100644
index 000000000..d5c7402b2
--- /dev/null
+++ b/src/windows/common/PortableDistribution.h
@@ -0,0 +1,106 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ PortableDistribution.h
+
+Abstract:
+
+ This file contains definitions and functions for portable WSL distributions
+ that can run from removable media (USB drives, external disks, etc.).
+
+--*/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "JsonUtils.h"
+
+namespace wsl::windows::common::portable {
+
+// Metadata structure for portable distributions stored in wsl-portable.json
+struct PortableDistributionMetadata
+{
+ std::wstring Name; // Distribution name
+ std::wstring FriendlyName; // Human-readable name
+ std::optional VhdxPath; // Relative or absolute path to VHDX file
+ ULONG Version; // WSL version (1 or 2)
+ ULONG DefaultUid; // Default user ID
+ std::optional Guid; // Distribution GUID (generated on first mount)
+ std::optional DefaultUser; // Default username
+ bool IsPortable; // Flag indicating this is a portable distribution
+
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
+ PortableDistributionMetadata,
+ Name,
+ FriendlyName,
+ VhdxPath,
+ Version,
+ DefaultUid,
+ Guid,
+ DefaultUser,
+ IsPortable);
+};
+
+// Structure to hold portable mount results
+struct PortableMountResult
+{
+ GUID DistroGuid;
+ std::wstring DistroName;
+ std::filesystem::path VhdxPath;
+ bool NewlyCreated;
+};
+
+// Check if a path is on removable media
+// Set allowFixed to true to permit fixed drives for development/testing
+bool IsRemovableDrive(_In_ const std::filesystem::path& path, _In_ bool allowFixed = false);
+
+// Check if a path is using Volume GUID format (\\?\Volume{UUID}\...)
+bool IsVolumeGuidPath(_In_ const std::wstring& path);
+
+// Normalize a path for portable storage (convert to relative if possible)
+std::filesystem::path NormalizePortablePath(_In_ const std::filesystem::path& basePath, _In_ const std::filesystem::path& targetPath);
+
+// Read portable distribution metadata from wsl-portable.json
+PortableDistributionMetadata ReadPortableMetadata(_In_ const std::filesystem::path& metadataPath);
+
+// Write portable distribution metadata to wsl-portable.json
+void WritePortableMetadata(_In_ const std::filesystem::path& metadataPath, _In_ const PortableDistributionMetadata& metadata);
+
+// Mount a portable distribution from removable media
+PortableMountResult MountPortableDistribution(
+ _In_ const std::filesystem::path& portablePath,
+ _In_opt_ LPCWSTR distroName = nullptr,
+ _In_ bool temporary = false,
+ _In_ bool allowFixed = false);
+
+// Unmount and cleanup a portable distribution
+void UnmountPortableDistribution(_In_ const GUID& distroGuid, _In_ bool removeRegistration = true);
+
+// Check if a distribution is registered as portable
+bool IsPortableDistribution(_In_ const GUID& distroGuid);
+
+// Get the portable base path for a portable distribution
+std::optional GetPortableBasePath(_In_ const GUID& distroGuid);
+
+// Validate that a path is suitable for portable WSL usage
+void ValidatePortablePath(_In_ const std::filesystem::path& path, _In_ bool allowFixed = false);
+
+// Create a new portable distribution from a tar/vhdx file
+// This function temporarily registers the distribution to extract the tar and create the VHDX,
+// then manually removes only the registry entry (not the VHDX file) to make it portable.
+// The VHDX remains on disk and can be mounted later using MountPortableDistribution.
+void CreatePortableDistribution(
+ _In_ const std::filesystem::path& portablePath,
+ _In_ LPCWSTR distroName,
+ _In_ const std::filesystem::path& sourceFile,
+ _In_ ULONG version,
+ _In_ ULONG flags,
+ _In_ bool allowFixed = false);
+
+} // namespace wsl::windows::common::portable
diff --git a/src/windows/common/WslClient.cpp b/src/windows/common/WslClient.cpp
index 04d167a39..00d813af0 100644
--- a/src/windows/common/WslClient.cpp
+++ b/src/windows/common/WslClient.cpp
@@ -17,6 +17,7 @@ Module Name:
#include "HandleConsoleProgressBar.h"
#include "Distribution.h"
#include "CommandLine.h"
+#include "PortableDistribution.h"
#include
#define BASH_PATH L"/bin/bash"
@@ -128,6 +129,8 @@ void PromptForKeyPress()
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);
+int MountRemovable(_In_ std::wstring_view commandLine);
+int UnmountRemovable(_In_ std::wstring_view commandLine);
LaunchProcessOptions ParseLegacyArguments(_Inout_ std::wstring_view& commandLine);
DWORD ParseVersionString(_In_ const std::wstring_view& versionString);
int SetSparse(GUID& distroGuid, bool sparse, bool allowUnsafe);
@@ -327,12 +330,16 @@ int ImportDistribution(_In_ std::wstring_view commandLine)
std::filesystem::path filePath;
ULONG flags = LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE;
DWORD version = LXSS_WSL_VERSION_DEFAULT;
+ bool portable = false;
+ bool allowFixed = false;
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.AddArgument(portable, WSL_IMPORT_ARG_PORTABLE);
+ parser.AddArgument(allowFixed, WSL_IMPORT_ARG_ALLOW_FIXED);
parser.Parse();
@@ -341,6 +348,36 @@ int ImportDistribution(_In_ std::wstring_view commandLine)
THROW_HR(E_INVALIDARG);
}
+ // If portable flag is set, create a portable distribution
+ if (portable)
+ {
+ try
+ {
+ wsl::windows::common::portable::CreatePortableDistribution(
+ *installPath,
+ name,
+ filePath,
+ version,
+ flags,
+ allowFixed);
+
+ wsl::windows::common::wslutil::PrintMessage(
+ Localization::MessagePortableDistroCreated(name, installPath->c_str()),
+ stdout);
+
+ wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
+ return 0;
+ }
+ catch (...)
+ {
+ const auto hr = wil::ResultFromCaughtException();
+ wsl::windows::common::wslutil::PrintMessage(
+ Localization::MessagePortableDistroCreationFailed(installPath->c_str()),
+ stderr);
+ THROW_HR(hr);
+ }
+ }
+
// Ensure that the install path exists.
bool directoryCreated = true;
if (!CreateDirectoryW(installPath->c_str(), nullptr))
@@ -1260,6 +1297,79 @@ int Unmount(_In_ const std::wstring& arg)
return 0;
}
+int MountRemovable(_In_ std::wstring_view commandLine)
+{
+ std::filesystem::path portablePath;
+ std::optional distroName;
+ bool temporary = false;
+ bool allowFixed = false;
+
+ ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
+ parser.AddPositionalArgument(UnquotedPath(portablePath), 0);
+ parser.AddArgument(distroName, WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION_LONG, WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION);
+ parser.AddArgument(temporary, WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION_LONG, WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION);
+ parser.AddArgument(allowFixed, WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION_LONG, WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION);
+ parser.Parse();
+
+ THROW_HR_IF(WSL_E_INVALID_USAGE, portablePath.empty());
+
+ try
+ {
+ auto result = wsl::windows::common::portable::MountPortableDistribution(
+ portablePath,
+ distroName.has_value() ? distroName->c_str() : nullptr,
+ temporary,
+ allowFixed);
+
+ wsl::windows::common::wslutil::PrintMessage(
+ Localization::MessagePortableDistroMounted(result.DistroName.c_str(), result.VhdxPath.c_str()),
+ stdout);
+
+ wsl::windows::common::wslutil::PrintSystemError(ERROR_SUCCESS);
+ return 0;
+ }
+ catch (...)
+ {
+ const auto hr = wil::ResultFromCaughtException();
+ wsl::windows::common::wslutil::PrintMessage(
+ Localization::MessagePortableDistroMountFailed(portablePath.c_str()),
+ stderr);
+ THROW_HR(hr);
+ }
+}
+
+int UnmountRemovable(_In_ std::wstring_view commandLine)
+{
+ LPCWSTR distributionName{};
+ ArgumentParser parser(std::wstring{commandLine}, WSL_BINARY_NAME);
+ parser.AddPositionalArgument(distributionName, 0);
+ parser.Parse();
+
+ THROW_HR_IF(WSL_E_INVALID_USAGE, distributionName == nullptr);
+
+ wsl::windows::common::SvcComm service;
+ const GUID distroGuid = service.GetDistributionId(distributionName);
+
+ // Check if it's a portable distribution
+ if (!wsl::windows::common::portable::IsPortableDistribution(distroGuid))
+ {
+ wsl::windows::common::wslutil::PrintMessage(
+ Localization::MessageNotPortableDistro(distributionName),
+ stderr);
+ return -1;
+ }
+
+ // Unmount the portable distribution
+ wsl::windows::common::portable::UnmountPortableDistribution(distroGuid, true);
+
+ wsl::windows::common::wslutil::PrintMessage(
+ Localization::MessagePortableDistroUnmounted(distributionName),
+ stdout);
+
+ 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);
@@ -1668,6 +1778,16 @@ int WslMain(_In_ std::wstring_view commandLine)
commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
return ImportDistributionInplace(commandLine);
}
+ else if (argument == WSL_MOUNT_REMOVABLE_ARG)
+ {
+ commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
+ return MountRemovable(commandLine);
+ }
+ else if (argument == WSL_UNMOUNT_REMOVABLE_ARG)
+ {
+ commandLine = wsl::windows::common::helpers::ConsumeArgument(commandLine, argument);
+ return UnmountRemovable(commandLine);
+ }
else if ((argument == WSL_LIST_ARG) || (argument == WSL_LIST_ARG_LONG))
{
return ListDistributions(commandLine);
diff --git a/src/windows/inc/wsl.h b/src/windows/inc/wsl.h
index ce4b9e430..2aa74e50d 100644
--- a/src/windows/inc/wsl.h
+++ b/src/windows/inc/wsl.h
@@ -87,6 +87,16 @@ Module Name:
#define WSL_MOUNT_ARG_TYPE_OPTION L't'
#define WSL_MOUNT_ARG_OPTIONS_OPTION L'o'
#define WSL_MOUNT_ARG_PARTITION_OPTION L'p'
+#define WSL_MOUNT_REMOVABLE_ARG L"--mount-removable"
+#define WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION_LONG L"--distro"
+#define WSL_MOUNT_REMOVABLE_ARG_DISTRO_OPTION L'd'
+#define WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION_LONG L"--temporary"
+#define WSL_MOUNT_REMOVABLE_ARG_TEMPORARY_OPTION L't'
+#define WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION_LONG L"--allow-fixed"
+#define WSL_MOUNT_REMOVABLE_ARG_ALLOW_FIXED_OPTION L'f'
+#define WSL_IMPORT_ARG_PORTABLE L"--portable"
+#define WSL_IMPORT_ARG_ALLOW_FIXED L"--allow-fixed"
+#define WSL_UNMOUNT_REMOVABLE_ARG L"--unmount-removable"
#define WSL_PARENT_CONSOLE_ARG L"--parent-console"
#define WSL_SET_DEFAULT_DISTRO_ARG L"-s"
#define WSL_SET_DEFAULT_DISTRO_ARG_LEGACY L"--setdefault"