@samitouri / QOSAMI-WSL / commits / ac739014

Add wslc system info command (#41408)

beena352 committed Sep 3, 2026 at 08:04 UTC ac739014fd826138ba09405dd0c7b5c8b4a8ae9b
12 files changed +398 -11
localization/strings/en-US/Resources.resw
+33
@@ -2901,6 +2901,39 @@ Example: tar -cf - files | wslc container cp - CONTAINER:/path</value>
2901 <data name="WSLCCLI_SystemCommandLongDesc" xml:space="preserve">
2902 <value>System-level management commands.</value>
2903 </data>
2904 + <data name="WSLCCLI_SystemInfoDesc" xml:space="preserve">
2905 + <value>Display system information.</value>
2906 + </data>
2907 + <data name="WSLCCLI_SystemInfoLongDesc" xml:space="preserve">
2908 + <value>Displays version information for the wslc client and the session manager service, along with the list of active sessions.</value>
2909 + <comment>{Locked="wslc"}Command line arguments should not be translated</comment>
2910 + </data>
2911 + <data name="WSLCCLI_SystemInfoClientHeader" xml:space="preserve">
2912 + <value>Client:</value>
2913 + </data>
2914 + <data name="WSLCCLI_SystemInfoVersions" xml:space="preserve">
2915 + <value>WSL version: {}
2916 +Kernel version: {}
2917 +Direct3D version: {}
2918 +DXCore version: {}
2919 +Windows version: {}</value>
2920 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2921 + </data>
2922 + <data name="WSLCCLI_SystemInfoServerHeader" xml:space="preserve">
2923 + <value>Server:</value>
2924 + </data>
2925 + <data name="WSLCCLI_SystemInfoSettingsFile" xml:space="preserve">
2926 + <value>Settings file: {}</value>
2927 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2928 + </data>
2929 + <data name="WSLCCLI_SystemInfoSessionManagerVersion" xml:space="preserve">
2930 + <value>Session manager version: {}</value>
2931 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2932 + </data>
2933 + <data name="WSLCCLI_SystemInfoSessions" xml:space="preserve">
2934 + <value>Sessions: {}</value>
2935 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2936 + </data>
2937 <data name="WSLCCLI_ImageTagDesc" xml:space="preserve">
2938 <value>Tag an image.</value>
2939 </data>
src/windows/wslc/commands/RootCommand.cpp
+1
@@ -45,6 +45,7 @@ std::vector<std::unique_ptr<Command>> RootCommand::GetCommands() const
45 commands.push_back(std::make_unique<ContainerExportCommand>(FullName()));
46 commands.push_back(std::make_unique<ImageListCommand>(FullName(), true));
47 commands.push_back(std::make_unique<ImageImportCommand>(FullName()));
48 + commands.push_back(std::make_unique<SystemInfoCommand>(FullName()));
49 commands.push_back(std::make_unique<InspectCommand>(FullName()));
50 commands.push_back(std::make_unique<ContainerKillCommand>(FullName()));
51 commands.push_back(std::make_unique<ContainerListCommand>(FullName()));
src/windows/wslc/commands/SystemCommand.cpp
+2 -1
@@ -12,8 +12,8 @@ Abstract:
12
13 --*/
14 #include "CLIExecutionContext.h"
15 -#include "SystemCommand.h"
15 #include "SessionCommand.h"
16 +#include "SystemCommand.h"
17
18 using namespace wsl::windows::wslc::execution;
19 using namespace wsl::shared;
@@ -22,6 +22,7 @@ namespace wsl::windows::wslc {
22 std::vector<std::unique_ptr<Command>> SystemCommand::GetCommands() const
23 {
24 std::vector<std::unique_ptr<Command>> commands;
25 + commands.push_back(std::make_unique<SystemInfoCommand>(FullName()));
26 commands.push_back(std::make_unique<SessionCommand>(FullName()));
27 return commands;
28 }
src/windows/wslc/commands/SystemCommand.h
+15
@@ -28,6 +28,21 @@ struct SystemCommand final : public Command
28
29 std::vector<std::unique_ptr<Command>> GetCommands() const override;
30
31 +protected:
32 + void ExecuteInternal(CLIExecutionContext& context) const override;
33 +};
34 +
35 +// System Info Command
36 +struct SystemInfoCommand final : public Command
37 +{
38 + constexpr static std::wstring_view CommandName = L"info";
39 + SystemInfoCommand(const std::wstring& parent) : Command(CommandName, parent)
40 + {
41 + }
42 + std::vector<Argument> GetArguments() const override;
43 + std::wstring ShortDescription() const override;
44 + std::wstring LongDescription() const override;
45 +
46 protected:
47 void ExecuteInternal(CLIExecutionContext& context) const override;
48 };
src/windows/wslc/commands/SystemInfoCommand.cpp new
+45
@@ -0,0 +1,45 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + SystemInfoCommand.cpp
8 +
9 +Abstract:
10 +
11 + Implementation of the system info command.
12 +
13 +--*/
14 +#include "CLIExecutionContext.h"
15 +#include "SessionTasks.h"
16 +#include "SystemCommand.h"
17 +#include "Task.h"
18 +
19 +using namespace wsl::windows::wslc::execution;
20 +using namespace wsl::windows::wslc::task;
21 +using namespace wsl::shared;
22 +
23 +namespace wsl::windows::wslc {
24 +std::vector<Argument> SystemInfoCommand::GetArguments() const
25 +{
26 + return {
27 + Argument::Create(ArgType::Format),
28 + };
29 +}
30 +
31 +std::wstring SystemInfoCommand::ShortDescription() const
32 +{
33 + return Localization::WSLCCLI_SystemInfoDesc();
34 +}
35 +
36 +std::wstring SystemInfoCommand::LongDescription() const
37 +{
38 + return Localization::WSLCCLI_SystemInfoLongDesc();
39 +}
40 +
41 +void SystemInfoCommand::ExecuteInternal(CLIExecutionContext& context) const
42 +{
43 + context << ShowSystemInfo;
44 +}
45 +} // namespace wsl::windows::wslc
src/windows/wslc/services/SessionService.cpp
+8
@@ -143,6 +143,14 @@ int SessionService::Enter(Terminal& terminal, const std::wstring& storagePath, c
143 return ConsoleService::AttachToCurrentConsole(terminal, console, launcher.Launch(*session.get()));
144 }
145
146 +WSLCVersion SessionService::ManagerVersion()
147 +{
148 + WSLCVersion version{};
149 + THROW_IF_FAILED(CreateSessionManager()->GetVersion(&version));
150 +
151 + return version;
152 +}
153 +
154 std::vector<SessionInformation> SessionService::List()
155 {
156 std::vector<SessionInformation> result;
src/windows/wslc/services/SessionService.h
+1
@@ -30,6 +30,7 @@ struct SessionService
30 static int Attach(Terminal& terminal, const wsl::windows::wslc::models::Session& session);
31 static int Enter(Terminal& terminal, const std::wstring& storagePath, const std::wstring& displayName);
32 static std::vector<SessionInformation> List();
33 + static WSLCVersion ManagerVersion();
34 // Opens an existing session by name. Throws if not found.
35 static wsl::windows::wslc::models::Session OpenSession(const std::wstring& name);
36 // Opens the default session. Throws WSLC_E_SESSION_NOT_FOUND if no default session exists.
src/windows/wslc/tasks/SessionTasks.cpp
+98 -10
@@ -14,19 +14,40 @@ Abstract:
14 #include "Argument.h"
15 #include "ArgumentConvertedTypes.h"
16 #include "CLIExecutionContext.h"
17 +#include "JsonUtils.h"
18 #include "SessionService.h"
19 #include "SessionTasks.h"
20 #include "TableOutput.h"
21 #include "Task.h"
22 +#include "WSLCUserSettings.h"
23
24 using namespace wsl::shared;
25 using namespace wsl::windows::common::string;
26 using namespace wsl::windows::common::wslutil;
27 using namespace wsl::windows::wslc::execution;
28 +using namespace wsl::windows::wslc::models;
29 using namespace wsl::windows::wslc::services;
30
31 namespace wsl::windows::wslc::task {
32
33 +static void WriteSessionTable(Terminal& terminal, const std::vector<SessionInformation>& sessions)
34 +{
35 + TableOutput<3> table(
36 + terminal,
37 + {Localization::MessageWslcHeaderId(), Localization::MessageWslcHeaderCreatorPid(), Localization::MessageWslcHeaderDisplayName()});
38 +
39 + for (const auto& session : sessions)
40 + {
41 + table.WriteRow({
42 + std::to_wstring(session.SessionId),
43 + std::to_wstring(session.CreatorPid),
44 + session.DisplayName,
45 + });
46 + }
47 +
48 + table.Complete();
49 +}
50 +
51 void AttachToSession(CLIExecutionContext& context)
52 {
53 auto& session = context.Data.Get<Data::Session>();
@@ -73,20 +94,87 @@ void ListSessions(CLIExecutionContext& context)
94 context.Terminal.Output(L"[wslc] Found {} session{}\n", sessions.size(), plural);
95 }
96
76 - TableOutput<3> table(
77 - context.Terminal,
78 - {Localization::MessageWslcHeaderId(), Localization::MessageWslcHeaderCreatorPid(), Localization::MessageWslcHeaderDisplayName()});
97 + WriteSessionTable(context.Terminal, sessions);
98 +}
99
80 - for (const auto& session : sessions)
100 +static std::wstring FormatManagerVersion(const WSLCVersion& version)
101 +{
102 + return std::format(L"{}.{}.{}", version.Major, version.Minor, version.Revision);
103 +}
104 +
105 +void ShowSystemInfo(CLIExecutionContext& context)
106 +{
107 + const auto windowsVersion = wsl::windows::common::helpers::GetWindowsVersionString();
108 + const auto settingsFilePath = settings::User().SettingsFilePath().wstring();
109 +
110 + switch (context.Args.GetValue<ArgType::Format>(FormatType::Table))
111 {
82 - table.WriteRow({
83 - std::to_wstring(session.SessionId),
84 - std::to_wstring(session.CreatorPid),
85 - session.DisplayName,
86 - });
112 + case FormatType::Json:
113 + {
114 + // A JSON document can't be emitted partially, so an unreachable service fails the whole command.
115 + const auto managerVersionText = FormatManagerVersion(SessionService::ManagerVersion());
116 + const auto sessions = SessionService::List();
117 +
118 + nlohmann::json root;
119 +
120 + auto& client = root["Client"];
121 + client["Version"] = std::string{WSL_PACKAGE_VERSION};
122 + client["KernelVersion"] = std::string{KERNEL_VERSION};
123 + client["Direct3DVersion"] = std::string{DIRECT3D_VERSION};
124 + client["DxCoreVersion"] = std::string{DXCORE_VERSION};
125 + client["WindowsVersion"] = windowsVersion;
126 + client["SettingsFile"] = settingsFilePath;
127 +
128 + if constexpr (!wsl::shared::OfficialBuild)
129 + {
130 + client["MsBuildVersion"] = _MSC_VER;
131 + client["Commit"] = std::string{COMMIT_HASH};
132 + client["BuildTime"] = std::string{__TIME__ " " __DATE__};
133 + }
134 +
135 + auto& server = root["Server"];
136 + server["SessionManagerVersion"] = managerVersionText;
137 +
138 + auto& sessionArray = server["Sessions"];
139 + sessionArray = nlohmann::json::array();
140 + for (const auto& session : sessions)
141 + {
142 + sessionArray.push_back({{"ID", session.SessionId}, {"CreatorPid", session.CreatorPid}, {"Name", session.DisplayName}});
143 + }
144 +
145 + context.Terminal.Output(L"{}\n", ToJsonW(root, c_jsonCompactIndent));
146 + break;
147 }
148 + case FormatType::Table:
149 + {
150 + const auto managerVersionText = FormatManagerVersion(SessionService::ManagerVersion());
151 + const auto sessions = SessionService::List();
152
89 - table.Complete();
153 + context.Terminal.Output(L"{}\n", Localization::WSLCCLI_SystemInfoClientHeader());
154 + context.Terminal.Output(
155 + L"{}\n", Localization::WSLCCLI_SystemInfoVersions(WSL_PACKAGE_VERSION, KERNEL_VERSION, DIRECT3D_VERSION, DXCORE_VERSION, windowsVersion));
156 +
157 + if constexpr (!wsl::shared::OfficialBuild)
158 + {
159 + context.Terminal.Output(L"{}\n", Localization::MessageBuildInfo(_MSC_VER, COMMIT_HASH, __TIME__ " " __DATE__));
160 + }
161 +
162 + context.Terminal.Output(L"{}\n", Localization::WSLCCLI_SystemInfoSettingsFile(settingsFilePath));
163 +
164 + context.Terminal.Output(L"\n{}\n", Localization::WSLCCLI_SystemInfoServerHeader());
165 + context.Terminal.Output(L"{}\n", Localization::WSLCCLI_SystemInfoSessionManagerVersion(managerVersionText));
166 + context.Terminal.Output(L"{}\n", Localization::WSLCCLI_SystemInfoSessions(sessions.size()));
167 +
168 + if (!sessions.empty())
169 + {
170 + WriteSessionTable(context.Terminal, sessions);
171 + }
172 +
173 + break;
174 + }
175 + default:
176 + THROW_HR(E_UNEXPECTED);
177 + }
178 }
179
180 void TerminateSession(CLIExecutionContext& context)
src/windows/wslc/tasks/SessionTasks.h
+1
@@ -24,5 +24,6 @@ void OpenDefaultSession(CLIExecutionContext& context);
24 void OpenSessionIfSpecified(CLIExecutionContext& context);
25 void ResolveSession(CLIExecutionContext& context);
26 void RunInSession(CLIExecutionContext& context);
27 +void ShowSystemInfo(CLIExecutionContext& context);
28 void TerminateSession(CLIExecutionContext& context);
29 } // namespace wsl::windows::wslc::task
test/windows/wslc/CommandLineTestCases.h
+12
@@ -36,6 +36,18 @@ COMMAND_LINE_TEST_CASE(L"container list --session foo", L"list", false) // --ses
36
37 // System command tests
38 COMMAND_LINE_TEST_CASE(L"system -?", L"system", true)
39 +COMMAND_LINE_TEST_CASE(L"system info", L"info", true)
40 +COMMAND_LINE_TEST_CASE(L"info", L"info", true)
41 +COMMAND_LINE_TEST_CASE(L"system info --help", L"info", true)
42 +COMMAND_LINE_TEST_CASE(L"system info --format json", L"info", true)
43 +COMMAND_LINE_TEST_CASE(L"system info --format table", L"info", true)
44 +COMMAND_LINE_TEST_CASE(L"info --format json", L"info", true)
45 +COMMAND_LINE_TEST_CASE(L"system info --format invalid", L"info", false)
46 +COMMAND_LINE_TEST_CASE(L"system info --notanarg", L"info", false)
47 +COMMAND_LINE_TEST_CASE(L"system info extraarg", L"info", false)
48 +COMMAND_LINE_TEST_CASE(L"info --format invalid", L"info", false)
49 +COMMAND_LINE_TEST_CASE(L"info --notanarg", L"info", false)
50 +COMMAND_LINE_TEST_CASE(L"info extraarg", L"info", false)
51 COMMAND_LINE_TEST_CASE(L"system session list", L"list", true)
52 COMMAND_LINE_TEST_CASE(L"system session list --verbose", L"list", true)
53 COMMAND_LINE_TEST_CASE(L"system session list --verbose --help", L"list", true)
test/windows/wslc/WSLCCLICommandUnitTests.cpp
+70
@@ -202,6 +202,76 @@ class WSLCCLICommandUnitTests
202 VERIFY_IS_TRUE(found, L"RootCommand should contain VersionCommand");
203 }
204
205 + // Test: Verify SystemInfoCommand has the correct name
206 + TEST_METHOD(SystemInfoCommand_HasCorrectName)
207 + {
208 + auto cmd = SystemInfoCommand(L"system");
209 + VERIFY_ARE_EQUAL(std::wstring_view(L"info"), cmd.Name());
210 + }
211 +
212 + // Test: Verify SystemInfoCommand has no subcommands
213 + TEST_METHOD(SystemInfoCommand_HasNoSubcommands)
214 + {
215 + auto cmd = SystemInfoCommand(L"system");
216 + VERIFY_ARE_EQUAL(0u, cmd.GetCommands().size());
217 + }
218 +
219 + // Test: Verify SystemInfoCommand exposes the --format argument (plus the auto-added --help)
220 + TEST_METHOD(SystemInfoCommand_HasFormatArgument)
221 + {
222 + auto cmd = SystemInfoCommand(L"system");
223 +
224 + auto args = cmd.GetArguments();
225 + VERIFY_ARE_EQUAL(1u, args.size());
226 +
227 + const auto& format = args[0];
228 + VERIFY_ARE_EQUAL(ArgType::Format, format.Type());
229 + VERIFY_ARE_EQUAL(Kind::Value, format.Kind());
230 + VERIFY_IS_FALSE(format.Required());
231 +
232 + // GetAllArguments also includes the auto-added --help.
233 + VERIFY_ARE_EQUAL(2u, cmd.GetAllArguments().size());
234 + }
235 +
236 + // Test: Verify SystemCommand contains SystemInfoCommand as a subcommand
237 + TEST_METHOD(SystemCommand_ContainsSystemInfoCommand)
238 + {
239 + auto cmd = SystemCommand(L"system");
240 + auto subcommands = cmd.GetCommands();
241 +
242 + bool found = false;
243 + for (const auto& subcmd : subcommands)
244 + {
245 + if (subcmd->Name() == SystemInfoCommand::CommandName)
246 + {
247 + found = true;
248 + break;
249 + }
250 + }
251 +
252 + VERIFY_IS_TRUE(found, L"SystemCommand should contain SystemInfoCommand");
253 + }
254 +
255 + // SystemInfoCommand is registered twice so that both `wslc system info` and the
256 + // `wslc info` alias resolve; this pins the second registration.
257 + TEST_METHOD(RootCommand_ContainsSystemInfoCommand)
258 + {
259 + auto root = RootCommand();
260 + auto subcommands = root.GetCommands();
261 +
262 + bool found = false;
263 + for (const auto& subcmd : subcommands)
264 + {
265 + if (subcmd->Name() == SystemInfoCommand::CommandName)
266 + {
267 + found = true;
268 + break;
269 + }
270 + }
271 +
272 + VERIFY_IS_TRUE(found, L"RootCommand should contain SystemInfoCommand");
273 + }
274 +
275 // RootCommand exposes Session as the sole CLI global option. The override
276 // is the entry point for future globals; the test pins the current shape.
277 TEST_METHOD(RootCommand_GlobalArguments_OnlySession)
test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
+112
@@ -18,6 +18,7 @@ Abstract:
18 #include "WSLCE2EHelpers.h"
19 #include "TestImageRegistry.h"
20 #include "WSLCSessionDefaults.h"
21 +#include "WSLCUserSettings.h"
22 #include "Argument.h"
23
24 using namespace WEX::Logging;
@@ -41,6 +42,19 @@ namespace {
42 return std::format(L"{}-{}", baseName, username);
43 }
44
45 + // TableOutput sizes each column to its widest row, so a session leaving re-pads the survivors.
46 + // Comparing leading ID tokens rather than whole rendered lines keeps comparisons padding-independent.
47 + std::vector<std::wstring> GetLeadingTokens(const std::vector<std::wstring>& lines)
48 + {
49 + std::vector<std::wstring> tokens;
50 + for (const auto& line : lines)
51 + {
52 + tokens.emplace_back(line.substr(0, line.find(L' ')));
53 + }
54 +
55 + return tokens;
56 + }
57 +
58 } // namespace
59
60 class WSLCE2EGlobalTests
@@ -187,6 +201,98 @@ class WSLCE2EGlobalTests
201 RunWslcAndVerify(L"version --format table", {.Stdout = GetVersionMessage(), .Stderr = L"", .ExitCode = 0});
202 }
203
204 + WSLC_TEST_METHOD(WSLCE2E_SystemInfoCommand)
205 + {
206 + auto result = RunWslc(L"system info");
207 + result.Verify({.Stderr = L"", .ExitCode = 0});
208 + VERIFY_IS_TRUE(result.Stdout.has_value());
209 +
210 + const auto& output = result.Stdout.value();
211 + VERIFY_IS_TRUE(output.find(Localization::WSLCCLI_SystemInfoClientHeader()) != std::wstring::npos);
212 + VERIFY_IS_TRUE(output.find(std::format(L"{}", WSL_PACKAGE_VERSION)) != std::wstring::npos);
213 + VERIFY_IS_TRUE(output.find(Localization::WSLCCLI_SystemInfoServerHeader()) != std::wstring::npos);
214 + VERIFY_IS_TRUE(output.find(Localization::WSLCCLI_SystemInfoSessionManagerVersion(GetExpectedManagerVersion())) != std::wstring::npos);
215 +
216 + const auto settingsFilePath = wsl::windows::wslc::settings::User().SettingsFilePath().wstring();
217 + VERIFY_IS_TRUE(output.find(Localization::WSLCCLI_SystemInfoSettingsFile(settingsFilePath)) != std::wstring::npos);
218 + }
219 +
220 + WSLC_TEST_METHOD(WSLCE2E_SystemInfoCommand_InvalidFormatOption)
221 + {
222 + const auto result = RunWslc(L"system info --format invalid");
223 + result.Verify({.Stdout = L"", .ExitCode = 1});
224 + VERIFY_IS_TRUE(result.StderrContainsSubstring(
225 + L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table."));
226 + }
227 +
228 + WSLC_TEST_METHOD(WSLCE2E_SystemInfoCommand_FormatJson)
229 + {
230 + auto result = RunWslc(L"system info --format json");
231 + result.Verify({.Stderr = L"", .ExitCode = 0});
232 +
233 + const auto root = VerifyCompactJsonOutput(result);
234 +
235 + const auto& client = root.at("Client");
236 + VERIFY_ARE_EQUAL(std::string{WSL_PACKAGE_VERSION}, client.at("Version").get<std::string>());
237 + VERIFY_ARE_EQUAL(std::string{KERNEL_VERSION}, client.at("KernelVersion").get<std::string>());
238 + VERIFY_ARE_EQUAL(std::string{DIRECT3D_VERSION}, client.at("Direct3DVersion").get<std::string>());
239 + VERIFY_ARE_EQUAL(std::string{DXCORE_VERSION}, client.at("DxCoreVersion").get<std::string>());
240 + VERIFY_IS_FALSE(client.at("WindowsVersion").get<std::string>().empty());
241 + VERIFY_IS_FALSE(client.at("SettingsFile").get<std::string>().empty());
242 +
243 + // WSLg and MSRDC aren't relevant to wslc.
244 + VERIFY_IS_FALSE(client.contains("WslgVersion"));
245 + VERIFY_IS_FALSE(client.contains("MsrdcVersion"));
246 +
247 + const auto& server = root.at("Server");
248 + VERIFY_ARE_EQUAL(
249 + wsl::shared::string::WideToMultiByte(GetExpectedManagerVersion()), server.at("SessionManagerVersion").get<std::string>());
250 +
251 + const auto& sessions = server.at("Sessions");
252 + VERIFY_IS_TRUE(sessions.is_array());
253 + for (const auto& session : sessions)
254 + {
255 + VERIFY_ARE_EQUAL(3u, session.size());
256 + VERIFY_IS_TRUE(session.contains("ID"));
257 + VERIFY_IS_FALSE(session.contains("Id"));
258 + VERIFY_IS_TRUE(session.contains("CreatorPid"));
259 + VERIFY_IS_TRUE(session.contains("Name"));
260 + }
261 + }
262 +
263 + WSLC_TEST_METHOD(WSLCE2E_SystemInfoCommand_RootAlias)
264 + {
265 + auto systemResult = RunWslc(L"system info --format json");
266 + systemResult.Verify({.Stderr = L"", .ExitCode = 0});
267 +
268 + auto rootResult = RunWslc(L"info --format json");
269 + rootResult.Verify({.Stderr = L"", .ExitCode = 0});
270 +
271 + // The server section can change between invocations; the client section cannot.
272 + VERIFY_ARE_EQUAL(
273 + VerifyCompactJsonOutput(systemResult).at("Client").dump(), VerifyCompactJsonOutput(rootResult).at("Client").dump());
274 + }
275 +
276 + WSLC_TEST_METHOD(WSLCE2E_SystemInfoCommand_DoesNotCreateSession)
277 + {
278 + auto before = RunWslc(L"system session list");
279 + before.Verify({.Stderr = L"", .ExitCode = 0});
280 +
281 + RunWslcAndVerify(L"system info --format json", {.Stderr = L"", .ExitCode = 0});
282 +
283 + auto after = RunWslc(L"system session list");
284 + after.Verify({.Stderr = L"", .ExitCode = 0});
285 +
286 + // An idle session can terminate between the two snapshots, so only assert that none appeared.
287 + const auto beforeIds = GetLeadingTokens(before.GetStdoutLines());
288 + for (const auto& id : GetLeadingTokens(after.GetStdoutLines()))
289 + {
290 + VERIFY_IS_TRUE(
291 + std::find(beforeIds.begin(), beforeIds.end(), id) != beforeIds.end(),
292 + std::format(L"'system info' must not create a session, but session '{}' appeared", id).c_str());
293 + }
294 + }
295 +
296 WSLC_TEST_METHOD(WSLCE2E_Session_DefaultElevated)
297 {
298 // Run container list to create the default elevated session
@@ -659,5 +765,11 @@ private:
765 {
766 return std::format(L"wslc {}\r\n", WSL_PACKAGE_VERSION);
767 }
768 +
769 + // The session manager reports only the first three version components.
770 + std::wstring GetExpectedManagerVersion() const
771 + {
772 + return std::format(L"{}.{}.{}", WSL_PACKAGE_VERSION_MAJOR, WSL_PACKAGE_VERSION_MINOR, WSL_PACKAGE_VERSION_REVISION);
773 + }
774 };
775 } // namespace WSLCE2ETests