Creating Setup Packages
Creating a setup package to install a VSTO customized document on a user's local machine requires us to build a couple of custom installer classes. We need to update the application manifest stored in the document to refer to the location on the user's machine, and we need to update the user's security policy. Let's walk through all the steps required to add a setup package to a customized spreadsheetsay, an expense reporting application.
Open the solution for the customized document and right-click the solution (the root of the tree) in the Solution Explorer. Choose Add > New Project, and create a setup project as shown in Figure 20-4.
Figure 20-4. Creating a setup project.
This step is not necessary if you have created an Outlook Add-In VSTO project. Visual Studio will automatically create an installer project which installs the DLL, creates a manifest, and updates the Outlook add-in registry key for you. However, you will still need to ensure that the right security policy is rolled out and that the VSTO runtime assemblies are installed on the client machines. |
Use the Properties pane for the setup project to customize strings such as the author, description, and so on, as shown in Figure 20-5.
Figure 20-5. Setting setup project properties.
We have not yet told the setup project what files it is going to be setting up. We want it to set up all the files produced by the expense report project in this solution.
Right-click the setup project and select Add > Project Output to view the Add Project Output Group dialog shown in Figure 20-6.
Figure 20-6. Telling the setup project which files to set up.
Select the Primary Output files associated with the Expense Report project and click OK.
At this point, if you want the users to take responsibility for installing the customization to a location that they trust, and do not care that that will have to copy around the customization assembly if they want to move the spreadsheet, you are done. You can build and execute the setup package, and it will copy the necessary files to the user's machine just as they are on the development machine.
However, you probably want to set the codebase in the application manifest so that it refers to the installation location rather than the current directory. You also probably want to set the user's security policy so that the customization location is fully trusted. That way the user can copy the document around without worrying about dragging the customization along with it.
To do that, we create yet another project in this solution. Right-click the solution in the Solution Explorer again and create a new, empty C# project (in the Visual C# > Windows branch of the tree view of the Add New Project dialog) called CustomSetup. When you have the project, right-click it and select the Properties pane for the project. Change the output type to Class Library, as shown in Figure 20-7.
Figure 20-7. Setting the custom installer class project to build a class library.
Right-click the project again and choose Add > New Item. Add a new Installer Class, as shown in Figure 20-8. In fact, add two: one for the security change, and one for the application manifest change.
Figure 20-8. Adding custom installer classes.
Right-click the References node in the CustomSetup project's tree view and add a reference to Microsoft.VisualStudio.Tools.Applications.Runtimewe are going to need to create the ServerDocument class, so we need a reference to the VSTO runtime library. Finally, right-click the application manifest installer and select View Code. The Visual Studio IDE should now look something like Figure 20-9.
Figure 20-9. Editing the custom installer classes.
Before we get into adding code to these custom actions, however, let's tell the installer about them. Right-click the installer project in the Server Explorer and choose View > Custom Actions. In the Custom Actions viewer, right-click Install and choose Add Custom Action to view the custom action dialog shown in Figure 20-10.
Figure 20-10. Selecting the custom install actions.
Click Application Folder and then Primary Output from CustomSetup. Doing so tells the setup project that it should look for classes decorated with the RunInstaller attribute in the assembly produced by the CustomSetup project. As you can see from the code editor, both the new custom install action classes are decorated with this attribute.
We must do one more thing to get the custom install actions working properly; they need to know the name of the assembly, the name of the document, and where they are located. To pass these strings from the installer to the custom action, we add the strings to the CustomActionData property of the custom action just created, as shown in Figure 20-11.
Figure 20-11. Setting the custom action data.
The custom action data consists of a set of keys and values:
/custassembly="[TARGETDIR]ExpenseReport.dll" /custdoc="[TARGETDIR]ExpenseReport.xls"
Finally, we are all set up to write the custom installation actions. First, we write the application manifest editor shown in Listing 20-4. We get the strings passed by the main installer out of the installation context object, create a ServerDocument on the installed document, and set the assembly path to the absolute path. Finally, because this is a derived class, we make sure that we call the base class install method in case it does anything interesting (such as write a success message to a log file).
Listing 20-4. Creating an Application Manifest Editor Custom Install Action with Server Document
using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using Microsoft.VisualStudio.Tools.Applications.Runtime; namespace CustomSetup { [RunInstaller(true)] public partial class AppManifestInstaller : Installer { public AppManifestInstaller() { InitializeComponent(); } public override void Install( System.Collections.IDictionary stateSaver) { string assemblyPath = this.Context.Parameters["custassembly"]; string documentPath = this.Context.Parameters["custdoc"]; ServerDocument sd = new ServerDocument(documentPath, true, System.IO.FileAccess.ReadWrite); try { sd.AppManifest.Dependency.AssemblyPath = assemblyPath; sd.Save(); } finally { sd.Close(); } base.Install(stateSaver); } } }
Second, we write code similar to the code we wrote in Chapter 19 to set the local security policy. This time, we set a user security policy that trusts the installation directory explicitly as shown in Listing 20-5.
Listing 20-5. A Custom Install Action Class to Set Local Security Policy
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using System.Security; using System.Security.Policy; namespace CustomSetup { [RunInstaller(true)] public partial class SecurityInstaller : Installer { public SecurityInstaller() { InitializeComponent(); } public override void Install( System.Collections.IDictionary stateSaver) { PolicyLevel enterprisePolicyLevel; PolicyLevel machinePolicyLevel; PolicyLevel userPolicyLevel; CodeGroup assemblyGroup; UrlMembershipCondition assemblyCondition; PolicyStatement policyStatement; PermissionSet fullTrust; string assemblyPath = this.Context.Parameters["custassembly"]; // Obtain the three policy levels: IEnumerator policyEnumerator = SecurityManager.PolicyHierarchy(); policyEnumerator.MoveNext(); enterprisePolicyLevel = (PolicyLevel)policyEnumerator.Current; policyEnumerator.MoveNext(); machinePolicyLevel = (PolicyLevel)policyEnumerator.Current; policyEnumerator.MoveNext(); userPolicyLevel = (PolicyLevel)policyEnumerator.Current; // Create a new group by combining a permission set with a // membership condition: fullTrust = userPolicyLevel.GetNamedPermissionSet("FullTrust"); policyStatement = new PolicyStatement(fullTrust, PolicyStatementAttribute.Nothing); assemblyCondition = new UrlMembershipCondition(assemblyPath); assemblyGroup = new UnionCodeGroup( assemblyCondition, policyStatement); // Add the new policy to the root: userPolicyLevel.RootCodeGroup.AddChild(assemblyGroup); SecurityManager.SavePolicy(); base.Install(stateSaver); } } }
If you build all three projects, right-click the installation project, and choose Install, you will see how the Installation Wizard allows the user to select the location, copies the files over, and then updates the user's security policy and sets the assembly codebase in the embedded application manifest.
Of course, this section has just given a bare-bones skeleton of a customized installation program. A more robust installer includes features such as custom logging, better error handling, user-interface elements, rollback/uninstall when things go wrong, and so on.