Creating an On-send prompt in Outlook with VSTO and in C# - c#

I have a requirement to intercept the sending of a new email. All I want to do initially is ask the user "Are you sure you want to send?" and then to either proceed with the sending or cancel it depending on their response.
I found this code snippet which looks perfect for my needs but couldn't get it to work in a Win Forms test application either in VB.Net or after trying to convert it to C#. It then occurred to me that the code may only work in a VSTO Add-in (Is this correct?).
So I then used this Walkthrough to create a VSTO Add-in in C# and made sure that it worked as described, which it does (it pumps some text into the Subject and Body of a new message).
I have tried to add the first example which is in VB.Net into the working C# example but I'm a novice and don't know enough about VSTO or C# to see where I'm going wrong.
The code compiles without errors but when run, Outlook takes a long time loading the Add-in and displays a dialogue stating:
An add-in could not be found or loaded
and then:
Could not create an instance of startup object
PromptToFile_Plug_in.ThisAddIn in assembly PromptToFile Plug-in,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
Where am I going wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Diagnostics;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using System.Windows.Forms;
namespace PromptToFile_Plug_in
{
public partial class ThisAddIn
{
Outlook.Inspectors inspectors;
Outlook.Application myOlApp = new Outlook.Application();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector +=
new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
}
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem != null)
{
if (mailItem.EntryID == null)
{
mailItem.Subject = "This text was added by using code";
mailItem.Body = "This text was added by using code";
}
}
}
private void Initialize_handler()
{
myOlApp = this.Application;
}
private void myOlApp_ItemSend(object Item, bool Cancel)
{
string prompt;
// prompt = "Are you sure you want to send " + Item.Subject + "?";
prompt = "Are you sure you want to send?";
MessageBox.Show(prompt, "Prompt to File", MessageBoxButtons.OKCancel);
//if (MessageBox.(prompt, Constants.vbYesNo + Constants.vbQuestion, "Sample") == Constants.vbNo)
// Cancel = true;
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}

First of all, there is no need to create a new Outlook Application instance in the add-in class:
Outlook.Application myOlApp = new Outlook.Application();
Instead, use the Application property available in your add-in class.
Second, there is no need to keep the following method because it will never be called:
private void Initialize_handler()
{
myOlApp = this.Application;
}
Unlike VBA, you can't subscribe to the events just declaring the source objects with keywords. Instead, you need to subscribe to the events in the code like you did for the NewInspector event. For example, the following code can be used for handling the ItemSend event in VSTO add-ins:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
void Application_ItemSend(object Item, ref bool Cancel)
{
if (Item is Outlook.MailItem)
{
Outlook.MailItem mail = (Outlook.MailItem)Item;
// your code goes here
}
}
Finally, pay attention to the ref attribute for the Cancel parameter in the event handler signature.

Related

How do I create an Outlook add-in that alerts the user when they attempt to reply to senders that are outside of their domain?

I am new to coding in c#. I am currently trying to create an Outlook add-in to prompt and alert users if they are to attempt to reply to anyone that is not from "#abc.com". For example if 'ben#abc.com' is to trying to reply to 'jack#def.com', a window to alert Ben will be prompted warning him "You are about to reply to someone that is not from '#abc.com'." with the options of 'ok' and 'cancel'.
I referred online for the code below but this add-in only allows me to edit the field values of the email I am trying to send. I am unable to even figure out how to address and implement the code to deal with replying. I have tried researching online and have seen methods like .reply() but I am confused as to how to apply them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
namespace FirstOutlookAddIn
{
public partial class ThisAddIn
{
Outlook.Inspectors inspectors;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector +=
new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem != null)
{
if (mailItem.EntryID == null)
{
mailItem.To = "Testing for Recipient.";
mailItem.Subject = "Currently testing add-in for Subject.";
mailItem.Body = "Currently testing add-in for Body.";
mailItem.CC = "Testing for CC.";
}
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
Firstly, you are only tracking Inspectors.NewInspector event. But in most cases replies will be inline - you also need Explorer.InlineResponse event (where Explorer comes from Application.ActiveExplorer, which can be null on startup, so you'd also need Application.Explorers.NewExplorer event).
Secondly, you will need to loop through all recipients in the mailItem.Recipients collection, and for each Recipient, check the Recipient.Address property (which you can test for the match).
Listen to send event, as it will be triggered irrespective of inline replies or inspector level replies.
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
then implementation of this can be done as below,
/// param : item -> mail item to be sent
/// param : cancel -> allows you to cancel sending mail in outlook. By default value is false.
private void Application_ItemSend(object item, ref bool cancel){
Outlook.MailItem mail = (Outlook.MailItem)item;
// read "mail.To" and check for your logic;
// if To list has other domains, then show the warning prompt maybe like below,
var result = MessageBox.Show("yourmessage","title",MessageBoxButtons.YesNo);
if(result == DialogResult.No)
cancel = true; // setting this to `true` will stop sending an email out.
}
you can read on this event in MS blog here Item_Send

COMException in Outlook Online (non-cached) Mode only

The example code shown below illustrates the issue I'm having, namely when I start Outlook in Online mode I cannot get most of the properties of the mail item passed to the OutboxItems_ItemAdd handler. The error returned is:
Attachments = {System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x8004010F
--- End of inner exception stack trace ---
at System.Runtime...
I do NOT get this error when attempting to retrieve the properties of the mail item in the SentItems_ItemAdd handler. Also, it is important to note that everything works perfectly when in Outlook cached mode; the issue in the Outbox handler is only when Outlook starts in Online mode. Is this a bug, or am I just doing something wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
namespace OnlineErrorTest{
public partial class ThisAddIn{
Outlook.Folder sentFolder;
Outlook.Folder outboxFolder;
Outlook.Items sentItems;
Outlook.Items outboxItems;
private void ThisAddIn_Startup(object sender, System.EventArgs e) {
sentFolder = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail) as Outlook.Folder;
outboxFolder = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderOutbox) as Outlook.Folder;
sentItems = sentFolder.Items;
outboxItems = outboxFolder.Items;
sentItems.ItemAdd += SentItems_ItemAdd;
outboxItems.ItemAdd += OutboxItems_ItemAdd;
}
private void OutboxItems_ItemAdd(object Item) {
Outlook.MailItem mi = Item as Outlook.MailItem;
Outlook.Recipients r = mi.Recipients; //CAUSES EXCEPTION //System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x8004010F
}
private void SentItems_ItemAdd(object Item) {
Outlook.MailItem mi = Item as Outlook.MailItem;
Outlook.Recipients r = mi.Recipients; //WORKS FINE
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e) {
}
private void InternalStartup(){
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
}
}
The error code is MAPI_E_NOT_FOUND, which means the item no longer exists - this is hardly surprising: by the time your code gets to it, Exchange Server most likely already sent the message and moved it to the Sent Items folder.
You should never touch the messages being submitted - even if you succeed in doing so, touching an item with OOM aborts the submission process. Use Application.ItemSend event instead - it is your last chance to access the item before it is handed over to the spooler and sent.

c# VSTO plugin for Outlook, that would download new messages when mailbox is in header only mode

I have a mailbox that can only operate in header only mode for Outlook. Header mode sucks to use day-to-day. What I want to do is set Outlook to header only mode for the entire mailbox, BUT automatically download full-items for all newly arrived messages.
I grabbed some code from MS website, but I cannot find a method on the mailitem object that does: "Download the rest of this message now" for that new mail item. Here is what I have so far, below. I think the simplest approach is to check for the download state property (https://msdn.microsoft.com/en-us/VBA/Outlook-VBA/articles/mailitem-downloadstate-property-outlook) if it is not downloaded, then "Download teh rest of this message now".
All of this would happen with a new mail item event. I am NOT a programmer. I know nothing. Any suggestions are much appreciated.
public partial class ThisAddIn {
Outlook.NameSpace outlookNameSpace;
Outlook.MAPIFolder inbox;
Outlook.Items items;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
outlookNameSpace = this.Application.GetNamespace("MAPI");
inbox = outlookNameSpace.GetDefaultFolder(
Microsoft.Office.Interop.Outlook.
OlDefaultFolders.olFolderInbox);
items = inbox.Items;
items.ItemAdd +=
new Outlook.ItemsEvents_ItemAddEventHandler(items_ItemAdd);
}
void items_ItemAdd(object Item)
{
//CHECK IF MAIL IS HEADER-ONLY. IF IT IS, DOWNLOAD FULL ITEM.
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
The message will be downloaded on demand when you touch it with the Outlook Object Model.

How to handle WPF WebBrowser control navigation exception

Let's say that WPF WebBrowser control shows some navigation errors and the page is not showing.
So there is an exception of WPF WebBrowser control.
I found some similar questions here but it is not what I need.
In fact, I need some method and object that has an exception to get it somehow.
How do we can handle it?
Thank you!
P.S. There is some approach for WinForm WebBrowser Control... Can we do something similar to WPF WebBrowser control?
public Form13()
{
InitializeComponent();
this.webBrowser1.Navigate("http://blablablabla.bla");
SHDocVw.WebBrowser axBrowser = (SHDocVw.WebBrowser)this.webBrowser1.ActiveXInstance;
axBrowser.NavigateError +=
new SHDocVw.DWebBrowserEvents2_NavigateErrorEventHandler(axBrowser_NavigateError);
}
void axBrowser_NavigateError(object pDisp, ref object URL,
ref object Frame, ref object StatusCode, ref bool Cancel)
{
if (StatusCode.ToString() == "404")
{
MessageBox.Show("Page no found");
}
}
P.S. #2 To host WinForm WebBrowser control under WPF App is not an answer I think.
I'm struggling with a similar problem. When the computer loses internet connection we want to handle that in a nice way.
In the lack of a better solution, I hooked up the Navigated event of the WebBrowser and look at the URL for the document. If it is res://ieframe.dll I'm pretty confident that some error has occurred.
Maybe it is possible to look at the document and see if a server returned 404.
private void Navigated(object sender, NavigationEventArgs navigationEventArgs)
{
var browser = sender as WebBrowser;
if(browser != null)
{
var doc = AssociatedObject.Document as HTMLDocument;
if (doc != null)
{
if (doc.url.StartsWith("res://ieframe.dll"))
{
// Do stuff to handle error navigation
}
}
}
}
It's an old question but since I have just suffered through this, I thought I may as well share. First, I implemented Markus' solution but wanted something a bit better as our Firewall remaps 403 message pages.
I found an answer here (amongst other places) that suggests using NavigationService as it has a NavigationFailed event.
In your XAML, add:
<Frame x:Name="frame"/>
In your code-behind's constructor, add:
frame.Navigated += new System.Windows.Navigation.NavigatedEventHandler(frame_Navigated);
frame.NavigationFailed += frame_NavigationFailed;
frame.LoadCompleted += frame_LoadCompleted;
frame.NavigationService.Navigate(new Uri("http://theage.com.au"));
The handlers can now deal with either a failed navigation or a successful one:
void frame_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
{
e.Handled = true;
// TODO: Goto an error page.
}
private void frame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
System.Diagnostics.Trace.WriteLine(e.WebResponse.Headers);
}
BTW: This is on the .Net 4.5 framework
It is also possible to use dynamic approach here.
wb.Navigated += delegate(object sender, NavigationEventArgs args)
{
dynamic doc = ((WebBrowser)sender).Document;
var url = doc.url as string;
if (url != null && url.StartsWith("res://ieframe.dll"))
{
// Do stuff to handle error navigation
}
};
I'd been struggling with this issue for some time. I discovered a cleaner way to handle this than the accepted answer. Checking for res://ieframe.dll didn't always work for me. Sometimes the document url is null when a navigation error happened.
Add the following References to you project:
Microsoft.mshtml
Microsoft.VisualStudio.OLE.Interop
SHDocVw (Under COM it's called "Microsoft Internet Controls")
Create the following helper class:
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Navigation;
/// <summary>
/// Adds event handlers to a webbrowser control
/// </summary>
internal class WebBrowserHelper
{
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "consistent naming")]
private static readonly Guid SID_SWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
internal WebBrowserHelper(WebBrowser browser)
{
// Add event handlers
browser.Navigated += this.OnNavigated;
// Navigate to about:blank to setup the browser event handlers in first call to OnNavigated
browser.Source = null;
}
internal delegate void NavigateErrorEvent(string url, int statusCode);
internal event NavigateErrorEvent NavigateError;
private void OnNavigated(object sender, NavigationEventArgs e)
{
// Grab the browser and document instance
var browser = sender as WebBrowser;
var doc = browser?.Document;
// Check if this is a nav to about:blank
var aboutBlank = new Uri("about:blank");
if (aboutBlank.IsBaseOf(e.Uri))
{
Guid serviceGuid = SID_SWebBrowserApp;
Guid iid = typeof(SHDocVw.IWebBrowser2).GUID;
IntPtr obj = IntPtr.Zero;
var serviceProvider = doc as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
if (serviceProvider?.QueryService(ref serviceGuid, ref iid, out obj) == 0)
{
// Set up event handlers
var webBrowser2 = Marshal.GetObjectForIUnknown(obj) as SHDocVw.IWebBrowser2;
var webBrowserEvents2 = webBrowser2 as SHDocVw.DWebBrowserEvents2_Event;
if (webBrowserEvents2 != null)
{
// Add event handler for navigation error
webBrowserEvents2.NavigateError -= this.OnNavigateError;
webBrowserEvents2.NavigateError += this.OnNavigateError;
}
}
}
}
/// <summary>
/// Invoked when navigation fails
/// </summary>
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "consistent naming")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:FieldNamesMustBeginWithLowerCaseLetter", Justification = "consistent naming")]
private void OnNavigateError(object pDisp, ref object URL, ref object Frame, ref object StatusCode, ref bool Cancel)
{
this.NavigateError.Invoke(URL as string, (int)StatusCode);
}
}
Then in your window class:
// Init the UI
this.InitializeComponent();
this.WebBrowserHelper = new WebBrowserHelper(this.MyBrowserPane);
// Handle nav error
this.WebBrowserHelper.NavigateError += this.OnNavigateError;

Tracking eMails in Outlook 2007

Greetings Overflowers,
I am trying to develop a VSTO/C# corporate email tracker for specially signed emails.
I am relying on:
Inspectors.NewInspector
Inspector.Close
Inspector.Activate
Inspector.Deactivate
Somehow, the Inspector events stop firing after sometime.
I register 2, 3 and 4 in the body of 1 after checking for the sign.
I tried to keep track of already registered inspectors but no hope.
Any clue ?
UPDATE: Here is a sample code. The evens OnSelect and OnOpen fires few times and then stops suddenly:
using System;
using System.Collections;
using Microsoft.Office.Interop.Outlook;
// using Microsoft.Office.Core;
namespace eMailTrackingSystem
{
public enum TrackingEvent
{
Opened, Closed, Forwarded, Deleted
}
public partial class eMTSAddIn
{
private ArrayList trackedEmails = new ArrayList();
private void InternalStartup()
{
this.Application.ActiveExplorer().SelectionChange += new ExplorerEvents_10_SelectionChangeEventHandler(OnSelect);
}
private void OnSelect()
{
Selection selection = this.Application.ActiveExplorer().Selection;
foreach (object item in selection)
{
if (item is MailItem)
{
MailItem email = (MailItem)item;
if (email.Subject == "eMTS" && !trackedEmails.Contains(email.EntryID))
{
email.Open += new ItemEvents_10_OpenEventHandler(OnOpen);
trackedEmails.Add(email.EntryID);
}
}
}
}
private void OnOpen(ref bool cancel)
{
}
private void OnClose()
{
}
}
}
Regards
Are you using Inspector Wrappers? They are essential for properly dealing with every item that is opened by a user:
Developing an Inspector Wrapper for Outlook 2010:
http://msdn.microsoft.com/en-us/library/ff973716.aspx
A similar approach can be used for monitoring the items a user has selected in the Explorer.

Categories