How to drop files onto .MAPIMail

2019-04-10 12:59发布

问题:

Given some files (or shell file objects) how do i invoke the .MAPIMail registered shell extension handler with them?


The Question

i have some files on the computer:

  • C:\Users\ian\AppData\Local\Temp\Contoso_Invoice_141174.pdf
  • C:\Users\ian\AppData\Local\Temp\Contoso_Invoice_141173.pdf
  • C:\Users\ian\AppData\Local\Temp\Contoso_Invoice_141171.pdf

That i want to do the programmatic equivalent of dropping them on the .MAPIMail registered handler:

The Sent to folder's Mail recipient option is actually a special registered .MAPIMail extension:

Which is a file type that is registered on the system:

HKEY_CLASSES_ROOT\.mapimail

How do i invoke a drop onto a ephermeral .mapimail file?

Can't you just look in the registry?

Now, i could be a bad developer, and spellunk the registry, the .mapimail entry's default value:

CLSID\{9E56BE60-C50F-11CF-9A2C-00A0C90A90CE}

Extract the clsid {9E56BE60-C50F-11CF-9A2C-00A0C90A90CE}, and confirm that class is registered:

HKEY_CLASSES_ROOT\CLSID\{9E56BE60-C50F-11CF-9A2C-00A0C90A90CE}
    (default) = Desktop Shortcut
    \InProcServer32
        (default) = %SystemRoot%\System32\sendmail.dll

And use CoCreateInstance to create that COM object:

IUnknown unk = CreateComObject("{9E56BE60-C50F-11CF-9A2C-00A0C90A90CE}");

And then i'm in an undocumented, unsupported world, where i don't know what interface i have to QueryInterface for, and what methods to call in what order.

So we're left with shell programming

What i'd like is to likely something involving the shell (pseudo-code):

IShellFolder desktop;
OleCheck(SHGetDesktopFolder(out desktop));

List<pidl> pidls = new List<pidl>();

ULONG chEaten = 0;
ULONG dwAttributes = 0;
PIDL pidl;

foreach (String filename in Files) do
{
    OleCheck(desktop.ParseDisplayName(0, nil, filename, out chEaten, out pidl, ref dwAttributes));

    pidls.Add(pidl);
}

//Get the shell folder of the temp folder
IShellFolder tempShellFolder;
desktop.ParseDisplayName(0, nil, GetTemporaryPath, out chEaten, out pidl, ref dwAttributes));
desktop.BindToObject(pidl, nil, IShellFolder, tempShellFolder);

//i have no idea what i've been doing; just throwing reasonable looking code together
//nobody will actually ever read this

IDontCare context;

tempShellFolder.GetUIObjectOf(0, pidls.Count, pidls, IDontCareAnymore, nil, ref context); 

Except all that code relies on the extistance of a context menu, which i don't have. Nobody says that .MAPIMail has to be in any context Send to menu.

i was asking how to drop files on a .mapimail file.

And my god.

Why not just use MAPI?

Because no MAPI client is installed when you're a 32-bit application running on Windows 64-bit with Office 64-bit installed. So i need to be able to accomplish what the user already can.

回答1:

Although it doesn't answer my question, Raymond pointed out that it's a stupid question. Nobody in their right mind should be trying to send mail to recipients. But i was desperate!

Turns out i'm not completely stuck. While there is a bitness nightmare when dealing with 64-bit Outlook (MAPI provider) from 32-bit applications (or vice versa), there is one out.

If i use just MapiSendMail, and no other MAPI functions, it is safe to cross the 32-bit/64-bit barrier. From Building MAPI Applications on 32-Bit and 64-Bit Platforms:

32-bit MAPI Application and 64-Bit Outlook

32-bit MAPI applications are not supported to run on a computer installed with 64-bit Outlook and 64-bit Windows. The application developer must update and rebuild the application as a 64-bit application for the 64-bit platform. This is because a 32-bit application cannot load a 64-bit Msmapi32.dll file. There are a small number of API changes that application developers must incorporate to build their code successfully for a 64-bit environment. MAPI header files have been updated with these changes to support the 64-bit platform. You can download these header files at Outlook 2010: MAPI Header Files. Developers can use this same set of MAPI header files to build both 32-bit and 64-bit MAPI applications

That makes it sound like all hope is lost. But, there is, on Windows 7:

Exception: MAPISendMail

However, one function call among all Simple MAPI and MAPI elements, MAPISendMail, would succeed in a Windows-32-bit-on-Windows-64-bit (WOW64) or Windows-64-bit-on-Windows-32-bit (WOW32) scenario and would not result in the above alert. This WOW64 scenario only applies to Windows 7. Figure 2 shows a WOW64 scenario in which a 32-bit MAPI application calls MAPISendMail on a computer installed with 64-bit Windows 7. In this scenario, the MAPI library makes a COM call to launch a 64-bit Fixmapi application. The Fixmapi application implicitly links to the MAPI library, which routes the function call to the Windows MAPI stub, which in turn forwards the call to the Outlook MAPI stub, enabling the MAPISendMail function call to succeed.

So, as a Delphi Jedi user, their Simple Send E-mail functions will fail (as they use too much of MAPI). So i had to create my own:

procedure MapiSimpleSendMail(slFiles: TStrings; ToEmailAddress: string=''; ToName: string='');
var
    mapiMessage: TMapiMessage;
    flags: LongWord;
//  senderName: AnsiString;
//  senderEmailAddress: AnsiString;
    emailSubject: AnsiString;
    emailBody: AnsiString;
//  sender: TMapiRecipDesc;
    recipients: packed array of TMapiRecipDesc;
    attachments: packed array of TMapiFileDesc;
    i: Integer;
    hr: Cardinal;
    es: string;
const
    MAPI_E_UNICODE_NOT_SUPPORTED = 27; //Windows 8. The MAPI_FORCE_UNICODE flag is specified and Unicode is not supported.
begin
    ZeroMemory(@mapiMessage, SizeOf(mapiMessage));

{   senderName := '';
    senderEmailAddress := '';

    ZeroMemory(@sender, sizeof(sender));
    sender.ulRecipClass := MAPI_ORIG; //MAPI_TO, MAPI_CC, MAPI_BCC, MAPI_ORIG
    sender.lpszName := PAnsiChar(senderName);
    sender.lpszAddress := PAnsiChar(senderEmailAddress);}
    mapiMessage.lpOriginator := nil; //PMapiRecipDesc; { Originator descriptor                  }

    if ToEmailAddress <> '' then
    begin
        SetLength(recipients, 1);
        recipients[0].ulRecipClass := MAPI_TO;
        recipients[0].lpszName := LPSTR(ToName);
        recipients[0].lpszAddress := LPSTR(ToEmailAddress);

        mapiMessage.lpRecips := @recipients[0]; //A value of NULL means that there are no recipients. Additionally, when this member is NULL, the nRecipCount member must be zero.
        mapiMessage.nRecipCount := 1;
    end
    else
    begin
        mapiMessage.lpRecips := nil; //A value of NULL means that there are no recipients. Additionally, when this member is NULL, the nRecipCount member must be zero.
        mapiMessage.nRecipCount := 0;
    end;

    mapiMessage.lpszMessageType := nil;

    if slFiles.Count > 0 then
    begin
        emailSubject := 'Emailing: ';
        emailBody :=
                '          '+#13#10+ //Yes, the shell really does create a blank mail with a leading line of ten spaces
                'Your message is ready to be sent with the following file or link attachments:'+#13#10;


    SetLength(attachments, slFiles.Count);
        for i := 0 to slFiles.Count-1 do
        begin
            attachments[i].ulReserved := 0; // Cardinal;        { Reserved for future use (must be 0)     }
            attachments[i].flFlags := 0; // Cardinal;           { Flags                                   }
            attachments[i].nPosition := $FFFFFFFF; //Cardinal;         { character in text to be replaced by attachment }
            attachments[i].lpszPathName := PAnsiChar(slFiles[i]);    { Full path name of attachment file       }
            attachments[i].lpszFileName := nil; // LPSTR;         { Original file name (optional)           }
            attachments[i].lpFileType := nil; // Pointer;         { Attachment file type (can be lpMapiFileTagExt) }

            if i > 0 then
                emailSubject := emailSubject+', ';
            emailSubject := emailSubject+ExtractFileName(slFiles[i]);
            emailBody := emailBody+#13#10+
                    ExtractFileName(slFiles[i]);
        end;

        emailBody := emailBody+#13#10+
                #13#10+
                #13#10+
                'Note: To protect against computer viruses, e-mail programs may prevent sending or receiving certain types of file attachments.  Check your e-mail security settings to determine how attachments are handled.';


        mapiMessage.lpFiles := @attachments[0];
        mapiMessage.nFileCount := slFiles.Count;
    end
    else
    begin
        emailSubject := '';
        emailBody := '';

        mapiMessage.lpFiles := nil;
        mapiMessage.nFileCount := 0;
    end;

    {
        Subject
        Emailing: 4388_888871544_MVM_10.tmp, amt3.log, swtag.log, wct845C.tmp, ~vs1830.sql

        Body
                  <-- ten spaces
        Your message is ready to be sent with the following file or link attachments:

        4388_888871544_MVM_10.tmp
        amt3.log
        swtag.log
        wct845C.tmp
        ~vs1830.sql


        Note: To protect against computer viruses, e-mail programs may prevent sending or receiving certain types of file attachments.  Check your e-mail security settings to determine how attachments are handled.
    }
    mapiMessage.lpszSubject := PAnsiChar(emailSubject);
    mapiMessage.lpszNoteText := PAnsiChar(emailBody);


    flags := MAPI_DIALOG;

    hr := Mapi.MapiSendMail(0, 0, mapiMessage, flags, 0);
    case hr of
    SUCCESS_SUCCESS: {nop}; //The call succeeded and the message was sent.
    MAPI_E_AMBIGUOUS_RECIPIENT:
        begin
            //es := 'A recipient matched more than one of the recipient descriptor structures and MAPI_DIALOG was not set. No message was sent.';
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_AMBIGUOUS_RECIPIENT', SysErrorMessage(hr)]);
        end;
    MAPI_E_ATTACHMENT_NOT_FOUND:
        begin
            //The specified attachment was not found. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_ATTACHMENT_NOT_FOUND', SysErrorMessage(hr)]);
        end;
    MAPI_E_ATTACHMENT_OPEN_FAILURE:
        begin
            //The specified attachment could not be opened. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_ATTACHMENT_OPEN_FAILURE', SysErrorMessage(hr)]);
        end;
    MAPI_E_BAD_RECIPTYPE:
        begin
            //The type of a recipient was not MAPI_TO, MAPI_CC, or MAPI_BCC. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_BAD_RECIPTYPE', SysErrorMessage(hr)]);
        end;
    MAPI_E_FAILURE:
        begin
            //One or more unspecified errors occurred. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_FAILURE', SysErrorMessage(hr)]);
        end;
    MAPI_E_INSUFFICIENT_MEMORY:
        begin
            //There was insufficient memory to proceed. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_INSUFFICIENT_MEMORY', SysErrorMessage(hr)]);
        end;
    MAPI_E_INVALID_RECIPS:
        begin
            //One or more recipients were invalid or did not resolve to any address.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_INVALID_RECIPS', SysErrorMessage(hr)]);
        end;
    MAPI_E_LOGIN_FAILURE:
        begin
            //There was no default logon, and the user failed to log on successfully when the logon dialog box was displayed. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_LOGIN_FAILURE', SysErrorMessage(hr)]);
        end;
    MAPI_E_TEXT_TOO_LARGE:
        begin
            //The text in the message was too large. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_TEXT_TOO_LARGE', SysErrorMessage(hr)]);
        end;
    MAPI_E_TOO_MANY_FILES:
        begin
            //There were too many file attachments. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_TOO_MANY_FILES', SysErrorMessage(hr)]);
        end;
    MAPI_E_TOO_MANY_RECIPIENTS:
        begin
            //There were too many recipients. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_TOO_MANY_RECIPIENTS', SysErrorMessage(hr)]);
        end;
    MAPI_E_UNICODE_NOT_SUPPORTED:
        begin
            //The MAPI_FORCE_UNICODE flag is specified and Unicode is not supported.
            //Note  This value can be returned by MAPISendMailW only.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_UNICODE_NOT_SUPPORTED', SysErrorMessage(hr)]);
        end;
    MAPI_E_UNKNOWN_RECIPIENT:
        begin
            //A recipient did not appear in the address list. No message was sent.
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_UNKNOWN_RECIPIENT', SysErrorMessage(hr)]);
        end;
    MAPI_E_USER_ABORT:
        begin
            es := 'The user canceled one of the dialog boxes. No message was sent.';
            raise Exception.CreateFmt('Error %s sending e-mail message: %s', ['MAPI_E_USER_ABORT', es]);
        end;
    else
        raise Exception.CreateFmt('Error %d sending e-mail message: %s', [hr, SysErrorMessage(hr)]);
    end;
end;

Note: Any code is released into the public domain. No attribution required.