Using Outlook for Microsoft Office 365 32 bit and Delphi XE8
I am evaluation Redemption.dll as Windows 11 has blocked my application from accessing Outlook.
I have Outlook configure with multiple profiles and stores
I can open the profiles find the store/account and can send/receive.
In one combination I do not receive the email.
The issue is with
Mail.ReplyRecipients.Add('xxx#gmail.com');
if I comment this line out I receive the email
In the sent folder I can dblclick and by clicking reply I see the Reply Address but in the inbox (of the recipient) I get nothing. I scanned all Outlook items to no avail.
In the reverse direction I do receive the email.
I intend ti purchase the product on completion of my evaluation. But this feature is important
procedure TForm61._Send(Profile, Sender, _To, _CC, _BCC, Subject, Body: String;
ReadReceiptRequested: Boolean; _Attachments: TArray<String>);
var
FolderFound: Boolean;
c, j, Count, k: Integer;
Session: IRDOSession;
Store: IRDOStore;
Drafts: IRDOFolder;
Recip: IRDORecipient;
MAil: IRDOMail;
IPMRoot: IRDOFolder;
ReplyRecip: IRDORecipient;
_name: String;
begin
Session:=CoRDOSession.Create;
Session.Logon(Profile,'', False, True,EmptyParam,EmptyParam);
Drafts:=Session.GetFolderFromPath('\\'+Sender+'\Inbox');
Mail:= Drafts.Items.Add(olMailItem);
Recip := Mail.Recipients.Add(_To);
Recip.type_:= olTo;
if not _CC.IsEmpty then
begin
Recip := Mail.Recipients.Add(_CC);
Recip.type_:= olCC;
end;
if not _BCC.IsEmpty then
begin
Recip := Mail.Recipients.Add(_BCC);
Recip.type_:= olBCC;
end;
// Recip.Resolve(EmptyParam, EmptyParam);
// ReplyRecip:=Mail.ReplyRecipients.Add('xxx#gmail.com');
Mail.ReplyRecipients.Add('xxx#gmail.com');
// ReplyRecip.Resolve(EmptyParam, EmptyParam);
for j:=0 to Length(_Attachments)- 1 do
Mail.Attachments.Add(_Attachments[j], EmptyParam, EmptyParam, EmptyParam);
Mail.Subject:=Subject;
Mail.ReadReceiptRequested:=ReadReceiptRequested;
Mail.HTMLBody:=Body;
if SignatureIndex>-1 then
begin
Signature:=Session.Signatures.Item(SignatureIndex+1); //noy zero-based
Signature.ApplyTo(Mail, False);
end;
Mail.OriginatorDeliveryReportRequested:=True;
Mail.Save;
Mail.Send;
end;
TIA
Ephraim
Related
I need to detect the Windows Firewall state (i.e. whether it is enabled or not) in order to display a message warning that a Firewall rule may need to be configured to allow inbound connections on specific ports when the Firewall is enabled, but not when it isn't. See below code example:
[Code]
//Check if Windows Firewall is enabled
function IsWindowsFirewallEnabled(): Boolean;
begin
//Method required here
Result := True;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
//Display a warning message on a Server install if Windows Firewall is enabled
if CurPageID = wpSelectComponents and IsComponentSelected('Server') and IsWindowsFirewallEnabled then
begin
MsgBox('Windows Firewall is currently enabled.' + #13#10 + #13#10 +
'You may need to enable inbound connections on ports 2638, 445 and 7.'
mbInformation, MB_OK);
Result := True;
end;
end;
What I need is a method for the IsWindowsFirewallEnabled function. One way I have read about, and ironically has now more or less been suggested below whilst I was in the middle of updating the question with this information anyway, would appear to be reading the EnableFirewall value from the Registry:
//Check if Windows Firewall is enabled
function IsWindowsFirewallEnabled(): Boolean;
var
crdFirewallState: Cardinal;
begin
RegQueryDwordValue(HKLM, 'SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile',
'EnableFirewall', crdFirewallState);
if crdFirewallState = 1 then
Result := True;
end;
However, I am not convinced by this method as the Registry values for all the profiles show enabled on my work PC, but looking in Control Panel the Domain profile shows disabled (I assume this is related to a Group Policy).
Note, that this needs to work for both Windows XP and Server 2003, and for Windows Vista and Server 2008 and above.
Therefore, what's the most reliable or recommended way to do this?
You would need to determine the registry entry and then query it in a manner similar to this using Innosetup's registry query ability.
var
Country: String;
begin
if RegQueryStringValue(HKEY_CURRENT_USER, 'Control Panel\International',
'sCountry', Country) then
begin
// Successfully read the value
MsgBox('Your country: ' + Country, mbInformation, MB_OK);
end;
end;
http://www.jrsoftware.org/ishelp/index.php?topic=isxfunc_regquerystringvalue
Allegedly this is the information for the registry key:
Path: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsFirewall\DomainProfile
Location: Local Machine
Value Name: EnableFirewall
Data Type: DWORD (DWORD Value)
Enabled Value: 0
Disabled Value: 1
The Windows ribbon framework markup supports an EnablePinning attribute for the recent items menu in the application menu:
<ApplicationMenu.RecentItems>
<RecentItems CommandName="MRU" EnablePinning="true" />
</ApplicationMenu.RecentItems>
I expected that there would be a matching property that can be queried/updated at runtime, but I can't find a property key. Does anyone know if there is one, and, if so, what it is?
Alternatively, is there another way to turn pinning on/off at runtime? Neither the element nor its parent support application modes.
TIA
Clarification: What I'm trying to do is enable/disable pinning for the entire menu at runtime. I'm not concerned about the pin states of the individual items.
I'm not sure if you can modify the pinned state from existing entries but it's definitely possible to programmatically query the state and add new items with a specific state using the UI_PKEY_Pinned property:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd940401(v=vs.85).aspx
Wrappers such as the Windows Ribbon Framework for Delphi or the Windows Ribbon for WinForms (.NET) provide an easy access to the API model. This CodeProject article also describes how to query/add recent items using C#.
If you want to change the state during runtime, you could for example query the state of all items, remove them from the list, adjust whetever you need and add them to the list again. Didn't do that yet, could be worth a try however.
Hmm... this will be quite difficult to accomplish as the flag is defined in the XML which will be compiled into a resource file that is linked to the application and then loaded on start up. You could create another resource definition and reload the ribbon if you want to disable/enable the flagging, but that's quite a lot overhead and certainly noticeable from an users perspective as it requires the creation of a new window handle.
I place the recent items by inside UpdateProperty
TRecentItem = class(TInterfacedObject, IUISimplePropertySet)
private
FRecentFile: TSSettings.TRecentFile;
protected
function GetValue(const key: TUIPropertyKey; out value: TPropVariant): HRESULT; stdcall;
public
procedure Initialize(const RecentFile: TSSettings.TRecentFile); safecall;
end;
function TMyForm.UpdateProperty(commandId: UInt32; const key: TUIPropertyKey;
currentValue: PPropVariant; out newValue: TPropVariant): HRESULT;
var
I: Integer;
psa: PSafeArray;
pv: Pointer;
RecentItem: TRecentItem;
begin
if (key = UI_PKEY_RecentItems) then
begin
psa := SafeArrayCreateVector(VT_UNKNOWN, 0, Settings.RecentFiles.Count);
if (not Assigned(psa)) then
Result := E_FAIL
else
begin
for I := 0 to Settings.RecentFiles.Count - 1 do
begin
RecentItem := TRecentItem.NewInstance() as TRecentItem;
RecentItem.Initialize(Settings.RecentFiles[I]);
pv := Pointer(IUnknown(RecentItem));
Check(SafeArrayPutElement(psa, I, pv^));
end;
Result := UIInitPropertyFromIUnknownArray(UI_PKEY_RecentItems, psa, PropVar);
SafeArrayDestroy(psa);
end;
end;
If a pin was changed, I get this command while closing the application menu:
function TMyForm.Execute(commandId: UInt32; verb: _UIExecutionVerb;
key: PUIPropertyKey; currentValue: PPropVariant;
commandExecutionProperties: IUISimplePropertySet): HRESULT; stdcall;
var
Count: Integer;
I: Integer;
Pinned: Boolean;
psa: PSafeArray;
pv: IUnknown;
RecentFile: UInt32;
SimplePropertySet: IUISimplePropertySet;
Value: TPropVariant;
begin
if ((commandId = cmdAppRecentItems)
and Assigned(key) and (key^ = UI_PKEY_RecentItems)
and Assigned(currentValue) and (currentValue^.vt = VT_ARRAY + VT_UNKNOWN)) then
begin
psa := nil;
Result := UIPropertyToIUnknownArrayAlloc(key^, currentValue^, psa);
if (Succeeded(Result)) then
begin
Result := SafeArrayGetUBound(psa, 1, Count);
for I := 0 to Count do
if (Succeeded(Result)) then
begin
Result := SafeArrayGetElement(psa, I, pv);
if (Succeeded(Result) and Assigned(pv)) then
begin
Result := pv.QueryInterface(IUISimplePropertySet, SimplePropertySet);
if (Succeeded(Result)) then
Result := SimplePropertySet.GetValue(UI_PKEY_Pinned, Value);
if (Succeeded(Result)) then
Result := UIPropertyToBoolean(UI_PKEY_Pinned, Value, Pinned);
if (Succeeded(Result)) then
Settings.RecentFiles.SetPinned(I, Pinned);
end;
end;
SafeArrayDestroy(psa);
end;
end
end;
... but I didn't find a documentation of this solution.
Is it possible to remove NotifyIcon from the notification area (system tray) when an app terminates abruptly?
if no, how can I remove it when the app runs for the next time?
Abruptly? No. Your program has ceased to exist, so there's no opportunity to run any code to tell the shell that it should remove the icon.
To remove the icon, move your mouse over it. The shell will try to notify your program, realize there's nothing there anymore, and remove the icon by itself.
On Windows 7 and later, notify icons can be identified by a user-defined GUID. On earlier versions, they are identified by a combination of HWND and ID number instead. Since your app is not guaranteed to get the same HWND value the next time it runs, the only way you can do anything to an old icon that is identified by HWND is if you remembered the previous HWND value so you can use it to remove the old icon, before then using a new HWND to add a new icon. But with a GUID-identified icon, the GUID needs to be persistent (as it is stored in the Registry to store app settings associated with the icon), so you should be able to simply keep updating the existing icon as needed, or remove it if desired.
FWIW, since code doesn't exist so far, I thought I'd throw this in. I don't know if it will help or not for the OP, but it should be good guidance in the right direction.
unit csystray;
{ removes dead system tray icons, by Glenn1234 # stackoverflow.com
since this uses "less than supported by Microsoft" means, it may
not work on all operating system. It was tested on Windows XP }
interface
uses commCtrl, shellapi, windows;
type
TTrayInfo = packed record
hWnd: HWnd;
uID: UINT;
uCallBackMessage: UINT;
Reserved1: array[0..1] of longint;
Reserved2: array[0..2] of longint;
hIcon: HICON;
end;
PTBButton = ^TTBButton;
_TBBUTTON = packed record
iBitmap: Integer;
idCommand: Integer;
fsState: Byte;
fsStyle: Byte;
bReserved: array[1..2] of Byte;
dwData: Longint;
iString: Integer;
end;
TTBButton = _TBBUTTON;
procedure RemoveStaleTrayIcons;
implementation
procedure RemoveStaleTrayIcons;
const
VMFLAGS = PROCESS_VM_OPERATION or PROCESS_VM_READ OR PROCESS_VM_WRITE;
var
ProcessID: THandle;
ProcessHandle: THandle;
trayhandle: HWnd;
ExplorerButtonInfo: Pointer;
i: integer;
ButtonCount: Longint;
BytesRead: Longint;
ButtonInfo: TTBButton;
TrayInfo: TTrayInfo;
ClassNameA: Array[0..255] of char;
outlen: integer;
TrayIconData: TNotifyIconData;
begin
// walk down the window hierarchy to find the notification area window
trayhandle := FindWindow('Shell_TrayWnd', '');
trayhandle := FindWindowEx(trayhandle, 0, 'TrayNotifyWnd', nil);
trayhandle := FindWindowEx(trayhandle, 0, 'SysPager', nil);
trayhandle := FindWindowEx(trayhandle, 0, 'ToolbarWindow32', nil);
if trayhandle = 0 then exit;
// find the notification area process and open it up for reading.
GetWindowThreadProcessId(trayhandle, #ProcessID);
ProcessHandle := OpenProcess(VMFLAGS, false, ProcessID);
ExplorerButtonInfo := VirtualAllocEx(ProcessHandle, nil, Sizeof(TTBButton),
MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE);
// the notification area is a tool bar. Get the number of buttons.
ButtonCount := SendMessage(trayhandle, TB_BUTTONCOUNT, 0, 0);
if ExplorerButtonInfo <> nil then
try
// iterate the buttons & check.
for i := (ButtonCount - 1) downto 0 do
begin
// get button information.
SendMessage(trayhandle, TB_GETBUTTON, i, LParam(ExplorerButtonInfo));
ReadProcessMemory(ProcessHandle, ExplorerButtonInfo, #ButtonInfo,
Sizeof(TTBButton), BytesRead);
// if there's tray data, read and process
if Buttoninfo.dwData <> 0 then
begin
ReadProcessMemory(ProcessHandle, PChar(ButtonInfo.dwData),
#TrayInfo, Sizeof(TTrayInfo), BytesRead);
// here's the validation test, this fails if the master window is invalid
outlen := GetClassName(TrayInfo.hWnd, ClassNameA, 256);
if outlen < 1 then
begin
// duplicate the shell icon removal, i.e. my component's DeleteTray
TrayIconData.cbSize := sizeof(TrayIconData);
TrayIconData.Wnd := TrayInfo.hWnd;
TrayiconData.uID := TrayInfo.uID;
TrayIconData.uCallbackMessage := TrayInfo.uCallBackMessage;
Shell_NotifyIcon(NIM_DELETE, #TrayIconData);
end;
end;
end;
finally
VirtualFreeEx(ProcessID, ExplorerButtonInfo, Sizeof(TTBButton), MEM_RELEASE);
end;
end;
end.
Is there a way to disable the Components Page for Upgrades? I would like to enable upgrades of my software but I don't want to allow the users to change the selection of components in case of an upgrade.
Instead the installer you upgrade all existing components from the first installation.
I am worried that it the user selects less components during the upgrade those missing components will stay installed as the old version and you get a mess.
I added the following to my script:
[Setup]
DisableDirPage=auto
DisableProgramGroupPage=auto
DirExistsWarning=auto
I just need a way to disable the components page and use the selection of the previous install (full install) for the upgrade. Is that possible?
I have found a related directive:
[Setup]
UsePreviousTasks=true
UsePreviousTasks is reading the existing section out of the registry which is good. Now I need to find a way to hide the selection window.
Thanks,
Wolfgang
To hide a page from user use the ShouldSkipPage event method. If you return True in this method, the page won't be shown to user. If False, the page will be displayed as usually. Here 's an example of how to check if the installation is an upgrade and if so, skip the Select Components wizard page:
[Setup]
AppId=B75E4823-1BC9-4AC6-A645-94027A16F5A5
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
; here is the place for your [Components] section and the rest of your script
[Code]
const
UninstallKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1';
function IsUpgrade: Boolean;
var
Value: string;
begin
Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> '');
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := (PageID = wpSelectComponents) and IsUpgrade;
end;
Another option you mentioned might be to disable all the controls of the page. The next script shows as the previous one how to check if the installation is an upgrade and if so, disables all the controls on the Select Components wizard page:
[Setup]
AppId=B75E4823-1BC9-4AC6-A645-94027A16F5A5
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
; here is the place for your [Components] section and the rest of your script
[Code]
const
UninstallKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1';
function IsUpgrade: Boolean;
var
Value: string;
begin
Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> '');
end;
procedure DisablePageControls(Page: TNewNotebookPage);
var
I: Integer;
begin
Page.Enabled := False;
for I := 0 to Page.ControlCount - 1 do
Page.Controls[I].Enabled := False;
end;
procedure InitializeWizard;
begin
if IsUpgrade then
DisablePageControls(WizardForm.SelectComponentsPage);
end;
The IsUpgrade function mentioned in TLama's answer has a bug. If AppId starts with a "{" which must be doubled, this isn't resolved and they registry key will not be found. Here's a corrected function that works for me:
function IsUpgrade: Boolean;
var
Value: string;
UninstallKey: string;
begin
UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' +
ExpandConstant('{#SetupSetting("AppId")}') + '_is1';
Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> '');
end;
Leave the separate const away for this function, it won't work with that extra function call.
Apart from that, 64-bit systems don't seem to cause any issues. If InnoSetup runs in 32-bit mode, the registry virtualisation is in effect and redirects you to the correct key already.
Something like that:
if CurPageID=wpSelectComponents then
begin
if ExtraOptionAvailable() then
begin
Wizardform.ComponentsList.Checked[6] := true;
Wizardform.ComponentsList.ItemEnabled[6] := true;
end else begin
Wizardform.ComponentsList.Checked[6] := false;
Wizardform.ComponentsList.ItemEnabled[6] := false;
end;
end;
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.
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.