In Deploying Office Solutions with a Setup Project (Mary Lee), you read about how to deploy Visual Studio Tools for Office solutions with Windows Installer (.msi) in Visual Studio 2008. With Visual Studio 2008 Service Pack 1, the steps are the same: add the project output to the Setup project, add registry keys, sign the application and deployment manifests, and so on.
However if you are using launch conditions to verify that the prerequisites are installed, you must update the launch condition to look for the VSTO 3.0 SP1 runtime.
In the Deploying a Visual Studio Tools for the Office System 3.0 Solution for the 2007 Microsoft Office System Using Windows Installer (Part 1 of 2) whitepaper, look at the To configure launch conditions procedure. Change step 6c from this:
Set the value of RegKey to Software\Microsoft\vsto runtime Setup\v9.0.21022
to the following:
Set the value of RegKey to Software\Microsoft\vsto runtime Setup\v9.0.30729
We are working to update the whitepaper to reflect both of these registry keys. If you have questions about the whitepapers, please visit the Visual Studio Tools for Office MSDN Forum to search for answers and ask your questions.
Happy deployment!
Mary Lee, Programming Writer.
When you are done developing your customized Microsoft Excel 2007 or Microsoft Word 2007 document-level project in Visual Studio 2008, you may want to publish it to a SharePoint document library for others to use from the SharePoint site, or they can copy the file from the site to their desktop to use locally. In either case, the customization assembly cannot be stored in the SharePoint document library, so you have to configure the workbook to store the assembly on a file share.
In Solution Explorer, right-click the Excel document-level project, and then click Publish. You'll see the Publish Wizard appear.
In the first page of the Publish Wizard, type the folder name where you want Visual Studio to copy the files, known as the publish location. In this case, it's a subfolder of the project folder.
In the second page of the Publish Wizard, type the folder name where the installation files will be located. In this example, the installation files are located on a configure the workbook to look for the assembly on a file share, known as the install location.
Note: You could have set the publish location to this file share in step 1 if you have access rights to the file share. Setting the install location separate from the publish location allows you to publish the files locally and then hand the files over to an IT administrator to upload the files to a file share.
The third and last page of the Publish Wizard is a summary of the publish options.
Copy the publish folder from step 1 to the file share, and upload the Excel workbook file to a SharePoint document library.
The final step is to trust the location of the SharePoint document library to the Trusted Location in Excel. Copy the URL of the SharePoint document library and remove the last Forms/Allitems.aspx text from the URL. For example, if your SharePoint document library is http://sharepoint/sites/writers/SampleDocLibrary/Forms/AllItems.aspx, the location to add to Excel is http://sharepoint/sites/writers/SampleDocLibrary.
1. Open Excel.
2. Click the Microsoft Office button.
3. Click Excel Options.
4. Click Trust Center.
5. Click Trust Center Settings.
6. Click Trusted Locations.
7. Check Allow Trusted Locations on my network.
8. Click Add new location.
9. In the Microsoft Office Trusted Location dialog box, in the Path textbox, type or paste the URL to the SharePoint document library. In this example, it's http://sharepoint/sites/writers/SampleDocLibrary.
10. Click OK to close the Microsoft Office Trusted Location dialog box.
11. Click OK to close the Trust Center dialog box.
12. Click OK to close the Excel Options dialog box.
Finally, open the Excel workbook file from the SharePoint document library. This starts the installation process for the Excel solution. Depending on the security settings, you may see the Microsoft Office Customization Installer dialog box prompting you to install the Excel solution.
This is the blog version of the following topic in the MSDN Library: How to: Deploy a Document-Level Office Solution to a SharePoint Server (2007 System). You can also see the video demonstration at Video How to: Deploy a Document-Level Office Solution to a SharePoint Server (2007 System).
In Walkthrough: Adding Controls to a Worksheet at Run Time in an Application-Level Project, you can see to add host controls at run time in an application-level add-in by using Visual Studio 2008 SP1. This video is posted to the How Do I Videos - Visual Basic page, part of the Visual Basic Developer Center.
How Do I: Add Controls to a Worksheet at Run Time in an Application-Level Project?
Microsoft Most Valuable Professionals (MVPs) are amazing! They work really hard at their “day jobs” as developers and then they volunteer to help their local communities. MVPs write blogs in many languages, deliver presentations at local User Groups, and answer questions on the Forums. Once a year they fly to Redmond for a special conference to meet with the product teams. This year 6 of the 15 VSTO MVPs came to Redmond, in the cold and rainy winter and a crappy economy, to learn what’s up in Microsoft and give us a ton of really detailed technical feedback.
This year’s MVPs from the VSTO specialty included:
Robert Green, Helmut Obertanner, Ryosuke Uemoto, Maarten van Stam, and Mike Walker.
The feedback flowed freely after a few Washington microbrews and greasy appetizers in one of the Microsoft cafeterias. Here’s a picture of me, Maarten, Steve and Mike in front of my rocket car, which is called the Cappisen 38.
What did we talk about? Everything! We previewed some future stuff, and we really dove into the deep technical architecture of the VSTO Runtime, the Primary Interop Assemblies, and how the Office security model effects deployment. Richard Cook and Misha Shneerson ran the deep dive discussion. Rachel Schaw and Lily Ma presented an end-to-end OBA demonstration and Saurabh Bhatia presented ClickOnce deployment in great detail.
So I want my add-in to place a custom command into the shortcut menu. The shortcut menu is that cool menu that appears when you right-click a document. Great, so I read some articles in MSDN, write some code, run the add-in and voila there it is! I give it to my buddy, he is proud of my accomplishment and and installs my add-in. Now he hates me because every time he opens up Word, a duplicate menu appears. Where did I go wrong?
Well actually, I didn’t do anything wrong. It’s just that Word requires a little more attention when it comes to handling menus. I guess you can say that Word is a bit more “needy” than other Office applications. But being “higher maintenance” does not have to mean “higher maintenance costs”. Hopefully this post will get your friend talking to you again.
Here is the code that did not work for me. BTW – I will paste in both Visual Basic and C# examples for this post.
[Visual Basic]
Private MyApplication As Word.Application Private WithEvents myControl As Office.CommandBarButton Private Sub ThisAddIn_Startup _ (ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup MyApplication = Me.Application AddMenuItem() End Sub Private Sub AddMenuItem() Dim menuItem As Office.MsoControlType = _ Office.MsoControlType.msoControlButton myControl = CType(MyApplication.CommandBars("Text").Controls.Add _ (menuItem, 1, True), Office.CommandBarButton) myControl.Style = Office.MsoButtonStyle.msoButtonCaption myControl.Caption = "My Menu Item" myControl.Tag = "MyMenuItem" End Sub Sub myControl_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _ ByRef CancelDefault As Boolean) Handles myControl.Click System.Windows.Forms.MessageBox.Show("My Menu Item clicked") End Sub
[C#]
private Word.Application myApplication; private Office.CommandBarButton myControl; private void ThisAddIn_Startup(object sender, System.EventArgs e) { myApplication = this.Application; AddMenuItem(); } private void AddMenuItem() { Office.MsoControlType menuItem = Office.MsoControlType.msoControlButton; myControl = (Office.CommandBarButton)myApplication.CommandBars["Text"].Controls.Add (menuItem,missing, missing, 1, true); myControl.Style = Office.MsoButtonStyle.msoButtonCaption; myControl.Caption = "My Menu Item"; myControl.Tag = "MyMenuItem"; myControl.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler (myControl_Click); } void myControl_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault) { System.Windows.Forms.MessageBox.Show("My Menu Item clicked"); }
Here is one issue I can see right off the bat. Note this line of code for adding a control:
myControl = CType(MyApplication.CommandBars("Text").Controls.Add _ (menuItem, 1, True), Office.CommandBarButton)
I set the last parameter of the Add method to True. This value specifies that I want my control to be temporary. I am trying to tell Word not to save the control so that duplicate menu items won’t be added every time Word opens. Only there is a problem here. Word ignores this parameter (at least for controls in a CommandBar collection anyway). So I can keep it set to true, but it really means false. Lovely.
So what is happening? Well, Word is actually saving your new menu command to the Normal.dot template every time a new instance of Word opens – hence the duplicates.
There are probably a billion creative ways to stop the duplicate menus from appearing, but here are three tips that work really well. Here they are:
This one is pretty easy. Just add code to your add-in that looks for a control that has the same tag as the control you are about to add. If one exists, perform one of the following actions:
I modified my code to delete the control. Here is my code.
Private Sub RemoveExistingMenuItem() Dim contextMenu As Office.CommandBar = _ MyApplication.CommandBars("Text") MyApplication.CustomizationContext = customTemplate Dim control As Office.CommandBarButton = contextMenu.FindControl _ (Office.MsoControlType.msoControlButton, System.Type.Missing, _ "MyMenuItem", True, True) If Not (control Is Nothing) Then control.Delete(True) End If End Sub
private void RemoveExistingMenuItem() { Office.CommandBar contextMenu = myApplication.CommandBars["Text"]; myApplication.CustomizationContext = customTemplate; Office.CommandBarButton control = (Office.CommandBarButton)contextMenu.FindControl (Office.MsoControlType.msoControlButton, missing, "MyMenuItem", true, true); if ((control != null)) { control.Delete(true); } }
The customization context of the application tells Word where to save your customizations. To specify the customization context, set the CustomizationContext property of the Application object.
By default, Word uses the Normal.dot template as it’s customization context. This is not reliable and can change. If you do not explicitly set the context, you might search for controls saved to one context such as a document, delete controls from another context such as a custom template and then add the control to another context such as Normal.dot.
To avoid these issues, always set the customization context of the application to the same document or template every time you search for, delete, or add controls to a menu.
Note that in a Word document-level customization, it is probably best to set the customization context to the active document. That way when the user uninstalls the customization, the document and the menu commands that pertain to that document disappear as expected.
In Word application-level add-in, the best practice is to use a custom template for reasons mentioned later on in this post.
In the following example, I highlighted in bold the line that sets the customization context. Further along in this post, I will show you where I got customTemplate.
Private Sub AddMenuItem() MyApplication.CustomizationContext = customTemplate Dim menuItem As Office.MsoControlType = _ Office.MsoControlType.msoControlButton myControl = CType(MyApplication.CommandBars("Text").Controls.Add _ (menuItem, 1, True), Office.CommandBarButton) myControl.Style = Office.MsoButtonStyle.msoButtonCaption myControl.Caption = "My Menu Item" myControl.Tag = "MyMenuItem" customTemplate.Saved = True
GC.Collect() End Sub
private void AddMenuItem() { myApplication.CustomizationContext = customTemplate; Office.MsoControlType menuItem = Office.MsoControlType.msoControlButton; myControl = (Office.CommandBarButton)myApplication.CommandBars["Text"].Controls.Add (menuItem,missing, missing, 1, true); myControl.Style = Office.MsoButtonStyle.msoButtonCaption; myControl.Caption = "My Menu Item"; myControl.Tag = "MyMenuItem"; myControl.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler (myControl_Click); customTemplate.Saved = true;
GC.Collect();
}
Also – a quick tip – set the Saved property of the template to true after you add the control. This stops that annoying prompt from appearing that asks if you would like to save your customizations to the template.
It is very difficult to delete a control before the add-in shuts down. For example, if you attempt to delete a control in the ThisAddIn_Shutdown event handler, you will receive a not so helpful COM exception. You will get similar results in the Quit event of Word.
That is because the template that you are using as your application’s customization context is not writable in either of those event handlers. So if you cannot easily delete the control when Word closes, that means that the control will always live inside of the template.
This is a problem if you are using Normal.dot to persist the controls. Here is why. Let’s say the user decides that he does not want to see your command in a menu anymore. With a caption such as “My Menu Item”, can you really blame him? So the user uninstalls your add-in. However, the menu command still lives in Normal.dot! When that user opens his document in Word, "My Menu Item” still appears. Doooh! Here comes the support calls!
The way around this is to provide your own custom template to store customizations such as custom menus and menu items. Your setup application can remove the template along with add-in. That way when the user uninstalls the add-in, they also remove the template that contains the menu items.
In the following example, I retrieve a custom template from the users documents folder. Yes, your setup application will probably use a different location to place the custom template, but this is just for an example.
Private Sub GetCustomTemplate()
Dim TemplatePath As String = Environment.GetFolderPath _ (Environment.SpecialFolder.MyDocuments) + "\MyCustomTemplate.dotx" Dim install As Boolean = True For Each installedTemplate As Word.Template In MyApplication.Templates If installedTemplate.FullName = DirectCast(TemplatePath, String) Then install = False End If Next If install = True Then MyApplication.AddIns.Add(TemplatePath.ToString(), True) End If customTemplate = MyApplication.Templates(TemplatePath)
End Sub
private void GetCustomTemplate() { object TemplatePath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments) + "\\MyCustomTemplate.dotx"; object install = true;
foreach (Word.Template installedTemplate in myApplication.Templates) { if (installedTemplate.FullName == (string)TemplatePath) { install = false; } } if ((bool)install) { myApplication.AddIns.Add(TemplatePath.ToString(), ref install); } customTemplate = myApplication.Templates.get_Item(ref TemplatePath); }
To provide context, here is the complete sample:
Public Class ThisAddIn Private MyApplication As Word.Application Private WithEvents myControl As Office.CommandBarButton Private customTemplate As Word.Template Private Sub ThisAddIn_Startup _ (ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup MyApplication = Me.Application GetCustomTemplate() RemoveExistingMenuItem() AddMenuItem() End Sub Private Sub GetCustomTemplate() Dim TemplatePath As String = Environment.GetFolderPath _ (Environment.SpecialFolder.MyDocuments) + "\MyCustomTemplate.dotx" Dim install As Boolean = True For Each installedTemplate As Word.Template In MyApplication.Templates If installedTemplate.FullName = DirectCast(TemplatePath, String) Then install = False End If Next If install = True Then MyApplication.AddIns.Add(TemplatePath.ToString(), True) End If customTemplate = MyApplication.Templates(TemplatePath) End Sub Private Sub RemoveExistingMenuItem() Dim contextMenu As Office.CommandBar = _ MyApplication.CommandBars("Text") MyApplication.CustomizationContext = customTemplate Dim control As Office.CommandBarButton = contextMenu.FindControl _ (Office.MsoControlType.msoControlButton, System.Type.Missing, _ "MyMenuItem", True, True) If Not (control Is Nothing) Then control.Delete(True) End If End Sub Private Sub AddMenuItem() MyApplication.CustomizationContext = customTemplate Dim menuItem As Office.MsoControlType = _ Office.MsoControlType.msoControlButton myControl = CType(MyApplication.CommandBars("Text").Controls.Add _ (menuItem, 1, True), Office.CommandBarButton) myControl.Style = Office.MsoButtonStyle.msoButtonCaption myControl.Caption = "My Menu Item" myControl.Tag = "MyMenuItem" customTemplate.Saved = True GC.Collect() End Sub Sub myControl_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, _ ByRef CancelDefault As Boolean) Handles myControl.Click System.Windows.Forms.MessageBox.Show("My Menu Item clicked") End Sub Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown End Sub End Class
public partial class ThisAddIn { private Word.Application myApplication; private Office.CommandBarButton myControl; private Word.Template customTemplate; private void ThisAddIn_Startup(object sender, System.EventArgs e) { myApplication = this.Application; GetCustomTemplate(); RemoveExistingMenuItem(); AddMenuItem(); } private void GetCustomTemplate() { object TemplatePath = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments) + "\\MyCustomTemplate.dotx"; object install = true; foreach (Word.Template installedTemplate in myApplication.Templates) { if (installedTemplate.FullName == (string)TemplatePath) { install = false; } } if ((bool)install) { myApplication.AddIns.Add(TemplatePath.ToString(), ref install); } customTemplate = myApplication.Templates.get_Item(ref TemplatePath); } private void RemoveExistingMenuItem() { Office.CommandBar contextMenu = myApplication.CommandBars["Text"]; myApplication.CustomizationContext = customTemplate; Office.CommandBarButton control = (Office.CommandBarButton)contextMenu.FindControl (Office.MsoControlType.msoControlButton, missing, "MyMenuItem", true, true); if ((control != null)) { control.Delete(true); } } private void AddMenuItem() { myApplication.CustomizationContext = customTemplate; Office.MsoControlType menuItem = Office.MsoControlType.msoControlButton; myControl = (Office.CommandBarButton)myApplication.CommandBars["Text"].Controls.Add (menuItem, missing, missing, 1, true); myControl.Style = Office.MsoButtonStyle.msoButtonCaption; myControl.Caption = "My Menu Item"; myControl.Tag = "MyMenuItem"; myControl.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler (myControl_Click); customTemplate.Saved = true; GC.Collect(); } void myControl_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault) { System.Windows.Forms.MessageBox.Show("My Menu Item clicked"); } private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { } }