Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,10 @@ Usage:
<value>Found {} WSLC session{}:</value>
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcTerminateSessionFailed" xml:space="preserve">
<value>Session termination failed: '{}'</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcShellExited" xml:space="preserve">
<value>{} exited with: {}</value>
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslc/commands/SessionCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ namespace wsl::windows::wslc {
std::vector<std::unique_ptr<Command>> SessionCommand::GetCommands() const
{
std::vector<std::unique_ptr<Command>> commands;
commands.reserve(2);
commands.push_back(std::make_unique<SessionListCommand>(FullName()));
commands.push_back(std::make_unique<SessionShellCommand>(FullName()));
commands.push_back(std::make_unique<SessionTerminateCommand>(FullName()));
return commands;
Comment thread
dkbennett marked this conversation as resolved.
}

Expand Down
15 changes: 15 additions & 0 deletions src/windows/wslc/commands/SessionCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ struct SessionShellCommand final : public Command
std::wstring ShortDescription() const override;
std::wstring LongDescription() const override;

protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};

// Terminate Command
struct SessionTerminateCommand final : public Command
{
constexpr static std::wstring_view CommandName = L"terminate";
SessionTerminateCommand(const std::wstring& parent) : Command(CommandName, parent)
{
}
std::vector<Argument> GetArguments() const override;
std::wstring ShortDescription() const override;
std::wstring LongDescription() const override;

protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};
Expand Down
45 changes: 45 additions & 0 deletions src/windows/wslc/commands/SessionTerminateCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

SessionTerminateCommand.cpp

Abstract:

Implementation of the session terminate command.

--*/
#include "CLIExecutionContext.h"
#include "SessionCommand.h"
#include "SessionTasks.h"
#include "Task.h"

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

namespace wsl::windows::wslc {
// Session Terminate Command
std::vector<Argument> SessionTerminateCommand::GetArguments() const
{
return {
Argument::Create(ArgType::SessionId),
};
}

std::wstring SessionTerminateCommand::ShortDescription() const
{
return {L"Terminate a session."};
}

std::wstring SessionTerminateCommand::LongDescription() const
{
return {L"Terminates an active session. If no session is specified, the default session will be terminated."};
}

void SessionTerminateCommand::ExecuteInternal(CLIExecutionContext& context) const
{
context << TerminateSession;
}
} // namespace wsl::windows::wslc
35 changes: 35 additions & 0 deletions src/windows/wslc/services/SessionService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,39 @@ Session SessionService::OpenSession(const std::wstring& displayName)
wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
return Session(std::move(session));
}

int SessionService::TerminateSession(const std::wstring& displayName)
{
THROW_HR_IF(E_INVALIDARG, displayName.empty());

wil::com_ptr<IWSLCSessionManager> sessionManager;
THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager)));
wsl::windows::common::security::ConfigureForCOMImpersonation(sessionManager.get());

wil::com_ptr<IWSLCSession> session;
HRESULT hr = sessionManager->OpenSessionByName(displayName.c_str(), &session);
if (FAILED(hr))
{
if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
{
wslutil::PrintMessage(Localization::MessageWslcSessionNotFound(displayName.c_str()), stderr);
return 1;
}

THROW_HR(hr);
}

wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
Comment thread
dkbennett marked this conversation as resolved.

hr = session->Terminate();
if (FAILED(hr))
{
auto errorString = wsl::windows::common::wslutil::ErrorCodeToString(hr);
wslutil::PrintMessage(
Localization::MessageErrorCode(Localization::MessageWslcTerminateSessionFailed(displayName.c_str()), errorString), stderr);
return 1;
}

return 0;
}
} // namespace wsl::windows::wslc::services
1 change: 1 addition & 0 deletions src/windows/wslc/services/SessionService.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ struct SessionService
static wsl::windows::wslc::models::Session CreateSession(const wsl::windows::wslc::models::SessionOptions& options);
static std::vector<SessionInformation> List();
static wsl::windows::wslc::models::Session OpenSession(const std::wstring& displayName);
static int TerminateSession(const std::wstring& displayName);
};
} // namespace wsl::windows::wslc::services
15 changes: 15 additions & 0 deletions src/windows/wslc/tasks/SessionTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,19 @@ void ListSessions(CLIExecutionContext& context)
table.Complete();
}

void TerminateSession(CLIExecutionContext& context)
{
std::wstring sessionId;
if (context.Args.Contains(ArgType::SessionId))
{
sessionId = context.Args.Get<ArgType::SessionId>();
}
else
{
sessionId = SessionOptions::GetDefaultSessionName();
}

context.ExitCode = SessionService::TerminateSession(sessionId);
}
Comment thread
dkbennett marked this conversation as resolved.

} // namespace wsl::windows::wslc::task
1 change: 1 addition & 0 deletions src/windows/wslc/tasks/SessionTasks.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ namespace wsl::windows::wslc::task {
void AttachToSession(CLIExecutionContext& context);
void CreateSession(CLIExecutionContext& context);
void ListSessions(CLIExecutionContext& context);
void TerminateSession(CLIExecutionContext& context);
} // namespace wsl::windows::wslc::task
2 changes: 2 additions & 0 deletions test/windows/wslc/CommandLineTestCases.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ COMMAND_LINE_TEST_CASE(L"session list --notanarg", L"list", false)
COMMAND_LINE_TEST_CASE(L"session list extraarg", L"list", false)
COMMAND_LINE_TEST_CASE(L"session shell session1", L"shell", true)
COMMAND_LINE_TEST_CASE(L"session shell", L"shell", true)
COMMAND_LINE_TEST_CASE(L"session terminate session1", L"terminate", true)
COMMAND_LINE_TEST_CASE(L"session terminate", L"terminate", true)

// Container command tests
COMMAND_LINE_TEST_CASE(L"container list", L"list", true)
Expand Down
142 changes: 142 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,148 @@ class WSLCE2EGlobalTests
result.Verify({.Stderr = L"", .ExitCode = S_OK});
}

TEST_METHOD(WSLCE2E_Session_CreateMixedElevation_Fails)
{
WSL2_TEST_ONLY();

EnsureSessionIsTerminated(L"wslc-cli");
EnsureSessionIsTerminated(L"wslc-cli-admin");

// Ensure elevated cannot create the non-elevated session.
auto result = RunWslc(L"container list --session wslc-cli", ElevationType::Elevated);
result.Verify({.Stderr = L"Element not found. \r\nError code: ERROR_NOT_FOUND\r\n", .ExitCode = 1});

// Ensure non-elevated cannot create the elevated session.
result = RunWslc(L"container list --session wslc-cli-admin", ElevationType::NonElevated);
result.Verify({.Stderr = L"Element not found. \r\nError code: ERROR_NOT_FOUND\r\n", .ExitCode = 1});
}

TEST_METHOD(WSLCE2E_Session_Terminate_Implicit)
{
WSL2_TEST_ONLY();

// Run container list to create the default session if it does not already exist
auto result = RunWslc(L"container list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session list shows the admin session name
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos);

// Terminate the session
result = RunWslc(L"session terminate");
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session no longer shows up
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos);

// Repeat test for non-elevated session.

// Run container list to create the default session if it does not already exist
result = RunWslc(L"container list", ElevationType::NonElevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session list shows the non-elevated session name
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos);

// Terminate the session
result = RunWslc(L"session terminate", ElevationType::NonElevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session no longer shows up
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos);
}

TEST_METHOD(WSLCE2E_Session_Terminate_Explicit)
{
WSL2_TEST_ONLY();

// Run container list to create the default session if it does not already exist
auto result = RunWslc(L"container list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session list shows the admin session name
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos);

// Terminate the session
result = RunWslc(L"session terminate wslc-cli-admin");
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session no longer shows up
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos);

// Repeat test for non-elevated session.

// Run container list to create the default session if it does not already exist
result = RunWslc(L"container list", ElevationType::NonElevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session list shows the non-elevated session name
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos);

// Terminate the session
result = RunWslc(L"session terminate wslc-cli", ElevationType::NonElevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session no longer shows up
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos);
}

TEST_METHOD(WSLCE2E_Session_Terminate_MixedElevation)
{
WSL2_TEST_ONLY();

// Run container list to create the default sessions if they do not already exist.
auto result = RunWslc(L"container list", ElevationType::Elevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});
result = RunWslc(L"container list", ElevationType::NonElevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify session list shows both sessions.
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos);
VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos);

// Attempt to terminate the admin session from the non-elevated process and fail.
result = RunWslc(L"session terminate wslc-cli-admin", ElevationType::NonElevated);
result.Verify({.Stderr = L"The requested operation requires elevation. \r\nError code: ERROR_ELEVATION_REQUIRED\r\n", .ExitCode = 1});

// Terminate the non-elevated session from the elevated process.
result = RunWslc(L"session terminate wslc-cli", ElevationType::Elevated);
result.Verify({.Stderr = L"", .ExitCode = S_OK});

// Verify non-elevated session no longer shows up
result = RunWslc(L"session list");
result.Verify({.Stderr = L"", .ExitCode = S_OK});
VERIFY_IS_TRUE(result.Stdout.has_value());
VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos);
Comment thread
dkbennett marked this conversation as resolved.
}

TEST_METHOD(WSLCE2E_Session_Targeting)
{
WSL2_TEST_ONLY();
Expand Down
25 changes: 25 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Module Name:
--*/

#include "precomp.h"
#include "SessionModel.h"
#include "windows/Common.h"
#include "WSLCExecutor.h"
#include "WSLCE2EHelpers.h"
Expand Down Expand Up @@ -332,4 +333,28 @@ void EnsureImageIsLoaded(const TestImage& image, const std::wstring& sessionName
auto loadResult = RunWslc(loadCommand);
loadResult.Verify({.Stderr = L"", .ExitCode = 0});
}

void EnsureSessionIsTerminated(const std::wstring& sessionName)
{
std::wstring targetSession = sessionName;
if (targetSession.empty())
{
targetSession = std::wstring{wsl::windows::wslc::models::SessionOptions::GetDefaultSessionName()};
}

auto listResult = RunWslc(L"session list");
listResult.Verify({.Stderr = L"", .ExitCode = 0});

auto stdoutLines = listResult.GetStdoutLines();
for (const auto& line : stdoutLines)
{
// Check if the line ends with the target session name
if (line.size() >= targetSession.size() && line.compare(line.size() - targetSession.size(), targetSession.size(), targetSession) == 0)
{
auto result = RunWslc(std::format(L"session terminate \"{}\"", targetSession));
result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
Comment thread
dkbennett marked this conversation as resolved.
break;
}
}
}
} // namespace WSLCE2ETests
1 change: 1 addition & 0 deletions test/windows/wslc/e2e/WSLCE2EHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ void EnsureContainerDoesNotExist(const std::wstring& containerName);
void EnsureImageIsLoaded(const TestImage& image, const std::wstring& sessionName = L"");
void EnsureImageIsDeleted(const TestImage& image);
void EnsureImageContainersAreDeleted(const TestImage& image);
void EnsureSessionIsTerminated(const std::wstring& sessionName = L"");

// Default timeout of 0 will execute once.
template <typename IntervalRep, typename IntervalPeriod, typename TimeoutRep, typename TimeoutPeriod>
Expand Down
Loading