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.
[View full size image]

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. |
Figure 20-5. Setting setup project properties.
[View full size image]

Figure 20-6. Telling the setup project which files to set up.

Figure 20-7. Setting the custom installer class project to build a class library.
[View full size image]

Figure 20-8. Adding custom installer classes.
[View full size image]

Figure 20-9. Editing the custom installer classes.
[View full size image]

Figure 20-10. Selecting the custom install actions.

Figure 20-11. Setting the custom action data.
[View full size image]

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).
/custassembly="[TARGETDIR]\ExpenseReport.dll"
/custdoc="[TARGETDIR]\ExpenseReport.xls"
Listing 20-4. Creating an Application Manifest Editor Custom Install Action with Server Document
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.
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);
}
}
}
Listing 20-5. A Custom Install Action Class to Set Local Security Policy
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.
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);
}
}
}