Outlook VSTO - determine correct 'email' signature based on user - outlook

I'm writing an Outlook 2010 add-in with VSTO, one part of which will automatically add the correct email signature to a new AppointmentItem. The issue I've come across is how to determine which signature is the correct one. For example, I have 2 email signatures set up in Outlook, which have rules on use based on which address my email is coming from. How can I access these rules?
My issue is not with finding the signature files, but in applying the correct rules based on the user's settings. Any ideas?

You can access the rules by using the code below. You can loop through them and get the rule type and actions
app is the current instance of the Outlook.Application
Outlook.Rules rules= app.Session.DefaultStore.GetRules();
foreach (Outlook.Rule r in rules)
{
}

I ended up solving this by creating a dictionary object with the key being the email address and the value as the filepath:
private Dictionary<string, string> signatureDictionary()
{
string sigDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + #"\Microsoft\Signatures";
Dictionary<string, string> returnValue = new Dictionary<string,string>();
Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\Outlook\9375CFF0413111d3B88A00104B2A6676", false);
string[] str = key.GetSubKeyNames();
foreach (string s in str)
{
Microsoft.Win32.RegistryKey subKey = key.OpenSubKey(s, false);
if (subKey.GetValue("New Signature") != null)
{
returnValue.Add(System.Text.Encoding.Unicode.GetString(subKey.GetValue("Account Name") as
Byte[],0,(subKey.GetValue("Account Name") as Byte[]).Length - 2)
, Path.Combine(sigDataDir,System.Text.Encoding.Unicode.GetString(
subKey.GetValue("New Signature") as Byte[], 0, (subKey.GetValue("New Signature") as
Byte[]).Length - 2) + #".rtf"));
}
}
key.Close();
return returnValue;
}
This answer to a similar question initially pointed me in the right direction, and figuring out that the "New Signature" key is only populated when a signature has been set for that account. Undoubtedly there will be situations where this doesn't work, but it sorts it out for my current issue. Since I use the WordEditor when I'm editing emails in VSTO I use the RTF files in this function, but there are also .HTM and .TXT files in the same directory so you can use those if you prefer.

Related

Change Outlook Personal Stationery via c#

I have just started learning how to program add-ins for Outlook using Visual Studio. I am having a hard time finding resources to read up on VSTO. How can I cannot modify the default fonts for "New mail messages" and "Replying or forwarding messages" under Personal Stationery in Outlook?
Revised my post to include the solution:
I am using code from this link (https://pcloadletter.co.uk/2010/05/19/outlook-default-font-and-signature/) and converted to c#. For those that are trying to do the same, here is how I did it.
private void SetFont()
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(#"SOFTWARE\Microsoft\Office\16.0\Common\MailSettings", true);
// set the font in Outlook and then export it from the registry. Use that value for our code.
string ComposeFontSimple = #"3c,00,00,00,1f,00,00,f8,00,00,00,00,c8,00,00,00,00,00,
00,00,ff,ff,00,dd,00,22,41,72,69,61,6c,00,00,00,00,00,00,00,00,00,00,00,00,
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00";
byte[] arrComposeFontSimpleBin = ArrayHexToDec(ComposeFontSimple.Split(','));
key.SetValue("ComposeFontSimple", arrComposeFontSimpleBin, RegistryValueKind.Binary);
}
public static byte[] ArrayHexToDec(string[] arrHex)
{
byte[] arrDec = new byte[arrHex.GetUpperBound(0)];
for (int i = 0; i < arrHex.GetUpperBound(0); i++)
{
if (arrHex[i] == "00")
{
arrDec[i] = 0;
}
else
{
arrDec[i] = byte.Parse(arrHex[i], System.Globalization.NumberStyles.HexNumber);
}
}
return arrDec;
}
The settings are stored at HKCU\Software\Microsoft\Office\%s.0\Common\MailSettings.
The value you want is ReplyFontSimple - font size starts at offset 12, and name start at offset 0x1A (0x0 terminated string).
Outlook keeps its setting in the windows registry. Try using the Process Monitor for tracking where exactly Outlook saves its settings.
If we speak about making changes to Outlook mail items at runtime, the message body can be modified using three different ways:
Body.
HTMLBody.
The Word editor. The WordEditor property of the Inspector class returns an instance of the Word Document which represents the message body.
See Chapter 17: Working with Item Bodies for more information.

How to transfer Activities (mails, notes, etc) from one contact to another in Dynamics 365

Due to some bad practices from one of our internal users. We need to transfer all activities (mails, notes, etc) from one contact to another contact. I was trying to achieve this via UI and I could not find a way to do this.
Is this possible? I'm looking for any way to achieve this, wether is CRMTool, SSIS, UI or any other way. Only admins will do this so we do not need anything fancy as it will be done maybe 4 times a year to clean up some data.
Thanks a lot :)
Tried using UI but no success.
I can think of two ways to do these updates.
The first method is selecting a view on an activity where the owner is listed (i.e. All Phone Calls) and exporting to Excel. This downloads a XLSX with some hidden columns at the start where IDs for the records are kept. You then update the owner column with the new owner (take care of copying the exact fullname), and then importing the Excel spreadsheet again. You would need to repeat this export/import steps for each activity type (phone calls, email, etc.). So this might be impractical if you have a large volume of date, because of the need to repeat and because there are max numbers of records that you can export.
The other way to do this is using some .NET code. Of course, to do this you will need to use Visual Studio 2019.
If that's the case, this will do the trick:
using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;
namespace ChangeActivitiesOwner
{
class Program
{
static void Main(string[] args)
{
string connectionString = "AuthType=Office365;Url=<TODO:URL>;Username=<TODO:User>;Password=<TODO:Pass>;";
string oldUserFullname = ""; // TODO: place here fullname for the user you want to overwrite
string newUserFullname = ""; // TODO: place here fullname for the user you want to overwrite with
CrmServiceClient client = new CrmServiceClient(connectionString);
IOrganizationService service = client.OrganizationWebProxyClient != null ? client.OrganizationWebProxyClient : (IOrganizationService)client.OrganizationServiceProxy;
QueryByAttribute qbyaOldUser = new QueryByAttribute("systemuser");
qbyaOldUser.AddAttributeValue("fullname", oldUserFullname);
Guid olduserid = (Guid)service.RetrieveMultiple(qbyaOldUser)[0].Attributes["systemuserid"];
QueryByAttribute qbyaNewUser = new QueryByAttribute("systemuser");
qbyaNewUser.AddAttributeValue("fullname", newUserFullname);
Guid newuserid = (Guid)service.RetrieveMultiple(qbyaNewUser)[0].Attributes["systemuserid"];
foreach (string activity in new string[]{ "task", "phonecall", "email", "fax", "appointment", "letter", "campaignresponse", "campaignactivity" }) // TODO: Add other activities as needed!!!
{
QueryExpression query = new QueryExpression(activity)
{
ColumnSet = new ColumnSet("activityid", "ownerid")
};
query.Criteria.AddCondition(new ConditionExpression("ownerid", ConditionOperator.Equal, olduserid));
foreach (Entity e in service.RetrieveMultiple(query).Entities)
{
e.Attributes["ownerid"] = new EntityReference("systemuser", newuserid);
service.Update(e);
}
}
}
}
}
Please complete the lines marked with "TODO" with your info.
You will need to add the packages Microsoft.CrmSdk.CoreAssemblies, Microsoft.CrmSdk.Deployment, Microsoft.CrmSdk.Workflow, Microsoft.CrmSdk.XrmTooling.CoreAssembly, Microsoft.IdentityModel.Clients.ActiveDIrectory and Newtonsoft.Json to your solution, and use .NET Framework 4.6.2.
Hope this helps.

Match EWS Conversation* to Outlook Add-in Conversation*

I wrote an add-in for Outlook years ago that adds entries to a database based on the Item's ConversationIndex/ConversationId properties. This works great and remains uniform across all clients interacting with the messages (e.g. "Bob" can see that "Mary" already processed this message because an entry with the ConversationIndex already exists).
I'm now trying to move this piece to a service (and connect via the EWS API) but I'm not having good luck matching these properties with the values coming from Outlook. For example:
The Outlook Add-In will give me the following values for a specific email I'm targeting:
ConversationID: 6B6369F5023EA646AA7BC161274BDAE8
ConversationIndex: 0101CF3C7EEC6B6369F5023EA646AA7BC161274BDAE8
However, from the EWS API I get the following:
ConversationID: AAQkADFhZThkNmJmLTlkODItNDQyZS1hM2YxLTQ2NWNkMTllYjhjOQAQAGtjafUCPqZGqnvBYSdL2ug=
ConversationIndex: new byte[]{1,1,207,60,126,236,107,99,105,245,2,62,166,70,170,123,193,97,39,75,218,232}
I recognize the first as a Base64 encoded string, however what I get decoded doesn't look like anything I recognize (or can decipher). Is there anyone familiar with this, or who can help to get these two values to align? I can only imagine these properties come from the exchange server is some fashion, but the Client probably performs some cleansing whereas the EWS API just gives me the raw value (less the Base64 for what I presume transport purposes given the XML medium).
If anyone is familiar with this or has done it before I would greatly appreciate any guidance.
Side Note:
There are probably better ways to identify emails but for now I'm stuck with trying to keep these two synonymous. Modifying the outlook add-in isn't really an option, and once I migrate a 1:1 translation to the server (and drop the add-in) I'll have flexibility changing how it work. But for now I need them to run side-by-side. I need to be able to see processes made within Outlook from the web server and vise-versa.
Just found out (I think).
Breakdown
With more Googling and a bit more effort I believe I was able to make them align 1:1 using the following:
ConversationId
This is apparently an assembled value made up of several properties. Luckily I was able to find a method Woodman posted re-implementing the original algorithm used by Outlook here. With some minor modifications (to work with EWS instead of Outlook) I was able to get it to work.
ConversationIndex
This turned out to simply be a matter of using the BitConverter (and removing the hyphens). Easy peasy.
Final Result:
public static class EwsEmailMessageExtensions
{
private const int c_ulConvIndexIDOffset = 6;
private const int c_ulConvIndexIDLength = 16;
private static ExtendedPropertyDefinition PidTagConversationIndexTracking = new ExtendedPropertyDefinition(0x3016, MapiPropertyType.Boolean);
// HUGE props to Woodman
// https://stackoverflow.com/a/21625224/298053
public static string GetOutlookConversationId(this EmailMessage emailMessage)
{
Boolean convTracking;
if (!emailMessage.TryGetProperty(PidTagConversationIndexTracking, out convTracking))
{
convTracking = true;
}
var convIndex = emailMessage.ConversationIndex;
byte[] idBytes;
if (convTracking && convIndex != null && convIndex.Length > 0)
{
// get Id from Conversation index
idBytes = new byte[c_ulConvIndexIDLength];
Array.Copy(convIndex, c_ulConvIndexIDOffset, idBytes, 0, c_ulConvIndexIDLength);
}
else
{
// get Id from Conversation topic
var topic = emailMessage.ConversationTopic;
if (string.IsNullOrEmpty(topic))
{
return string.Empty;
}
if (topic.Length >= 265)
{
topic = topic.Substring(0, 256);
}
topic = topic.ToUpper();
using (var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider())
{
idBytes = md5.ComputeHash(Encoding.Unicode.GetBytes(topic));
}
}
return BitConverter.ToString(idBytes).Replace("-", string.Empty);
}
public static String GetOutlookConversationIndex(this EmailMessage emailMessage)
{
var convIndex = emailMessage.ConversationIndex;
return BitConverter.ToString(convIndex).Replace("-", String.Empty);
}
}
Usage:
// Prep
ExchangeService service = new ExchangeService(...);
Folder inbox = Folder.bind(service, WellKnownFolderName.Inbox);
Item item = /* inbox.FindItems(...).First() */
// Implmentation
EmailMessage emailMessage = item as EmailMessage;
if (emailMessage != null)
{
String conversationId = emailMessage.GetOutlookConversationId();
String conversationIndex = emailMessage.GetOutlookConversationIndex();
/* ... */
}

Exchange Web Services (EWS) API "To" header for alias

I have an inbox set up in exchange, hello#mycompany.com
Additionally, there is an alias for this, news#mycompany.com, so all emails to the news address end up in the hello inbox.
Ideally, I want to be able to tell which alias an email has been sent to, using EWS.
When I send an email to news#mycompany.com, and examine the Internet headers of the message using Microsoft Outlook, the To: header reads To: Hello <news#mycompany.com> which is exactly what I want to see.
However, using EWS, when I look at the ToRecipients property of the message, the reported email address is always that of the primary SMTP address. Also the InternetMessageHeaders property of the Webservices.Data.Item does not contain the To: property. I also can't seem to see the correct address using EWSEditor to examine all the properties of the message.
The answer to this forum post seems to suggest that,
...The Information about the actual email address a message is sent to is stored in the recipients collection which you can't access (outside of exportmessage) in EWS...
How would I go about doing this programatically so I can find the correct To: address?
This works for me:
private static string GetToAddress()
{
ExchangeService exService = new ExchangeService();
exService.Credentials = new NetworkCredential("username", "password", "domain");
exService.Url = new Uri("https://youraddress/EWS/Exchange.asmx");
ExtendedPropertyDefinition PR_TRANSPORT_MESSAGE_HEADERS = new ExtendedPropertyDefinition(0x007D,MapiPropertyType.String);
PropertySet psPropSet = new PropertySet(BasePropertySet.FirstClassProperties)
{PR_TRANSPORT_MESSAGE_HEADERS, ItemSchema.MimeContent};
FindItemsResults<Item> fiResults = exService.FindItems(WellKnownFolderName.Inbox, new ItemView(1));
foreach (Item itItem in fiResults.Items)
{
itItem.Load(psPropSet);
Object valHeaders;
if (itItem.TryGetProperty(PR_TRANSPORT_MESSAGE_HEADERS, out valHeaders))
{
Regex regex = new Regex(#"To:.*<(.+)>");
Match match = regex.Match(valHeaders.ToString());
if (match.Groups.Count == 2)
return match.Groups[1].Value;
}
return ToAddress;
}
return "Cannot find ToAddress";
}
The code is from:
http://social.technet.microsoft.com/Forums/en-au/exchangesvrdevelopment/thread/1e5bbde0-218e-466e-afcc-cb60bc2ba692

Saving/Organizing/Searching Outlook E-mail outside of Outlook

My company requires me to use Outlook for my E-mail. Outlook does virtually nothing the way I want to do it and it frustrates me greatly. (I'm not trying to start a flame war here, it must do exactly what thousands of CEO's want it to do, but I'm not a CEO.)
I would like to be able to automatically extract the thousands of E-mails and attachments currently in my Outlook account and save them in my own alternative storage format where I can easily search them and organize them the way I want. (I'm not requesting suggestions for the new format.)
Maybe some nice open source program already can do this... that would be great. Please let me know.
Otherwise, how can I obtain the message content and the attachments without going through the huge collection manually? Even if I could only get the message content and the names of the attachments, that would be sufficient. Is there documentation of the Outlook mail storage format? Is there a way to query Outlook for the data?
Maybe there is an alternative approach I haven't considered?
My preferred language to do this is C#, but I can use others if needed.
Outlook Redemption is the best thing currently to use that I have found. It will allow you to get into the messages and extract the attachments and the message bodies. i am using it now to do just that.
Here is some code I use in a class. I included the constructor and the processing function I use to save off the attachments. I cut out the code that is specific to my needs but you can get an idea of what to use here.
private RDOSession _MailSession = new RDOSession();
private RDOFolder _IncommingInbox;
private RDOFolder _ArchiveFolder;
private string _SaveAttachmentPath;
public MailBox(string Logon_Profile, string IncommingMailPath,
string ArchiveMailPath, string SaveAttPath)
{
_MailSession.Logon(Logon_Profile, null, null, true, null, null);
_IncommingInbox = _MailSession.GetFolderFromPath(IncommingMailPath);
_ArchiveFolder = _MailSession.GetFolderFromPath(ArchiveMailPath);
_SaveAttachmentPath = SaveAttPath;
}
public void ProcessMail()
{
foreach (RDOMail msg in _IncommingInbox.Items)
{
foreach (RDOAttachment attachment in msg.Attachments)
{
attachment.SaveAsFile(_SaveAttachmentPath + attachment.FileName);
}
}
if (msg.Body != null)
{
ProcessBody(msg.Body);
}
}
}
edit:
This is how I call it and what is passed
MailBox pwaMail = new MailBox("Self Email User", #"\\Mailbox - Someone\Inbox",
#"\\EMail - Incomming\Backup", #"\\SomePath");
If you want to extract your e-mails take a look at
Outlook Email Extractor
at codeproject
http://69.10.233.10/KB/dotnet/OutlookEmailExtractor.aspx
rob
www.filefriendly.com

Resources