Outlook Interop - Read Calendars And Check for Private Appointments - c#

So I'm working on an app that will be able to read the outlook calendars for a set of individuals and build an itinerary for them. I've got it pretty much working except that when I scrape a user's calendar, I'm not getting any information on the events they have marked as private.
Let me make it clear that I'm not trying to peer into a user's calendar and read their darkest secret appointment titles, but I would still like to be able to show that this person is unavailable during that time. For example in Outlook proper it just shows as "Private Appointment" - which is exactly what I want to be able to do in my app.
Here's the code I've got for scraping the user's calendar:
private Calendar CreateCalendar(String User_Name, String Initials, DateTime Week)
{
string filter = "[Start] >= '"
+ Week.ToString("g")
+ "' AND [End] <= '"
+ Week.AddDays(7).ToString("g") + "'";
Microsoft.Office.Interop.Outlook.Application App = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace mapiNamespace = App.GetNamespace("MAPI");
//Resolve the person whose calendar We're trying to read.
Outlook.Recipient rm = App.Session.CreateRecipient(User_Name);
if (rm.Resolve())
{
try
{
Outlook.Folder calFolder = App.Session.GetSharedDefaultFolder(rm, Outlook.OlDefaultFolders.olFolderCalendar) as Outlook.Folder;
Outlook.Items CalItems = calFolder.Items;
CalItems.IncludeRecurrences = true;
CalItems.Sort("[Start]", Type.Missing);
//Search for Items within the pre-defined time range.
Outlook.Items restrictItems = CalItems.Restrict(filter);
if (restrictItems.Count == 0)
{
Debug.Print("No Items Found");
return new Calendar(User_Name, Initials);
}
else
{
//Create our internal representation of their calendar, for use elsewhere.
Calendar cal = new Calendar(User_Name, Initials);
Outlook.AppointmentItem i = (Outlook.AppointmentItem)restrictItems.GetFirst();
do
{
cal.AddAppointment(i.Subject, i.Start, i.End);
i = restrictItems.GetNext();
} while (i != null);
App = null;
mapiNamespace = null;
return cal;
}
}
catch (Exception ex)
{
App = null;
mapiNamespace = null;
throw new AccessViolationException(User_Name + " was resolved but could not access their calendar. Have they set you up as a delegate?", ex);
}
}
else
throw new AccessViolationException("The name " + User_Name + " was not resolved, make sure their name in the RM List is spelled correctly. If you can enter their name as a recipient in an outlook mail message and it resolves correctly when you click 'Check Names', then it should work!");
}
I'm not sure if private events are contained in the same "Folder" in outlook's data structures, so maybe I'm just not looking in the right spot?
I've been having trouble googling this because "private" is a reserved word in C# that basically hits on every single post with code and a function declaration in it. Hopefully you guys will be more helpful.
Thanks!

Related

c# Outlook Add-in get Sent date after email is in Sent mailbox

I have made an Outlook Add-In (Outlook 2013 and 2016 VSTO Add-In) for business purposes to save the email details to our database. The add-in is launched when a new email is composed, but closes when the email is sent.
The sent date of the email is only added after the email is moved to the Sent mailbox. Is there a way to use my current add-in (or another add-in) can be used to get that sent date after it has been closed without letting the user wait for it to be moved to the sent mailbox?
I know it can easily be done in VBA, but I want to preferably use an Add-in so that it can be easily loaded to all users with exchange server.
Wouldn't that date be today's date/time (Now) or something close to it?
Everything you can do in VBA you can do in a COM addin - subscribe to the Items.ItemAdd event on the Sent Items folder and retrieve the date when the event fires.
Thank you Dmitry for the reply. It set me on the right path. I used my existing VSTO add in to fire when a new item is added to the Sent mailbox. I did not know that when inserting this in the "ThisAddIn_Startup" method re-activates the add-in, which now makes sense.
I followed this example.
Here is my code:
Outlook.NameSpace outlookNameSpace;
Outlook.MAPIFolder Sent_items;
Outlook.Items items;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
outlookNameSpace = this.Application.GetNamespace("MAPI");
Sent_items = outlookNameSpace.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderSentMail);
items = Sent_items.Items;
items.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(items_ItemAdd);
}
void items_ItemAdd(object Item)
{
Outlook.MailItem mail = (Outlook.MailItem)Item;
string strMailItemNumber_filter = mail.UserProperties["MailItemNumber"].Value;
if ((Item != null) && (!string.IsNullOrWhiteSpace(strMailItemNumber_filter)))
{
if (mail.MessageClass == "IPM.Note" &&
mail.UserProperties["MailItemNumber"].Value.ToUpper().Contains(strMailItemNumber_filter.ToUpper())) //Instead of subject use other mail property
{
//Write 'Sent date' to DB
System.Windows.Forms.MessageBox.Show("Sent date is: "+ mail.SentOn.ToString()+ " MailNr = "+strMailItemNumber_filter);
}
}
}
I had to create a new mail user defined property to match the email I sent to find the correct email in the sent mailbox:
private void AddUserProperty(Outlook.MailItem mail)
{
Outlook.UserProperties mailUserProperties = null;
Outlook.UserProperty mailUserProperty = null;
try
{
mailUserProperties = mail.UserProperties;
mailUserProperty = mailUserProperties.Add("MailItemNrProperty", Outlook.OlUserPropertyType.olText, false, 1);
// Where 1 is OlFormatText (introduced in Outlook 2007)
mail.UserProperties["MailItemNumber"].Value = "Any value as string...";
mail.Save();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
finally
{
if (mailUserProperty != null) Marshal.ReleaseComObject(mailUserProperty);
if (mailUserProperties != null) Marshal.ReleaseComObject(mailUserProperties);
}
}

Add members to Outlook GAL Distribution List using C# in a Console app

I'm trying to write a C# console app that can programmatically update an Outlook distribution list (DL) in the Global Address List (GAL). I have permission to update this DL. I can do it interactively on my PC using Outlook, and I can do it in Perl code using Win32::NetAdmin::GroupAddUsers.
After adding a reference to COM library "Microsoft Outlook 14.0 Object Library", and then accessed via:
using Outlook = Microsoft.Office.Interop.Outlook;
I can successfully read from a DL, even recursing through DL's inside the "main" DL being searched. Here's that working code (critiques not needed for this piece):
private static List<Outlook.AddressEntry> GetMembers(string dl, bool recursive)
{
try
{
List<Outlook.AddressEntry> memberList = new List<Outlook.AddressEntry>();
Outlook.Application oApp = new Outlook.Application();
Outlook.AddressEntry dlEntry = oApp.GetNamespace("MAPI").AddressLists["Global Address List"].AddressEntries[dl];
if (dlEntry.Name == dl)
{
Outlook.AddressEntries members = dlEntry.Members;
foreach (Outlook.AddressEntry member in members)
{
if (recursive && (member.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeDistributionListAddressEntry))
{
List<Outlook.AddressEntry> sublist = GetMembers(member.Name, true);
foreach (Outlook.AddressEntry submember in sublist)
{
memberList.Add(submember);
}
}
else {
memberList.Add(member);
}
}
}
else
{
Console.WriteLine("Could not find an exact match for '" + dl + "'.");
Console.WriteLine("Closest match was '" + dlEntry.Name +"'.");
}
return memberList;
}
catch
{
// This mostly fails if running on a PC without Outlook.
// Return a null, and require the calling code to handle it properl
// (or that code will get a null-reference excception).
return null;
}
}
I can use the output of that to examine the members closely, so I think I understand the DL/member objects a bit.
But, the following code will NOT add a member to a DL:
private static void AddMembers(string dl)
{
Outlook.Application oApp = new Outlook.Application();
Outlook.AddressEntry ae = oApp.GetNamespace("MAPI").AddressLists["Global Address List"].AddressEntries[dl];
try {
ae.Members.Add("EX", "Tuttle, James", "/o=EMC/ou=North America/cn=Recipients/cn=tuttlj");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
ae.Update();
}
The arguments to Members.Add() are defined here, and the values shown in my code come exactly from examining my own Member object from another DL.
The exception displayed is simply "The bookmark is not valid." A similar question was asked before, but the solution was to use P/Invoke or LDAP. I really have no idea how to use P/Invoke (strictly a C# and Perl programmer, not a Windows/C/C++ programmer), and I don't have access to the LDAP server, so I really want to get this working through the Microsoft.Office.Interop.Outlook objects.
Any help is GREATLY appreciated!
After experimenting with several different .NET objects, using System.DirectorServices.AccountManagement as posted in Adding and removing users from Active Directory groups in .NET is what finally code this working for me. Closing out my own question.

Multiple MailBoxes - Access "drafts" folder of "outlook" email account without using the folder name as a string

I develop an outlook add-in using Visual studio 2013 and Add-in express v.7.7.4087.
I have to deal with multiple email accounts (stores). Please see following snapshot and code
private void timerSendFromDraftsFolder_Tick(object sender, EventArgs e)
{
Outlook.Stores stores = null; // CC and OL accounts,
Outlook.Store store = null;
Outlook.MAPIFolder rootFolder = null;
Outlook.Folders rootFolderFolders = null;
Outlook.MAPIFolder draftsFolder = null;
Outlook.Items items = null;
Outlook.MailItem mailItem = null;
bool itemSent = true;
bool allMailItemsSent = true;
try
{
if (Helper.IsOnline())
{
Debug.DebugMessage(3, "AddinModule : timerSendFromSaleswingsFolder_Tick : Fired");
string version = OutlookApp.Version;
if (String.Compare(version, "13") > 0)
{
stores = Globals.ObjNS.Stores;
for (int i = 1; i <= stores.Count; i++)
{
try
{
store = stores[i];
string storeName = store.DisplayName;
if (store.ExchangeStoreType != Outlook.OlExchangeStoreType.olExchangePublicFolder)
{
rootFolder = store.GetRootFolder();
rootFolderFolders = rootFolder.Folders;
if (rootFolderFolders != null)
{
try
{
draftsFolder = rootFolderFolders["drafts"]; // not working for "xxxxxxx#outlook.com" type email accounts
}
catch (Exception )
{
Debug.DebugMessage(3, "AddinModule : timerSendFromSaleswingsFolder_Tick : Excep");
draftsFolder = rootFolderFolders["Drafts (This computer only)"];
}
}
I need to access the drafts folder of each mail account, but the email account of “xxxxxxx#outlook.com“ shows drafts folder as "Drafts (This computer only)" instead of "drafts".
I works fine for me. But I don’t like to introduce this to the production version. Becaues I think this will not work for non-English environments.
Can you please suggest me a solution for that
In redemption (http://www.dimastr.com/redemption/home.htm), is there a solution for that?
P.S
I have used this code in some of my projects
oFolder = oNS.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderDrafts);
But it gives the drafts folder of primary mail account. In my code there is no such method for the “store” object here.
Use the GetDefaultFolder method of the Store class instead. It allows to get a Folder object that represents the default folder in the store and that is of the type specified by the FolderType argument.
This method is similar to the GetDefaultFolder method of the NameSpace object. The difference is that this method gets the default folder on the delivery store that is associated with the account, whereas NameSpace.GetDefaultFolder returns the default folder on the default store for the current profile.
The Redemption library provides the GetDefaultFolder method of the RDOStore class.
GetSharedDefaultFolder is the way to go - call Namespace.CreateRecipient / Recipient.Resolve / Namespace.GetSharedDefaultFolder.

Updating AD User information

I am having a problem updating user information in an Active Directory DB...
When I run the following code I get this error:
The specified directory service attribute or value does not exist
The problem is the path it is using to save the information is this:
CN=AD Test,OU=Container Name,DC=us,DC=flg,DC=int
Ad Test is the username in AD that I am trying to update.
and I believe it should be:
CN=Ad Test,OU=Container Name, OU=Server Name,DC=us,DC=flg,DC=int
I am new to Directory services so I would greatly appreciate any help in finding out why I cannot update... Thank you in advance
public bool UpdateActiveDirectory(string LdapServerName, string CustId, Employee SQLresult)
{
try
{
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://" + LdapServerName, "usrename", "password", AuthenticationTypes.Secure);
DirectorySearcher searcher = new DirectorySearcher(rootEntry);
searcher.Filter = "(sAMAccountName=" + SQLresult.LogonNT + ")";
searcher.PropertiesToLoad.Add("title");
searcher.PropertiesToLoad.Add("street");
searcher.PropertiesToLoad.Add("1");
searcher.PropertiesToLoad.Add("st");
searcher.PropertiesToLoad.Add("postalCode");
searcher.PropertiesToLoad.Add("department");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("manager");
searcher.PropertiesToLoad.Add("telephoneNumber");
SearchResult result = searcher.FindOne();
if (result != null)
{
// create new object from search result
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
entryToUpdate.Properties["title"].Value = SQLresult.Title;
entryToUpdate.Properties["street"].Value = SQLresult.Address;
entryToUpdate.Properties["1"].Value = SQLresult.City;
entryToUpdate.Properties["st"].Value = SQLresult.State;
entryToUpdate.Properties["postalCode"].Value = SQLresult.ZipCode;
entryToUpdate.Properties["department"].Value = SQLresult.Department;
entryToUpdate.Properties["mail"].Value = SQLresult.EMailID;
entryToUpdate.Properties["manager"].Value = SQLresult.ManagerName;
entryToUpdate.Properties["telephoneNumber"].Value = SQLresult.Phone;
entryToUpdate.CommitChanges();
Console.WriteLine("User Updated");
}
else
{
Console.WriteLine("User not found!");
}
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return true;
}
Maybe just a typo?
The third property you're trying to update:
entryToUpdate.Properties["1"].Value = SQLresult.City;
is that a one (1) in there? It should be a small L (l) instead.
Also: the manager's name must be the Distinguished Name of the manager - the whole
CN=Manager,CN=Ad Test,OU=Container Name, OU=Server Name,DC=us,DC=flg,DC=int
thing - not just the name itself.
If that doesn't help anything - just go back to old-school debugging technique:
update just a single property; if it fails --> that's your problem case - figure out why it's a problem.
If it works: uncomment a second property and run again
-> repeat over and over again, until you find your culprit

Copy Calendar Items from Shared Calendar to Personal Calendar with Outlook API

I am in the process of writing an app that will use the Outlook API to copy Calendar Items from a shared calendar to my personal calendar. Here is what I have so far...
using Outlook = Microsoft.Office.Interop.Outlook;
public Outlook.Items GetPublicEntries(string calendar)
{
Microsoft.Office.Interop.Outlook.Items CalendarFolderItems = null;
Outlook.Application oApp;
oApp = new Outlook.Application();
Outlook.NameSpace oNS = oApp.GetNamespace("MAPI");
//oNS.Logon(Missing.Value, Missing.Value, true, true);
Outlook.Recipient oRecip = (Outlook.Recipient)oNS.CreateRecipient(calendar);
Outlook.MAPIFolder usersCalendarFolder = (Outlook.MAPIFolder)oNS.GetSharedDefaultFolder(oRecip, Outlook.OlDefaultFolders.olFolderCalendar);
CalendarFolderItems = usersCalendarFolder.Items;
return CalendarFolderItems;
}
static void Main(string[] args)
{
String[] Cals = { "Appointments","Deadlines","Hearings"};
foreach (string cal in Cals)
{
CalendarItems calobj = new CalendarItems();
Outlook.Items calobjs = calobj.GetPublicEntries(cal);
foreach (Microsoft.Office.Interop.Outlook.AppointmentItem item in calobjs)
{
try
{
Console.WriteLine(item.Subject + " -> " + item.Start.ToLongDateString() + " - " + item.GlobalAppointmentID);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Console.ReadKey();
}
}
I am able to return a list of items from the three calendars, but now I need to copy them to my personal calendar and that is where I am stuck. Anyone know how to go about doing this?
Thanks!
Tony
Outlook will not let you copy directly to a specified folder (unlike AppointmentItem.Move() which takes a MAPIFolder object as a parameter) - use AppointmentItem.Copy, followed by AppointmentItem.Move:
AppointmentItem copiedItem = Item.Copy();
AppointmentItem newItem = copiedItem.Move(YourDestinationFolder);
newItem.Save();
Beware that Outlook will wipe out the global appointment id when you call Copy() - this means message updates, even if they come directly to your Inbox, will not find the appointment.
You can avoid the intermediary step if you use Redemption (I am its author) - its RDOAppointmentItem (derived from the RDOMail object) lets you pass the target folder when calling CopyTo().

Categories