How do I read the Received Date from Outlook MSG files -without- the Outlook API? - outlook

I need to read stuff from an Outlook msg file. Currently I'm using a class from CodeProject.com project to accomplish this, since deploying VSTO and Outlook on a server is not an option.
This class gets To, From, CC, Subject, Body, and everything else I need from the msg file, except Date information (such as Received Date and Sent Date).
There is some (really, really low-level) documentation on how to get stuff out of msg files on MSDN, but it's a little beyond the scope of this project and doesn't mention dates at all.
Ideally I'd be able to have a drop-in replacement for the class I am using now (OutlookStorage.cs in the previously mentioned CodeProject) or be able to modify the existing class a bit. To modify, I would need the correct 4 character hexidecimal prop identifier for received date. For instance, Subject is listed as PR_SUBJECT = "0037" and Body is listed as PR_BOY = "1000".

If you're using OutlookStorage.cs from CodeProject, then add the following:
private const string PR_RECEIVED_DATE="007D";
private const string PR_RECEIVED_DATE_2 = "0047";
...
/// <summary>
/// Gets the date the message was received.
/// </summary>
public DateTime ReceivedDate
{
get
{
if (_dateRevieved == DateTime.MinValue)
{
string dateMess = this.GetMapiPropertyString(OutlookStorage.PR_RECEIVED_DATE);
if (String.IsNullOrEmpty(dateMess))
{
dateMess = this.GetMapiPropertyString(OutlookStorage.PR_RECEIVED_DATE_2);
}
_dateRevieved = ExtractDate(dateMess);
}
return _dateRevieved;
//return ExtractDate(dateMess);
}
}
private DateTime _dateRevieved = DateTime.MinValue;
private DateTime ExtractDate(string dateMess)
{
string matchStr = "Date:";
string[] lines = dateMess.Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
if (line.StartsWith(matchStr))
{
string dateStr = line.Substring(matchStr.Length);
DateTime response;
if (DateTime.TryParse(dateStr, out response))
{
return response;
}
}
}
return DateTime.MinValue;
}

I think the Aspose library will do what you want, ok it a 3rd party lib so may not be what you want. There are a few vbs scripts around that get basic infomation out of msg files that could be translated.

Got a hint from this:
string fullFileName = "c:\message.msg";
DateTime dateRevieved = new DateTime();
StreamReader sr = new StreamReader(fullFileName, Encoding.Default);
string full = sr.ReadToEnd();
string date;
int iStart;
int iLast;
string caption;
//This -should- handle all manner of screwage
//The ONLY way it would not is if someone guessed the -exact- to-the-second
//time that they send the message, put it in their subject in the right format
while (true) { //not an infinite loop, I swear!
caption = "Date:";
if (full.IndexOf("Date:") > -1) { //full shortens with each date is removed
string temp = "";
iStart = full.LastIndexOf(caption);
temp = full.Remove(0, iStart + caption.Length);
full = full.Substring(0, iStart);
iLast = temp.IndexOf("\r\n");
if (iLast < 0) {
date = temp;
} else {
date = temp.Substring(0, iLast);
}
date = date.Trim();
if (date.Contains(subject) || subject.Contains(date)) {
continue; //would only happen if someone is trying to screw me
}
try {
dateRevieved = DateTime.Parse(date); //will fail if not a date
break; //if not a date breaks out of while loop
} catch {
continue; //try with a smaller subset of the msg
}
} else {
break;
}
}
This is kind of a hack compared to the ways you can get other things from msg files using something this lovely project. Still, it's stood up to everything I have thrown against it, and as noted the -only- way to fool it is to put the exact to-the-second date in the subject line in the proper format.

to combine your two posts I would suggest the following solution:
To modify, I would need the correct 4 character hexidecimal prop identifier for recieved date. For instance, Subject is listed as PR_SUBJECT = "0037" and Body is listed as PR_BOY = "1000".
Look for "007D".
Use the method you posted in your second post on the received data to eliminate the problem when the same (date) string is inside the subject.
I have to mention that this method doesn't seem to work on internal eMails: In mails I receive from colleagues, there is no substg1.0_007Dxxxx-Property.
Here, the date seems to be hidden in substg1.0_0047xxxx.
All the best!
inno

Related

Outlook add-in: Automatically changing signature won't display images

I've had to write my first Outlook add-in.
Basically, I have two signatures to choose from: "oferta" and "default". Depending on the words contained in the mail subject, a different signature will be used.
Everything works fine with text-only signatures, but when pictures are included, these are never sent and ares displayed as blank squares instead.
However, if I manually select any of the signatures in Outlook, the pictures are properly displayed.
I guess the problem is in the GetSignature() method, which I borrowed form someone else's answer (sorry, I can't find where I got this from!).
How could I solve this? Is there a better way to automatically change the signatures?
This is my code:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
// When an email is sent a different signature is appended depending on the subject.
private void Application_ItemSend(object Item, ref bool Cancel)
{
MailItem mail = (MailItem)Item;
string subject = mail.Subject;
string firma = subject.ToUpper().Contains("PEDIDO") ? GetSignature("oferta") : GetSignature("default");
mail.HTMLBody += firma;
if (mail != null) Marshal.ReleaseComObject(mail);
}
// Finds and returns the .htm signature file.
private string GetSignature(string signatureName)
{
string appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Microsoft\\Signatures";
string signature = string.Empty;
DirectoryInfo diInfo = new DirectoryInfo(appDataDir);
if (diInfo.Exists)
{
FileInfo[] fiSignature = diInfo.GetFiles(signatureName + ".htm");
if (fiSignature.Length > 0)
{
StreamReader sr = new StreamReader(fiSignature[0].FullName, Encoding.Default);
signature = sr.ReadToEnd();
if (!string.IsNullOrEmpty(signature))
{
string fileName = fiSignature[0].Name.Replace(fiSignature[0].Extension, string.Empty);
signature = signature.Replace(fileName + "_files/", appDataDir + "/" + fileName + "_files/");
}
}
}
return signature;
}
}
Of course - pictures are added as separate attachments, and you never deal with them.
Also, concatenating two HTML strings (mail.HTMLBody += firma;) does not necessarily produce a valid HTML string.
If using Redemption is an option (I am its author), it exposes RDOSignature.ApplyTo method, whcih inserts a signature including its attachments and styles.

Implementing file attachments in JHipster

I have a monolithic JHipster (v5.6.1) project, and I need to implement file attachments to an entity.
I don't know where to start with this. DTOs are sent to REST controllers to create or update them, how should I send the file?
I could attach the Base64 to my DTO and send it that way, but I'm not sure how (are there any angular plugins to accomplish this?). This would require extra work for the server and, I think, extra file size.
Another option is to send the entity (DTO) and file separately, again I'm not entirely sure how.
I see no reason to leave this question unanswered since I have implemented attachments successfully in several of my projects now.
If you prefer, you can skip this explanation and check the github repository I created at vicpermir/jhipster-ng-attachments. It's a working project ready to fire up and play.
The general idea is to have an Attachment entity with all the required fields (file size, name, content type, etc...) and set a many-to-many relation to any entity you want to implement file attachments for.
The JDL is something like this:
// Test entity, just to showcase attachments, this should
// be the entity you want to add attachments to
entity Report {
name String required
}
entity Attachment {
filename String required // Generated unique filename on the server
originalFilename String required // Original filename on the users computer
extension String required
sizeInBytes Integer required
sha256 String required // Can be useful for duplication and integrity checks
contentType String required
uploadDate Instant required
}
// ManyToMany instead of OneToMany because it will be easier and cleaner
// to integrate attachments into other entities in case we need to do it
relationship ManyToMany {
Report{attachments} to Attachment{reports}
}
I have both filename and originalFilename because one of my requirements was to keep whatever file name the user uploaded it with. The generated unique name that I use on the server side is transparent to the user.
Once you generate a project with a JDL like that, you will have to add the file payload to your DTO (or entity if you don't use DTOs) so that the server can receive it in base64 and store it.
I have this in my AttachmentDTO:
...
private Instant uploadDate;
// File payload (transient)
private byte[] file;
public Long getId() {
return id;
}
...
Then, you only have to process those byte arrays on the server side, store them and save a reference to the location into the database.
AttachmentService.java
/**
* Process file attachments
*/
public Set<AttachmentDTO> processAttachments(Set<AttachmentDTO> attachments) {
Set<AttachmentDTO> result = new HashSet<>();
if (attachments != null && attachments.size() > 0) {
for (AttachmentDTO a : attachments) {
if (a.getId() == null) {
Optional<AttachmentDTO> existingAttachment = this.findBySha256(a.getSha256());
if(existingAttachment.isPresent()) {
a.setId(existingAttachment.get().getId());
} else {
String fileExtension = FilenameUtils.getExtension(a.getOriginalFilename());
String fileName = UUID.randomUUID() + "." + fileExtension;
if (StringUtils.isBlank(a.getContentType())) {
a.setContentType("application/octet-stream");
}
Boolean saved = this.createBase64File(fileName, a.getFile());
if (saved) {
a.setFilename(fileName);
}
}
}
result.add(a);
}
}
return result;
}
What I do here is check if the attachment already exists (using the SHA256 hash). If it does I use that one, otherwise I store the new file and persist the new attachment data.
What's left now is to manage attachments on the client side. I created two components for this so it is extremely easy to add attachments to new entities.
attachment-download.component.ts
...
#Component({
selector: 'jhi-attachment-download',
template: , // Removed to reduce verbosity
providers: [JhiDataUtils]
})
export class JhiAttachmentDownloadComponent {
#Input()
attachments: IAttachment[] = [];
}
This just calls a mapping that takes the attachment ID, looks for the associated file on the server and returns that file for the browser to download. Use this component in your entity detail view with:
<jhi-attachment-download [attachments]="[your_entity].attachments"></jhi-attachment-download>
attachment-upload.component.ts
...
#Component({
selector: 'jhi-attachment-upload',
template: , // Removed to reduce verbosity
providers: [JhiDataUtils]
})
export class JhiAttachmentUploadComponent {
#Input()
attachments: IAttachment[] = [];
loadingFiles: number;
constructor(private dataUtils: JhiDataUtils) {
this.loadingFiles = 0;
}
addAttachment(e: any): void {
this.loadingFiles = 0;
if (e && e.target.files) {
this.loadingFiles = e.target.files.length;
for (let i = 0; i < this.loadingFiles; i++) {
const file = e.target.files[i];
const fileName = file.name;
const attachment: IAttachment = {
originalFilename: fileName,
contentType: file.type,
sizeInBytes: file.size,
extension: this.getExtension(fileName),
processing: true
};
this.attachments.push(attachment);
this.dataUtils.toBase64(file, (base64Data: any) => {
attachment.file = base64Data;
attachment.sha256 = hash
.sha256()
.update(base64Data)
.digest('hex');
attachment.processing = false;
this.loadingFiles--;
});
}
}
e.target.value = '';
}
getExtension(fileName: string): string {
return fileName.substring(fileName.lastIndexOf('.'));
}
}
Use this component in your entity update view with:
<jhi-attachment-upload [attachments]="editForm.get('attachments')!.value"></jhi-attachment-upload>
Once sent to the server, the files will be stored on the folder you configured in your application-*.yml separated in subdirectories by year and month. This is to avoid storing too many files on the same folder, which can be a big headache.
I'm sure many things could be done better, but this has worked for me.

CRM 2011 accessing webcontext with outlook plugin

I have found some plugin code on the web that enables me to get the entity ID and the object type code for an entity in a plugin. The plugin is fired on RetrieveMultiple on activitypointer. The code lets me get the id and object code of the entity that is currently being viewed (which is displaying the activities grid which is firing the plugin).
This code works fine when using the web interface. However I need it to also work in the Outlook preview pane and currently it does not. The activities grid in the Outlook preview pane just says "an error has occurred". Below is the code that the plugin is using to get the details from the web header.
internal static Dictionary<string, string> GetHeaderFields(HttpContext webcontext, string objectTypeCode, string objectId)
{
Dictionary<string, string> fields = new Dictionary<string, string>();
string callerentitytype = null;
string callerentityidstring = null;
try
{
// Activities Navigation Pane
if (new List<string>(webcontext.Request.Params.AllKeys).Contains("oType"))
{
callerentitytype = webcontext.Request.Params["oType"];
callerentityidstring = webcontext.Request.Params["oId"];
}
// Activities Sub Grid
else
{
string requeststring = webcontext.Request.UrlReferrer.Query;
requeststring = requeststring.Substring(1);
string[] parts = requeststring.Split(new string[] { "=", "&" }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < parts.Length - 1; i++)
if (parts[i].ToLower() == "otype" || parts[i].ToLower() == "etc")
callerentitytype = parts[i + 1];
else if (parts[i].ToLower() == "oid" || parts[i].ToLower() == "id")
callerentityidstring = parts[i + 1];
}
fields.Add(objectTypeCode, callerentitytype);
fields.Add(objectId, callerentityidstring);
}
catch (Exception ex)
{
throw new Plugin.LoggableException(string.Format("Failed to obtain header information; {0}", ex.Message), ex.InnerException);
}
return fields;
}
The reason is that webcontext.Request.UrlReferrer is NULL. Is there anywhere else I can get this info of the 'calling' entity? (Not the activity sub grid that is triggering the plugin, but the actual parent entity that the sub grid is on).
Thanks for any help or direction with this.
This might work. Each of the activitypointers that are returned should all be "regarding" the same record (if in a sub grid). If you take say the 1st one and examine the regardingobjectid property, that should be an entity reference which will give you the logical name of the parent and it's guid. If that works, it will work across all clients (in theory anyway).

isolated storage in windows phone developement

Im using VS to develop a windows phone app. Im doing it wp8 but it doesnt matter because it the code works for 7 too. Anyway, I have a text box and a button. When the text from the text box is entered, and the button is clicked it adds that to isolated storage.
On my other page, I have a textblock. Which should display what I wrote in the text box. It does work, but first let me sho you my code.
if (appsettings.Contains("name"))
{
appsettings.Remove("name");
appsettings.Add("name", TitleTextBox.Text); //rename if already exists
}
and then the second page that collects the info is below.
if (appsettings.Contains("name"))
{
string content = appsettings["name"].ToString(); //converts to string
titleTextBlock.Text = content; //shows title in text block
}
The problem is, the "name" works. However, if I call it ANYTHING else it does not. I want to add a different name because i want to be able to input two lots. For example two text box's and then when you press the button and go to the other page, it has two textblocks displaying each string in each one. I can't seem to do this because only "name" works. Ive changed it to other names and it doesnt work. Does anyone know why?
IsolatedStorageSettings works as a Dictionary. If you want to acces a specific key it should exist in the Dictionary.
If you try to change the value that already exists you can do like this:
if (appSettings.Contains("key")) appSettings["key"] = "new value";
else appSettings.Add("key", "new value");
Don't also forget to save your appSettings:
appSettings.Save();
And also according to your code - in ISS you can put not only string - it can be any object, if you want to get it, you should make a cast or use as:
string content = (string)appsettings["name"]; //converts to string
string content = appsettings["name"] as string;
EDIT - after comments, rebuild once more
If you want to have a to-do-list and you know that every task has its specific title, description and time then I would advise to create a special class for this, for example:
public class myTodo
{
public string TaskTitle { get; set; }
public string TaskDescription { get; set; }
public TimeSpan ElapsedTime { get; set; }
}
I used TimeSpan because I think it's easier to manage Time with it. Then if you want to Save/Load your myTodo you can do like this:
// create an example of your task
myTodo newTask = new myTodo() { TaskTitle = "Clean", TaskDescription = "Clean room", ElapsedTime = new TimeSpan(2, 0, 0) };
// add it to ISS and save
if (appSettings.Contains("firatTask")) appSettings["firatTask"] = newTask;
else appSettings.Add("firatTask", newTask);
appSettings.Save();
// try to load
myTodo read = appSettings["firatTask"] as myTodo;
You can access your item like this:
read.Title = TitleTextBox.Text; // and so on
Consider also making a List<myToDo> and be aware that ISS shoul also handle this:
List<myTodo> listJob = new List<myTodo>();
listJob.Add(firstTask); // firstTask is myToDo
listJob.Add(secondTask); // secondTask is myToDo
if (appSettings.Contains("listTask")) appSettings["listTask"] = listJob;
else appSettings.Add("listTask", listJob);
appSettings.Save();
List<myTodo> readList = appSettings["listTask"] as List<myTodo>;

Protobuf C# Message Translation to JAVA

I am trying to translate a message generated using C# to JAVA. As a first step, i generated proto file and this is what i got
package Om.Business.Scanner;
message ScannerActivityDetail {
optional string ActivityId = 1;
optional string ContextId = 2;
optional int32 ActivityStart = 3;
optional bcl.DateTime ActivityEnd = 4;
}
How do i interpret bcl.DateTime in java world?
I am using protobuf-net and trying to de-serialize message generated by C# app.
Thanks in advance for your help.
Looking at bcl.proto, it should be pretty straightforward. Create a Map<DateTime.TimeSpanScale, TimeUnit> in the obvious way, then:
public static Date toDate(bcl.DateTime proto) {
TimeUnit unit = SCALE_TO_UNIT_MAP.get(proto.getScale());
if (unit == null) {
throw new IllegalArgumentException("Invalid scale: " + proto.getScale());
}
long millis = unit.toMillis(proto.getValue());
return new Date(millis);
}
You could use Joda Time's DateTime type in exactly the same way, as it has a constructor accepting a long too. (You might want to think about which time zone to specify though...)

Resources