Just an update for those that are interested.
Below is the reply I got from Autodesk support team
I went with option 2 as I already had a command reactor implemented.
Hi Richard,
Sorry for long pause, I looked into this, and -ETRANSMIT cannot be driven programmatically. Even the CLI version has a strong internal UI dependency (modal progress dialogs via DoModal()), so acedCommandS always returns RTREJ.
Two alternatives
Option 1 — COM API (recommended)
Use AcETransmit20.tlb, which is a registered COM type library that ships with every AutoCAD installation. The key object is ITransmittalOperation:
Call addDrawingFile() — the engine automatically resolves all dependencies (xrefs, fonts, plot styles, etc.)
Call createTransmittalPackage() — copies all files to a staging folder
ZIP the staging folder using your preferred method (PowerShell Compress-Archive, 7-Zip CLI, or native C++ via 7z.h)
This approach is fully synchronous, has no UI dependency, and runs cleanly inside an ARX command.
Prerequisite: aceTransmitui.arx must be loaded before the first call. AutoCAD loads it automatically when ETRANSMIT is first used in a session, or you can load it explicitly using acrxLoadApp.
Option 2 — Command reactor
Use commandWillStart / commandEnded to intercept the standard ETRANSMIT command executed via sendStringToExecute, then trigger your email logic in commandEnded.
Why sendStringToExecute works but acedCommandS doesn’t
In the sendStringToExecute case, the command string is posted to the document’s input queue and returns immediately. Your ARX command then exits cleanly, releasing the document lock.
Only after your command has fully completed does AutoCAD process the queued strings and execute -ETRANSMIT as a normal interactive command—with its own full message loop and properly functioning modal dialogs.
Option 1 - C++ / COM
#import "C:\\Program Files\\Common Files\\Autodesk Shared\\AcETransmit20.tlb" no_namespace rename("GetObject","TLB_GetObject")
// TransmittalOperation coclass {A994CA23-17EB-4C43-8345-EDEE893F2186}
static const CLSID CLSID_TransmittalOperation =
{ 0xa994ca23, 0x17eb, 0x4c43, { 0x83, 0x45, 0xed, 0xee, 0x89, 0x3f, 0x21, 0x86 } };
// ---- helpers -------------------------------------------------------
// Formats a failed HRESULT as "0x8xxxxxxx — <system message>".
static AcString formatHr(HRESULT hr)
{
LPWSTR buf = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, hr, 0, (LPWSTR)&buf, 0, nullptr);
AcString msg;
if (buf) {
int len = (int)wcslen(buf);
while (len > 0 && (buf[len-1] == L'\r' || buf[len-1] == L'\n')) buf[--len] = L'\0';
msg.format(_T("0x%08X \u2014 %s"), hr, buf);
LocalFree(buf);
} else {
msg.format(_T("0x%08X"), hr);
}
return msg;
}
// Runs a PowerShell command synchronously. Returns true on success.
static bool runPowerShell(const wchar_t* psCommand, DWORD timeoutMs = 60000)
{
wchar_t cmd[1024];
_snwprintf_s(cmd, _countof(cmd), _TRUNCATE,
L"powershell.exe -NoProfile -NonInteractive -Command \"%s\"", psCommand);
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = {};
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
if (!CreateProcessW(nullptr, cmd, nullptr, nullptr, FALSE,
CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))
return false;
WaitForSingleObject(pi.hProcess, timeoutMs);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
// Deletes a directory tree (equivalent to "rd /s /q").
static void removeDirectoryTree(const wchar_t* path)
{
wchar_t cmd[512];
_snwprintf_s(cmd, _countof(cmd), _TRUNCATE,
L"Remove-Item -LiteralPath '%s' -Recurse -Force", path);
runPowerShell(cmd);
}
// ---- ETRANSMITCOM command ------------------------------------------
void eTransmitViaCOM()
{
// Destination paths — adapt as needed.
const wchar_t* destFolder = L"D:\\ToDelete\\TransmittalTemp";
const wchar_t* zipPath = L"D:\\ToDelete\\transmittal.zip";
// --- 1. Get the active drawing path ---
AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase();
if (!pDb) { acutPrintf(_T("\nNo active database.")); return; }
const TCHAR* pFname = nullptr;
pDb->getFilename(pFname);
if (!pFname || !pFname[0]) {
acutPrintf(_T("\nPlease save the drawing first (QSAVE)."));
return;
}
acutPrintf(_T("\nCreating transmittal for: %s"), pFname);
// --- 2. Create the transmittal engine ---
// NOTE: Do NOT call CoInitialize/CoUninitialize — AutoCAD already owns COM
// on the main thread. aceTransmitui.arx must be loaded first; AutoCAD does
// this automatically when ETRANSMIT is first used in the session.
ITransmittalOperationPtr pOp;
HRESULT hr = pOp.CreateInstance(CLSID_TransmittalOperation);
if (FAILED(hr)) {
acutPrintf(_T("\nError: CoCreateInstance(TransmittalOperation) failed — %s"),
formatHr(hr).constPtr());
acutPrintf(_T("\nTip: run the ETRANSMIT command once manually to load aceTransmitui.arx."));
return;
}
// --- 3. Add the drawing; the engine resolves all dependencies ---
ITransmittalFilePtr pRootFile;
AddFileReturnVal addResult;
hr = pOp->raw_addDrawingFile(_bstr_t(pFname), &pRootFile, &addResult);
if (FAILED(hr) || !pRootFile) {
acutPrintf(_T("\nError: addDrawingFile failed — %s"), formatHr(hr).constPtr());
return;
}
// --- 4. Configure output options ---
ITransmittalInfoPtr pInfo = pOp->getTransmittalInfoInterface();
if (!pInfo) { acutPrintf(_T("\nError: getTransmittalInfoInterface returned null.")); return; }
CreateDirectoryW(destFolder, nullptr);
pInfo->PutdestinationRoot(_bstr_t(destFolder));
pInfo->PutdefaultOverwriteValue(eOverwriteYesToAll); // no overwrite prompts
pInfo->PutincludeXrefDwg(TRUE);
pInfo->PutincludeFontFile(TRUE);
pInfo->PutincludeImageFile(TRUE);
pInfo->PutpreserveSubdirs(FALSE);
// --- 5. Copy all files to the staging folder ---
// createTransmittalPackage() == copyFiles() internally (confirmed from source).
// ZIP packaging is always done by the caller — see step 6.
hr = pOp->raw_createTransmittalPackage();
if (FAILED(hr)) {
acutPrintf(_T("\nError: createTransmittalPackage failed — %s"), formatHr(hr).constPtr());
return;
}
// Print the dependency report
_bstr_t report = pOp->getTransmittalReport();
if (report.length() > 0)
acutPrintf(_T("\n%s"), (const wchar_t*)report);
// --- 6. ZIP the staging folder ---
DeleteFileW(zipPath); // remove stale output if any
wchar_t psCmd[512];
_snwprintf_s(psCmd, _countof(psCmd), _TRUNCATE,
L"Compress-Archive -Path '%s\\*' -DestinationPath '%s' -Force",
destFolder, zipPath);
if (runPowerShell(psCmd) && GetFileAttributesW(zipPath) != INVALID_FILE_ATTRIBUTES)
acutPrintf(_T("\nTransmittal complete. Output: %s"), zipPath);
else
acutPrintf(_T("\nError: ZIP creation failed."));
// --- 7. Clean up staging folder ---
removeDirectoryTree(destFolder);
}
Option 2
class CTransmittalEndReactor : public AcEditorReactor
{
public:
void commandEnded(const ACHAR* cmdStr) override
{
if (_tcsicmp(cmdStr, _T("-ETRANSMIT")) != 0)
return;
// ETRANSMIT finished — ZIP is on disk, safe to send email now.
acutPrintf(_T("\n[Reactor] ETRANSMIT finished ZIP ready, sending email..."));
// TODO: replace with real email / post-processing logic
acutPrintf(_T("\n[Reactor] Email sent."));
// Self-unregister so the reactor only fires once per invocation.
acedEditor->removeReactor(this);
delete this;
}
void commandCancelled(const ACHAR* cmdStr) override
{
if (_tcsicmp(cmdStr, _T("-ETRANSMIT")) != 0)
return;
acutPrintf(_T("\n[Reactor] ETRANSMIT was cancelled."));
acedEditor->removeReactor(this);
delete this;
}
void commandFailed(const ACHAR* cmdStr) override
{
if (_tcsicmp(cmdStr, _T("-ETRANSMIT")) != 0)
return;
acutPrintf(_T("\n[Reactor] ETRANSMIT failed."));
acedEditor->removeReactor(this);
delete this;
}
};
void testCommandSSToEx()
{
AcApDocument* pDoc = acDocManager->curDocument();
if (!pDoc) { acutPrintf(_T("\nNo active document.")); return; }
// Register the reactor BEFORE posting the command string so no
// commandEnded notification is missed.
acedEditor->addReactor(new CTransmittalEndReactor());
// Delete any existing ZIP so -ETRANSMIT never asks an overwrite prompt.
_wremove(L"D:\\ToDelete\\tst.zip");
const TCHAR* cmdString =
_T("._FILEDIA 0\n")
_T("._QSAVE\n")
_T("._-ETRANSMIT\n")
_T("_CH\nStandard\n")
_T("_C\n")
_T("D:\\ToDelete\\tst.zip\n")
_T("._FILEDIA 1\n");
acutPrintf(_T("\nPosting -ETRANSMIT via sendStringToExecute (async)..."));
acDocManager->sendStringToExecute(pDoc, cmdString, true, false, true);
acutPrintf(_T("\nFunction returned — waiting for reactor to fire."));
}