I am working with Microsoft Visual Studio 2015 and I am trying to convert something from list form to string form. I have found some solutions for similar problems but not this one in particular.
I would like to eventually have this code work:
Dim info As Byte() = New UTF8Encoding(True).GetBytes(Utilities.GetEmailInfo(msg.Message).Attachments)
My end game is to take the text from Attachments and write it to a file. If I use some of the other data types listed below such as ToData, the file turns out properly but I encounter an error with the code above because GetBytes cannot get the text from a list. Is there another function I could use to get the text from the list?
The class that I need to convert contains the following:
Public Class EmailInfo
Public FromData As String = vbNullString 'FROM:
Public ToData As String = vbNullString 'TO:
Public DateData As String = vbNullString 'DATE:
Public SubjectData As String = vbNullString 'SUBJECT:
Public MessageBody As EmailItem 'contents of message body
Public AlternateViews As New Collections.Generic.List(Of EmailItem) 'list of alternate views
Public Attachments As New Collections.Generic.List(Of EmailItem) 'list of attachments
End Class
The resource that I want to access is EmailInfo.Attachments. This resource is stored as a list of type EmailItem. The code for this type is as follows:
Public Class EmailItem
Public ContentType As String = vbNullString 'CONTENT-TYPE data
Public ContentTypeData As String = vbNullString 'filename or text encoding
Public ContentTypeDataIsFilename As Boolean = False 'True if ContentTypeData specifies a filename
Public ContentEncoding As String = vbNullString 'CONTENT-TRANSFER-ENCODING data
Public ContentBody As String = vbNullString 'raw data of block
End Class
I have tried using some code such as String.Join but I end up with a blank string.
Please pardon my ignorance as I am new to VB.
Thank you all for all of your help!
Ryan
This no trivial task. Attachments could be any number of proprietary formats: ".pdf", ".doc", ".xls", ".ppt", ".csv", ".vsd", ".zip", ".rar", ".txt", ".html", ".proj", etc, etc , etc.
Good news is all the work has already been done for you and I will show you how to generically read almost any file format and extract the text in this answer:
Generically read any file format and convert it to .txt format
Make sure you read the Info to set it up paragraph.
So go ahead and reference TikaOnDotnet & TikaOnDotnet.TextExtractor to your project using NuGet (Tools menu > NuGet Package Manager).
I am assuming you have written code to extract the email attachments, using an Outlook Add-In MSDN How to: Programmatically Save Attachments from Outlook E-Mail Items or just an app that uses Outlook via Interop, eg:
In C#:
private TextExtractor _textExtractor;
private string _attachmentTextFilepath = #"c:\temp\EmailAttachmentText.txt";
static void IterateMessages(Outlook.Folder folder)
{
var fi = folder.Items;
if (fi != null)
{
foreach (Object item in fi)
{
Outlook.MailItem mi = (Outlook.MailItem)item;
var attachments = mi.Attachments;
if (attachments.Count != 0)
{
for (int i = 1; i <= mi.Attachments.Count; i++)
{
//Save email attachments
mi.Attachments[i].SaveAsFile(#"C:\temp\" + mi.Attachments[i].FileName);
//Use TIKA to read the contents of the file
TextExtractionResult textExtractionResult = _textExtractor.Extract(#"C:\temp\" + mi.Attachments[i].FileName);
//Save attachment text to a txt file
File.AppendAllText(_attachmentTextFilepath, textExtractionResult.Text);
}
}
}
}
}
In VB.Net:
Private _textExtractor As TextExtractor
Private _attachmentTextFilepath As String = "c:\temp\EmailAttachmentText.txt"
Private Shared Sub IterateMessages(folder As Outlook.Folder)
Dim fi = folder.Items
If fi IsNot Nothing Then
For Each item As [Object] In fi
Dim mi As Outlook.MailItem = DirectCast(item, Outlook.MailItem)
Dim attachments = mi.Attachments
If attachments.Count <> 0 Then
For i As Integer = 1 To mi.Attachments.Count
'Save email attachments
mi.Attachments(i).SaveAsFile("C:\temp\" + mi.Attachments(i).FileName)
'Use TIKA to read the contents of the file
Dim textExtractionResult As TextExtractionResult = _textExtractor.Extract("C:\temp\" + mi.Attachments(i).FileName)
'Save attachment text to a txt file
File.AppendAllText(_attachmentTextFilepath, textExtractionResult.Text)
Next
End If
Next
End If
End Sub
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.
Here is my situation.
I've made a Application Windows Form which is editing an Excel sheet depending on what the user is doing (buttons / toggle / text box / etc..).
Once the edtion is complete the new Excel file is generated.
My programm then selects a range of cells, and copies it.
It goes to Clipboard.
I've done multiple tests (if(){}else{} / saving into a .jpg / etc..) and everything is checked true.
I don't want to ATTACH my image.
I don't want to save it, even temporarily, then paste it in the body through the saved .jpg file.
I "just" want to make a Ctrl+V, into my Outlook eMail Body.
here's how i get the image from my clipboard ("MessageBody" is declared at the top as a public string so that i can call it through different regions):
public void ReadData()
{
Excel excel = new Excel(#"E:\c#\Project#XX\Resources\TEST.xlsx", 1);
excel.CopyRange(0, 0, 32, 12);
IDataObject iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Bitmap))
{
Bitmap MessageBody = (iData.GetData(DataFormats.Bitmap, true) as Bitmap);
//pbx.Image = Image;
//image.Save(#"E:\c#\Project#XX\Resources\bitmap1.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
//MessageBody = image;
}
excel.Close();
excel.Quit();
}
Here's my message building code :
private void flatCustButton013_Click(object sender, EventArgs e)
{
ReadData();
string MessageSubject = $"SomeSubject";
Outlook.MailItem newMail = (Outlook.MailItem)application.CreateItem(Outlook.OlItemType.olMailItem);
newMail.Subject = MessageSubject;
newMail.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
MessageHTMLBody = "<html><body>SomeText.<img src="cid:MessageBody"</img></body></html>";
newMail.HTMLBody = MessageHTMLBody;
//newMail.Body = System.Windows.Input.ApplicationCommands.Paste;
//newMail.Display(false);
//newMail.BodyFormat = Outlook.OlBodyFormat.olFormatHTML; ;
//newMail.HTMLBody = System.Windows.Input.ApplicationCommands.Paste;
//newMail.Body = MessageBody;
newMail.To = ToAddress;
newMail.SentOnBehalfOfName = FromAddress;
newMail.CC = CcAddress;
newMail.BCC = BccAddress;
//System.Windows.Input.ApplicationCommands.Paste;
//wrdEdit = application._Inspector.WordEditor
newMail.Send();
}
In the previous version, people had to open an Excel file, change the Cells manually, and then click a button (with a macro in VB).
It would open a Outlook eMail item (display:true) with the image pasted already, then just needed to click "Send" and it was ok.
My 2.0 version is meant to automatise this by just click a "Send //Generated Excel Sheet//"Button.
Here's the macro code in VB :
Sub SendMail()
Dim Messager As New Outlook.Application
Dim Mail As Outlook.MailItem
Dim WrdEdit
Range("my_range").CopyPicture
Set Messager = New Outlook.Application
Set Mail = Messager.CreateItem(olMailItem)
With Mail
.To = "some folks"
.CC = "some folks"
'.BCC = ""
.Subject = "SomeSubject"
.Display
.HTMLBody = Mess + .HTMLBody
End With
Set WrdEdit = Messager.ActiveInspector.WordEditor
WrdEdit.Application.Selection.Paste
Set WrdEdit = Nothing
Set Messager = Nothing
Set Mail = Nothing
ActiveWorkbook.Close SaveChanges:=False
End Sub
But i don't get why i would pass through the Inspector (which i have no knowledge about) if i'm succeeding in retrieving the clipboard image.
I found a way by adding an attachment from a local saved file to the mail and displaying it into the body with HTML.
Like so :
newMail.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
Outlook.Attachment attachment = newMail.Attachments.Add(#"path.imageformat", Outlook.OlAttachmentType.olEmbeddeditem, null, $"someTitle");
string imagecid = "whatever";
attachment.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3613041E", imagecid);
newMail.HTMLBody = String.Format("<body><img src=\"cid:{0}\"></body>", imageCid);
the "http://schemas.microsoft.com/mapi/proptag/0x3712001E" code is here : http://www.outlookcode.com/codedetail.aspx?id=1915.
And it works.
However, i really don't like having urls in my code, is there another way ?
And also, it looks like whatever i put into my 'imagecid' string nothing changes.
I'm trying hard to understand what i did with the code, and not just letting it work as it is.
Looks like i need some authorisation from Office to work through an application ? And especially when i want to paste image in a body (which can contain other stuff than just pixel data).
If i did not need this autorization with the VB script, i suppose, it is because i had to "physicly/humanly" press "Send Mail" button in Outlook ?
Are there any other possibilities ? I'm still using a file though to paste in my body, can't i just make a Ctrl+C Ctrl+V programmatically ? :(
I'm building a program that saves data to a text file and retrieves it again for later use. One of the features the program must have is that it must save dates and times.
So far I am using the following code to read and write these dates and times:
//Write dates to text file:
String path = string.Concat(Environment.CurrentDirectory, #"\database\bob.txt");
StreamWriter save = new StreamWriter(path);
DateTime time = DateTime.Now;
save.WriteLine(time);
save.Dispose();
and:
//Read data from text file:
StreamReader reader = new StreamReader(path);
label.Content = reader.ReadLine();
reader.Dispose();
Now, from doing this I can confirm that the date gets saved to the text file but the program doesn't read it into the label. Is there anyway around this were it will read into the label so that I can display the date?
It is usually better to save an object graph rather than writing the data piecemeal like this.
If you have control over the format (which it appears you do), then I would suggest a JSON or XML format.
This is VB, but easily translatable to C#:
''' JSON, Use NuGet to Install NewtonSoft.Json
Imports Newtonsoft.Json
Imports System.IO
Public Class SomethingIWantToSave
Public Shared Function Load(byval path As String) As SomethingIWantToSave
Dim result As SomethingIWantToSave = Nothing
Dim content As String = String.Empty
If File.Exists(path) Then
content = File.ReadAllText(path)
result = JsonConvert.DeserializeObject(Of SomethingIWantToSave)(content)
End If
return result
End Function
Public Sub Save(byval path As String)
Dim content As String = JsonConvert.Serialize(Me)
File.WriteAllText(path, content)
End Sub
End Class
Use the class like so:
' load existing data
Dim data As SomethingIWantToSave = SomethingIWantToSave.Load(Path.Combine("c:\yadda", "yadda", "content.json"))
' make changes if you like.
...
' save data back to disk
data.Save(Path.Combine("c:\yadda", "yadda", "content.json"))
A similar technique can be used for XML using the System.Xml.XmlSerializer class.
I am trying to get the text from a PDF stored in localStorage in a Windows Phone 8.1 application,but I always get an FileNotFoundException.
To explain the whole story, I get a PDF from an online source, I store it to a folder with name same as the username (The username is an email address, but I tried also without the # sign) of the user and then I want to get some text from the PDF file. I use iTextSharp and follow the examples, but cannot succeed. When I send the PDF to the Launcher is opening succesfully with another app like Acrobat Reader.
My function is like below. I first send an PDF Object, which has an attribute called Path and it is stored to folder specific to the username of the user.
Then I get the pdf as a StorageFile Item. When I create the PDFReader calling the constructor I get a FileNotFoundException. Does anybody knows or can guess what can be the problem? Is iTextSharp compatible with Windows Phone 8.1?
internal async Task<bool> OpenPdfFromDownloadedCollections(PDF pdfToOpen, string username)
{
try
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
var pdfFolder = await folder.GetFolderAsync(username + "PDFs");
var pdf = await pdfFolder.GetFileAsync(Object.Path);
StringBuilder text = new StringBuilder();
using (PdfReader reader = new PdfReader(pdf.Path))
{
for (int i = 1; i <= reader.NumberOfPages; i++)
{
string thePage = PdfTextExtractor.GetTextFromPage(reader, i, its);
string[] theLines = thePage.Split('\n');
foreach (var theLine in theLines)
{
text.AppendLine(theLine);
}
}
}
return true;
}
catch (Exception)
{
return false;
}
}
var pdf = await pdfFolder.GetFileAsync(Object.Path);
In this line of code you should only pass the file name but you are giving the whole Path as parameter. As pdfFolder currently represents the path.
How to prepend/append text beginning of the existing data in a text file.
Basically i need to provide a header before this data in a text file. This header is a dynamic data. Please note this data is coming from external source or SQL package or from somewhere. So After getting data in a text file then i want to provide a header text with comma separated in the existing entries/data of a text file.
I've sample data in a text file as below:
123,"SAV","CBS123",2010-10-10 00:00:00
456,"CUR","CBS456",2012-02-01 00:00:00
Header text to Prepend:
HDR<TableName><DateTime>
Output i need as below:
TableName: Account
DateTime: 2012-05-09 12:52:00
HDRAccount2012-05-09 12:52:00
123,"SAV","CBS123",2010-10-10 00:00:00
456,"CUR","CBS456",2012-02-01 00:00:00
Please help me how to get the same in both languages VB6.0, C#.NET
Note that you can't technically 'insert' into a file and have all contents 'shift' down. Best you can do is read the file and rewrite it with a new line. Here's one way to do it efficiently:
static void InsertHeader(string filename, string header)
{
var tempfile = Path.GetTempFileName();
using (var writer = new StreamWriter(tempfile))
using (var reader = new StreamReader(filename))
{
writer.WriteLine(header);
while (!reader.EndOfStream)
writer.WriteLine(reader.ReadLine());
}
File.Copy(tempfile, filename, true);
File.Delete(tempfile);
}
Credits to this answer for the idea but improved enough to make it worth posting separately.
Now if you want something that accepts the table name and date time, just add this as a second function:
static void InsertTableHeader(string filename, string tableName, DateTime dateTime)
{
InsertHeader(filename,
String.Format("HDR{0}{1:yyyy-MM-dd HH:MM:ss}",
tableName,
dateTime));
}
So just call InsertHeader(filename, "Account", DateTime.Now) or similar as needed.
var fn = #"c:\temp\log.csv";
var hdr1 = "Account";
var hdr2 = "2012-05-09 12:52:00";
System.IO.File.WriteAllText(fn, System.String.Format("HDR {0} {1}\n{2}", hdr1, hdr2, System.IO.File.ReadAllText(fn)))
String[] headerLines = new String[]{"HDR<TableName><DateTime>"};
String filename = "1.txt";
var newContent = headerLines.Union(File.ReadAllLines(filename));
File.WriteAllLines(filename, newContent);
VB6 translation of yamen's answer. Air code! I haven't compiled this, much less run
it!
Sub InsertHeader(ByVal filename As String, ByVal header As String)
Dim tempfile As String
Dim readUnit As Integer
Dim writeUnit As Integer
tempfile = "c:\tempfile" '' TODO generate better temporary filename -
'' here is a link to help with getting path of temporary directory
'' http://vb.mvps.org/samples/SysFolders
readUnit = FreeFile
Open filename For Input As #readUnit
writeUnit = FreeFile
Open tempfile For Output As #writeUnit
Print #writeUnit, header
Do Until Eof(readUnit)
Dim nextLine As String
Line Input #readUnit, nextLine
Print #writeUnit, nextLine
Loop
Close readUnit
Close writeUnit
Kill filename
FileCopy tempfile, filename
Kill tempfile
End sub
You can do it in the reverse order of the 1st answere, meanse first your write the header in text file then open that text file in append mode and then woirite the data ..for opening the file in append mode use following code line:
FileStream aFile = new FileStream(filePath, FileMode.Append,
FileAccess.Write);
StreamWriter sw = new StreamWriter(aFile);
sw.Write(text);
sw.Close();
aFile.Close();