The Outlook object model is described in detail in the MSDN library. However, while that tells you what methods, properties and events exist, there's not a huge amount of information about what they're actually for. I found Carter & Lippert's Visual Studio Tools for Office 2007 book very useful in filling in many gaps, but some experimentation was certainly necessary.

I used to write lots of little C# console apps to delve into the guts of the object model but more recently have started using PowerShell for the same thing. As a dynamic language it's much quicker to explore the object model than the edit-compile-debug cycle, especially when the objects identify themselves as just a COM object in the C# debugger, meaning I need to insert casts all over the place. Any dynamic language would do, but PowerShell "knows" about so many different classes of object interface (.NET, COM, WMI, file system, ...) that it's very convenient for all sorts of things.

The point of this add-in is to be able to set the reply all flag in an email message object. From Scott Hanselman's post, we already know we want to tweak the message object's Action elements, but it's probably worth finding out a little more about them first. With PowerShell in one hand and the Outlook object model MSDN pages in the other, let's begin... Fire up the PowerShell ISE (Integrated Scripting Environment) or command shell, and execute the following to get hold of Outlook:

$outlook = new-object -com Outlook.Application

If you now execute "$outlook.GetType()" PowerShell will tell you what sort of object you have - not too surprisingly, an ApplicationClass object - and you can see what the documentation says about this.

$inbox = $outlook.Session.GetDefaultFolder(6)

gets hold of the inbox (the "6" comes from the OlDefaultFolders enumeration) and the following gets us the first message in the inbox:

$item = $inbox.Items.Item(1)

You can now examine the attributes of the object (type MailItem) such as the body or recipient lists, but let's focus on the actions. "$item.Actions" dumps the complete array though the following gives a more easily understood list with just the properties we're interested in.

$item.Actions | select Name,Enabled

which results in:

Name Enabled
Reply True
Reply to All True
Forward True
Reply to Folder True

I've not seen anything that lists a complete set of actions (but, to be honest, I've not looked very hard). The documentation indicates that the Actions list can be indexed by integer or by name, and $item.Actions.Item(1) does result in the same action as $item.Actions.Item("Reply") - additionally indicating that the integer index is 1-based, as most (all?) of the Office collections tend to be. The lookup by name is what the VBA macro that inspired this used, and I used it in the first version of my add-in too. However, a user pointed out that it didn’t work in his German installation of Office: it turns out that the action names are localized, but inspection and experimentation suggests that the integer index values are consistent. Now, I've not seen that documented anywhere, and it’s possible that future versions of Outlook might break my assumption, but I’ve not come up with a better idea.

Having confirmed the name based lookup, and discovered a means of potentially avoiding language problems, it's time to get back to the add-in. First, for some more coverage of using PowerShell with Outlook, see James Manning's blog post on the topic. I also have to admit that using the VBA macro editor built into Outlook would have made the exploration just as simple, perhaps even easier, but I wanted an excuse to start using PowerShell.

Change the stub ComposeButton_Click from last time to the following:

public void ComposeButton_Click(Office.IRibbonControl control, bool pressed)
{
    var inspector = control.Context as Outlook.Inspector;
    if (inspector != null)
    {
        var item = inspector.CurrentItem as Outlook.MailItem;
        if (item != null)
        item.Actions[2].Enabled = !pressed;
}
}

The "context" for the control passed into the callback is the Outlook window container object associated with the ribbon, an Inspector in this case. From that, we can get hold of the mail item being displayed, and can then enable or disable the value of the appropriate Action (remember, the index is 1-based). I'm not sure of those null checks are necessary or if there's guaranteed to always be an inspector and a mail item but I guess it's better to perform an extra check than it is to crash Outlook, especially since this isn't really performance critical. The inspector check does protect against me accidentally showing this ribbon on a window with some other object in it (e.g., the Outlook main window).

Now, programmatic interaction with Office applications is really COM based, but VSTO does a really good job of hiding, say, COM reference counting from us. Most of the time it works just fine, but I did have some problems reports about phantom windows appearing on the screen, or Outlook not exiting - both of these problems may have been symptoms of something hanging on to a reference when they shouldn't have. Perhaps .NET garbage collection would have released the references but it didn't seem to be doing it in time for these two problems. Of course, these were well nigh impossible to debug because the debugger would also manipulate the references, obscuring whatever what happening within the application. I found that the problems "went away" when I explicitly released all COM objects when I'd finished with them - I don't know if this is the right thing to do, and I'm a bit uneasy at the potential for now causing objects to be prematurely destroyed, but everything does seem to work with this change. My actual button click handler is the more paranoid version that follows - note the bold lines:

public void ComposeButton_Click(Office.IRibbonControl control, bool pressed)
{
    var inspector = control.Context as Outlook.Inspector;
    if (inspector != null)
    {
        var item = inspector.CurrentItem as Outlook.MailItem;
    if (item != null)
{
var action = items.Actions[2];
        action.Enabled = !pressed;
            Marshal.ReleaseComObject(action);
            Marshal.ReleaseComObject(item);
}
        Marshal.ReleaseComObject(inspector);
}
}

The Marshal class is in the System.Runtime.InteropServices namespace. Note that I'm releasing only references which I've created (for example, not the control passed in), and I do it after my last interaction with the reference. As I said earlier, I'm not sure how much of this is necessary, but doing it everywhere seems to be less problematic than doing it nowhere!

Run this, and you'll see that you can send messages with reply-all disabled. But try this: create a new message, click the button, send the message, create a second message - wait a moment, the button seems to indicate that reply-all is already disabled here... If you send that message though, you'll see that reply-all has not been disabled! What's going on here? What seems to be happening is that the ribbon is loaded once and cached in whatever state it was last left in for use in new windows (I guess this is for efficiency: most of the time the ribbon will be identical for different windows of the same type), so the fix is to cause a newly created window to reset its ribbon controls... Perhaps not reset as such, but set it to the value associated with the mail item being displayed in the window (this will be useful later on, when we want to display the state for received messages).

First, we need to add a callback to the ribbon to update the button. Add attribute getPressed="ComposeButton_IsPressed" to the button in the XML script and then add the callback method to Ribbon.cs:

public bool ComposeButton_IsPressed(Office.IRibbonControl control)
{
bool pressed = false;     var inspector = control.Context as Outlook.Inspector;
    if (inspector != null)
    {
        var item = inspector.CurrentItem as Outlook.MailItem;
    if (item != null)
{
var action = items.Actions[2];
        pressed = !action.Enabled;
            Marshal.ReleaseComObject(action);
            Marshal.ReleaseComObject(item);
}
        Marshal.ReleaseComObject(inspector);
}
return pressed;
}

Of course, a better implementation would extract the code common between this and the previous method into a separate function, but I'll leave that as an exercise for the reader.

Creating this callback is not actually sufficient, since it's currently only invoked the very first time the ribbon is displayed - we need to force it to be triggered when new inspector windows are created, and that happens in ThisAddin.cs via, not unexpectedly, a NewInspector event handler. We've already got CreateRibbonExtensibilityObject, so extend it slightly to squirrel away a reference to the ribbon object and make the NewInspector event handler call the ribbon's InvalidateControl method. Something like the following:

private Ribbon ribbon;
private Outlook.Inspectors inspectors;

protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
this.ribbon = new Ribbon();
return this.extension;
}

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.inspectors = this.Application.Inspectors;
this.inspectors.NewInspector += OnNewInspector;
}

private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
this.inspectors.NewInspector -= OnNewInspector;
Marshal.ReleaseComObject(this.inspectors);
}

private void OnNewInspector(Outlook.Inspector inspector)
{
if (this.ribbon != null)
this.ribbon.InvalidateControl("btnNoReplyAll");
}

Note that I capture the Inspectors collection as a class variable - if I were to just replace the body ot ThisAddin_Startup with "this.Application.Inspectors.NewInspector += ..." there is a chance that garbage collection would cause the .NET Inspectors proxy to be reclaimed and the event handler detached. Keeping the collection in a variable with a lifetime as long as required for the add-in means the event handler stays around too. That final function, though short, is a bit untidy, poking its nose inside the Ribbon object - it would be better to write a RibbonInvalidateRibbon method which hides the details and invoke that from here, but I've done enough typing for today.

After all that, you should have a fully functioning no-reply-all button, at least on the compose window. Next time, I'll add some more buttons...