OpenSharedItem for opening .MSG files showing Error in Outlook C# - c#

I am using the following code to open the signed/unsigned
Outlook messages and I display the content in WebBrowser control.
Microsoft.Office.Interop.Outlook.Application app = new Microsoft.Office.Interop.Outlook.Application();
var item = app.Session.OpenSharedItem(msgfile) as Microsoft.Office.Interop.Outlook.MailItem;
string message = item.HTMLBody;
app.Session.Logoff();
It is working fine for the first time the file is opening, but after
closing the Outlook file trying to reopen the file it showing the
following error:
"Cannot open file: C:\tion.msg. The file may not exist, you may not
have permission to open it, or it may be open in another program.
Right-click the folder that contains the file, and then click
Properties to check your permissions for the folder."
After some time later it is opening fine. For this strange behavior
what could be the reason and how to rectify the the error message?

Outlook manages its own cache of items when you are opening and closing messages. Your best bet would be to use a randomly generated filename (i.e. Path.GetRandomFilename) when opening via OpenSharedItem so that you don't get issues. I would also use a temporary path instead of root c:\ (i.e. Path.GetTempPath).
You can try and free the MailItem reference (i.e. setting it to null), but there is no guarantee when Outlook will release the item from its cache.

Would any combination of the Quit[1], Close[2] or ReleaseComObject[3] methods work for you? My code worked better but not perfect after I used them.[4]
using Outlook = Microsoft.Office.Interop.Outlook;
.
.
.
var app = new Outlook.Application();
var item = app.Session.OpenSharedItem(msgfile) as Outlook.MailItem;
//Do stuff with the mail.
item.Close(OlInspectorClose.olDiscard);
app.Quit();
Marshal.ReleaseComObject(item);
Another solution, according to Microsoft - Help and Support[5], is to delay the opening of the file. However, it doesn't sound like a good solution to me since, like #SliverNinja also said, you'll never know when Outlook releases its lock of the file.
Notes and references
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._application.quit.aspx, read 2014-10-14, 16:19.
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook._mailitem.close%28v=office.15%29.aspx, read 2014-10-14, 16:19.
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx, read 2014-10-14, 16:19.
For example, if I had opened Outlook for som regular work, the Quit-method would close that window as well.
http://support2.microsoft.com/kb/2633737, read 2014-10-08, 16:19.

Hello you have two options .
set the Read-only attribute to the msg file
or
disable the following permissions for the users or usergroups to the parent folder:
Write Attributes
Write Extended Attributes
the msg file can now open multiple times but is write protect

I had this problem, in my case it was the space in the file name
import win32com.client
import os
path = 'C:/testes/mail'
files = [f for f in os.listdir(path) if '.msg' in f]
for file in files:
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
msg = outlook.OpenSharedItem(os.path.join(path, file))
att=msg.Attachments
for i in att:
i.SaveAsFile(os.path.join('C:/testes/email_download', i.FileName))
I don't know if in your case the OpenSharedItem method can help ...

Firstly, try to release the message as soon as you are done with it using Marshal.ReleaseComObject(). This may or may not help since Outlook likes to cache its last opened item.
Secondly, you are logging off from Outlook while it might still be running and visible to the user - Outlook is a singleton, so you will end up with the existing instance if it was already running. Either don't call Logoff at all, or check that there are no open inspectors and explorers (Application.Explorers.Count == 0 && Application.Inspectors.Count == 0).
Thirdly, reading HTMLBody alone won't work properly if there are embedded images - they are stored as regular attachments. You can save the message as an MHTML file (which most browsers would be happy to show) using MailItem.SaveAs(..., olMHTML).
You can also use Redemption (I am its author) for that - call RDOSession.GetMessageFromMsgFile.
If you need to release the message immediately after you are done, call Marshal.ReleaseComObject()
In case of Redemption, you can also cast RDOMail object to the IDisposable interface and call IDisposable.Dispose(). In addition to the MHTML format, Redemption can also save in the HTML format with image attachments converted into embedded images- use RDOMail.SaveAs(..., olHTMLEmbeddedImages) (olHTMLEmbeddedImages == 1033).

Related

C# OpenFileDialog multiple filename filters including exclude

I have a requirement to allow users to open a specific file for processing. The open file dialog is currently
OpenFileDialog ofg = new OpenFileDialog
{
FileName = "BaseFileName*",
Filter = "CSV File (*.CSV)|*.csv",
Multiselect = false,
InitialDirectory = #"N:\Downloads"
};
However the process adds a suffix of _Processed along with timestamp data to the filename and I want to exclude these renamed files the next time the OpenFileDialog is used to prevent the user trying to reprocess the same file.
I have to leave the original files where they are for internal audit reasons.
So I need an additional filename filter of not equal to "_Processed".
Is there any way to do this with OpenFileDialog or does anyone know of a custom c#/.net component that can do this?
You are asking to omit specific items from the file dialog view.
According to MSDN, this is no longer possible as of Windows 7, but was possible previously.
The C# file dialogs (both WPF and WinForms) use the IFileDialog API.
Here is the function that could have made this work, but is no longer supported:
https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfilter
As it is, you are stuck with checking the file for correctness after the user has already selected it and confirmed it with OK.
You can help the situation a little bit: If you enjoy pain, then you can copy the whole IFileDialog COM interop code from the .NET source code, and implement IFileDialogEvents. This way, when the user clicks "OK", you can deny the selection and display an error before the dialog closes, leaving the dialog open so the user can select a different file.
If you are sane and you don't want to do that, then you'll have to open the dialog again after the verification fails.
The easy way is just saving the processed data with another extension e.g. "BaseFileName_Processed_20105640640.cvs1", that way you keep the data and your file dialog will not show this file.
Another way could be to call the OpenFileDialog() in an if statement (and compare the return to DialogResult.OK), then split the file name for {'_','.'}, then run a loop to count the occurrences of the word Processed( >0), and possibly as a safety check determine whether a timestamp is present in one of the split strings. Finally, reload the FileOpenDialog in the same folder when the wrong file was selected.

System.Runtime.InteropServices.COMException (0x80030020): The operation failed

I'm trying to save a Outlook.MailItem to a folder using:
MailItem.SaveAs(path, Outlook.OlSaveAsType.olMSG)
This works fine most of the time. However, when there's about 20 emails hitting Outlook at once, for a few of the I get the following exception:
System.Runtime.InteropServices.COMException (0x80030020): The operation failed.
at Microsoft.Office.Interop.Outlook._MailItem.SaveAs(String Path, Object Type)
I checked the code 0x80030020 which seems to mean "A share violation has occurred." However, I'm not sure what it means. In my application, I have the two below lines where the first one saves the email to a folder and the second method trying to POST that file to an API:
file = MailItemHelper.SaveMailItemToOFEFolder(mailItem);
bool isEmailSentByHttp = MailItemHelper.SendMailItemToOFEApi(_userid, file)
The path has the subject of the email which is cleaned to remove non-alpha-numeric characters by doing:
Regex nonAlphaNumericRgx = new Regex(#"\W|_");
subject = nonAlphaNumericRgx.Replace(mail.Subject, "_");
I guess I can't tell directly what is the problem, but I can try giving you some tips.
According to MS documentation, Outlook seems to be using the StgOpenStorage function, throwing the STG_E_SHAREVIOLATION error:
Access denied because another caller has the file open and locked.
That means that either Outlook is messing up (the user clicked on the "message" and outlooks locks the file, or something else) or maybe after changing the file name with your regex your string ends up to be the same of another file, while it is in the middle of writing the other.
How are you hooking up to outlook? Is your app using a new thread per mail arrived? You could add a log4net and log all operations in your code, like:
// receive and email and store subject name on var subject
log.Info($"E mail with subject '{subject}' arrived.");
// convert name and save on var newSubject
log.Info($"Mail will be saved as '{newSubject}'.");
// notify the beginning of the operation
log.Info("Calling MailItemHelper.SaveMailItemToOFEFolder.");
// notify the end of the operation
log.Info("Mail saved successfully.");
// notify Post in REST API
log.Info("Sending in MailItemHelper.SendMailItemToOFEApi");
// notify end of Post in REST API
log.Info("MailItemHelper.SendMailItemToOFEApi sent");
This will give a clear idea of how all mails are being retrieved and if there is some thread issues you can check which ones are finishing/starting first.

open specific word doc. depending on user selection c#

i currently have a word doc. file merging program and am attempting to open a specific word doc. depending on user selection.
// sample of code used:
string outputFolder = null;
...
// file selection
...
string outcomeFolder = outputFolder;
string outputFile = "Combined Folder " + fileDate + " # " + fileTime + ".docx";
string outputFileName = Path.Combine(outcomeFolder, outputFile);
in the program, outputFolder is selected by the user via a fileBrowserDialog
currently, the program runs correctly and merges the files in the folder selected by the user however it fails to open Microsoft Word as well as the outcome document merged.
i've attempted to use:
Microsoft.Office.Interop.Word.Application officeApp =
new Microsoft.Office.Interop.Word.Application();
...
// merging code
...
Document documentTest = officeApp.Documents.Open(outputFileName);
i've noticed that though the program fails to launch Word, Task Manager continues to create a new instance of Word. The merged document formed also cannot be deleted as it claims the file is currently in use. It's as if program it's opening in the background however not physically launching. The Task Manager instance of Word must then be killed before the merged file can be edited / deleted
Any suggestions as to remedy this? Am i missing something simplistic or is the issue due to the non-static file path? - if any additional information is required please ask. thank you
Update 1:
Since implementing the officeApp.Visible = true; the program now launches the file created which can then be edited / re-saved etc. However if i immediately run the program again, attempting to create another merged file within the same folder etc. I am presented with "RPC server is unavailable. (Exception from HRESULT: 0x800706BA)"
Update 2:
As listed above, I was getting a generic HRESULT error code which I have now since remedied. I moved the "new officeApp" into the "Merge" handler which seems to be allowing multiple merges in quick succession without throwing errors.
Update 3:
To make things more simplistic, i've experimented with implementing Process.Start(outputFileName); to open the document. This is due to the additional check box I am now introducing which allows the user to decide whether the merged doc. will be launched / presented once created. This new code also prevents the additional Word.exe's from being created if the file visibility is set to false.
thank you all for your suggestions and help.
Have you tried making Word visible?
officeApp.Visible = true;

Manipulating "filename" of file dialog opened using a "print to pdf" option

I have over a thousand e-mails that need to be converted to individual PDFs. The code I've written is able to process the faux-print job up to the "Save PDF As..." Dialog Box.
I need to manipulate the filename in that dialog to the original filename. I cannot find a way to post a string to the filename section of the dialogbox, since this is an unconventional method posting on a separate process.
How do I send this information to an active dialog window that is unrelated to the process the application is calling?
public static void Main(string[] args)
{
string folderIn = Path.GetDirectoryName(#"Z:\Files up to 3-6-13\");
string folderOut = Path.GetDirectoryName(#"c:\users\athomas\desktop\Output");
string[] fileList = Directory.GetFiles(folderIn);
foreach(var fileName in fileList)
{
Console.WriteLine(fileName);
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo(fileName);
info.Verb = "Print";
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(info);
// This is where I need to say something like
// SaveDialog.SaveAs(fileName.SafeFileName)
if(p.HasExited == false)
{
p.WaitForExit(1000);
}
}
Console.ReadLine();
}
You are asking basically about hacking around, which means there is no safe way to do that. The possible way may look like this:
1) You enumerate windowses shown on the screen, via EnumWindows
2) You use for every window GetWindowText to find a Title of the Dialog you are searching for.
3) Ocassionaly there may be more then one dialog, Murphy's law is always there, so may be even with the same title. You can reduce collision risk by
3.a Call GetWindowLong with HWND of "dialog" you found
3.b Call GetParent to get HWND of parent window, that you already know
If you can avoid or may not care about case of window style&title collision, just jump over point (3)
4) Once you found your dialog, run over its HWND EnumChildWindows to get all controls of it
5) Find the contrlol you interested in via some attribute (usually Class_ID)
6) Execute SetWindowtext to set the text you like.
As I said before, there are several places were this process can fail.
For example:
You may happen to have multiple dialogs on the screen in the same moment
You may download a new version of PDF processor, and in SaveAs dialog may have something changed, so your code will break.
But considering that you are hacking, you may assume your own risks.
An excellent tool, may be the best tool, for window investigation on Windows, is probably Spy ++, which is available within visual studio installation. Using it you can try to find some unique attributes of a textbox of that dialog where you want to put the text in. Having that in your hands you can reliably query dialog's children collection to find the textbox.
After you mentioned .msg, I did a Google Search on the .msg file format and I was surprised to find it was well documented. It is an OLESS file, and there are APIs for loading those. I even found a CodeProject C# sample that was able to load an email I just saved from Outlook 2013!
It will be a bit of work since you will have to write code to "render" message how you see fit. Ex: It gives you the body, the recipients, and the attachments. You would have to decide what to do with the attachments and code that up. Then find a PDF library. But at least it is a programmatic solution, rather than relying on hacking the UI.
Also: A Google search for .MSG to PDF returned loads of tools to do this. One is a forum post referring to an Adobe Product called "Acrobat PDFMaker" so maybe you don't even need to write code.
Sorry to post 2 answers. I have a solution to the exact problem of prompting for the file. These instructions vary slightly based on the version of Windows, but you will get the idea:
Add a new printer that uses PDF printer driver
When it asks for the "port" click "Add Port" then "Local Port"
Enter in a file name. Ex: "C:\Users\Myself\MyPdf.pdf"
Complete the printing wizard as usual.
I tested this with the XPS printer and it worked
When you print to this printer, it writes to that exact file: No prompting! You will have to monitor the file (I suggest FileSystemWatcher) or poll for when the file is closed or something like that. After that, you can move the file elsewhere.

Modify File Properties in C# Other Than Date

I'm working in .NET 3.5, Visual Studio 2010. I'm working on an Outlook Add-In that saves some email into a folder. I've gotten it to work using the Microsoft.Office.Interop.Outlook.MailItem.SaveAs function. However, the file properties have only the current time (time when the file was exported through the Add-In) as their Date Modified/Date Created etc., and other properties such as To, From, CC, BCC are not there.
If you open a folder in Windows Explorer (I'm using Windows 7), go to the top where it says Name, Date Modified, Type, etc., you can click on More, and see other various columns that might be relevant like "Album Artist", "To", "From", etc.
C# has a really easy way to modify the timings, File.SetCreationTime(filename, DateTime object);. However, there's no .SetTo or .SetAlbumArtist or anything like that. How would I go about modifying those properties?
Update 1: through research, I found this link: Read/Write 'Extended' file properties (C#), so that might contain the answer...but I have no idea how. The accepted answer mentions running a method on a shell using a .dll. The second answer contains C# code, a commenter then asked basically what I want to know (how to modify the properties of a particular file), and the next commenter responded with "you can't set these"...so I'm still at square 1.
Update 2: I also tried the following:
foreach (Object selectedObject in explorer.Selection)
{
Outlook.MailItem email = (selectedObject as Outlook.MailItem);
//Modify the information about the email
email.To = "I filled in To";
email.SaveAs(filename, OlSaveAsType.olMSG);
}
This code successfully grabs the selected email(s) and save them under filename. However, the email.To = "I filled in To" changes the information when you open the .msg, but not the file properties.
This cannot be changed, because it actually is not any file property in the terms of the filesystem (like file creation or modification datetime).
Columns in Windows Explorer you are talking about are "virtual" and they are "only" the feature of Windows Explorer. It "understand" content of some file types and it can handle showing and sorting columns like that.
If you want to change To, From etc. you have to change the content of the file you are saving, i.e. change the To or From in the message.
To do so, if You have an Microsoft.Office.Interop.Outlook.MailItem object (which you are just saving), set desired properties on that object before you save it to file, i.e.:
MailItem mail = ...;
mail.To = "some new to";
mail.Subject = "new subject";
mail.SaveAs(fileToSave, OlSaveAsType.OlMSG);
I don't know if it also changes email stored in the Outlook, if it so, create a copy of the email before changing properties
MailItem copyOfMailToSave = (MailItem)mail.Copy();

Categories

Resources