Showing posts with label Outlook. Show all posts
Showing posts with label Outlook. Show all posts

Wednesday, December 28, 2011

Room Finder - Outlook 2007 Add-in

Often, when we want to book a meeting room, we end up gathering all the rooms in the vicinity, removing those that are already booked, and then narrowing down our choices based on our preferred rooms. This sounds like quite a lot of manual effort each time we want to book a room. Fortunately, the Outlook APIs are good enough to allow us to automate most of this.

Introducing, the Room Finder - an add-in for Outlook 2007 users.

Sources of inspiration:
1. A large retailer's IT company that I used to work with had a Room Finder utility added in Outlook. This was back in 2008. I wasnt allowed access to the source code, though. So, I had to create one on my own, based on my experiences as an end-user.

2. Outlook 2010 has this feature. So, for those who haven't got a chance to use Outlook 2010 yet, this is a feature that you would be using once the upgrade happens. Using this add-in in Outlook 2007 makes you get used to this feature.

Steps to create the Room Finder add-in:
1. Using Visual Studio 2010, create an Outlook 2007 Add-in project

2. Add an Outlook Form Region.

Name it RoomFinderFormRegion instead of the default FormRegion1.
How would you like to create this form region?
Select 'Design a new form region'
What type of form region do you want to create?
Select 'Adjoining' type. Replacement is not supported for built-in forms.
What name, title and description do you want to use?
Name it 'Room Finder'
In which display modes should this form region appear?
Turn on the first checkbox and turn off the other 2. We only want this to be available in compose mode.
Which standard message classes will display this form region?
Turn on the first one checkbox for Appointment. Turn off the others, including the default one for Mail Message. We only want this for Appointments / Meeting Requests.
We don't need to specify any custom message classes either.
Click on Finish
(Optional) If you used the default name of FormRegion1 and want to rename it to RoomFinderFormRegion, rename it on the file instead of the class. This keeps the file and the class name in sync. For sake of completeness, there are some more regions where this name would need to be udpated. Find and replace all in the solution for 'FormRegion1'. You would find a string literal, some comments, a corresponding factory class and event handlers where this needs to be updated.
(Note) A pfx key would also be added automatically to the project. You don't need to worry about that.

3. Open the RoomFinderFormRegion in design mode. Add a normal (Windows Forms) Button from the toolbox. Change the Name and Text properties of this button to 'btnSuggest' and 'Su&ggest me a room...' respectively. The & is obviously just to make the g a hot-key so that one can get to it sooner by using the Alt-G key combination.
Ensure that you reduce any whitespace on the form region, except for the button. Resize the form region vertically. Resizing horizontally is not required, as the form region would occupy the entire width of the appointment item anyway.

4. Add a button click event handler. Include the following code in the event-handler.
            try
            {
                var item = (Outlook.AppointmentItem)this.OutlookItem;
                var mapi = Globals.ThisAddIn.Application.GetNamespace("MAPI");
                mapi.Logon();
                // get appointment time info
                DateTime startOn = item.Start;
                DateTime endOn = item.End;
                //TODO: sort rooms based on floor, user preferences & people count
                string identifiedRoom = string.Empty;
                foreach (var room in rooms)
                {
                    //get meeting room's calendar properties
                    var resource = mapi.CreateRecipient(room);
                    bool isFree = resource.AddressEntry.GetFreeBusy(startOn, endOn);
                    if (isFree)
                    {
                        identifiedRoom = room;
                        break;
                    }
                }
                mapi.Logoff();
                if (!string.IsNullOrEmpty(identifiedRoom))
                {
                    item.Resources = identifiedRoom;
                    item.Location = identifiedRoom;
                }
                else
                {
                    MessageBox.Show("No rooms found for this time period! Please consider scheduling the appointment at another time.");
                }
            }
            catch (System.Exception ex)
            {
                MessageBox.Show(string.Format("Error: {0}", ex.ToString(), "Error!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation));
            }
This code won't compile yet. Complete the following steps for that.

5. Add the following class-level member variable to the RoomFinderFormRegion class.
        private string[] rooms = new string[] {
            "Conf Room 1",
            "Conf Room 2",
        };
The array needs to contain all the rooms that we are intersted in. For each room, the string can either be the display name, alias or the full SMTP e-mail address of the meeting room.

6. Add a using statement for the MessageBox.
You can see a few more that were automatically added to the RoomFinderFormRegion class. Use Remove and Sort to remove the redundant ones. You will find that these are the only ones we need for now:
using System;
using System.Windows.Forms;
using Outlook = Microsoft.Office.Interop.Outlook;

7. If you attempt to use the in-built AddressEntry.GetFreeBusy method, it doesn't feel a lot DotNetty. It returns a string, with each character being a free/busy indicator for a certain time interval, the default being 30 minutes. The appointment, on the other hand, already has the start and end datetime values specified. So, what we actual need is for the AddressEntry object to give us a method that would tell us if the resource (meeting room) is available during that time. Since the AddressEntry object doesnt have a method of that kind, we use extensions. And for that, we need to put that in a separate static class, as shown here.
    public static class AppointmentHelper
    {
        private const int DEFAULT_INTERVAL = 30;
        public static bool GetFreeBusy(this AddressEntry addressEntry, DateTime start, DateTime end)
        {
            string freeBusyInfo = addressEntry.GetFreeBusy(start.Date, DEFAULT_INTERVAL, false);
            int position = (int) start.TimeOfDay.TotalMinutes / DEFAULT_INTERVAL;
            TimeSpan ts = (end - start);
            int blocks = (int) ts.TotalMinutes / DEFAULT_INTERVAL;
            return freeBusyInfo.Substring(position, blocks).All(c => c.Equals('0'));
        }
    }

8. Build the code. Ensure that the Outlook client has been closed. You can now run/debug the code. When you create a new appointment, you can now see the new region at the bottom part of the item window.
Click on the button in this Room Finder region to invoke the code that looks for a room which is free during the time selected in the appointment. If no rooms could be identified, a corresponding message is shown. Once a room is identified from the order in which it is present in the array, the search is called off, and the room is added as a resource to the appointment item.

9. Notice that I have left a TODO comment in the code above, which states that another feature could be built into this to sort the rooms based on location/floor, user preferences & people count. For instance, you wouldn't want to book a room which is good enough for 28 people if only 3 of you are going to use it. All this logic would execute to give a sorted list of the rooms array mentioned earlier. The rest of the code remains the same.

10. It is typical to expect the list of rooms to be configured externally instead of being hard-coded as an array of strings.
Add an application configuration file to the project. Include a key named Rooms and a value having a semi-colon delimited string of conf rooms. This is what it would look like.
<configuration>
  <appSettings>
    <add key="Rooms" value="Conf Room 1; Conf Room 2"/>
  </appSettings>
</configuration>

Add a reference to System.Configuration, and add a using statement for the same namespace. Add the following code at the top of the button click event hander, making sure this is still within the try block.
                //TODO: provide a way to edit and store this from Outlook
                string roomsDelimited = ConfigurationManager.AppSettings["Rooms"];
                rooms = roomsDelimited.Split(';');
You can also remove the array initializer since it is now being done only on the button click. The array definition can remain there.
private string[] rooms;
Again, I have left a TODO comment here since managing rooms from Outlook (another button in the add-in, plus storing this data in the mailbox) would be a better approach than having to manage it thru the config file.

11. Note that once you start the project, it will publish this add-in to Outlook. Any changes that you make to the add-in reflect in Outlook during the next launch. Even if you are launching Outlook separately after that, these changes are reflected.
To remove a certain add-in locally, you can use Tools → Trust Center → Add-ins.
Click on Go, select the add-in you don't want anymore (ignore the checkboxes - those are only for enable/disable) and click on Remove.

12. To deploy/distribute & for others to install
Use the Publish wizard. This also creates the setup file.
I suggest publishing to a website so that you can put in updates to the same place.
The default behavior is for the add-in to check for updates every 7 days.

References:
http://msdn.microsoft.com/en-us/library/bb410039(v=office.12).aspx
http://www.eggheadcafe.com/microsoft/Outlook-Program-Forms/35554170/how-to-read-single-instance-of-recursive-meetings-in-outlook.aspx
http://www.tech-recipes.com/rx/1959/outlook_2007_disabling_enabling_add_ins/
http://www.codeproject.com/KB/office/Outlook_Add-in.aspx
http://msdn.microsoft.com/en-us/library/bb147704(v=office.12).aspx
http://msdn.microsoft.com/en-us/library/ms526723(v=exchg.10).aspx
http://support.microsoft.com/kb/310259

Monday, October 31, 2011

Outlook folder renamed

I noticed today that a folder in my Outlook 2010 mailbox had been renamed by some strange force. This is what I could see. So, I googled for this issue, and found that a few folks have had this problem with their Inbox being renamed to image001.jpg. For me, it was the Outbox folder though. The fix was the same, to start Outlook with a switch.

Outlook.exe /resetfoldernames

BEFORE
AFTER

http://www.slickit.ca/2010/03/outlook-2007-inbox-renamed.html