WORD 2003/EXCEL 2003: VSTO Customization added using ServerDocument.AddCustomization method does not add customization if your document contained an embedded VSTO customized Document/WorkBook

PROBLEM DESCRIPTION

Consider this scenario. You are adding VSTO customizations dynamically to Word 2003/Excel 2003 documents using ServerDocument.AddCustomization method.  The ServerDocument.AddCustomization method succeeds. But when you try to open this customized document, the customization does not load. If you have the “VSTO_SUPPRESSDISPLAYALERTS” environment variable set to “0” you will get the following error:  “The customization assembly could not be found or could not be loaded.”

clip_image002

This is a known issue with Word 2003 and Excel 2003 VSTO customizations, containing embedded customized documents. It doesn’t occur with Word 2007 (or Excel 2007) VSTO customization.

How to avoid this?

To workaround this issue, you can add the customization to the outer document using VSTO related document properties.

  • _AssemblyName:   This property contains an asterisk (*). This indicates to the Microsoft Office application that the document has a Visual Studio Tools for Office customization.
  • _AssemblyLocation: This property contains a string that provides details about the deployment manifest for the customization.

For more details on document properties, please visit Custom Document Properties Overview

The _AssemblyLocation custom document property needs to point to the deployment manfiest location. In order to generate the deployment manifest, follow these steps:

  1. Open the Solution that you would like to publish using Visual Studio
  2. Click  on the View, Select Solution Explorer (This will display Solution explorer window)
  3. Right-click the project node in Solution Explorer.
  4. Click Publish on the shortcut menu.
  5. The Publish Wizard appears.
  6. In the Specify the location to publish this application box, type the path to the folder where you want to deploy the solution. Optionally, you can click Browse and navigate to the folder using the dialog box that appears.
  7. Verify that the path is correct, and then click Finish.

The document, assemblies, and manifests are copied to the publish location, with the assemblies and application manifest (<filename>.dll.mainfest) in a subdirectory under the document and deployment manifest (<filename>.application)

For more details please refer to: How to: Deploy Solution Files Using the Publish Wizard (2003 System)

You can add these custom document properties using either of the two approaches:-

  1. Use IPropertyStorage APIs to add the custom properties.
  2. Add the custom properties using Word/Excel Object Model (Please see KB 257757 if you intend to do this task from un-attended environments).

Approach 1: Adding Custom Document Properties programmatically using IPropertyStorage

The other approach that you can use is to add the properties using IPropertyStorage interface.You can use the concepts demonstrated in DSOFile sample, to add the custom document properties. This sample can be downloaded using the link https://support.microsoft.com/kb/224351.

Information about DSOFile: The Dsofile.dll sample file demonstrates how to use the OLE32 IPropertyStorage interface to access the extended properties of OLE structured storage files. The component converts the data to Automation friendly data types for easier use by high level programming languages such as Visual Basic 6.0, Visual Basic .NET, and C#. The Dsofile.dll sample file is given with full source code and includes sample clients written in Visual Basic 6.0 and Visual Basic .NET 2003 (7.1).Warning The Dsofile.dll, the source code, and the associated samples are provided "as is" without warranty of any kind, either expressed or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose. Use at your own risk.

Let’s see how to use DSOFile sample to attach custom properties to the document, but before that we need to ensure that DSOFile.dll is registered on the development machine.  The self-extracting setup installs and registers the DsoFile.dll component in a location that you want.  But, if you move the DLL to another location or to another computer, you have to re-register the DLL before you can use it again. To do this, type regsvr32 [filepath]\dsofile.dll in the Run dialog box on the Start menu (or from elevated rights command prompt in case of Vista).

Let’s start by creating a new windows application project which references DSOFile.dll and adds these properties.

  1. Launch Visual Studio

  2. On the File menu, point to New, and then click Project.

  3. In the Project Types pane, expand Visual C# or Visual Basic, and then Click on Windows.

  4. In the templates pane, select “Windows Form Application”

  5. In the Name box, type AddPropertiesSample

  6. Click OK. (This will open Visual Studio and by default one Window form Form1 will be added to the project)

  7. Click  on the View, Select Solution Explorer (This will display Solution explorer window)

  8. Right click on the References and Select “Add References…” from the drop down menu.

  9. The “Add Reference” dialog will be displayed, Click on the COM tab in the dialog.

  10. Locate “DSO OLE Document Properties Reader 2.1” in the Component Name and Click on OK.

  11. Click  on the View, Select ToolBox (This will display ToolBox)

  12. Expand “All Windows Forms” in the ToolBox

  13. Drag and Drop button control on the Form1, a new control named Button1 will be added to the form.

  14. Double click on the button1 to add button1_Click event, paste the following code

        1:  string fileName = @"C:\temp\Sample.doc";
    
        2:  object vstoDocumentLocation = @"C:\Temp\WordDocument.application";
    
        3:   
    
        4:  DSOFile.OleDocumentPropertiesClass doc = new DSOFile.OleDocumentPropertiesClass();
    
        5:  doc.Open(fileName, false, DSOFile.dsoFileOpenOptions.dsoOptionDefault);
    
        6:  object assemblyName = "*";
    
        7:  DSOFile.CustomProperties customProperties = doc.CustomProperties;
    
        8:  //Check if _AssemblyName property already exists
    
        9:  try
    
       10:    {
    
       11:       DSOFile.CustomProperty nameProp = customProperties["_AssemblyName"];
    
       12:       nameProp.set_Value(ref assemblyName);
    
       13:    }
    
       14:  catch (Exception)
    
       15:    {
    
       16:       customProperties.Add("_AssemblyName", ref assemblyName);
    
       17:    }
    
       18:  //Check if _AssemblyLocation property already exists
    
       19:  try
    
       20:    {
    
       21:        DSOFile.CustomProperty loc = customProperties["_AssemblyLocation"];
    
       22:        loc.set_Value(ref assemblyLocation);
    
       23:    }
    
       24:  catch (Exception ex)
    
       25:    {
    
       26:        customProperties.Add("_AssemblyLocation", ref vstoDocumentLocation);
    
       27:    }
    
       28:   
    
       29:  doc.Save();
    
       30:  doc.Close(false);
    

    Approach 2: Add the properties using Word/Excel Object Model:

    One more approach that you can use is to add the properties using Excel/Word object model.

    Let’s start by creating a new windows application project which references Excel/Word libraries and adds these properties using CustomDocumentProperties Object.

    Create an Automation Client for Microsoft Word

    1. Start Visual Studio .NET.

    2. On the File menu, click New, and then click Project. Select Windows Application from the Visual C# Project types. Form1 is created by default.

    3. Add a reference to Microsoft Word Object Library. To do this, follow these steps:

      1. On the Project menu, click Add Reference.
      2. On the COM tab, locate Microsoft Word Object Library, and then click Select.
    4. Click OK in the Add References dialog box to accept your selections. On the View menu, select Toolbox to display the Toolbox, and then add a button to Form1.

    5. Double-click Button1. The code window for the form appears.

    6. In the code window, replace the following code

          1:  private void button1_Click(object sender, EventArgs e)
      
          2:  {
      
          3:  }
      
          4:   
      
          5:  With:-
      
          6:   
      
          7:  private void button1_Click(object sender, EventArgs e)
      
          8:  {
      
          9:  Word.Application oWord;
      
         10:  Word._Document oDoc;
      
         11:  object oMissing = Missing.Value;
      
         12:  object oDocBuiltInProps;
      
         13:  object oDocCustomProps;
      
         14:  object fileName = @"C:\test.doc";
      
         15:   
      
         16:  //Create an instance of Microsoft Word and make it visible.
      
         17:  oWord = new Word.Application();
      
         18:  oWord.Visible = true;
      
         19:   
      
         20:  //Create a new Document and get the BuiltInDocumentProperties collection.
      
         21:  oDoc = oWord.Documents.Open(ref fileName,ref oMissing,ref oMissing,
      
         22:                              ref oMissing,ref oMissing,ref oMissing, 
      
         23:                              ref oMissing,ref oMissing,ref oMissing, 
      
         24:                              ref oMissing,ref oMissing,ref oMissing,
      
         25:                              ref oMissing,ref oMissing,ref oMissing,
      
         26:                              ref oMissing);
      
         27:  oDocBuiltInProps = oDoc.BuiltInDocumentProperties;
      
         28:  Type typeDocBuiltInProps = oDocBuiltInProps.GetType();
      
         29:   
      
         30:  //Get the Author property and display it.
      
         31:  string strName;
      
         32:  string strValue;
      
         33:   
      
         34:  //Add a property/value pair to the CustomDocumentProperties collection.
      
         35:  oDocCustomProps = oDoc.CustomDocumentProperties;
      
         36:  object oDocAssemblyNameProp;
      
         37:  Type typeDocAssemblyNameProp; 
      
         38:  Type typeDocCustomProps = oDocCustomProps.GetType();
      
         39:  try
      
         40:  {
      
         41:  strName = "_AssemblyName";
      
         42:  oDocAssemblyNameProp = typeDocCustomProps.InvokeMember("Item", 
      
         43:                          BindingFlags.Default | BindingFlags.GetProperty, 
      
         44:                          null, oDocCustomProps, new object[] { strName });
      
         45:  typeDocAssemblyNameProp = oDocAssemblyNameProp.GetType();
      
         46:  strValue = typeDocAssemblyNameProp.InvokeMember("Value", 
      
         47:              BindingFlags.Default | BindingFlags.GetProperty, 
      
         48:              null, oDocAssemblyNameProp, new object[] { }).ToString();
      
         49:  }
      
         50:  catch
      
         51:  {
      
         52:  strName = "_AssemblyName";
      
         53:  strValue = "*";
      
         54:  object[] oArgs = { strName, false, MsoDocProperties.msoPropertyTypeString, strValue };
      
         55:  typeDocCustomProps.InvokeMember("Add",
      
         56:  BindingFlags.Default | BindingFlags.InvokeMethod, null, 
      
         57:  oDocCustomProps, oArgs);
      
         58:  }
      
         59:  try
      
         60:  {
      
         61:  strName = "_AssemblyLocation";
      
         62:  oDocAssemblyNameProp = typeDocCustomProps.InvokeMember("Item", 
      
         63:  BindingFlags.Default | BindingFlags.GetProperty, 
      
         64:  null, oDocCustomProps, new object[] { strName });
      
         65:   
      
         66:  typeDocAssemblyNameProp = oDocAssemblyNameProp.GetType();
      
         67:  strValue = typeDocAssemblyNameProp.InvokeMember("Value", 
      
         68:  BindingFlags.Default | BindingFlags.GetProperty, null, 
      
         69:  oDocAssemblyNameProp, new object[] { }).ToString();
      
         70:  }
      
         71:  catch
      
         72:  {
      
         73:  strName = "_AssemblyLocation";
      
         74:        strValue = @"C:\WordDocument.application"; ;
      
         75:        object[] oArgs = { strName, false, MsoDocProperties.msoPropertyTypeString, strValue };
      
         76:        typeDocCustomProps.InvokeMember("Add", 
      
         77:        BindingFlags.Default | BindingFlags.InvokeMethod, 
      
         78:        null, oDocCustomProps, oArgs);
      
         79:  }
      
         80:   
      
         81:  oDoc.Save();
      
         82:  oDocBuiltInProps = null;
      
         83:  oDocCustomProps = null;
      
         84:  oDoc.Close(ref oMissing, ref oMissing,ref oMissing);
      
         85:  oDoc = null;
      
         86:  oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
      
         87:  oWord = null;
      
         88:   
      
         89:  }
      
    7. Scroll to the top of the code window, and then add the following lines to the end of the list of using directives:

          1:  using Microsoft.Office.Core;
      
          2:  using Word = Microsoft.Office.Interop.Word;
      
          3:  using System.Reflection;
      
    8. Press F5 to run the application.

    Note:- The DocumentProperties and the DocumentProperty interfaces are late bound interfaces. To use these interfaces, you must treat them like you would an IDispatch interface.

    For more details, please refer to: How to Use Automation to Get and to Set Office Document Properties with Visual C#.

    The next time the document is opened and saved, the Visual Studio Tools for Office runtime attaches the solution assembly to the document, and creates the Runtime Storage Control, if necessary. The Visual Studio Tools for Office runtime also sets the value of the _AssemblyLocation custom document property to the GUID for the Runtime Storage Control.