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