I'm developing a Visual Studio extension that adds a command to VS to compose a message in Outlook with an attachment. The attachment is simple .csv file that is also produced by the extension.
So when user fires the command, outlook opens a window with an email already composed and attachment added. The user might just click send.
Now I've been asked to add possibility to send those logs compressed with zip. I would like to compose a message with an attachment already compressed, but I don't want any temporary .zip files retained after message is sent (or cancelled). How can I achieve this?
My code that composes the message and opens Outlook window:
using Microsoft.Office.Interop.Outlook;
...
private static bool TrySendMethod1(string subject, string toAddr, string body, string logPath)
{
try
{
Application app = new Application();
MailItem mailItem = app.CreateItem(OlItemType.olMailItem);
mailItem.Subject = subject;
mailItem.To = toAddr;
mailItem.Body = body;
if (logPath != null) mailItem.Attachments.Add(logPath);
mailItem.Importance = OlImportance.olImportanceNormal;
mailItem.Display(false);
return true;
}
catch (System.Exception e)
{
return false;
}
}
update
Once the email is composed, an outlook window with a composed message is displayed (it has the attachment already set). Now, the user might just send it, cancel it or whatever. He might even leave the window opened for hours (although this is not common) and then send it (even after VS has been closed). Again, i don't want any temporary archives to exist on the disk after the message is sent or cancelled.
I added full method that sends the email. It's not much more, but this method is just invoked when user selects a new Visual Studio command that my extension adds to it (tools -> send TFS logs -> from this month). There just an additional method between button handler and it simply sets some parameters for the one presented here (sets the subject, logPath and so on...)
If it's not possible, then I can also accept such an answer.
You can compress a file using this:
public static void Compress(FileInfo fileToCompress, string compressedFileName)
{
using (FileStream originalFileStream = fileToCompress.OpenRead())
{
using (FileStream compressedFileStream = File.Create(compressedFileName)
{
using (GZipStream compressionStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
{
originalFileStream.CopyTo(compressionStream);
}
}
}
}
Modified from: http://msdn.microsoft.com/en-us/library/ms404280(v=vs.110).aspx
For deleting the file afterwards you can attach to the MailItem.Unload Event.
This event occurs after the Close event for the Outlook item occurs,
but before the Outlook item is unloaded from memory, allowing an
add-in to release any resources related to the object. Although the
event occurs before the Outlook item is unloaded from memory, this
event cannot be canceled.
Source: http://msdn.microsoft.com/en-us/library/office/ff868564(v=office.15).aspx
Then you TrySendMethod1 could look like this
private static bool TrySendMethod1(string subject, string toAddr, string body, string logPath)
{
try
{
Application app = new Application();
MailItem mailItem = app.CreateItem(OlItemType.olMailItem);
mailItem.Subject = subject;
mailItem.To = toAddr;
mailItem.Body = body;
string compressedFileName = logpath + ".gz";
CompressedStack(logpath, compressedFileName);
if (logPath != null) mailItem.Attachments.Add( compressedFileName );
mailItem.Importance = OlImportance.olImportanceNormal;
mailItem.Display(false);
mailItem.Unload += (e) => {File.Delete(compressedFileName);};
return true;
}
catch (System.Exception e)
{
return false;
}
}
Exception handling for the File.Delete is missing, and I am not sure 100% certaing about the signature of the Unload event, but have a try and let us know.
This will NOT handle the case where Visual Studio is closed BEFORE the mail is sent! (I think that might not even be possible.)
Related
I'm programming an outlook add-in.
I want to modify the mail before it gets sent. Therefore I have registered me for an event before the email gets sent. I can modify it but when I m trying to change the recipient of the mail (so mail.To) it gives me an error (not while my code is running but when outlook tries to sent the mail).
Error says: '...Can not resolve the receivername' (i have translated it so it is not the real error text but close to it)
Here is my code:
void Application_ItemSend(object item, ref bool cancel)
{
if (item is Outlook.MailItem mail)
{
var to = mail.To;
var body = mail.Body;
var editedBody = to + "#" + body;
mail.Body = editedBody;
mail.To = #"<another email>";
}
}
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
//Register the new event
Globals.ThisAddIn.Application.ItemSend += Application_ItemSend;
}
You are resetting all To recipients. Is that what you really want to do? Try to use MailItem.Recipients.Add (retuns Recipient object) followed by Recipient.Resolve.
You are also setting the plain text Body property wiping out all formatting. Consider using HTMLBody instead, just keep in mind that two HTML strings must be merged rather than concatenated to produce valid HTML.
You need to cancel the action by setting the cancel parameter to true and schedule re-sending operation with a new email address. A timer which fires the Tick event on the main thread can help with that task.
Also you may consider making a copy of the email, changing recipients (do any changes on the copy) and submit it. In that case you will have to differentiate such messages and skip them in the ItemSend event handler. To get that working you may use UserProperties.
Got it working:
var to = string.Empty;
mail.Recipients.ResolveAll();
foreach (Outlook.Recipient r in mail.Recipients)
{
to += r.Address + ";";
}
if (to.Length > 0)
to = to.Substring(0, to.Length - 1);
mail.Body = to + "#" + mail.Body;
//Modify receiver
mail.To = string.Empty;
Outlook.Recipient recipient = mail.Recipients.Add("<email>");
recipient.Resolve();
I have made small C# WFP application that sends emails with embedded images using C# and Outlook Interop. It seems that many mail clients reacts diffently to emails sent from this application compared to manually created emails where images are pasted directly in using CTRL+V.
I have tried to use OutlookSpy to compare the two emails, but I can't see any difference that could cause the different way the emails are being handled.
When an email sent from the C# application is received in an Outlook Application on an Iphone, the images are blank. It works just fine when the image is manually created and images are pasted in using CTRL+V.
1) How do I make sure that the emails sent from my application are handled the same way as when I manually create an email in Outlook and copy an image using CTRL+V?
If I can make this work, then it should be possible to make the images display correctly in Outlook for Iphone.
2) How does outlook handle an image when it is pasted in using CTRL+V? It seems that it uses Cid, the same way as I do in my application, but how does it refer to the image if it is not attached?
Here is the code for the sample application:
private void SendEmailButton_OnClick(object sender, RoutedEventArgs e)
{
SendMail();
}
public void SendMail()
{
var application = GetOutlookApplication();
MailItem newMail = (MailItem)application.CreateItem(OlItemType.olMailItem);
newMail.To = "!!!EnterValidEmailAddress!!!";
newMail.Subject = "Image test";
//Create image as embedded attachment
Attachment attachment1 = newMail.Attachments.Add(#"C:/SomeImage1.png", OlAttachmentType.olByValue);
string imageCid1 = "SomeImage_1";
//The property Accessor to be able to refer to the image from Email HTML Body using CID
attachment1.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E", imageCid1);
//Set property Accesor to hide attachment
attachment1.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x7FFE000B", true);
//Refer the attachment using cid
newMail.HTMLBody = String.Format("<img src=\"cid:{1}\">", "google.dk", imageCid1);
newMail.Save();
newMail.Display();
}
public static OutlookApplication GetOutlookApplication()
{
// Start outlook process if it is not startet already
if (!IsOutlookRunning())
{
using (Process p = new Process())
{
p.StartInfo.FileName = "OUTLOOK.EXE";
p.Start();
if (!p.WaitForInputIdle(10000))
Thread.Sleep(5000);
}
}
// Start the outlook application (API)
OutlookApplication oApp = null;
if (Process.GetProcessesByName("OUTLOOK").Any())
{
try
{
oApp = Marshal.GetActiveObject("Outlook.Application") as OutlookApplication;
}
catch (System.Exception)
{
if (oApp == null)
oApp = new OutlookApplication();
}
return oApp;
}
oApp = new OutlookApplication();
return oApp;
}
private static bool IsOutlookRunning()
{
Process[] p = Process.GetProcessesByName("Outlook");
return p.Length != 0;
}
Make sure that you get a well-formed HTML markup finally. The following line of code just adds the <a> tag to the end of body string:
newMail.HTMLBody += String.Format("<img src=\"cid:{1}\">", "google.dk", imageCid1);
Instead, you need to find a right place for the image between the <body> and </body> tags.
I have an outlook addin in which I update programmatically user properties.
Until now, I executed the addin and outlook with administrator rights. Everything was working good.
I attach the event on a forwarded mail :
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
try
{
MailItem mail = Inspector.CurrentItem as MailItem;
if (mail != null)
{
((Outlook.ItemEvents_10_Event)mail).Forward += new ItemEvents_10_ForwardEventHandler(Forward_Event);
}
finally
{
Marshal.ReleaseComObject(Inspector);
}
}
Then I update the user properties of the mail and save it.
void Forward_Event(object Forward, ref bool Cancel)
{
MailItem mail = Forward as MailItem;
if (mail != null && OutlookHelper.GetUserProperty(mail) != null)
{
OutlookHelper.SetUserProperty(mail, null);
EventLog.WriteEntry("Application", "Before save forward : " + mail.Saved); // mail.Saved = false
mail.Save();
EventLog.WriteEntry("Application", "After save forward : " + mail.Saved); // mail.Saved = true
}
}
I can see in the logs that the property Saved of my mail is set to True after the call to the Save() method.
However, since I use a standard user to execute the addin (linked to customer needs), when I close the email, a popup appears asking me to save manually the user properties of the forwarded mail. The problem does not appear with admin rights.
Thanks to everybody for help
Well, after some intensive researches, the mail is "read only" and then the call to the Save() method does not really save. When I removed the call to the save, the popup disappeared... I do not really understand this behavior, but it works !
Hope this will help some other people.
I`m trying to create outlook mails from templates, slightly edit them and then show to user so he can send that mail.
There is no problem in creation of the mail and displaying it. But when I`m trying to read (or edit) HTMLBody of the mail there is a error:
Operation aborted (Exception from HRESULT: 0x80004004 (E_ABORT))
Here is my code:
using Outlook = Microsoft.Office.Interop.Outlook;
...
try
{
var app = new Outlook.Application();
Outlook.MailItem mailItem = app.CreateItemFromTemplate("C:\\Test\\template.oft");
var body = mailItem.HTMLBody; //Here is the exception
mailItem.HTMLBody = body.Replace("#firstname", "Test Testy");
mailItem.To = message.EmailAddress;
mailItem.Display(mailItem);
}
catch (Exception ex)
{
...
}
Added example project on github.
var app = new Outlook.Application();
Before creating a new instance of the Outlook Application class I'd suggest checking whether it is already run and get the running instance then:
if (Process.GetProcessesByName("OUTLOOK").Any())
app = System.Runtime.InteropServices.Marshal.GetActiveObject("Outlook.Application");
Outlook is a singleton. You can't run multiple instances at the same time.
Also I'd suggest saving the newvly created item before accessing the HTMLBody property value:
Outlook.MailItem mailItem = app.CreateItemFromTemplate("C:\\Test\\template.oft");
mailIte.Save();
var body = mailItem.HTMLBody; //Here is the exception
Finally, the Display method doesn't take a MailItem instance. Instead, you can pass true to get the inspector shown as a modal window or just omit the parameter (false is used by default).
BTW Where and when do you run the code?
I have the below code that I harvested from MSDN. The code seems to have been originally used in an Add In and as such I am having a problem with the this.Application.CreateItem(...) portion. What do I need to do differently to do this from my winform app?
private void AddAttachment(){
Outlook.MailItem mail =this.Application.CreateItem(Outlook.OlItemType.olMailItem)as Outlook.MailItem;
mail.Subject = "An attachment for you!";
OpenFileDialog attachment = new OpenFileDialog();
attachment.Title = "Select a file to send";
attachment.ShowDialog();
if (attachment.FileName.Length > 0)
{
mail.Attachments.Add(
attachment.FileName,
Outlook.OlAttachmentType.olByValue,
1,
attachment.FileName);
mail.Recipients.Add("Armando Pinto ");
((Outlook._MailItem)mail).Send();
}
Make sure you're in a VSTO project so you get the Office References loaded. If it's complaining up there, it's probably because it doesn't know what object you're trying to create.