How do you assign the Assembly Location of an Excel VSTO Installation?

Viewed 6290

I'm creating a Document-Level Workbook/Template written in C# and using a VSTO Installer to deploy the code. After installing the project I have full functionality of the Spreadsheet however, when I save or copy the installed sheet to another path outside the installation folder I get the following error:

Uh Oh!

With the following full details:

Name: 
From: file:///C:/Users/Kronos/Desktop/ExcelTemplate1.vsto

************** Exception Text **************
System.Deployment.Application.DeploymentDownloadException: Downloading file:///C:/Users/Kronos/Desktop/ExcelTemplate1.vsto did not succeed. ---> System.Net.WebException: Could not find file 'C:\Users\Kronos\Desktop\ExcelTemplate1.vsto'. ---> System.Net.WebException: Could not find file 'C:\Users\Kronos\Desktop\ExcelTemplate1.vsto'. ---> System.IO.FileNotFoundException: Could not find file 'C:\Users\Kronos\Desktop\ExcelTemplate1.vsto'.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)
   at System.Net.FileWebStream..ctor(FileWebRequest request, String path, FileMode mode, FileAccess access, FileShare sharing, Int32 length, Boolean async)
   at System.Net.FileWebResponse..ctor(FileWebRequest request, Uri uri, FileAccess access, Boolean asyncHint)
   --- End of inner exception stack trace ---
   at System.Net.FileWebResponse..ctor(FileWebRequest request, Uri uri, FileAccess access, Boolean asyncHint)
   at System.Net.FileWebRequest.GetResponseCallback(Object state)
   --- End of inner exception stack trace ---
   at System.Net.FileWebRequest.EndGetResponse(IAsyncResult asyncResult)
   at System.Net.FileWebRequest.GetResponse()
   at System.Deployment.Application.SystemNetDownloader.DownloadSingleFile(DownloadQueueItem next)
   --- End of inner exception stack trace ---
   at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.GetManifests(TimeSpan timeout)
   at Microsoft.VisualStudio.Tools.Applications.Deployment.ClickOnceAddInDeploymentManager.InstallAddIn()

I realize that this is due the to .VSTO, .manifest & .DLL files not being properly referenced as the Excel spreadsheet no longer resides in the installed path. After doing some research I can manually fix this by changing the copied/saved Excels custom.xml file within the .xlsx file from this:

name="_AssemblyLocation"><vt:lpwstr>ExcelTemplate1.vsto|ca022788-e7c0-41d8-b8ae-2c0ba9edbbf8|vstolocal

To this:

name="_AssemblyLocation"><vt:lpwstr>file://c:/<path to install dir>/ExcelTemplate1.vsto|ca022788-e7c0-41d8-b8ae-2c0ba9edbbf8|vstolocal

As this isn't a viable solution for my customers, how do I make the above changes using either C# code, or (more preferably) the installer?

Note: I've tried creating a Custom Install Action (per this MSDN Tutorial) where the following is set for CustomActionData:

/assemblyLocation="[TARGETDIR]ExcelWorkbookProject.dll"/deploymentManifestLocation="[TARGETDIR]ExcelWorkbookProject.vsto"/documentLocation="[TARGETDIR]ExcelWorkbookProject.xlsx"

to no avail.

2 Answers

While James Mertz answer is spot on, it's also a bit dated (although it just saved me after a day of pulling my hair out). For newer versions of Visual Studios the instructions for this part of the application build have moved to here. I suggest that you go read them, or you can read my cliff notes version below:

The new console application that should be built as a new project in your existing solution. The Program.cs should look like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Microsoft.VisualStudio.Tools.Applications;

namespace SetExcelDocumentProperties
{
    class Program
    {
        static void Main(string[] args)
        {
            string assemblyLocation = "";
            Guid solutionID = new Guid();
            Uri deploymentManifestLocation = null;
            string documentLocation = "";
            string[] nonpublicCachedDataMembers = null;

            for (int i = 0; i <= args.Count() - 1; i++)
            {
                Console.WriteLine(args[i]);
                string[] oArugment = args[i].Split('=');

                switch (oArugment[0])
                {
                    case "/assemblyLocation":
                        assemblyLocation = oArugment[1];
                        break;
                    case "/deploymentManifestLocation":
                        if (!Uri.TryCreate(oArugment[1], UriKind.Absolute, out deploymentManifestLocation))
                        {
                            Console.WriteLine("Error creating URI");
                        }
                        break;
                    case "/documentLocation":
                        documentLocation = oArugment[1];
                        break;
                    case "/solutionID":
                        solutionID = Guid.Parse(oArugment[1]);
                        break;
                }
            }
            try
            {
                ServerDocument.RemoveCustomization(documentLocation);
                ServerDocument.AddCustomization(documentLocation, assemblyLocation,
                                            solutionID, deploymentManifestLocation,
                                            true, out nonpublicCachedDataMembers);

            }
            catch (System.IO.FileNotFoundException)
            {
                Console.WriteLine("The specified document does not exist.");
            }
            catch (System.IO.IOException)
            {
                Console.WriteLine("The specified document is read-only.");
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine("The customization could not be removed.\n" +
                    ex.Message);
            }
            catch (DocumentNotCustomizedException ex)
            {
                Console.WriteLine("The document could not be customized.\n" +
                    ex.Message);
            }
        }
    }
}

There's no need to change any of that as we will be passing parameters to it from the InstallShield Setup.

This requires two references in the new project:

  • Microsoft.VisualStudio.Tools.Applications.Runtime
  • Microsoft.VisualStudio.Tools.Applications.ServerDocument

Now to call this new application after installation is complete and pass it parameters (also in that instruction link): add the Primary Output from that new project to your Install Shield "Application Files"

Then go to "Custom Action" in Install Shield and double click on "After Setup Complete Success Dialog" in the "Custom Actions During Installation" section. This is where you can run vbscript, jscript, or an executable once installation is complete. Here we will run our new console application executable passing it parameters.

In your new custom action, in the list for the Source Location property, choose "Installed with the Product" and browse to your new projects primary output file where ever your VSTO application is to be installed on the destination.

Now in the "Command Line" property box you put the list of arguments that your console application requires to run:

/assemblyLocation="[INSTALLDIR]ExcelWorkbook.dll" /deploymentManifestLocation="[INSTALLDIR]ExcelWorkbook.vsto" /documentLocation="[INSTALLDIR]ExcelWorkbook.xlsx" /solutionID="Your Solution ID"

Those have to be changed for your dll, vsto, workbook, and solution id. The solution ID can be found inside of your .csproj file (out in a file explorer go find it and pop it open in notepad).

Now you're golden. When your application installs successfully this console application is launched and it will change the manifestLocation of your workbook to the install path instead of the relative path of the current workbook.

If the folks installing don't have admin privileges on their computer and you are installing into Program Files directory you may find that before performing this step they were able to install and run, but afterwards they can't. That's because the workbook is read-only in that location. You'll have to pick another location to install your application in that event (AppDataLocal is a good choice).

Related