ETRANSMIT command programmatically fails with -5001 error code

I’m trying to implement the ETRANSMIT command programmatically but keep getting a -5001 (RTERROR) error code.

I can type out the commands on the command line OK,
but running the code below, the etransmit cmd fails with -5001, all other commands return 5100 (RTNORM)

Cheers
Rick

resval = acedCommandS(RTSTR,L"FILEDIA",RTSTR, L"0",RTNONE);
resval = acedCommandS(RTSTR,L"_qsave",RTNONE);

resval = acedCommandS(
RTSTR, L"-etransmit",
RTSTR, L"_CH",
RTSTR, L"Standard",
RTSTR, L"_C",
RTSTR, L"C:\\Develop\\test_dwg\\tst.zip", RTNONE);

resval = acedCommandS(RTSTR, L"FILEDIA", RTSTR, L"1", RTNONE);

Rick -

I assume you tested this successfully on vanilla AutoCAD?

I’m thinking there might be a required runtime that has been disabled in your OEM configuration, could you run a test project with everything enabled in the MakeWizard and see whether it still fails please?

Can you also see at what stage it fails running the three acedCommands?

Paavo,

This is on vanilla AutoCAD 2025.

It fails on the line:

resval = acedCommandS(
RTSTR, L"-etransmit",
RTSTR, L"_CH",
RTSTR, L"Standard",
RTSTR, L"_C",
RTSTR, L"C:\\Develop\\test_dwg\\tst.zip", RTNONE)

resval returns -5001 (RTERROR)

All other commands return 5100 (RTNORM)

Thanks Rick. As this seems to be an issue with core AutoCAD, I’d suggest you log the issue with the ADN, https://adn.autodesk.io/

Perhaps it is already a known issue, there may be a workaround or solution in one of the more previous releases, see the C++ customization forum: Search - Autodesk Community

Let me know if you find out anything that we can also apply to OEM, thanks.

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."));
}