• Wiz/dumb

    Custom Calendar Providers for Outlook 2007

    • 26 Comments

    So if you were using Stephen Toub's Custom Calendar Provider sample for Outlook 2003, and you are testing out the awesome new Outlook 2007 features, you may have noticed that the code sample he provided doesn't work any more. Stephen is planning to update the article for 2007 eventually, but until then, here's the fix.

    The GetList web service method is supposed to return a <Fields> node filled with <Field> nodes that describe each field in that list, including type information, display names, read-only properties, etc. In the sample above, Stephen is not returning the <Fields> node as a part of the response. The reason is that Outlook 2003 ignored this information when it was performing the sync. Outlook 2007 is a little more intelligent in this regard and it does need this information returned. It's not a big deal when linking to a SharePoint Events list, because SharePoint always returns that data. In an effort to keep the code simple, Stephen ignored it.

    So the answer is that we need to add that information back in. So what you'll want to do is edit the WssListResponse.cs file as follows:

    1. Add a new private static method called AddFieldElement as follows:
    2.   /// <summary>
        /// Adds a Field node to the Fields parent node
        /// </summary>
        /// <param name="parentDoc">XmlDocument parent doc used for creating the new element</param>
        /// <param name="parentNode">The parent Fields node to which to add the new Field element</param>
        /// <param name="type">Type attribute</param>
        /// <param name="colName">ColName attribute</param>
        /// <param name="name">Name attribute</param>
        /// <param name="displayName">DisplayName attribute</param>
        /// <param name="readOnly">ReadOnly attribute</param>
        /// <param name="hidden">Hidden attribute</param>
        /// <param name="fromBaseType">FromBaseType attribute</param>
        /// <returns></returns>
        private static XmlElement AddFieldElement(XmlDocument parentDoc,XmlNode parentNode,string type, string colName, string name, string displayName,bool readOnly, bool hidden, bool fromBaseType)
        {
         XmlElement fieldEl = parentDoc.CreateElement(null,"Field",_wssns);
         parentNode.AppendChild(fieldEl);

         if(type != null)  AddAttribute(fieldEl,null,"Type",null,type);
         if(colName != null)  AddAttribute(fieldEl,null,"ColName",null,colName);
         if(name != null)  AddAttribute(fieldEl,null,"Name",null,name);
         if(displayName != null) AddAttribute(fieldEl,null,"DisplayName",null,displayName);
         if(readOnly)   AddAttribute(fieldEl,null,"ReadOnly",null,"TRUE");
         if(hidden)    AddAttribute(fieldEl,null,"Hidden",null,"TRUE");
         if(fromBaseType)  AddAttribute(fieldEl,null,"FromBaseType",null,"TRUE");

         return fieldEl;
         
        }

    3. Modify the GetListDescription Method as follows:

      public static XmlNode GetListDescription(string title, Guid id)
      {
       // Create the response document
       XmlDocument doc = new XmlDocument();

       // Add a List element
       XmlNode list = doc.CreateElement(null, "List",  _wssns);
       doc.AppendChild(list);

       // Add attributes about the list to the List element
       AddAttribute(list, null, "DocTemplateUrl", null, "");
       AddAttribute(list, null, "DefaultViewUrl", null, "/Lists/" + id.ToString("N") + "/AllItems.aspx");
       AddAttribute(list, null, "ID", null, id.ToString("B"));
       AddAttribute(list, null, "Title", null, title);
       AddAttribute(list, null, "Description", null, title);
       AddAttribute(list, null, "ImageUrl", null, "/_layouts/images/itevent.gif");
       AddAttribute(list, null, "Name", null, id.ToString("N"));
       AddAttribute(list, null, "BaseType", null, "0");
       AddAttribute(list, null, "ServerTemplate", null, "106");
       AddAttribute(list, null, "Created", null, DateTime.MinValue.AddDays(2).ToString("s").Replace("T", " "));
       AddAttribute(list, null, "Modified", null, DateTime.MinValue.AddDays(3).ToString("s").Replace("T", " "));
       AddAttribute(list, null, "LastDeleted", null, DateTime.MinValue.AddDays(3).ToString("s").Replace("T", " "));
       AddAttribute(list, null, "Version", null, "0");
       AddAttribute(list, null, "Direction", null, "none");
       AddAttribute(list, null, "ThumbnailSize", null, "");
       AddAttribute(list, null, "WebImageWidth", null, "");
       AddAttribute(list, null, "WebImageHeight", null, "");
       AddAttribute(list, null, "Flags", null, "4096");
       AddAttribute(list, null, "ItemCount", null, "1"); // isn't used, so no point in recomputing size of list
       AddAttribute(list, null, "AnonymousPermMask", null, "");
       AddAttribute(list, null, "RootFolder", null, "/Lists/" + id.ToString("N"));
       AddAttribute(list, null, "ReadSecurity", null, "1");
       AddAttribute(list, null, "WriteSecurity", null, "1");
       AddAttribute(list, null, "Author", null, "1");
       AddAttribute(list, null, "AnonymousPermMask", null, "");
       AddAttribute(list, null, "EventSinkAssembly", null, "");
       AddAttribute(list, null, "EventSinkClass", null, "");
       AddAttribute(list, null, "EventSinkData", null, "");
       AddAttribute(list, null, "EmailInsertsFolder", null, "");
       AddAttribute(list, null, "AllowDeletion", null, "TRUE");
       AddAttribute(list, null, "AllowMultiResponses", null, "FALSE");
       AddAttribute(list, null, "EnableAttachments", null, "TRUE");
       AddAttribute(list, null, "EnableModeration", null, "FALSE");
       AddAttribute(list, null, "EnableVersioning", null, "FALSE");
       AddAttribute(list, null, "Hidden", null, "FALSE");
       AddAttribute(list, null, "MultipleDataList", null, "FALSE");
       AddAttribute(list, null, "Ordered", null, "FALSE");
       AddAttribute(list, null, "ShowUser", null, "TRUE");

       //Create Fields node
       XmlElement fieldsNode = doc.CreateElement(null, "Fields",  _wssns);
       list.AppendChild(fieldsNode);

       XmlElement tmpEl;

       //Append Fields
       tmpEl = AddFieldElement(doc,fieldsNode,"Counter","tp_ID","ID","ID",true,false,true);
       tmpEl = AddFieldElement(doc,fieldsNode,"Text","nvarchar1","Title","Title",false,false,true);
       tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","tp_Modified","Modified","Modified",true,false,true);
       AddAttribute(tmpEl,null,"StorageTZ",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","tp_Created","Created","Created",true,false,true);
       AddAttribute(tmpEl,null,"StorageTZ",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"User","tp_Author","Author","Created By",true,false,true);
       AddAttribute(tmpEl,null,"List",null,"UserInfo");
       tmpEl = AddFieldElement(doc,fieldsNode,"User","tp_Editor","Editor","Modified By",true,false,true);
       AddAttribute(tmpEl,null,"List",null,"UserInfo");
       tmpEl = AddFieldElement(doc,fieldsNode,"Integer","tp_Version","owshiddenversion","owshiddenversion",true,true,true);
       AddAttribute(tmpEl,null,"SetAs",null,"owshiddenversion");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Attachments","tp_HasAttachment","Attachments","Attachments",true,false,true);
    //   tmpEl = AddFieldElement(doc,fieldsNode,"ModStat","tp_ModerationStatus","_ModerationStatus","Approval Status",true,true,true);
    //   AddAttribute(tmpEl,null,"CanToggleHidden",null,"TRUE");
    //   AddAttribute(tmpEl,null,"Required",null,"FALSE");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext1","_ModerationComments","Approver Comments",true,true,true);
    //   AddAttribute(tmpEl,null,"CanToggleHidden",null,"TRUE");
    //   AddAttribute(tmpEl,null,"Sortable",null,"FALSE");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"Edit","Edit",true,false,true);
    //   AddAttribute(tmpEl,null,"Sortable",null,"FALSE");
    //   AddAttribute(tmpEl,null,"Filterable",null,"FALSE");
    //   AddAttribute(tmpEl,null,"AuthoringInfo",null,"(link to edit item)");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"LinkTitleNoMenu","Title",true,false,true);
    //   AddAttribute(tmpEl,null,"AuthoringInfo",null,"(linked to item)");
    //   AddAttribute(tmpEl,null,"Dir",null,"");
    //   AddAttribute(tmpEl,null,"DisplayNameSrcField",null,"Title");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"LinkTitle","Title",true,false,true);
    //   AddAttribute(tmpEl,null,"AuthoringInfo",null,"(linked to item with edit menu)");
    //   AddAttribute(tmpEl,null,"DisplayNameSrcField",null,"Title");
    //   AddAttribute(tmpEl,null,"ClassInfo",null,"Menu");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"SelectTitle","Select",true,true,true);
    //   AddAttribute(tmpEl,null,"AuthoringInfo",null,"(web part connection)");
    //   AddAttribute(tmpEl,null,"Dir",null,"");
    //   AddAttribute(tmpEl,null,"Sortable",null,"FALSE");
    //   AddAttribute(tmpEl,null,"CanToggleHidden",null,"TRUE");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Integer","tp_InstanceID","InstanceID","InstanceID",true,true,true);
    //   AddAttribute(tmpEl,null,"Sortable",null,"TRUE");
    //   AddAttribute(tmpEl,null,"Filterable",null,"TRUE");
    //   AddAttribute(tmpEl,null,"Min",null,"0");
    //   AddAttribute(tmpEl,null,"Max",null,"99991231");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Number","tp_ItemOrder","Order","Order",false,true,true);
       tmpEl = AddFieldElement(doc,fieldsNode,"Guid","tp_Guid","GUID","GUID",true,true,true);
       tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","datetime1","EventDate","Begin",false,false,true);
       AddAttribute(tmpEl,null,"Format",null,"DateTime");
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       AddAttribute(tmpEl,null,"Required",null,"TRUE");
       AddAttribute(tmpEl,null,"Filterable",null,"FALSE");
       AddAttribute(tmpEl,null,"FilterableNoRecurrence",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","datetime2","EndDate","End",false,false,true);
       AddAttribute(tmpEl,null,"Format",null,"DateTime");
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       AddAttribute(tmpEl,null,"Filterable",null,"FALSE");
       AddAttribute(tmpEl,null,"FilterableNoRecurrence",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext2","Description","Description",false,false,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       AddAttribute(tmpEl,null,"Sortable",null,"False");
       tmpEl = AddFieldElement(doc,fieldsNode,"Text","nvarchar","Location","Location",false,false,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Recurrence","bit1","fRecurrence","Recurrence",false,false,false);
       AddAttribute(tmpEl,null,"DisplayImage",null,"recur.gif");
       AddAttribute(tmpEl,null,"HeaderImage",null,"recur.gif");
       AddAttribute(tmpEl,null,"ClassInfo",null,"Icon");
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       AddAttribute(tmpEl,null,"NoEditFormBreak",null,"TRUE");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"CrossProjectLink","bit2","WorkspaceLink","Workspace",false,false,false);
    //   AddAttribute(tmpEl,null,"Format",null,"EventList");
    //   AddAttribute(tmpEl,null,"DisplayImage",null,"mtgicon.gif");
    //   AddAttribute(tmpEl,null,"HeaderImage",null,"mtgicnhd.gif");
    //   AddAttribute(tmpEl,null,"ClassInfo",null,"Icon");
    //   AddAttribute(tmpEl,null,"Title",null,"Meeting Workspace");
    //   AddAttribute(tmpEl,null,"Filterable",null,"TRUE");
    //   AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int1","EventType","Event Type",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Guid","uniqueidentifier1","UID","UID",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","datetime3","RecurrenceID","Recurrence ID",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       AddAttribute(tmpEl,null,"CalType",null,"1");
       AddAttribute(tmpEl,null,"Format",null,"ISO8601Gregorian");
       tmpEl = AddFieldElement(doc,fieldsNode,"Boolean","bit3","EventCanceled","Event Canceled",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int2","Duration","Duration",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext3","RecurrenceData","RecurrenceData",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int3","TimeZone","TimeZone",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
       tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext4","XMLTZone","XMLTZone",false,true,false);
       AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int4","MasterSeriesItemID","MasterSeriesItemID",false,true,false);
    //   AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
    //   tmpEl = AddFieldElement(doc,fieldsNode,"URL","nvarchar3","Workspace","WorkspaceUrl",false,true,false);
    //   AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
    //   AddAttribute(tmpEl,null,"ColName2",null,"nvarchar4");

       // Return the XML
       return doc;
      }

    You'll notice that I have commented out a few lines in this method. There are probably more lines that can be commented out. We're not returning all the information that SharePoint would return, but it's not all useful to Outlook, so why clog the ethernet lines with useless data?!

    Anyway, once you've made those modifications, you can recompile the web application and you should be golden!

  • Wiz/dumb

    HOWTO: Exposing your VSTO 2005 SE Add-In to External Code

    • 2 Comments

    So with regular COM AddIns in Outlook, to expose your COM Add-In to external applications involved setting the Application.ComAddins.Item("ProgID").Object property equal to an instance of the object. Typically, in your OnConnection event handler, you'd do something like this:

    Application.ComAddins.Item("PROGID").Object = Me

    That allowed external code to get a reference to your COM addin class through its Object property and call public functions on your addin. With VSTO 2005 SE with Outlook 2007, we have a new (better) way of handling this scenario.

    There is a new virtual method which you can override and specify ANY object to be set in that value when the add-in is created.

    You do this by overriding the RequestComAddinAutomationService method in your VSTO add in. See the following MSDN article: http://msdn2.microsoft.com/en-us/library/microsoft.office.tools.addin.requestcomaddinautomationservice(VS.80).aspx

    The article on MSDN actually has a good example of what you need to do. Just realize that you do have to include the attributes on your interface class to make them IDispatch and ComVisible.

  • Wiz/dumb

    Office 2007 Are Tea Ems?

    • 0 Comments

    It seems we're done with Office 2007 and have given the "ok" to start printing the disks.

    http://www.microsoft.com/presspass/press/2006/nov06/11-062007OfficeRTMPR.mspx

    It's been a long ride with over 3 million Beta downloads! 

  • Wiz/dumb

    Hook, Line, and Sink’r

    • 0 Comments

    So I recently helped a customer develop a managed Exchange Store Sink. One thing I noticed is that it doesn't seem to be any public step-by-step instructions for the Exchange-development impaired, so I decided to at least give a high level HOWTO on doing this. So here ya go…

    Step 1…The first thing I would suggest doing is writing your code (or as much as possible) in a console or windows application and make sure that as much of the functionality of your code is tested using one of these application types. It's a lot harder to debug a store sink running in a COM+ Application than it is to debug a .net console app.

    Step 2…Decide what type and what scope of sink you need. For most people, you are going to want to develop an Asynchronous sink. These will fire events (OnSave and OnDelete) after the change has been made, while Synchronous events (OnSyncSave, OnSyncDelete) occur during the change. The synchronous events, while tempting, should be avoided if an asynchronous sink will do the job. When running a synchronous sink, the Exchange store has to halt operation and wait for your code to finish before continuing its normal processing. If you plan to do something that could take a good bit of time (in processor time, not Congress time), and you don't need to prevent the action from taking place, you should use an asynchronous store.

    For scope, you have the options for exact (just this folder), shallow (me and my children), or deep (me and my descendants).

    Step 3…Build your sink. Basically, you want to follow these steps to build your sink. The things to keep in mind here are that you can copy exoledb.dll to your development machine to build your interop assemblies. Also, pay attention when building your sink about the flags passed in as this is your key to deciding whether to act or not. The flags describe what triggered the event to occur.

    Step 4…Once you have your sink built, copy it and the .tlb file and the "Interop" assemblies over to the Exchange server you want to register it on. Place it in a file where it will live. Remember, it's bad karma to move a COM dll.

    Step 5…Register the assembly using regasm (in your Framework directory) on your Exchange server. Just open a command prompt and navigate (cd) to the %systemroot%\Microsoft.NET\Framework\v1.1.4322 folder. Then call

    regasm yourassembly.dll

    That will register your assembly in the registry.

    Step 6…Create a COM+ application and add your component. On your Exchange server, Go to Administrative Tools and open the Component Services snap-in. Drill into "Component Services" > Computers > My Computer > COM+ Applications. Right click on the COM+ Applications folder and say New > Application. Just create an empty application and call it whatever you want. Once you go into the properties of the application, set it to use an account whose you want your code to run in. This is the "Impersonation" idea you're used to from .net land.

    Once you have your Application created, drill into it and right click on Components and click New > Component. Choose to install a new component and point to your dll or tlb file. This will add your component to this COM+ Application so that whenever your component is initialized, it is done in the context of this application.

    Step 7…Register your sink on the folder of interest. There are quite a few ways to do this.

    Remember to use the scope you decided upon earlier (deep, exact, or shallow).

    Once your sink is registered, you're done!

    Congrats. Wanna cookie?

Page 1 of 1 (4 items)