none
VSTO document-level add-in fails to start due to the suffix "|vstolocal" in the custom property _AssemblyLocation RRS feed

  • Question

  • Dear experts,

    My situation is as follows:

    We have developed a document-level add-in application for MS Excel in visual studio 2010 (Visual Basic .NET targeted on .NET Framework 4.0). Part of our solution is an Excel template (ExcelWorkbook.xltm) which contains the active content.

    We use a 3rd party installer for deploying our solution (not ClickOnce nor Windows installer). In the last step of the installation process, VSTOInstaller.exe is called to register our VSTO file. Everything works well except the following...

    If I start our template ExcelWorkbook.xltm from installation directory, the add-in is correctly loaded and I can create my file. Once I save the result into new document Example1.xlsm and save it outside the installation directory, it cannot be started as the VSTO file cannot be located.

    I have now find out that the root cause of this is a suffix "|vstolocal" which is added to the custom property _AssemblyLocation within the template ExcelWorkbook.xltm during compilation.

    Here is explained that this is done only when you start the application from the Visual Studio but not when you publish the solution via ClickOnce:

    http://msdn.microsoft.com/en-us/library/ds87aeyf.aspx

    However, as I have mentioned before, we do not use ClickOnce publish function. The compilation is done on build machine via devenv and still the "|vstolocal" is added.

    So the question is simple. How to force the compiler not to add this suffix? Or is there any way how to remove this suffix after compilation?

    Since the compilation, signing and also creation of the installation package is done in an automated way (via scrip) on the build machine, the solution has to be somehow included in there...

    Thanks in advance for any advice,

    Tomas




    • Edited by Meluzin Thursday, June 6, 2013 2:47 PM
    Thursday, June 6, 2013 2:18 PM

Answers

  • Hi Mezulin,

    Thanks for your feedback on this:  I will pass this to the team.  Glad you were able to find a workaround for the time being.

    Do you mind sharing the VB project code for future reference of folks looking for this answer on the forums?

    Thanks,

    - Michael


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

    Hi Michael,

    I can confirm that this solution works correctly. Here is how I have applied that into our solution:

    • I did not add the source code of this fix into my solution, I only included the compiled executable file (_AssemblyLocationFix.exe) as a resource file into my solution.
      It is however important to set the "Build Action" property of this resource file to "None", otherwise you may face some errors during compilation.
    • Afterwards, I have defined following post-build action in the project settings, on tab "Compile", button "Build Events...":
      "$(ProjectDir)Resources\_AssemblyLocationFix.exe" "$(TargetDir)ExcelWorkbook.xltm"

    It works correctly, after the successful build, this command is executed and it will remove the "|vstolocal" suffix from the property _AssemblyLocation. One more advice - this is good to apply only on the build server, not good for DEV machines because there you want this suffix for easier debugging.

    Here is the code of the executable (it is a C# project, not VB project as I have incorrectly mentioned before). Please also take into account the disclaimer.

    'DISCLAIMER:
    'Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
    'THIS SAMPLE CODE AND ANY RELATED INFORMATION 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.
    'We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object
    'code form of the Sample Code, provided that. You agree: (i) to not use Our name, logo, or trademarks to market Your software
    'product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the
    'Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims
    'or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Reflection;
     
    //OpenXML Imports 
    using DocumentFormat.OpenXml;
    using DocumentFormat.OpenXml.CustomProperties;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.VariantTypes;
    using System.IO;
    using System.IO.Packaging;
    using System.Xml.Linq;
    using System.Xml;
     
    
    namespace XLTM_DocAddIn_AssemblyLocationFix_Sample
    {
        class Program
        {
            static string ReadInputArgs(string[] args)
            {
                Console.WriteLine("Number of command line parameters = {0}", args.Length);
                if (args.Length != 1)
                {
                    Console.WriteLine("Error! Incorrect input parameters.\n");
                    for (int i = 0; i < args.Length; i++)
                    {
                        Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]);
                    }
                    DisplayHelpInConsole();
                    return "error";
                }
    
                //check if the user wants to see the help;
                if (String.Compare(args[0], "/?") == 0)
                {
                    DisplayHelpInConsole();
                    return "help";
                }
    
    
                //check if the user entered a valid file name;
                if (File.Exists(args[0]))
                {
                    return args[0];
                }
                else
                {
                    Console.WriteLine("Error! Input file does not exist.\n");
                    return "error";
                }
    
            }
    
    
            public static void DisplayHelpInConsole()
            {
                Console.WriteLine("\n\n\n\n\n\n\n---------------------------------------------------------");
                Console.WriteLine("\n     * Sample: Fix XLTM _AssemblyLocation Sample*");
                Console.WriteLine("\n---------------------------------------------------------");
                Console.WriteLine("\n\nUsage: \nXLTM_DocAddIn_AssemblyLocationFix_Sample.exe <XLSX/XLSM/XLTM file_path> | /?\n");
                Console.WriteLine("       /?  Display help.");
            }//END OF DisplayHelpInConsole
    
            
          public static void XLFixCustomProperty(string fileName)
            {
                Console.WriteLine("Input file: [" + fileName + "]\r\n");
                try
                {
                using (var workbook = SpreadsheetDocument.Open(fileName, true))
                {
        
                    var extProps  = workbook.ExtendedFilePropertiesPart;
    
                    if (extProps != null) //Check if ExtendedFileProperties package part is present.
                       {
                        var props = extProps.Properties;
                            
                        if (props != null) //Check if Workbook contains ExtendedFileProperties entries.
                        {
    
                            //get the number of items found in the Extended Document Properties collection;
                            //dynamically allocate a String array with these dimensions:
                            //    > size of Extended Document Properties collection ('count');
                            //    > plus a fixed number (5), representing the Core Document Properties ('Title', 'Subject' ..);
    
                            int count = extProps.Properties.Count();
                            Console.WriteLine("Found [" + count + "] BuiltIn Workbook Properties");
    
                            int currentIndex = 0;
                            foreach (var item in extProps.Properties)
                            {
                                Console.WriteLine(" > [" + currentIndex + "]\t" + item.LocalName + "\t" + item.InnerText);
                                currentIndex++;
                            }
                        }//END IF - Check if Workbook contains ExtendedFileProperties entries.
                       }//END IF - ExtendedFileProperties package part is present.
                    else
                       {
                        Console.WriteLine("No built-in properties found!");
                       }//END IF - ExtendedFileProperties package part is NOT present.
    
    
                    CustomFilePropertiesPart custProps = workbook.CustomFilePropertiesPart;
                    if (custProps != null)
                        {
                            int count = custProps.Properties.Count();
                            Console.WriteLine("\r\nFound [" + count + "] Custom Workbook Properties");
    
                            int currentIndex = 0;
                            foreach (CustomDocumentProperty item in custProps.Properties)
                            {
                                Console.WriteLine(" > [" + currentIndex + "]\t" + item.Name + "\t" + item.InnerText);
                                currentIndex++;
    
                                if ((item.Name.ToString().ToLower().CompareTo("_assemblylocation") == 0) &&
                                   (item.InnerText.ToString().ToLower().IndexOf("|vstolocal") > 0))
                                {
    
                                    Console.WriteLine(" -> FOUND '|vstolocal' flag!");
                                    Console.WriteLine(" -> Entry was removed.");
                                    item.VTLPWSTR = new VTLPWSTR (item.InnerText.ToString().Substring(0,
                                                                  item.InnerText.ToString().ToLower().IndexOf("|vstolocal")));
    
                                    Console.WriteLine(" -> New value: " + item.InnerText.ToString().Substring(0,
                                                      item.InnerText.ToString().ToLower().IndexOf("|vstolocal")));
                                }
    
                            }
                        }//END IF - check if Workbook contains Custom Properties
                    else
                        {
                            Console.WriteLine("\r\nNo built-in properties found!");
                        }//END IF - Custom Properties package part is NOT present.
                                
    
                    }//END OF 'using' block; Here is where all OpenXML objects are garbage collected.
                }//END OF 'try'
    
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Source, ex.Message);
                }
            }//END 
           static void Main(string[] args)
            {
                
                Console.WriteLine("------------------------------------------\r\n" +
                                  "XLTM_DocAddIn_AssemblyLocationFix_Sample  \r\n");
               
                string input = ReadInputArgs(args);
    
                if ((input.CompareTo("error") == 0) || (input.CompareTo("help") == 0))
                {
                    return;
                }
    
                XLFixCustomProperty(input);
    
    
                Console.WriteLine("------------------------------------------\r\n");
                //Console.WriteLine("Press any key to continue..");
                //Console.ReadKey();
            }
    
        }
    }

    I hope this helps others. This issue costed us lot of time and effort to solve.

    Best regards,
    Meluzin


    • Marked as answer by Meluzin Tuesday, June 18, 2013 12:34 PM
    • Edited by Meluzin Thursday, June 20, 2013 12:43 PM disclaimer for the source code was added
    Tuesday, June 18, 2013 12:34 PM

All replies

  • Hi Tomas,

    Thank you for posting in the MSDN Forum.

    I'm trying to involve some senior engineers into this issue and it will take some time. Your patience will be greatly appreciated.

    Sorry for any inconvenience and have a nice day!

    Best regards,


    Quist Zhang [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, June 11, 2013 6:34 AM
    Moderator
  • Hello Mezulin,

    You can publish your applicaiton to a folder and then used the files in the published folder in your 3rd party installer.

    Regards,

    Ajay

    Thursday, June 13, 2013 5:59 PM
  • Hello Mezulin,

    You can publish your applicaiton to a folder and then used the files in the published folder in your 3rd party installer.

    Regards,

    Ajay

    Hi Ajay,

    thank you for this hint. Unfortunately, it is not very "nice" solution for us because we would like to automate this step on build machine and as far as I know, it is not possible to use "publish" build via command line...

    This would mean, we would have to publish on the DEV machine first, then take the compiled template (XLTM file without the suffix) from the publish folder and include it into the solution as an external resource file. On the build machine, we would have to take into the installation package this external template instead of the currently compiled template.

    This is not very nice solution especially because it means double maintenance for the template file.

    Anyway, I have also opened official support case for MS under this ID 113061010501182. I got currently a better solution - a small VB project (compiled into exe file) which will remove the suffix from the template. This executable will be executed after compilation just before the installation package is created.

    I will test this solution during this week  (it will take some time to get this into the script on build machine). 

    Anyway, I can see this as deficiency of the compiler. Since the "Publish" function included in the Visual Studio environment is able to create the  correct template file (without "|vstolocal" suffix), there should be definitely the same option available also for the command line compiler (simple parameter).

    Best regards,
    Tomas


    Monday, June 17, 2013 6:52 AM
  • Hi Mezulin,

    Thanks for your feedback on this:  I will pass this to the team.  Glad you were able to find a workaround for the time being.

    Do you mind sharing the VB project code for future reference of folks looking for this answer on the forums?

    Thanks,

    - Michael


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

    Monday, June 17, 2013 8:06 PM
    Moderator
  • Hi Mezulin,

    Thanks for your feedback on this:  I will pass this to the team.  Glad you were able to find a workaround for the time being.

    Do you mind sharing the VB project code for future reference of folks looking for this answer on the forums?

    Thanks,

    - Michael


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

    Hi Michael,

    I can confirm that this solution works correctly. Here is how I have applied that into our solution:

    • I did not add the source code of this fix into my solution, I only included the compiled executable file (_AssemblyLocationFix.exe) as a resource file into my solution.
      It is however important to set the "Build Action" property of this resource file to "None", otherwise you may face some errors during compilation.
    • Afterwards, I have defined following post-build action in the project settings, on tab "Compile", button "Build Events...":
      "$(ProjectDir)Resources\_AssemblyLocationFix.exe" "$(TargetDir)ExcelWorkbook.xltm"

    It works correctly, after the successful build, this command is executed and it will remove the "|vstolocal" suffix from the property _AssemblyLocation. One more advice - this is good to apply only on the build server, not good for DEV machines because there you want this suffix for easier debugging.

    Here is the code of the executable (it is a C# project, not VB project as I have incorrectly mentioned before). Please also take into account the disclaimer.

    'DISCLAIMER:
    'Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
    'THIS SAMPLE CODE AND ANY RELATED INFORMATION 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.
    'We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object
    'code form of the Sample Code, provided that. You agree: (i) to not use Our name, logo, or trademarks to market Your software
    'product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the
    'Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims
    'or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.

     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Reflection;
     
    //OpenXML Imports 
    using DocumentFormat.OpenXml;
    using DocumentFormat.OpenXml.CustomProperties;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.VariantTypes;
    using System.IO;
    using System.IO.Packaging;
    using System.Xml.Linq;
    using System.Xml;
     
    
    namespace XLTM_DocAddIn_AssemblyLocationFix_Sample
    {
        class Program
        {
            static string ReadInputArgs(string[] args)
            {
                Console.WriteLine("Number of command line parameters = {0}", args.Length);
                if (args.Length != 1)
                {
                    Console.WriteLine("Error! Incorrect input parameters.\n");
                    for (int i = 0; i < args.Length; i++)
                    {
                        Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]);
                    }
                    DisplayHelpInConsole();
                    return "error";
                }
    
                //check if the user wants to see the help;
                if (String.Compare(args[0], "/?") == 0)
                {
                    DisplayHelpInConsole();
                    return "help";
                }
    
    
                //check if the user entered a valid file name;
                if (File.Exists(args[0]))
                {
                    return args[0];
                }
                else
                {
                    Console.WriteLine("Error! Input file does not exist.\n");
                    return "error";
                }
    
            }
    
    
            public static void DisplayHelpInConsole()
            {
                Console.WriteLine("\n\n\n\n\n\n\n---------------------------------------------------------");
                Console.WriteLine("\n     * Sample: Fix XLTM _AssemblyLocation Sample*");
                Console.WriteLine("\n---------------------------------------------------------");
                Console.WriteLine("\n\nUsage: \nXLTM_DocAddIn_AssemblyLocationFix_Sample.exe <XLSX/XLSM/XLTM file_path> | /?\n");
                Console.WriteLine("       /?  Display help.");
            }//END OF DisplayHelpInConsole
    
            
          public static void XLFixCustomProperty(string fileName)
            {
                Console.WriteLine("Input file: [" + fileName + "]\r\n");
                try
                {
                using (var workbook = SpreadsheetDocument.Open(fileName, true))
                {
        
                    var extProps  = workbook.ExtendedFilePropertiesPart;
    
                    if (extProps != null) //Check if ExtendedFileProperties package part is present.
                       {
                        var props = extProps.Properties;
                            
                        if (props != null) //Check if Workbook contains ExtendedFileProperties entries.
                        {
    
                            //get the number of items found in the Extended Document Properties collection;
                            //dynamically allocate a String array with these dimensions:
                            //    > size of Extended Document Properties collection ('count');
                            //    > plus a fixed number (5), representing the Core Document Properties ('Title', 'Subject' ..);
    
                            int count = extProps.Properties.Count();
                            Console.WriteLine("Found [" + count + "] BuiltIn Workbook Properties");
    
                            int currentIndex = 0;
                            foreach (var item in extProps.Properties)
                            {
                                Console.WriteLine(" > [" + currentIndex + "]\t" + item.LocalName + "\t" + item.InnerText);
                                currentIndex++;
                            }
                        }//END IF - Check if Workbook contains ExtendedFileProperties entries.
                       }//END IF - ExtendedFileProperties package part is present.
                    else
                       {
                        Console.WriteLine("No built-in properties found!");
                       }//END IF - ExtendedFileProperties package part is NOT present.
    
    
                    CustomFilePropertiesPart custProps = workbook.CustomFilePropertiesPart;
                    if (custProps != null)
                        {
                            int count = custProps.Properties.Count();
                            Console.WriteLine("\r\nFound [" + count + "] Custom Workbook Properties");
    
                            int currentIndex = 0;
                            foreach (CustomDocumentProperty item in custProps.Properties)
                            {
                                Console.WriteLine(" > [" + currentIndex + "]\t" + item.Name + "\t" + item.InnerText);
                                currentIndex++;
    
                                if ((item.Name.ToString().ToLower().CompareTo("_assemblylocation") == 0) &&
                                   (item.InnerText.ToString().ToLower().IndexOf("|vstolocal") > 0))
                                {
    
                                    Console.WriteLine(" -> FOUND '|vstolocal' flag!");
                                    Console.WriteLine(" -> Entry was removed.");
                                    item.VTLPWSTR = new VTLPWSTR (item.InnerText.ToString().Substring(0,
                                                                  item.InnerText.ToString().ToLower().IndexOf("|vstolocal")));
    
                                    Console.WriteLine(" -> New value: " + item.InnerText.ToString().Substring(0,
                                                      item.InnerText.ToString().ToLower().IndexOf("|vstolocal")));
                                }
    
                            }
                        }//END IF - check if Workbook contains Custom Properties
                    else
                        {
                            Console.WriteLine("\r\nNo built-in properties found!");
                        }//END IF - Custom Properties package part is NOT present.
                                
    
                    }//END OF 'using' block; Here is where all OpenXML objects are garbage collected.
                }//END OF 'try'
    
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Source, ex.Message);
                }
            }//END 
           static void Main(string[] args)
            {
                
                Console.WriteLine("------------------------------------------\r\n" +
                                  "XLTM_DocAddIn_AssemblyLocationFix_Sample  \r\n");
               
                string input = ReadInputArgs(args);
    
                if ((input.CompareTo("error") == 0) || (input.CompareTo("help") == 0))
                {
                    return;
                }
    
                XLFixCustomProperty(input);
    
    
                Console.WriteLine("------------------------------------------\r\n");
                //Console.WriteLine("Press any key to continue..");
                //Console.ReadKey();
            }
    
        }
    }

    I hope this helps others. This issue costed us lot of time and effort to solve.

    Best regards,
    Meluzin


    • Marked as answer by Meluzin Tuesday, June 18, 2013 12:34 PM
    • Edited by Meluzin Thursday, June 20, 2013 12:43 PM disclaimer for the source code was added
    Tuesday, June 18, 2013 12:34 PM
  • Hello Meluzin,

    Thanks for sharing the solution!

    -Ajay

    Tuesday, June 18, 2013 3:15 PM