shahine.com/omar/

homepage | Send mail to the author(s) contact

yet another Microsoft blogger
Previous Page Page 2 of 3 in the ProgrammingOffice category Next Page

# Monday, January 24, 2005

Office 2003 Primary Interop Assemblies Article

Everything you wanted to know about the Office 2003 Primary Interop Assemblies (ignore if you aren't writing a managed code for Office).

"Learn how to get and install the Office 2003 primary interop assemblies (PIAs), and how to reference and troubleshoot them."

Of course it won't address the issues Josh raised.

Posted Tuesday, January 25, 2005    Permalink    Comments [0]  View blog reactions

 

# Monday, December 06, 2004

When ReleaseComObject is necessary in Outlook

Some might tell you that you never need to call Marhal.ReleaseComObject when writing managed code in Outlook. Well there are two very specific situations in which you must call RCO or else you will encounter problems. They are:

  1. You create toolbar buttons in new Inspector windows
  2. You use the Explorer.ActiveExplorer() method.

This is also a good point to tell you that if you are going to call RCO, you need to use a shim or you will hose other add-ins that are running in the same Domain as Outlook (the default).

You may also wan to read Eric Carter's post on Getting Outlook to shut down which offers a different approach for problem #2. I'm not sure if his approach fixes the corrupted folder list. He makes some great points in that post, and sadly in my case it is in fact necessary to call RCO (as far as I can tell).

1. Creating a Toolbar in new Inspector Windows

It's pretty common for an add-in developer that has created a toolbar to wish to have them appear in the Inspector windows. My Send to OneNote Powertoy does this, and I didn't find out about this problem till a few users reported it. Some kind folks at Microsoft told me how to work around it.

Basically, what happens is this. Whenever you create or modify a new Sticky Note in Outlook, Outlook tries to add your toolbar button to the Inspector. However, you can't do that. Outlook deals with this by presenting the following two dialogs. The first happens if you Lock your computer, then unlock it. The second happens when you quit Outlook.

Exhibit A: "Could not complete the operation. One or more parameter values are not valid."

Exhibit B: "The note will close and your changes will not be saved."

The solution to this problem is to:

  1. Handle the OnNewInspector Event
  2. Call Marshal.ReleaseComObject on the Sticky Note Inspector

Handle the OneNewInspector Event

See code below:

public void OnStartupComplete(ref Array custom)
{
    this.ApplicationInspectors = this.ApplicationObject.Inspectors;

    try
    {
        this.ApplicationInspectors.NewInspector += 
             new InspectorsEvents_NewInspectorEventHandler(OnNewInspector);
    }
    catch (Exception e)
    {
        Debug.WriteLine(e);
    }
}

Where ApplicationInspectors is an instance of type Inspectors and ApplicationObject is an instance of Outlook.Application.

Call Marshal.ReleaseComObject on the Sticky Note Inspector

This is where you prevent the errors from happening.

private void OnNewInspector(Inspector inspector)
{
    object item = inspector.CurrentItem;
    if (item is NoteItem)
    {
        Marshal.ReleaseComObject(inspector);
        inspector = null;
    }
    else
    {
        / ... /
    }
    if (item != null)
    { 
        Marshal.ReleaseComObject(item);
        item = null;
    }
}

Problem solved. Now on to problem two which is more complicated.

2. Handling Explorers in Outlook

It is very common for an Outlook add-in to use the Explorer object. If you are adding toolbars to the Explorer, or you are manipulating such things as messages, contacts etc, or you wish to get the selected item you need to call Explorer.ActiveExplorer(). The problem with calling this is that you can run into a situation where if you do not call RCO on the ActiveExplorer then you can cause Outlook to not persist the collapsed state of the folder list (all items will be collapsed when you restart) or even worse, Outlook won't close. I've found that Send to OneNote has both these problems. I wasn't aware of the collapse folder list bug till a few days ago, but I've known about the Outlook shutdown bug for months, but I could not explain it. The problem only seemed to happen when you had many add-ins installed. If my add-in was living on its own in Outlook I never saw any problems.

In order to call Marshal.ReleaseComObject() on the Explorer that you are using you need to do some work.

  1. Create a custom ExplorerCloseEvent class that holds on to the Explorer object
  2. Handle the OnExplorerClose event
  3. Call Marshal.ReleaseComObject on the closing Explorer

The reason that you have to do this is because by default the Explorer.Close() event does not pass in the current Explorer, so you have to write your own class with it's own event handler to do this.

Create a custom ExplorerCloseEvent class that holds on to the Explorer object

public class OfficeExplorerCloseEvent : IDisposable
{
    private Outlook.ExplorerEvents_Event explorer;
    private Handler handler;

    public delegate void Handler(object sender, EventArgs args);

    public OfficeExplorerCloseEvent(object explorer, Handler handler)
    {
        if (explorer == null)
            throw new ArgumentNullException("explorer");
        if (handler == null)
            throw new ArgumentNullException("handler");

        this.explorer = (Outlook.ExplorerEvents_Event) explorer;
        this.handler = handler;

        HookEvent();
    }

    public object Explorer
    {
        get
        {
            return (this.explorer);
        }
    }

    public void Dispose()
    {
        this.explorer.Close -= 
            new Microsoft.Office.Interop.Outlook.ExplorerEvents_CloseEventHandler(this.ForwardExplorerEvent);
    }

    private void HookEvent()
    {
        this.explorer.Close +=
            new Microsoft.Office.Interop.Outlook.ExplorerEvents_CloseEventHandler(this.ForwardExplorerEvent);
    }

    private void ForwardExplorerEvent()
    {
        this.handler(this, new EventArgs());
    }
}

Handle the OnExplorerClose event. This code builds on the first code snippet of the OnStartupComplete() method.

public void OnStartupComplete(ref Array custom)
{
    this.ApplicationExplorers = this.ApplicationObject.Explorers;
    this.ApplicationInspectors = this.ApplicationObject.Inspectors;

    try
    {
        this.ApplicationInspectors.NewInspector += 
             new InspectorsEvents_NewInspectorEventHandler(OnNewInspector);
    }
    catch (Exception e)
    {
        Debug.WriteLine(e);
    }
    
    if (this.ApplicationObject.Explorers.Count > 0)
    {
        this.ApplicationExplorer = this.ApplicationObject.ActiveExplorer();
        new OfficeExplorerCloseEvent(this.ApplicationObject.ActiveExplorer(), 
            new OfficeExplorerCloseEvent.Handler(this.OnExplorerClose));
    }

    Marshal.ReleaseComObject(this.ApplicationObject.ActiveExplorer());
    Marshal.ReleaseComObject(this.ApplicationExplorer);
}

As you can see in the last two lines I call RCO on the ActiveExplorer and the ApplicationExplorer (which is just an instance of the current Explorer). I'm not 100% sure if you have to do this, but I gave up debugging this nonsense after a few hours and just left it in there.

You must also be sure to see if you have any existing Explorers before doing this as you don't want to hook the event unless Outlook is starting in UI mode (it can be instantiated through ActiveSync for example w/o any UI and in that case Explorers will not exist).

Call Marshal.ReleaseComObject on the closing Explorer

Now that we have hooked the OnExplorerClose lets see what we do in that Method.

private void OnExplorerClose(object sender, EventArgs args)
{
    Explorer explorer = ((OfficeExplorerCloseEvent) sender).Explorer as Explorer;
    ((IDisposable) sender).Dispose();


    while (true)
    {
        if (Marshal.ReleaseComObject(explorer) == 0)
        {
            break;
        }
    }


    explorer = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();
}

In the above code snippet I am getting the Explorer instance that the OfficeExplorerCloseEvent is holding, and calling RCO on it till the RefCount is 0. This ensures that the are all disposed. Then I call the GarbageCollector to clean things up for me.

Final Thoughts

I hope this shows you that doing what appears to be straightforward with managed code in Outlook isn't. I could write a few more blog posts about things I've encountered, and probably will when time permits. I'm pretty excited because for the first time in months, Outlook is shutting down cleanly 100% of the time!

I would also like to thank all those folks that helped me with this problem, or provided code to guide me. I can't actually remember who helped me get this far...


Posted Tuesday, December 07, 2004    Permalink    Comments [5]  View blog reactions

 

# Wednesday, May 12, 2004

Programming for Outlook using managed code is better with a Shim

Ok, recently I posted an article titled: Programming for Outlook using managed code is hard. This article summarized and documented some of the pain that I experienced in writing my Outlook2OneNote Add-in.

All this time I was aware that there was another route I could take, one that Dan Crevier did a great job explaining in a recent post. Originally I didn't want to go the Shim route because:

  1. It was unfamiliar territory.
  2. I don't know C++ and was afraid I'd encounter a problem I could not solve.
  3. I had philosophical problems going this route.

Luckily Dan helped me with #1 and #2, and well, I got over #3. I mentioned that I had been working with some MS folks that are domain experts in this area and they have always suggested that the Shim route was best. Rather than go with their expert advice I went and suggested that people do something that was explicitly unsupported (targeting the Office XP Interop Assemblies rather than the 2003 Primary Interop Assemblies).

Now, for one thing it's not good for me to be offering you unsupported solutions if there are better solutions out there. Secondly, there are some benefits to the Shim route that you won't get otherwise. With a Shim you get your own AppDomain and if you Authenticode Sign the Shim, and Strong Name sign the managed add-in you will not get the Outlook Security prompt when accessing e-mail fields like body. The net result is that you will be in a situation that you have more control over, offers a better user experience, and protects your add-in.

The only downside to this is that you still have to rely on the user successfully installing or having access to the Office 2003 PIAs. It's unfortunate that there is no redistributable version of the 2003 PIAs, but the good news is that for the majority of users, they should be automatically installed when your add-in first loads.

So starting with Outlook2OneNote working against the XP Interop Assemblies here is what I did to get the Shim working.

  1. Read the step by step overview.
  2. Read Dan's post.
  3. Download the Shim bits in step 1.
  4. Follow all the steps in the article from step 1.
  5. Strong Name sign your add-in.
  6. Add the Shim project to your Solution.
  7. Build
  8. Test to make sure that your add-in loads
  9. Authenticode Sign the Shim

You no longer need to keep building the Shim as you have a signed DLL that has the public key of your managed add-in to load in outlook. You just need to ensure that you register the assembly if you are debugging and make sure it's located in the same directory as the managed dll (/bin/Debug).

Now you also need to make changes to the Setup project.

  1. Remove all the current project output
  2. Add the Authenticode signed unmanaged shim as an assembly and mark it for vsdraCOM and then add the assembly from your managed shim (from bin/Release).

I would like to thank Misha Shneerson, Siew-Moi Khor, Art Leonard and Andrew Cherry for helping me do this the “right way” and helping me debug some of the problems.

Posted Thursday, May 13, 2004    Permalink    Comments [2]  View blog reactions

 

# Wednesday, April 28, 2004

When COM Registration fails in an MSI (vsdraCOM has no affect)

 Summary

In this weeks’ installment of I hate COM, lets talk about a frustrating problem I’ve been having (turns out I had this problem when I wrote my Media Center Front Panel Display but found a different workaround I mention below). It would be nice if there were a KB article on this topic, but there isn’t and I don’t know why.

Say you are exposing a .NET component as a COM component. Not to uncommon. I already do this in my Outlook2OneNote add-in as well as my Media Center Front Panel Display. In both these cases I am implementing a COM Interface: Extensibility.IDTExtensibility2 for Outlook2OneNote and EHLib.IMediaStatusSink for my Media Center Sink.

In Outlook2OneNote the project structure looks like

  1. Project 1 – contains the guid and COM Inteface
  2. Project 2 – inherits from a class in Project 1 and is marked for COM registration
  3. Setup – Primary Output from Project 2’s Register value is vsdraCOM

In MediaCenterSink the project structure looks like

  1. Project 1 – contains the guid and COM Inteface and marked for COM registration
  2. Project 2 – contains the code for the Sink
  3. Setup – Primary Output from Project 1’s Register value is vsdraCOM

In both cases the MSI does not register the Primary Output for COM Interop. It’s not obvious why this fails, but I spent many hours trying various things (you know how trial and error is when you don’t have an understanding for what is going on or what is broken right?). So you can imagine how frustrating this is. Even more frustrating is when this happens to you twice in the course of a few months.

Approach 1: Custom Action

Well, google had no answers for me except a bunch of other people who were saying “COM Registration during install using MSI is broken…” or “vsdraCOM doesn’t do anything…” etc. The only solution that I found was to write some code in the Primary Output project like so (you need a similar Uninstall and Rollback override as well):

public override void Install(System.Collections.IDictionary stateSaver)
{
    base.Install(stateSaver);

    try
    {
        RegistrationServices regSrv;

        // Register the assembly
        regSrv = new System.Runtime.InteropServices.RegistrationServices();

        if (!regSrv.RegisterAssembly(
            this.GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
        {
            throw new InstallException("Failed to register componenet for COM interop");
        }
    }
    catch
    {

    }
}

And then create a Custom Action in your MSI that points to the Primary Output for Install, Uninstall and Rollback. This actually works pretty well, and I was happy with it.

Well when this problem happened for the second time just this week when I switched my Outlook2OneNote project structure from being a simple Project that had all the logic to one that inherited from another class in another project I started wondering and thinking this has to be a bug. Well using my super hero powers I tracked this problem down and found a better work around.

Additionally, I've been told that custom actions should be avoided for a number of reasons. One good reason is that if you ever use the MSI in the future to upgrade your assembly, it will fail.

Approach 2: Simple workaround for bug

Rather than adding the Primary Output of the project that needs to be registered for COM Interop, do the following:

  1. In your setup project remove the Primary output from <Project Name>.
  2. Select your setup project, right click, select Add->Assembly
  3. Navigate to the bin/Release directory of your Primary project and select your dll
  4. Select the Assembly in the setup project and set the Register value to vsdraCOM

Summary

By adding your assembly directly to the MSI and not relying on the Custom Action workaround mentioned above, you can rely on the MSI to register your component for COM Interop.

BTW – IMHO, developing .NET components and exposing them as COM components is a nasty business and one that I NO LONGER ENJOY!!! I am writing this because it’s a bit therapeutic and I hope it helps someone else who is having this problem (rather than hurl my laptop against the wall).

update: I made some corrections to this post based on some feedback from Rob Mensching.

Posted Wednesday, April 28, 2004    Permalink    Comments [10]  View blog reactions

 

# Sunday, April 25, 2004

Programming for Outlook using managed code is hard

Background

Programming for Outlook using managed code is hard. I take that back. Programming for Outlook is relatively easy if you can figure out where to stick your code. Working out the problems you’ll encounter is hard (and I’m still working on them). I’ll try and give you some best practices. There will actually be some follow up to this specifically on:

  1. How to keep your objects from going out of scope
  2. How to prevent the dreaded security pop-up in Outlook from appearing.

You aren't the only managed add-in

The hardest part to getting your addin to work is to be enlightened. You see, your add-in may work very well if it's the only add-in installed. However, if there are any other add-ins installed, and there are many excellent add-ins, your add-in may fail to load. Some of the reasons your add-in may fail to load are:

  1. Another add-in is installing the Office 2003 Primary Interop Assemblies in their own application directory.
  2. Another add-in is using a private version of the Office XP Primary Interop Assemblies that conflicts with the version that you are loading. Depending on an number of factors, your assumption that the set of assemblies you targeted will not be loaded.
  3. Another add-in installed a version of the Office PIAs in the GAC that are incompatible with yours, or, are getting loaded in place of the assemblies you targeted.
  4. Another add-in is calling Release COM Object and you are getting hosed (see http://blogs.officezealot.com/whitechapel/ for even more info on the subject).
  5. Some other reason that I am sure exists and I am not aware of.

You have no control

So, given these things it seems to me that it's really difficult to have a lot of control over your deployment given the eco system for managed add-ins out there. Bottom line, other products and add-ins can invariable affect you whether you like it or not. The only sure way to protect yourself is to implement a shim as outlined in this article. I've forgone the shim route because I find it overly complicated and to me it taints the whole .NET developer experience I've grown to love. Everything else is so darn simple and elegant why does this have to be so hard? That article is about 14 pages and the other 15 pages of reading to get your own AppDomain. Not to mention C++ code (I don't do C++). It's wonderful that we publish a documentation on how to get your own AppDomain but I don't currently know what my own AppDomain will buy me.

However, even if you go the shim route, there is nothing preventing another application from messing with the Interop Assemblies and horking your assumptions about what you've targeted.

Office XP PIAs, Office 2003 PIAs Oh My!

To make things even more complicated, you are only supposed to use the Office 2003 PIAs for Office 2003, and the XP PIAs for Office 2000 and Office XP. If you read this article, you'll find this statement:

"Microsoft does not guarantee that the Office PIAs will be backwardly compatible or that the Office XP PIAs and the Office 2003 PIAs can be run side-by-side in the same instance of an Office application. Office XP managed code add-ins must be built against the Office XP PIAs. The Office 2003 managed code add-ins must be built against the Office 2003 PIAs. Therefore, if you build an add-in solution that you intend to use with both versions of Office, Microsoft recommends that you build a version of your add-in for each version of Office that you intend to support."

So, what this basically means to me is that if my add-in fails to load because I'm using the Office 2003 PIAs and Application Y is using the Office XP PIAs, my only recourse is to convince the developer of Application Y to stop doing this. Meanwhile the developer of Application Y is like, huh? You mean I have to target and test against two different versions of the PIAs for 3 different versions of Office? And to make matters worse, it's actually more like Developer A, B, C, D, E, F and G and so on. From what I can tell there are very few add-ins that target or install the Office 2003 PIAs... although I know of at least two. Finally it seems that if one add-in loads the 2003 PIAs and one loads the XP PIAs bad things can happen.

I started building my add-in by creating the Shared Add-in project in VS.NET 2003. By default, if you are using Office 2003, the PIAs will get added to your setup and installed in your App directory. This is a No-No and to make matters worse, you can really mess things up for other add-ins when your add-in is uninstalled. When I stopped installing the PIAs (after a few dozen people installed my add-in) problems started to ensue. I spent many days with Kent Compton as my willing guinea pig tracking down (Thanks Kent!). I also spent some time discussing this with many fine folks who work on this stuff in Office. The end result was that the only way I could get Kent's issues resolved (as well as some I was having) was to remove my dependency on the Office 2003 PIAs and instead rely on the Office XP PIAs (I found this out after 40 or so hours of trial and error and lots of head scratching as well as some conversations I've had with Mike who works on Lookout). So far (knock on wood) this works well for him and I'm able to use Lookout, and Newsgator without any problems. However, this solution is not a silver bullet and not something I plan on doing long term. I will go the shim route and load my add-in into it's own AppDomain and link to the Office 2003 PIAs.

What to do?

So where does this leave the managed Office add-in developer? I just close my eyes and hope for the best. To me the managed add-in eco system is a bit like a public swimming pool. There are a set of posted rules, but there is no one really there to "police" the rules being enforced... anyone can swim in the pool and not everyone will be barred from entering the pool because they didn't shower before hand. This is a far cry from doing Windows Forms programming or ASP.NET development.

How it should work

Based on what I've learned, and my limited knowledge of COM and the Office Object Model, if I could change this here is what I would do:

  1. Give each add-in it's own AppDomain
  2. Ship a set of PIAs for Office that go into the GAC during the default installation that work against Office 2000, Office XP and Office 2003 (as well as the next version of Office). Don't make me worry about installing any necessary Interop Assemblies or what version of Office is installed.
  3. Make this as easy as developing a Windows Forms app, Class library or Console App.
  4. Add better intellisense for the Office properties, methods, and classes (as well as integrated VS.NET help) - I just had to throw this one in there ;-).

Why this matters to me

You may wonder why I care about all this stuff. Well I am working on an Add-in for OneNote called Outlook2OneNote. This add-in will allow you to copy items from Outlook such as e-mail, notes and post items into OneNote for annotating and archiving or whatever. I am also working on a simple Office Button SDK with Dan Crevier that will allow you to easily create buttons, popup buttons and combo boxes in Office toolbars that react to the Click event allowing you to do all sorts of nifty things.

BTW, here are a list of managed Outlook Add-ins that I know of:

  • Newsgator - RSS Aggregator
    • Office XP PIA installed in GAC (thanks for the correction Greg!)
  • DataLens - nifty calendar view
    • Office 2003 PIA installed in GAC
  • Tablet Enhancement Pack for Outlook - use your pen to create tasks, appointments and contacts
    • Office 2003 PIA not installed in GAC
  • Lookout - fast search tool for Outlook
    • Latest version: Office XP PIA installed in GAC
    • Current version: Office XP PIA not installed in GAC

I have a few more things to try to feel sure that my add-in will work in the real world. Preliminary results indicate that ditching the Office 2003 PIA for the XP PIA was the ticket, but I have something else to try before I can be confident. Also, I've had cases where things worked, failed and then worked on my own machine and I don't have any explanation for why. I'm going to have to mess around with Virtual PC to test out some theories of mine as my current machines aren't good "testing" environments any more!

update: while using the Office XP Interop Assemblies seemed like a silver bullet, it's not the "right solution". The right solution is to use the 2003 PIAs and write a Shim that will give me my own AppDomain and insulate me from other add-ins. See this post on the my thoughts on creating and using a Shim for Outlook2OneNote.

Posted Monday, April 26, 2004    Permalink    Comments [5]  View blog reactions

 

Previous Page Page 2 of 3 in the ProgrammingOffice category Next Page