From cde1a383f0e2a704e93050792a7dab67bd7d7d51 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 24 Apr 2026 10:05:40 -0700
Subject: [PATCH 1/2] Init container prune
---
localization/strings/en-US/Resources.resw | 14 ++
.../wslc/commands/ContainerCommand.cpp | 1 +
src/windows/wslc/commands/ContainerCommand.h | 15 ++
.../wslc/commands/ContainerPruneCommand.cpp | 50 +++++
src/windows/wslc/services/ContainerModel.h | 6 +
.../wslc/services/ContainerService.cpp | 15 ++
src/windows/wslc/services/ContainerService.h | 1 +
src/windows/wslc/tasks/ContainerTasks.cpp | 17 ++
src/windows/wslc/tasks/ContainerTasks.h | 1 +
.../wslc/e2e/WSLCE2EContainerPruneTests.cpp | 184 ++++++++++++++++++
.../wslc/e2e/WSLCE2EContainerTests.cpp | 1 +
11 files changed, 305 insertions(+)
create mode 100644 src/windows/wslc/commands/ContainerPruneCommand.cpp
create mode 100644 test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp
diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index de258d29d..7a3109d9d 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -2242,6 +2242,20 @@ For privacy information about this product please visit https://aka.ms/privacy.<
View logs for a container.
+
+ Deleted: {}
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+
+ Remove stopped containers.
+
+
+ Removes all stopped containers.
+
+
+ Total reclaimed space: {:.2f} MB
+ {FixedPlaceholder="{:.2f}"}Command line arguments, file names and string inserts should not be translated
+
Remove containers.
diff --git a/src/windows/wslc/commands/ContainerCommand.cpp b/src/windows/wslc/commands/ContainerCommand.cpp
index c8877950a..c9f42a8ba 100644
--- a/src/windows/wslc/commands/ContainerCommand.cpp
+++ b/src/windows/wslc/commands/ContainerCommand.cpp
@@ -29,6 +29,7 @@ std::vector> ContainerCommand::GetCommands() const
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
diff --git a/src/windows/wslc/commands/ContainerCommand.h b/src/windows/wslc/commands/ContainerCommand.h
index 503618a6d..b92b001b0 100644
--- a/src/windows/wslc/commands/ContainerCommand.h
+++ b/src/windows/wslc/commands/ContainerCommand.h
@@ -194,6 +194,21 @@ struct ContainerStopCommand final : public Command
std::wstring ShortDescription() const override;
std::wstring LongDescription() const override;
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
+// Prune Command
+struct ContainerPruneCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"prune";
+ ContainerPruneCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};
diff --git a/src/windows/wslc/commands/ContainerPruneCommand.cpp b/src/windows/wslc/commands/ContainerPruneCommand.cpp
new file mode 100644
index 000000000..e9a550b5a
--- /dev/null
+++ b/src/windows/wslc/commands/ContainerPruneCommand.cpp
@@ -0,0 +1,50 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ ContainerPruneCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "ContainerCommand.h"
+#include "CLIExecutionContext.h"
+#include "ContainerTasks.h"
+#include "SessionTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Container Prune Command
+std::vector ContainerPruneCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::Session),
+ };
+}
+
+std::wstring ContainerPruneCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_ContainerPruneDesc();
+}
+
+std::wstring ContainerPruneCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_ContainerPruneLongDesc();
+}
+
+void ContainerPruneCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context //
+ << CreateSession //
+ << PruneContainers;
+}
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h
index 5997a2736..a0690f2e7 100644
--- a/src/windows/wslc/services/ContainerModel.h
+++ b/src/windows/wslc/services/ContainerModel.h
@@ -270,6 +270,12 @@ struct VolumeMount
}
};
+struct PruneContainersResult
+{
+ std::vector DeletedContainers;
+ ULONGLONG SpaceReclaimed{};
+};
+
struct TmpfsMount
{
std::string ContainerPath() const
diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp
index 90af89e87..6dbeb06dc 100644
--- a/src/windows/wslc/services/ContainerService.cpp
+++ b/src/windows/wslc/services/ContainerService.cpp
@@ -492,4 +492,19 @@ void ContainerService::Logs(Session& session, const std::string& id, bool follow
// TODO: Handle ctrl-c.
io.Run({});
}
+
+models::PruneContainersResult ContainerService::Prune(models::Session& session)
+{
+ WSLCPruneContainersResults results{};
+ THROW_IF_FAILED(session.Get()->PruneContainers(nullptr, 0, 0, &results));
+
+ models::PruneContainersResult result;
+ result.SpaceReclaimed = results.SpaceReclaimed;
+ for (ULONG i = 0; i < results.ContainersCount; ++i)
+ {
+ result.DeletedContainers.push_back(results.Containers[i]);
+ }
+
+ return result;
+}
} // namespace wsl::windows::wslc::services
diff --git a/src/windows/wslc/services/ContainerService.h b/src/windows/wslc/services/ContainerService.h
index aeddaff9f..31eda5e1d 100644
--- a/src/windows/wslc/services/ContainerService.h
+++ b/src/windows/wslc/services/ContainerService.h
@@ -33,5 +33,6 @@ struct ContainerService
static int Exec(models::Session& session, const std::string& id, models::ContainerOptions options);
static wsl::windows::common::wslc_schema::InspectContainer Inspect(models::Session& session, const std::string& id);
static void Logs(models::Session& session, const std::string& id, bool follow);
+ static models::PruneContainersResult Prune(models::Session& session);
};
} // namespace wsl::windows::wslc::services
diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp
index 804f54282..b95995702 100644
--- a/src/windows/wslc/tasks/ContainerTasks.cpp
+++ b/src/windows/wslc/tasks/ContainerTasks.cpp
@@ -17,6 +17,7 @@ Module Name:
#include "ContainerModel.h"
#include "ContainerService.h"
#include "ContainerTasks.h"
+#include "ImageModel.h"
#include "SessionModel.h"
#include "SessionService.h"
#include "TableOutput.h"
@@ -394,4 +395,20 @@ void ViewContainerLogs(CLIExecutionContext& context)
bool follow = context.Args.Contains(ArgType::Follow);
ContainerService::Logs(session, WideToMultiByte(containerId), follow);
}
+
+void PruneContainers(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+
+ auto result = ContainerService::Prune(session);
+
+ for (const auto& container : result.DeletedContainers)
+ {
+ PrintMessage(Localization::WSLCCLI_ContainerPruneDeleted(container));
+ }
+
+ PrintMessage(L"");
+ PrintMessage(Localization::WSLCCLI_ContainerPruneSpaceReclaimed(static_cast(result.SpaceReclaimed) / WSLC_IMAGE_1MB));
+}
} // namespace wsl::windows::wslc::task
diff --git a/src/windows/wslc/tasks/ContainerTasks.h b/src/windows/wslc/tasks/ContainerTasks.h
index 0554a9589..ba392d5c1 100644
--- a/src/windows/wslc/tasks/ContainerTasks.h
+++ b/src/windows/wslc/tasks/ContainerTasks.h
@@ -42,4 +42,5 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context);
void StartContainer(CLIExecutionContext& context);
void StopContainers(CLIExecutionContext& context);
void ViewContainerLogs(CLIExecutionContext& context);
+void PruneContainers(CLIExecutionContext& context);
} // namespace wsl::windows::wslc::task
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp
new file mode 100644
index 000000000..427d54296
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp
@@ -0,0 +1,184 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EContainerPruneTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC container prune command.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2EContainerPruneTests
+{
+ WSLC_TEST_CLASS(WSLCE2EContainerPruneTests)
+
+ static constexpr auto TestContainerName1 = L"prune-test-1";
+ static constexpr auto TestContainerName2 = L"prune-test-2";
+ static constexpr auto TestContainerRunning = L"prune-test-running";
+
+ TEST_CLASS_SETUP(ClassSetup)
+ {
+ EnsureImageIsLoaded(DebianImage);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureContainerDoesNotExist(TestContainerName1);
+ EnsureContainerDoesNotExist(TestContainerName2);
+ EnsureContainerDoesNotExist(TestContainerRunning);
+ return true;
+ }
+
+ TEST_METHOD_SETUP(MethodSetup)
+ {
+ EnsureContainerDoesNotExist(TestContainerName1);
+ EnsureContainerDoesNotExist(TestContainerName2);
+ EnsureContainerDoesNotExist(TestContainerRunning);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Prune_HelpCommand)
+ {
+ const auto result = RunWslc(L"container prune --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Prune_NoStoppedContainers)
+ {
+ const auto result = RunWslc(L"container prune");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyStdoutContains(result, L"Total reclaimed space:");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Prune_StoppedContainer)
+ {
+ // Create a stopped container
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()))
+ .Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyContainerIsListed(TestContainerName1, L"created");
+
+ const auto result = RunWslc(L"container prune");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyStdoutContains(result, L"Deleted:");
+ VerifyStdoutContains(result, L"Total reclaimed space:");
+
+ // Verify the container was actually removed
+ VerifyContainerIsNotListed(TestContainerName1);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Prune_MultipleStoppedContainers)
+ {
+ // Create two stopped containers
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()))
+ .Verify({.Stderr = L"", .ExitCode = 0});
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName2, DebianImage.NameAndTag()))
+ .Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyContainerIsListed(TestContainerName1, L"created");
+ VerifyContainerIsListed(TestContainerName2, L"created");
+
+ const auto result = RunWslc(L"container prune");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyStdoutContains(result, L"Total reclaimed space:");
+
+ // Verify both containers were removed
+ VerifyContainerIsNotListed(TestContainerName1);
+ VerifyContainerIsNotListed(TestContainerName2);
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Prune_RunningContainerNotPruned)
+ {
+ // Start a running container
+ RunWslc(std::format(L"container run -d --name {} {} sleep infinity", TestContainerRunning, DebianImage.NameAndTag()))
+ .Verify({.Stderr = L"", .ExitCode = 0});
+
+ auto cleanup = wil::scope_exit([&]() { EnsureContainerDoesNotExist(TestContainerRunning); });
+
+ VerifyContainerIsListed(TestContainerRunning, L"running");
+
+ const auto result = RunWslc(L"container prune");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ // Running container should still exist after prune
+ VerifyContainerIsListed(TestContainerRunning, L"running");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Prune_IdempotentSecondPrune)
+ {
+ // Create and prune a container, then prune again
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()))
+ .Verify({.Stderr = L"", .ExitCode = 0});
+
+ RunWslc(L"container prune").Verify({.Stderr = L"", .ExitCode = 0});
+ VerifyContainerIsNotListed(TestContainerName1);
+
+ // Second prune should succeed with nothing to prune
+ const auto result = RunWslc(L"container prune");
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ VerifyStdoutContains(result, L"Total reclaimed space:");
+ }
+
+private:
+ const TestImage& DebianImage = DebianTestImage();
+
+ static void VerifyStdoutContains(const WSLCExecutionResult& result, const std::wstring& substring)
+ {
+ for (const auto& line : result.GetStdoutLines())
+ {
+ if (line.find(substring) != std::wstring::npos)
+ {
+ return;
+ }
+ }
+
+ VERIFY_FAIL(std::format(L"Expected stdout to contain '{}'", substring).c_str());
+ }
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_ContainerPruneLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc container prune []\r\n\r\n";
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n"
+ << L" --session " << Localization::WSLCCLI_SessionIdArgDescription() << L"\r\n"
+ << L" -?,--help " << Localization::WSLCCLI_HelpArgDescription() << L"\r\n"
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp
index 73ee77a90..24f11b7fb 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp
@@ -76,6 +76,7 @@ class WSLCE2EContainerTests
{L"kill", Localization::WSLCCLI_ContainerKillDesc()},
{L"logs", Localization::WSLCCLI_ContainerLogsDesc()},
{L"list", Localization::WSLCCLI_ContainerListDesc()},
+ {L"prune", Localization::WSLCCLI_ContainerPruneDesc()},
{L"remove", Localization::WSLCCLI_ContainerRemoveDesc()},
{L"run", Localization::WSLCCLI_ContainerRunDesc()},
{L"start", Localization::WSLCCLI_ContainerStartDesc()},
From 6454b119e93d5832941cf625a107a647846c3061 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 24 Apr 2026 15:45:35 -0700
Subject: [PATCH 2/2] Clang format
---
test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp
index 427d54296..4bfdf7660 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerPruneTests.cpp
@@ -66,8 +66,7 @@ class WSLCE2EContainerPruneTests
WSLC_TEST_METHOD(WSLCE2E_Container_Prune_StoppedContainer)
{
// Create a stopped container
- RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()))
- .Verify({.Stderr = L"", .ExitCode = 0});
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
VerifyContainerIsListed(TestContainerName1, L"created");
@@ -84,10 +83,8 @@ class WSLCE2EContainerPruneTests
WSLC_TEST_METHOD(WSLCE2E_Container_Prune_MultipleStoppedContainers)
{
// Create two stopped containers
- RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()))
- .Verify({.Stderr = L"", .ExitCode = 0});
- RunWslc(std::format(L"container create --name {} {}", TestContainerName2, DebianImage.NameAndTag()))
- .Verify({.Stderr = L"", .ExitCode = 0});
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName2, DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
VerifyContainerIsListed(TestContainerName1, L"created");
VerifyContainerIsListed(TestContainerName2, L"created");
@@ -122,8 +119,7 @@ class WSLCE2EContainerPruneTests
WSLC_TEST_METHOD(WSLCE2E_Container_Prune_IdempotentSecondPrune)
{
// Create and prune a container, then prune again
- RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag()))
- .Verify({.Stderr = L"", .ExitCode = 0});
+ RunWslc(std::format(L"container create --name {} {}", TestContainerName1, DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
RunWslc(L"container prune").Verify({.Stderr = L"", .ExitCode = 0});
VerifyContainerIsNotListed(TestContainerName1);