Skip to content
6 changes: 6 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,12 @@ On first run, creates the file with all settings commented out at their defaults
<data name="WSLCCLI_SettingsResetConfirm" xml:space="preserve">
<value>Settings reset to defaults.</value>
</data>
<data name="WSLCCLI_VersionDesc" xml:space="preserve">
<value>Show version information.</value>
</data>
<data name="WSLCCLI_VersionLongDesc" xml:space="preserve">
<value>Show version information for this tool.</value>
</data>
<data name="WSLCCLI_AllArgDescription" xml:space="preserve">
<value>Show all regardless of state.</value>
</data>
Expand Down
4 changes: 3 additions & 1 deletion src/windows/wslc/commands/RootCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Module Name:
#include "ImageCommand.h"
#include "SessionCommand.h"
#include "SettingsCommand.h"
#include "VersionCommand.h"

using namespace wsl::windows::wslc::execution;
using namespace wsl::shared;
Expand Down Expand Up @@ -47,6 +48,7 @@ std::vector<std::unique_ptr<Command>> RootCommand::GetCommands() const
commands.push_back(std::make_unique<ImageSaveCommand>(FullName()));
commands.push_back(std::make_unique<ContainerStartCommand>(FullName()));
commands.push_back(std::make_unique<ContainerStopCommand>(FullName()));
commands.push_back(std::make_unique<VersionCommand>(FullName()));
return commands;
}

Expand All @@ -71,7 +73,7 @@ void RootCommand::ExecuteInternal(CLIExecutionContext& context) const
{
if (context.Args.Contains(ArgType::Version))
{
wsl::windows::common::wslutil::PrintMessage(std::format(L"{} v{}", s_ExecutableName, WSL_PACKAGE_VERSION));
VersionCommand::PrintVersion();
return;
}

Expand Down
39 changes: 39 additions & 0 deletions src/windows/wslc/commands/VersionCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

VersionCommand.cpp

Abstract:

Implementation of the version command.

--*/
#include "VersionCommand.h"

using namespace wsl::windows::wslc::execution;

namespace wsl::windows::wslc {
std::wstring VersionCommand::ShortDescription() const
{
return Localization::WSLCCLI_VersionDesc();
}

std::wstring VersionCommand::LongDescription() const
{
return Localization::WSLCCLI_VersionLongDesc();
}

void VersionCommand::PrintVersion()
{
wsl::windows::common::wslutil::PrintMessage(std::format(L"{} {}", s_ExecutableName, WSL_PACKAGE_VERSION));
}

void VersionCommand::ExecuteInternal(CLIExecutionContext& context) const
{
UNREFERENCED_PARAMETER(context);
PrintVersion();
}
Comment on lines +34 to +38
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version output formatting is duplicated here and in RootCommand’s --version handling (PrintMessage(std::format(L"{} v{}", ...))). To avoid the two paths drifting over time, consider centralizing the version-printing logic (shared helper or utility) and reuse it from both the flag and the subcommand.

Copilot uses AI. Check for mistakes.
} // namespace wsl::windows::wslc
31 changes: 31 additions & 0 deletions src/windows/wslc/commands/VersionCommand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

VersionCommand.h

Abstract:

Declaration of the VersionCommand.

--*/
#pragma once
#include "Command.h"

namespace wsl::windows::wslc {
struct VersionCommand final : public Command
{
constexpr static std::wstring_view CommandName = L"version";
VersionCommand(const std::wstring& parent) : Command(CommandName, parent)
{
}
static void PrintVersion();
std::wstring ShortDescription() const override;
std::wstring LongDescription() const override;

protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};
} // namespace wsl::windows::wslc
4 changes: 4 additions & 0 deletions test/windows/wslc/CommandLineTestCases.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ COMMAND_LINE_TEST_CASE(L"image list -q", L"list", true)
COMMAND_LINE_TEST_CASE(L"image pull ubuntu", L"pull", true)
COMMAND_LINE_TEST_CASE(L"pull ubuntu", L"pull", true)

// Version command tests
COMMAND_LINE_TEST_CASE(L"version", L"version", true)
COMMAND_LINE_TEST_CASE(L"version --help", L"version", true)
COMMAND_LINE_TEST_CASE(L"version extraarg", L"version", false)
// Settings command
COMMAND_LINE_TEST_CASE(L"settings", L"settings", true)
COMMAND_LINE_TEST_CASE(L"settings reset", L"reset", true)
Expand Down
43 changes: 43 additions & 0 deletions test/windows/wslc/WSLCCLICommandUnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Module Name:
#include "RootCommand.h"
#include "ContainerCommand.h"
#include "SessionCommand.h"
#include "VersionCommand.h"

using namespace wsl::windows::wslc;
using namespace WSLCTestHelpers;
Expand Down Expand Up @@ -95,6 +96,48 @@ class WSLCCLICommandUnitTests
VERIFY_IS_NOT_NULL(subcmd.get());
}
}

// Test: Verify VersionCommand has the correct name
TEST_METHOD(VersionCommand_HasCorrectName)
{
auto cmd = VersionCommand(L"wslc");
VERIFY_ARE_EQUAL(std::wstring_view(L"version"), cmd.Name());
}

// Test: Verify VersionCommand has no subcommands
TEST_METHOD(VersionCommand_HasNoSubcommands)
{
auto cmd = VersionCommand(L"wslc");
VERIFY_ARE_EQUAL(0u, cmd.GetCommands().size());
}

// Test: Verify VersionCommand has no arguments (only the auto-added --help)
TEST_METHOD(VersionCommand_HasNoArguments)
{
auto cmd = VersionCommand(L"wslc");
VERIFY_ARE_EQUAL(0u, cmd.GetArguments().size());
// Test out that auto added help command is the only one
VERIFY_ARE_EQUAL(1u, cmd.GetAllArguments().size());
}

// Test: Verify RootCommand contains VersionCommand as a subcommand
TEST_METHOD(RootCommand_ContainsVersionCommand)
{
auto root = RootCommand();
auto subcommands = root.GetCommands();

bool found = false;
for (const auto& subcmd : subcommands)
{
if (subcmd->Name() == VersionCommand::CommandName)
{
found = true;
break;
}
}

VERIFY_IS_TRUE(found, L"RootCommand should contain VersionCommand");
}
};

} // namespace WSLCCLICommandUnitTests
11 changes: 11 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class WSLCE2EGlobalTests
RunWslcAndVerify(L"INVALID_CMD", {.Stdout = GetHelpMessage(), .Stderr = L"Unrecognized command: 'INVALID_CMD'\r\n", .ExitCode = 1});
}

TEST_METHOD(WSLCE2E_VersionCommand)
{
WSL2_TEST_ONLY();
RunWslcAndVerify(L"version", {.Stdout = GetVersionMessage(), .Stderr = L"", .ExitCode = 0});
}
Comment on lines +54 to +58
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WSLCE2E_HelpCommand and invalid-command tests build expected help output via GetAvailableCommands(), but adding VersionCommand to RootCommand means the real --help output will now include a version entry. Without updating the expected help message builder, the existing help-related E2E tests will fail due to output mismatch (even if wslc version itself works).

Copilot uses AI. Check for mistakes.
TEST_METHOD(WSLCE2E_Session_DefaultElevated)
{
WSL2_TEST_ONLY();
Expand Down Expand Up @@ -400,6 +405,11 @@ class WSLCE2EGlobalTests
return output.str();
}

std::wstring GetVersionMessage() const
{
return std::format(L"wslc {}\r\n", WSL_PACKAGE_VERSION);
}

std::wstring GetDescription() const
{
return L"WSLC is the Windows Subsystem for Linux Container CLI tool. It enables management and interaction with WSL "
Expand Down Expand Up @@ -435,6 +445,7 @@ class WSLCE2EGlobalTests
{L"save", Localization::WSLCCLI_ImageSaveDesc()},
{L"start", Localization::WSLCCLI_ContainerStartDesc()},
{L"stop", Localization::WSLCCLI_ContainerStopDesc()},
{L"version", Localization::WSLCCLI_VersionDesc()},
};

size_t maxLen = 0;
Expand Down
Loading