x-header missing for Outlook MeetingItems when received - vsto

I am using the following approach in a VSTO Outlook Addin (using Addin-Express library) to set a custom x-header attribute on mails and other items you can send from Microsoft Outlook, like meetings. It's a security classification which is evaluated by a mail gateway appliance for making sure encryption is activated on outgoing mails, depending on the classification. Inside the organization, it will just be displayed on the receiving side, which is Outlook 2016 desktop client.
Before sending, I set the x-header property like this:
string headerNamespace = "http://schemas.microsoft.com/mapi/string /{00020386-0000-0000-C000-000000000046}/";
public void SetHeader(PropertyAccessor acc, string header, string value)
{
acc.SetProperty(headerNamespace + "x-mycustomheader", value);
}
I always receive the header for mails, but not for Meetings. I know there is an associated appointment, but I tried reading the header from any object I could think of
On the receiving end, I get the current item from the explorer or inspector window and try to retrieve that header. The attribute is not visible in OutlookSpy and it's not inside the transport header. Is it possible that Outlook stored it somewhere else, or has it been removed? This process is working fine for MailItem types.
I stripped some parts like releasing the com objects for better readability. OutlookMeetingItem2 is a wrapper class. I have two alternative methods for reading the header, one is the PropertyAccessor and the other is used here, both can't find the header attribute.
using (OutlookMessageItemWrapper outlookItem = OutlookMessageItemFactory.GetMessageItem(currentItem))
{
object outlookitemMapi = outlookItem.MAPIOBJECT;
classificationString = mapi.GetHeader(outlookItem.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
if (classificationString == "" && outlookItem is OutlookMeetingItem2)
{
OutlookMeetingItem2 meetingItem = outlookItem as OutlookMeetingItem2;
MeetingItem meeting = meetingItem.Item;
object mapiObject = meeting.MAPIOBJECT;
classificationString = mapi.GetHeader(meeting.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
if (classificationString == "")
{
AppointmentItem appointment = meeting.GetAssociatedAppointment(false);
if (appointment != null)
{
classificationString = mapi.GetHeader(appointment.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
}
}
}
}

Related

Using mailitem.PrintOut() to print a single page?

I'm working on a simple Outlook 2016/2019 VSTO plugin.
When an email is selected and a ribbon button is pressed, it needs to print just the first page of the email to the default printer. mailitem.PrintOut(); works, but will print the whole email. Is there a way to specify the first page only?
var m = e.Control.Context as Inspector;
var mailitem = m.CurrentItem as MailItem;
if (mailitem != null)
{
mailitem.PrintOut();
}
Update: See my answer for the code I used to get this working.
The Outlook object model doesn't provide any property or method for that. You need to parse the message body on your own and use .net mechanisms for printing this piece on your own.
Note, you may try using the Word object model for printing the message bodies (a specific range of pages). The Document.PrintOut method prints all or part of the specified document. Optional parameters allow specifying the page range.
The Outlook object model provides three main ways for working with item bodies:
Body - a string representing the clear-text body of the Outlook item.
HTMLBody - a string representing the HTML body of the specified item.
Word editor - the Microsoft Word Document Object Model of the message being displayed. The WordEditor property of the Inspector class returns an instance of the Document class from the Word object model which you can use to deal with the message body.
You can read more about all these ways in the Chapter 17: Working with Item Bodies.
As #Eugene said, there's no way to specify a single page using mailItem.PrintOut.
I've finally managed to find a way to do this. I save the document as a .doc file in the temp directory, then using Microsoft.Office.Interop.Word to setup the page margins / size and then send the current page to the printer. Hopefully this helps someone as I couldn't find any working examples for c#!
private void btnPrintOnePage_Click(object sender, RibbonControlEventArgs e)
{
string randFile = Path.GetTempPath() + "POP_" + RandomString(35) + ".doc";
var m = e.Control.Context as Inspector;
var mailitem = m.CurrentItem as MailItem;
if (mailitem != null)
{
mailitem.SaveAs(randFile, OlSaveAsType.olDoc);
Word.Application ap = new Word.Application();
Word.Document document = ap.Documents.Open(randFile);
document.PageSetup.PaperSize = Word.WdPaperSize.wdPaperA4;
document.PageSetup.TopMargin = 25;
document.PageSetup.RightMargin = 25;
document.PageSetup.BottomMargin = 25;
document.PageSetup.LeftMargin = 25;
Word.WdPrintOutRange printRange = Word.WdPrintOutRange.wdPrintCurrentPage;
document.PrintOut(false,null,printRange);
document.Close(false, false, false);
File.Delete(randFile);
}
}
public static string RandomString(int length)
{
Random random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}

VSTO Outlook Plugin: Cannot get AppointmentItem in Item_Change event when recurring appointment is dragged and dropped by user

I want to catch the change event on an AppointmentItem. I use Outlook 2017 for tests.
To achieve I use:
I attached the events like this:
public void AttachEvents()
{
_CalendarItems.ItemAdd += Item_Add;
_CalendarItems.ItemChange += Item_Change;
_DeletedItems.ItemAdd += Item_Delete_Add;
The Item_Change method looks like this:
public void Item_Change(Object item)
{
if (item != null && item is Outlook.AppointmentItem)
{
Outlook.AppointmentItem myAppointment = item as Outlook.AppointmentItem;
To test the code I created a recurring appointment series. I double-clicked on appointment in the calendar and entered some title and body and saved.
Now I started my code and inspected the item.
Unfortunately, item points to the series and NOT to the individual appointment when item changed is initiated.
How can I retrieve the actual AppointmentItem when Item_Changed is initiated?
Related Stackoverflow Posting: Outlook Addin: Moving Appointment in Calendar does not reflect new date/time in AppointmentItem (catch Calendar.ItemChange) But still there is no solution to this
More on this topic:
https://www.add-in-express.com/forum/read.php?FID=5&TID=15384
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/4ec55891-fb64-408f-b1cf-4bf05765b866/outlook-get-original-time-of-recurring-exception-item-that-is-opened-with-drag-drop?forum=vsto
Exceptions are not actual appointments - they are stored as embedded message attachments on the master appointments. You get the master appointment, and you would need to access its exceptions to see what changed.
There is a tricky solution to that.
Assumption: The user uses the calendar view to change the item. As the event is thrown by the calendar view this should be true in all cases:
_CalendarItems = calendarFolder.Items;
_CalendarItems.ItemChange += Item_Change;
[...]
now we can use the CalendarView to calculate the selected Startdate and compare it to all Exceptions stored in the RecurrencePattern ...
if (myAppointment.IsRecurring)
{
// in case of recurring appointments at this point we always get
// only a reference to the series master NOT the occurrence
// Assumption: The user clicked on the AppointmentItem in the calendar view
// So we can calculate the selected Start Time from this selection range
// then compare this against all Exceptions in the OccurrencePattern of the recurring pattern
// if we find one AppointmentItem in the Exceptions which has the same DateTime then we found the correct one.
//
Outlook.Application application = new Outlook.Application();
Outlook.Explorer explorer = application.ActiveExplorer();
Outlook.Folder folder = explorer.CurrentFolder as Outlook.Folder;
Outlook.View view = explorer.CurrentView as Outlook.View;
// get the current calendar view
if (view.ViewType == Outlook.OlViewType.olCalendarView)
{
Outlook.CalendarView calView = view as Outlook.CalendarView;
Outlook.RecurrencePattern pattern = myAppointment.GetRecurrencePattern();
for (int i = 1; i <= pattern.Exceptions.Count; i++)
{
Outlook.Exception myException = pattern.Exceptions[i];
Outlook.AppointmentItem exceptionItem = myException.AppointmentItem;
DateTime itemDateStart = exceptionItem.Start;
if (itemDateStart == calView.SelectedStartTime)
{
updateMyPluginMeeting(exceptionItem);
return; // the use may only select on AppointmentItem so we can skip the rest
}
}
}
}
If you know any better solution to this let me know.

Email sent from C# OOM stays in Outbox if Outlook is closed until next Outlook start

I'm trying to send emails from a .NET application using Outlook Object Model.
My application displays the Outlook message window so the user can see what we're sending and edit it first. When the user hits the Send button, the Outlook window closes, and the message gets sent. This works perfectly as long as the Outlook application is already running.
If the Outlook application isn't already running, the message gets stuck in the Outbox, and will not send until I start Outlook. When I start Outlook, I can see the message sitting in the Outbox folder for a few seconds, then it gets sent.
I need to show the New Message form to Outlook user to select the recipient(s) and possibly edit the message before sending.
Note: I know that this question was already asked here Email sent with Outlook Object Model stays in Outbox until I start Outlook
and the solution exists, but it is not provided (only the small hint is provided) and unfortunately I cannot ask for clarification / code example because I have not enough "reputation".
I tried to write my own implementation of the hint provided, but the SyncEnd event is fired only when Outlook is already open (just to remind, the question is about the case then Outlook is closed).
My code below. What is wrong?
using Microsoft.Office.Interop.Outlook;
using OutlookApp = Microsoft.Office.Interop.Outlook.Application;
class Mailer
{
AutoResetEvent mailSentEvent = new AutoResetEvent(false);
public void CreateMail()
{
OutlookApp outlookApp = null;
MailItem mailItem = null;
try
{
outlookApp = new OutlookApp();
mailItem = outlookApp.CreateItem(OlItemType.olMailItem);
mailItem.Subject = "Test Message";
mailItem.Body = "This is the message.";
string reportPath = #"C:\temp\aaaaa.pdf";
mailItem.Attachments.Add(reportPath);
mailItem.Display(true);
StartSync(outlookApp);
bool result = mailSentEvent.WaitOne();
}
catch (System.Exception)
{
throw;
}
finally
{
if (mailItem != null) Marshal.ReleaseComObject(mailItem);
if (outlookApp != null) Marshal.ReleaseComObject(outlookApp);
}
}
private static SyncObject _syncObject = null;
private void StartSync(OutlookApp outlookApp)
{
var nameSpace = outlookApp.GetNamespace("MAPI");
_syncObject = nameSpace.SyncObjects[1];
_syncObject.SyncEnd += new Microsoft.Office.Interop.Outlook.SyncObjectEvents_SyncEndEventHandler(OnSyncEnd);
_syncObject.Start();
}
private void OnSyncEnd()
{
mailSentEvent.Set();
}
}
the SyncEnd event is fired only when Outlook is already open
That is not true. The SyncObjects collection contains all Send\Receive groups. You need to iterate over all objects in the collection and call the Start method, for example:
Set sycs = nsp.SyncObjects
For i = 1 To sycs.Count
Set syc = sycs.Item(i)
strPrompt = MsgBox("Do you wish to synchronize " &; syc.Name &;"?", vbYesNo)
If strPrompt = vbYes Then
syc.Start
End If
Next

Creating a copy of MailItem and then deleting creates a new mail in the same folder

I created a copy of a mail in my Inbox folder by calling MailItem.copy() function and then I deleted the newly created mail item.
Following is sample code:
IDispatch * lDispatch;
_MailItemPtr lReadModeMailItem;
HRESULT lReturn = mMailItem->Copy(&lDispatch);
if (lReturn != S_OK)
{
return;
}
lReturn = lDispatch->QueryInterface(IID__MailItem, (LPVOID*)&lReadModeMailItem);
if (lReturn != S_OK || lReadModeMailItem == NULL)
{
return;
}
lReadModeMailItem->Close(olDiscard);
HRESULT lMyRet = lReadModeMailItem->Delete();
if (lMyRet != S_OK)
{
return;
}
lDispatch->Release();
After I am done executing this code, 2 new mail items are created in my Inbox folder.
Then, if I move to any other folder and come back to Inbox or restart Outlook, then 1 new mail item remains.
Why does 1 extra mail item remain in Inbox, even when I called MailItem.Delete() function?
How can I permanently delete the mail item created using MailItem.Copy() ?
That's the lack of IMAP accounts in Outlook.
You may consider using the following workarounds to get rid of such issues:
Programmatically switch folders in Outlook by setting the Explorer.CurrentFolder property which allows setting a Folder object that represents the current folder displayed in the explorer.
Use the SyncObject.Start method which begins synchronizing a user's folders using the specified Send\Receive group.

How to include Voting Button in Outlook Mail in VB.NET

I'm fairly new to VB and I have created an email to be sent to a recipient which is coded in vb from the example; How to: Programmatically Create an E-Mail Item
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
CreateMailItem();
}
private void CreateMailItem()
{
Outlook.MailItem mailItem = (Outlook.MailItem)
this.Application.CreateItem(Outlook.OlItemType.olMailItem);
mailItem.Subject = "This is the subject";
mailItem.To = "someone#example.com";
mailItem.Body = "This is the message.";
mailItem.Importance = Outlook.OlImportance.olImportanceLow;
mailItem.Display(false);
}
Furthermore, I briefly looked at the Outlook MAPI refrence today since it's used in my office but I'm confused on how to proceed.
What I want to include in the email is a voting button e.g Approve/Reject so I can filter the received email from the Recipient based on the subject "Approve: blabla" using the MAPI .
P.S Outlook gives the subject title of the response in either "Approve:blasddd" or "Reject:hjjkkkk" . Please any suggestion will be greatly appreciated.
You need to use the MailItem.VotingOptions property. See https://msdn.microsoft.com/en-us/library/office/ff424466.aspx