• Wiz/dumb

    Rude Operator

    • 0 Comments

    If you are automating Outlook in a standalone application or other app outside of Outlook and have trouble on some machines with your CreateObject call succeeding when Outlook is not already running, this article may be for you.

    Have you ever called someone only to be put on hold as soon as they answer? Do you hang up immediately, or do you hold on for a few minutes to see if they pick up? If you’ve ever ordered a pizza where I order mine, I know you’ve experienced this. Well, it turns out, Outlook [or any Office application for that matter] acts just like this rude operator. When you try to automate Outlook, the Outlook.exe process has to be running in order to serve your requests. Outlook is what’s called a COM server. So your call to CreateObject (or "new”) makes a call into the COM subsystem which does the work of spinning up an instance of Outlook for you to work from using the –Embedding switch which tells Outlook not to display any UI by default. Part of Outlook’s start up logic involves loading all the COM add-ins and calling into the OnConnect and OnStartupComplete events. This is where things could go badly for you.

    If your user is an comaddinophile they may have some add-ins installed that take a looooooong time to load. They may be calling into a database or a webservice or just have really poor code that takes a long time to execute. Outlook does all this work on its main thread – they aren’t spun off to a background thread – so all the time it takes for these add-ins to load and run is time the COM subsystem is spending growing very impatient. It can, after a while, just time out or it could succeed the initial load, but if Outlook is busy handling other requests your call to get the Application dispatch object could be actively refused because Outlook is blocking. In these cases, you could end up with a RPC_E_SERVERCALL_RETRYLATER error bubbling up in your .net automation code. The error you get will be something like “Call was rejected by callee.”

    Oftentimes, you can follow up your failed CreateObject call with a second one and it succeeds because at this time Outlook.exe is running and may be unblocked and ready to service your request. However, it may just block again. In these cases, you will need to implement an IMessageFilter to be able to tell the COM subsystem that you’re ok waiting and to keep retrying your call until it succeeds or you get a different error.

    The best sample I found for this was describing implementing this to solve this same problem when doing Visual Studio automation, but the same logic applies. Andrew Whitechapel wrote a sample also. In his case he was describing a similar problem when you try to make object model calls on a background thread from an add-in. Here’s a VB.NET sample I wrote.

    Public Class MessageFilter
        Implements IOleMessageFilter

        Public Shared Sub Register()
            Dim newFilter As IOleMessageFilter = New MessageFilter()
            Dim oldFilter As IOleMessageFilter = Nothing
            CoRegisterMessageFilter(newFilter, oldFilter)
        End Sub

        Public Shared Sub Revoke()
            Dim oldFilter As IOleMessageFilter = Nothing
            CoRegisterMessageFilter(Nothing, oldFilter)
        End Sub
        Public Function HandleInComingCall(ByVal dwCallType As Integer, ByVal hTaskCaller As System.IntPtr, ByVal dwTickCount As Integer, ByVal lpInterfaceInfo As System.IntPtr) As Integer Implements IOleMessageFilter.HandleInComingCall
            Return 0
        End Function

        Public Function MessagePending(ByVal hTaskCallee As System.IntPtr, ByVal dwTickCount As Integer, ByVal dwPendingType As Integer) As Integer Implements IOleMessageFilter.MessagePending
            Return 2
        End Function

        Public Function RetryRejectedCall(ByVal hTaskCallee As System.IntPtr, ByVal dwTickCount As Integer, ByVal dwRejectType As Integer) As Integer Implements IOleMessageFilter.RetryRejectedCall
            If (dwRejectType = 2) Then
                Return 99
            Else
                Return -1
            End If
        End Function


        <DllImport("Ole32.dll")>
        Private Shared Function CoRegisterMessageFilter(ByVal newFilter As IOleMessageFilter, ByRef oldFilter As IOleMessageFilter) As Integer
        End Function

    End Class



    <ComImport(), Guid("00000016-0000-0000-C000-000000000046"),
        InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)>
    Public Interface IOleMessageFilter
        <PreserveSig()>
        Function HandleInComingCall(
                                   ByVal dwCallType As Integer,
                                   ByVal hTaskCaller As IntPtr,
                                   ByVal dwTickCount As Integer,
                                   ByVal lpInterfaceInfo As IntPtr) As Integer

        <PreserveSig()>
        Function RetryRejectedCall(
                 ByVal hTaskCallee As IntPtr,
                ByVal dwTickCount As Integer,
                ByVal dwRejectType As Integer) As Integer

        <PreserveSig()>
        Function MessagePending(
                ByVal hTaskCallee As IntPtr,
                ByVal dwTickCount As Integer,
                ByVal dwPendingType As Integer) As Integer


    End Interface

    To use it, just drop that in your code and then wrap your automation code like this:

    Private Sub Automate()

        MessageFilter.Register()

        Try

            Dim myOutApp As Outlook.Application = New Outlook.Application()
            Dim myOutNS As Outlook.NameSpace = myOutApp.Session
            Dim myOutlookFolder As Outlook.MAPIFolder

            myOutlookFolder = myOutNS.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)

            MessageBox.Show(String.Format("{0} unread items in Inbox.", myOutlookFolder.UnReadItemCount))

            System.Runtime.InteropServices.Marshal.ReleaseComObject(myOutlookFolder)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(myOutNS)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(myOutApp)


        Catch ex As Exception

            'Handle Exception

        Finally

            MessageFilter.Revoke()

        End Try
    End Sub

    The important part to notice is the calls to MessageFilter.Register and MessageFilter.Revoke.

  • Wiz/dumb

    Neither Silverlight 3 nor 4 Work on Outlook Folder Homepages

    • 0 Comments

    Starting in Silverlight 3, the algorithm that SL uses to determine the hosting URL of the SL control doesn’t work properly when the page is viewed as part of an Outlook Folder Home Page. This is actually a known issue and is being looked at for Silverlight 5 but was not discovered early enough to make it into Silverlight 4.

    If you are developing a solution that includes the use of Outlook Folder Home Pages, I’d recommend you consider another option if you plan on doing any advanced things in your solution. Folder homepages are good for hosting plain ol’ HTML and an occasional Outlook View Control, but because of the security lock-downs that are added in each new version of Outlook, the flexibility you have for using various techniques inside OFHPs is limited. Depending on what you are trying to accomplish, you may want to consider using Form Regions and Ribbon customization or else simply providing a link to your high-complexity web page instead of providing the content inside Outlook itself.

  • Wiz/dumb

    Moving your apps to Office 2010

    • 0 Comments

    The Office team put out some Channel9 videos and some tools to help you get your add-ins and applications onto the Office 2010 platform.

    http://channel9.msdn.com/learn/courses/Office2010/AppCompatUnit/

Page 1 of 1 (3 items)