I want to use/read attached files from an outlook email into a WinForm solution.
Ex: the email has a TXT file attached; I want to perform a Drag&Drog of the TXT file into the WinForm and read the TXT at the same time.
This is an old question, but I'll provide another answer anyhow that doesn't involve using the Outlook objects.
This URL provides working code that is about 13 years old, but still seems to work, on how to handle the "FileGroupDescriptor" and "FileContents" data that Outlook passes to the DropDrop event. Just in case that link dies, here is the relevant code, copy/pasted directly:
DragEnter event:
private void Form1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
// for this program, we allow a file to be dropped from Explorer
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{ e.Effect = DragDropEffects.Copy;}
// or this tells us if it is an Outlook attachment drop
else if (e.Data.GetDataPresent("FileGroupDescriptor"))
{ e.Effect = DragDropEffects.Copy;}
// or none of the above
else
{ e.Effect = DragDropEffects.None;}
}
DragDrop event:
private void Form1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
string [] fileNames = null;
try
{
if ( e.Data.GetDataPresent(DataFormats.FileDrop,false) == true)
{
fileNames = (string []) e.Data.GetData(DataFormats.FileDrop);
// handle each file passed as needed
foreach( string fileName in fileNames)
{
// do what you are going to do with each filename
}
}
else if (e.Data.GetDataPresent("FileGroupDescriptor"))
{
//
// the first step here is to get the filename
// of the attachment and
// build a full-path name so we can store it
// in the temporary folder
//
// set up to obtain the FileGroupDescriptor
// and extract the file name
Stream theStream = (Stream) e.Data.GetData("FileGroupDescriptor");
byte [] fileGroupDescriptor = new byte[512];
theStream.Read(fileGroupDescriptor,0,512);
// used to build the filename from the FileGroupDescriptor block
StringBuilder fileName = new StringBuilder("");
// this trick gets the filename of the passed attached file
for(int i=76; fileGroupDescriptor[i]!=0; i++)
{ fileName.Append(Convert.ToChar(fileGroupDescriptor[i]));}
theStream.Close();
string path = Path.GetTempPath();
// put the zip file into the temp directory
string theFile = path+fileName.ToString();
// create the full-path name
//
// Second step: we have the file name.
// Now we need to get the actual raw
// data for the attached file and copy it to disk so we work on it.
//
// get the actual raw file into memory
MemoryStream ms = (MemoryStream) e.Data.GetData(
"FileContents",true);
// allocate enough bytes to hold the raw data
byte [] fileBytes = new byte[ms.Length];
// set starting position at first byte and read in the raw data
ms.Position = 0;
ms.Read(fileBytes,0,(int)ms.Length);
// create a file and save the raw zip file to it
FileStream fs = new FileStream(theFile,FileMode.Create);
fs.Write(fileBytes,0,(int)fileBytes.Length);
fs.Close(); // close the file
FileInfo tempFile = new FileInfo(theFile);
// always good to make sure we actually created the file
if ( tempFile.Exists == true)
{
// for now, just delete what we created
tempFile.Delete();
}
else
{ Trace.WriteLine("File was not created!");}
}
}
catch (Exception ex)
{
Trace.WriteLine("Error in DragDrop function: " + ex.Message);
// don't use MessageBox here - Outlook or Explorer is waiting !
}
}
Note that this code doesn't Dispose of objects that it should, such as the MemoryStream and FileStream objects.
You can get the running Outlook instance by using the GetActiveObject method which allows to obtain a running instance of the specified object from the running object table (ROT). Then you can automate Outlook to get the currently selected or opened item from which an attachment might be dragged. See C# app automates Outlook (CSAutomateOutlook) for the sample code.
Related
I have folder full of *.msg files saved from Outlook and I'm trying to convert them to Word.
There is a loop that loads each *.msg as MailItem and saves them.
public static ConversionResult ConvertEmailsToWord(this Outlook.Application app, string source, string target)
{
var word = new Word.Application();
var emailCounter = 0;
var otherCounter = 0;
var directoryTree = new PhysicalDirectoryTree();
foreach (var node in directoryTree.Walk(source))
{
foreach (var fileName in node.FileNames)
{
var currentFile = Path.Combine(node.DirectoryName, fileName);
var branch = Regex.Replace(node.DirectoryName, $"^{Regex.Escape(source)}", string.Empty).Trim('\\');
Debug.Print($"Processing file: {currentFile}");
// This is an email. Convert it to Word.
if (Regex.IsMatch(fileName, #"\.msg$"))
{
if (app.Session.OpenSharedItem(currentFile) is MailItem item)
{
if (item.SaveAs(word, Path.Combine(target, branch), fileName))
{
emailCounter++;
}
item.Close(SaveMode: OlInspectorClose.olDiscard);
}
}
// This is some other file. Copy it as is.
else
{
Directory.CreateDirectory(Path.Combine(target, branch));
File.Copy(currentFile, Path.Combine(target, branch, fileName), true);
otherCounter++;
}
}
}
word.Quit(SaveChanges: false);
return new ConversionResult
{
EmailCount = emailCounter,
OtherCount = otherCounter
};
}
The save method looks likes this:
public static bool SaveAs(this MailItem mail, Word.Application word, string path, string name)
{
Directory.CreateDirectory(path);
name = Path.Combine(path, $"{Path.GetFileNameWithoutExtension(name)}.docx");
if (File.Exists(name))
{
return false;
}
var copy = mail.GetInspector.WordEditor as Word.Document;
copy.Content.Copy();
var doc = word.Documents.Add();
doc.Content.Paste();
doc.SaveAs2(FileName: name);
doc.Close();
return true;
}
It works for most *.msg files but there are some that crash Outlook when I call copy.Content on a Word.Document.
I know you cannot tell me what is wrong with it (or maybe you do?) so I'd like to findit out by myself but the problem is that I am not able to catch the exception. Since a simple try\catch didn't work I tried it with AppDomain.CurrentDomain.UnhandledException this this didn't catch it either.
Are there any other ways to debug it?
The mail that doesn't let me get its content inside a loop doesn't cause any troubles when I open it in a new Outlook window and save it with the same method.
It makes sense to add some delays between Word calls. IO operations takes some time to finish. Also there is no need to create another document in Word for copying the content:
var copy = mail.GetInspector.WordEditor as Word.Document;
copy.Content.Copy();
var doc = word.Documents.Add();
doc.Content.Paste();
doc.SaveAs2(FileName: name);
doc.Close();
Instead, do the required modifications on the original document instance and then save it to the disk. The original mail item will remain unchanged until you call the Save method from the Outlook object model. You may call the Close method passing the olDiscard which discards any changes to the document.
Also consider using the Open XML SDK if you deal with open XML documents only, see Welcome to the Open XML SDK 2.5 for Office for more information.
Do you actually need to use Inspector.WordEditor? You can save the message in a format supported by Word (such as MHTML) using OOM alone by calling MailItem.Save(..., olMHTML) and open the file in Word programmatically to save it in the DOCX format.
Context:
I am able to copy (using CTRL-C) and paste programmatically a file from the clipboard when the file copied to the clipboard is for example a file on the desktop. This is simple enough using the following syntax:
File.Copy(Clipboard.GetFileDropList()[0], savePath)
where Clipboard.GetFileDropList()[0] returns the path of the copied file and savePath is the paste location.
However, i find that the above syntax does NOT work if the copied file (using CTRL-C) is a file attachment in an Outlook email. In that scenario, Clipboard.ContainsFileDropList() returns false and Clipboard.GetFileDropList()[0] results in the following error message:
"ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index"
This is despite the fact that pressing CTRL-V does successfully paste the file, confirming that the file was initially successfully copied to the clipboard.
Question:
Sorry if i missed something very basic. My question is how to programmatically paste/save an email attachment (PDF, Word, etc...) from the clipboard to a file location when that email attachment was copied into the clipboard using CTRL-C from within Outlook.
Note that i do understand that what i am trying to do can be solved by skipping the Clipboard and interacting programmatically with Outlook to access a selected email attachment. However, my goal here is to learn how to interact programmatically with the Clipboard under different scenario.
You are using the wrong DataFormat. You can always get a list of currently present data formats by calling Clipboard.GetDataObject().GetFormats().
You need to use:
"FileGroupDescriptor" to retrieve the file names
private static async Task<List<string>> GetAttachedFileNamesFromClipboardAsync(IDataObject clipboardData)
{
if (!clipboardData.GetDataPresent("FileGroupDescriptor"))
{
return new List<string>();
}
using (var descriptorStream = clipboardData.GetData("FileGroupDescriptor", true) as MemoryStream)
{
using (var streamReader = new StreamReader(descriptorStream))
{
var streamContent = await streamReader.ReadToEndAsync();
string[] fileNames = streamContent.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
return new List<string>(fileNames.Skip(1));
}
}
}
"FileContents" to retrieve the raw file contents
// Returns the attachment file content as string
private static async Task<string> GetAttachmentFromClipboardAsync(IDataObject clipboardData)
{
if (!clipboardData.GetDataPresent("FileContents"))
{
return string.Empty;
}
using (var fileContentStream = clipboardData.GetData("FileContents", true) as MemoryStream)
{
using (var streamReader = new StreamReader(fileContentStream))
{
return await streamReader.ReadToEndAsync();
}
}
}
// Returns the attachment file content as MemoryStream
private static MemoryStream GetAttachmentFromClipboard(IDataObject clipboardData)
{
if (!clipboardData.GetDataPresent("FileContents"))
{
return null;
}
return clipboardData.GetData("FileContents", true) as MemoryStream;
}
Save attached file to disk
Since Windows only adds the first selected attachment to the system clipboard, this solution can only save a single attachment. Apparently the office clipboard is not accessible.
private static async Task SaveAttachmentFromClipboardToFileAsync(IDataObject clipboardData, string destinationFilePath)
{
if (!clipboardData.GetDataPresent("FileContents"))
{
return;
}
using (var attachedFileStream = clipboardData.GetData("FileContents", true) as MemoryStream)
{
using (var destinationFileStream = File.Open(destinationFilePath, FileMode.OpenOrCreate))
{
await attachedFileStream.CopyToAsync(destinationFileStream);
}
}
}
Save attached files to disk using the Office API
Requires to reference Microsoft.Office.Interop.Outlook.dll. This solution does not depend on the system clipboard. It just reaads the selected attachments from the currently open message item in the Outlook explorer.
You still can monitor the system clipboard to trigger the process to save the attachments.
private static void SaveSelectedAttachementsToFolder(string destinationFolderPath)
{
var outlookApplication = new Microsoft.Office.Interop.Outlook.Application();
Explorer activeOutlookExplorer = outlookApplication.ActiveExplorer();
AttachmentSelection selectedAttachments = activeOutlookExplorer.AttachmentSelection;
foreach(Attachment attachment in selectedAttachments)
{
attachment.SaveAsFile(Path.Combine(destinationFolderPath, attachment.FileName));
}
}
My current project uses a direct file path of this excel document to read information off of the excel file. I need to get my project ready for release so I cannot have the project hard code a file path in the form of a string.
I want to embed the Excel File in my resource, which I have done, but know how can I get the file path from Resource, and send a file path to the class which reads the Excel file. The class must be feed a filepath so I was thinking of making a copy of this Excel file, and in the Temp folder then referenceing the file path for the class to read the Excel file.
FileName = #"D:\SomeFolder\ExcelFile.xlsx"; //This is the old code, hard coded
//I need code that is going to make a copy of this file from the Resources and save it somewhere in a temp folder, but then give me
the File path in the form of a string.
string FileName;
// I need the file name to have the directory of this excel that is in the Resource folder
//Call Class to Create XML File and store Data from BIN File Locally on Program
ReadExcel_CreateXML = new ExcelRecorder(FileName);
Something else to think about is that you are probably reading the current files using a FileStream and either a BinaryReader or StreamReader. If that's the case, the consumer of the file could be written to accept an arbitrary Stream instead and then you can create a MemoryStream to pass to the consuming class:
// The resource will be a byte array, I'm just creating a
// byte array manually for example purposes.
var fileData = System.Text.Encoding.UTF8.GetBytes("Hello\nWorld!");
using (var memoryStream = new MemoryStream(fileData))
using (var streamReader = new StreamReader(memoryStream))
{
// Do whatever you need with the file's contents
Console.WriteLine(streamReader.ReadLine());
Console.WriteLine(streamReader.ReadLine());
}
This approach means you won't be cluttering up the client computer with temporary files that you'll need to clean up. It also means your consuming class will become more flexible if you ever need to process data over any other type of Stream.
I'm not sure if this the best solution, but it will work:
1st get the byte[] array of the file in your resources:
byte[] fileByteArray = global::YourProjectNameSpace.Properties.Resources.ExcelFileName
2nd Export the file to a temporary location using this function:
(I got from here: Write bytes to file)
public bool ByteArrayToFile(string _FileName, byte[] _ByteArray)
{
try
{
// Open file for reading
System.IO.FileStream _FileStream =
new System.IO.FileStream(_FileName, System.IO.FileMode.Create,
System.IO.FileAccess.Write);
// Writes a block of bytes to this stream using data from
// a byte array.
_FileStream.Write(_ByteArray, 0, _ByteArray.Length);
// close file stream
_FileStream.Close();
return true;
}
catch (Exception _Exception)
{
// Error
Console.WriteLine("Exception caught in process: {0}",
_Exception.ToString());
}
// error occured, return false
return false;
}
And last access that temporary file like you normally would
Use:
Just create a button in a form and put this code in the button's click event
private void button1_Click(object sender, EventArgs e)
{
byte[] fileByteArray = global::YourProjectNameSpace.Properties.Resources.ExcelFileName;
if (ByteArrayToFile(#"C:\Temp\file.xlsx", fileByteArray))
{
//File was saved properly
}
else
{
//There was an error saving the file
}
}
Hope it works
I am trying to create a torrent for the files in my desktop using monotorrent i have tried like the below code
i am able to get the byte code i am not able to save it as torrent it shows access denied
enter code here string path = "C:/Users/snovaspace12/Desktop/monotorrent-0.90/files";
string savepath = "D:/results";
TorrentCreator nnnn = new TorrentCreator();
nnnn.CreateTorrent(path, savepath);
public void CreateTorrent(string path, string savePath)
{
// The class used for creating the torrent
TorrentCreator c = new TorrentCreator();
// Add one tier which contains two trackers
//RawTrackerTier tier = new RawTrackerTier();
//tier.Add("http://localhost/announce");
//c.Announces.Add(tier);
c.Comment = "This is the comment";
c.CreatedBy = "Doug using " + VersionInfo.ClientVersion;
c.Publisher = "www.aaronsen.com";
// Set the torrent as private so it will not use DHT or peer exchange
// Generally you will not want to set this.
c.Private = true;
// Every time a piece has been hashed, this event will fire. It is an
// asynchronous event, so you have to handle threading yourself.
c.Hashed += delegate(object o, TorrentCreatorEventArgs e)
{
Console.WriteLine("Current File is {0}% hashed", e.FileCompletion);
Console.WriteLine("Overall {0}% hashed", e.OverallCompletion);
Console.WriteLine("Total data to hash: {0}", e.OverallSize);
};
// ITorrentFileSource can be implemented to provide the TorrentCreator
// with a list of files which will be added to the torrent metadata.
// The default implementation takes a path to a single file or a path
// to a directory. If the path is a directory, all files will be
// recursively added
ITorrentFileSource fileSource = new TorrentFileSource(path);
// Create the torrent file and save it directly to the specified path
// Different overloads of 'Create' can be used to save the data to a Stream
// or just return it as a BEncodedDictionary (its native format) so it can be
// processed in memory
c.Create(fileSource, savePath);
}
public void Create(ITorrentFileSource fileSource, string savePath)
{
Check.SavePath(savePath);
var file = Create(fileSource);//getting the fbyte code
File.WriteAllBytes( savePath, Create(fileSource).Encode()); //getting exception here
}
when i checked the byte code is returning properly to the file
it shows access is denied
You’ve probably solved this already but I just encountered the same issue. The solution, at least in my case, was pretty simple.
The problem originated with the savePath parameter in c.Create(fileSource, savePath);
I assumed savePath was a directory where the torrent would be saved. It should be a file path instead. For example savePath = “C:\pathtomytorrents\content.torrent”
Hopefully that works for you!
In my application the user can drag and drop multiple text files onto a GUI control to convert them to another format. Here is the relevant code:
private void panelConverter_DragDrop(object sender, DragEventArgs e)
{
string[] filenames = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (string filename in filenames)
{
convertFile(filename);
}
}
private void convertFile(string filename)
{
// build name of output file
string convertedFile = Path.ChangeExtension(filename, ".out");
// open input file for reading
FileInfo source = new FileInfo(filename);
StreamReader srcStream = source.OpenText();
// open output file for writing
StreamWriter dstStream = new StreamWriter(convertedFile);
// loop over input file
string line;
do
{
// get next line from input file
line = srcStream.ReadLine();
if (!Regex.IsMatch(line, #"fred=\d+"))
{
dstStream.WriteLine(line);
dstStream.Flush();
}
} while (line != null);
}
The problem is that when I drop multiple files on the GUI, only one of them actually gets processed. I have found that if I comment out the Regex line, all of the dropped files are processed. Am I missing something in my handling of regular expressions in this context?
Try following variation of the method:
private void convertFile(string filename)
{
// build name of output file
string convertedFile = Path.ChangeExtension(filename, ".out");
// open input file for reading
FileInfo source = new FileInfo(filename);
StreamReader srcStream = source.OpenText();
// open output file for writing
using (StreamWriter dstStream = File.CreateText(convertedFile))
{
// loop over input file
string line;
do
{
// get next line from input file
line = srcStream.ReadLine();
if (!Regex.IsMatch(line, #"fred=\d+"))
{
dstStream.WriteLine(line);
dstStream.Flush();
}
} while (line != null);
}
Debug.WriteLine(string.Format("File written to: {0}", convertedFile));
}
The main modification is use of using keyword which would guarantee disposal and closing of the file resource. If problem is not still resolved then try followings:
Do you have any global exception handlers? Make sure you check Debug > Exceptions... so that Visual Studio automatically breaks on the line where exception is thrown. See this article on how-to.
Make sure files are written at correct places. If files have full path then the Debug.WriteLine statement above would tell you were the files are being written.
You should get at least 0 length file written on the disk if no exceptions are occurring.