Walkthrough: Copying a Document to the End User Computer after a ClickOnce Installation
Updated: September 2010
Using a ClickOnce post-deployment action, you can install document-level Office solutions, and then copy the document to the end user computer. This requires that you modify the application manifest, and re-sign both the application and the deployment manifests before installation.
Applies to: The information in this topic applies to document-level projects and application-level projects for Microsoft Office 2010 and the 2007 Microsoft Office system. For more information, see Features Available by Office Application and Project Type.
This walkthrough illustrates the following tasks:
Creating an Office solution to deploy.
Implementing a post-deployment action that copies a document to the end user's desktop.
Modifying the application manifest of the Office solution to run the post-deployment action.
Re-signing the application and deployment manifests.
Note |
|---|
|
Your computer might show different names or locations for some of the Visual Studio user interface elements in the following instructions. The Visual Studio edition that you have and the settings that you use determine these elements. For more information, see Visual Studio Settings. |
You need the following components to complete this walkthrough:
-
An edition of Visual Studio 2010 that includes the Microsoft Office developer tools. For more information, see Configuring a Computer to Develop Office Solutions.
-
Excel 2007 or Excel 2010.
-
A test computer.
First, create an Excel workbook project.
To create a new Excel project
-
Create an Excel document-level project. Name the project ExcelWorkbook, and save the project to the %USERPROFILE%\Documents\Visual Studio 2010\Projects directory. For more information, see How to: Create Office Projects in Visual Studio.
Visual Studio opens the new Excel workbook in the designer and adds the ExcelWorkbook project to Solution Explorer.
You must define the post-deployment action in a separate class library. The post-deployment action performs copies the document to the end user computer.
To create a class library for the post-deployment action
-
In the File menu, point to Add, and then click New Project.
-
In the Add New Project dialog box, in the Installed Templates pane, click Windows.
-
In the Templates pane, click Class Library.
-
In the Name field, type FileCopyPDA, and then click OK.
-
In Solution Explorer, click FileCopyPDA.
-
On the Project menu, click Add Reference.
-
In the Add Reference dialog box, in the .NET tab, add the following references:
-
If your Excel project targets the .NET Framework 3.5, add references to Microsoft.VisualStudio.Tools.Applications.Runtime.v10.0 and Microsoft.VisualStudio.Tools.Applications.ServerDocument.v10.0.
-
If your Excel project targets the .NET Framework 4, add references to Microsoft.VisualStudio.Tools.Applications.Runtime and Microsoft.VisualStudio.Tools.Applications.ServerDocument.
-
-
In the Class1 code file, add the following using or Imports statements to the top of the code file.
-
Rename the class to FileCopyPDA, and then add the following code to the FileCopyPDA class. This code indicates that FileCopyPDA class inherits from IAddInPostDeploymentAction.
-
Add the following code to implement the IAddInPostDeploymentAction.Execute method. This code performs the following tasks:
-
Copies the Excel workbook file to the user's desktop if the solution is installed or updated.
-
Changes the _AssemblyLocation property from a relative path to a fully qualified path for the deployment manifest. This is done using the AddCustomization and RemoveCustomization methods.
-
Deletes the file if the solution is uninstalled.
Note
The post-deployment action is run during the uninstall step for .NET Framework 3.5.
public void Execute(AddInPostDeploymentActionArgs args) { string dataDirectory = @"Data\ExcelWorkbook.xlsx"; string file = @"ExcelWorkbook.xlsx"; string sourcePath = args.AddInPath; Uri deploymentManifestUri = args.ManifestLocation; string destPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); string sourceFile = System.IO.Path.Combine(sourcePath, dataDirectory); string destFile = System.IO.Path.Combine(destPath, file); switch (args.InstallationStatus) { case AddInInstallationStatus.InitialInstall: case AddInInstallationStatus.Update: File.Copy(sourceFile, destFile); ServerDocument.RemoveCustomization(destFile); ServerDocument.AddCustomization(destFile, deploymentManifestUri); break; case AddInInstallationStatus.Uninstall: if (File.Exists(destFile)) { File.Delete(destFile); } break; } }
-
Use the Publish Wizard or the Project Page to build and publish the Office solutions to your development computer.
To publish the Excel project
-
In Solution Explorer, right-click the FileCopyPDA project, and then click Build.
-
In Solution Explorer, right-click the ExcelWorkbook project, and then click Build.
-
In Solution Explorer, right-click the ExcelWorkbook project, and then click Add Reference.
-
In the Add Reference dialog box, click the Projects tab.
-
Click FileCopyPDA, and click OK.
-
In Solution Explorer, click the ExcelWorkbook project.
-
On the Project menu, click New Folder.
-
Type Data and press the Enter key.
-
In Solution Explorer, click the Data folder.
-
On the Project menu, click Add Existing Item.
-
In the Add Existing Item dialog box, browse to the output directory for the ExcelWorkbook project.
-
Click ExcelWorkbook.xlsx, and click Add.
-
In Solution Explorer, click ExcelWorkbook.xlsx.
Note
If you modify this file later, make sure to update the file by adding the latest version of the file.
-
In the Properties window, change the Build Action property to Content, and the Copy to Output Directory property to Copy if newer.
-
Publish the ExcelWorkbook project to the c:\publish folder. For more information, see How to: Publish an Office Solution by Using ClickOnce.
Use the XML editor in Visual Studio to modify the application manifest to run the File Copy post-deployment action. The content of an application manifest is similar to a bill of materials, which lists the entire contents of a box; an application manifest lists all dependent and prerequisite assemblies. The application manifest for an Office solution also lists the assemblies that should be loaded by an Office application for application-level add-ins and document-level customizations.
To add the installation dependencies to the application manifest
-
Open the c:\publish directory through Windows Explorer.
-
Open the Application Files folder and then open the ExcelWorkbook_1_0_0_0 folder.
-
Open the ExcelWorkbook.dll.manifest file in a text editor.
-
Add following code after the </vstav3:update> element. For the class attribute of the <vstav3:entryPoint> element, use the following syntax: NamespaceName.ClassName. In this example, the namespace and class names are the same, so that the resulting entry point name is FileCopyPDA.FileCopyPDA.
<vstav3:postActions> <vstav3:postAction> <vstav3:entryPoint class="FileCopyPDA.FileCopyPDA"> <assemblyIdentity name="FileCopyPDA" version="1.0.0.0" language="neutral" processorArchitecture="msil" /> </vstav3:entryPoint> <vstav3:postActionData> </vstav3:postActionData> </vstav3:postAction> </vstav3:postActions>
The following procedure signs the application manifest and updates the deployment manifest. This ensures that tampered files are not installed on end user computers.
To re-sign the application and deployment manifests
-
Copy the ExcelWorkbook_TemporaryKey.pfx certificate file from the %USERPROFILE%\Documents\Visual Studio 2010\Projects\ExcelWorkbook\ExcelWorkbook solution directory into the c:\publish\Application Files\ExcelWorkbook_1_0_0_0 directory.
-
Open the Visual Studio command prompt.
-
Change to the c:\publish\Application Files\ExcelWorkbook_1_0_0_0 directory.
-
Sign the modified application manifest with the following command:
mage -sign ExcelWorkbook.dll.manifest -certfile ExcelWorkbook_TemporaryKey.pfx
The message "ExcelWorkbook.dll.manifest successfully signed" appears.
-
Change to the c:\publish directory.
-
Update and sign the deployment manifest with the following command:
mage -update ExcelWorkbook.vsto -appmanifest "Application Files\Ex celWorkbook_1_0_0_0\ExcelWorkbook.dll.manifest" -certfile "Application Files\ExcelWorkbook_1_0_0_0\ExcelWorkbook_TemporaryKey.pfx"
The message "ExcelWorkbook.vsto successfully signed" appears.
-
Copy the ExcelWorkbook.vsto file to the c:\publish\Application Files\ExcelWorkbook_1_0_0_0 directory.
The following procedure ensures that the updated manifest installs the Excel workbook and copies the workbook to the end user's desktop.
To test the post-deployment action
-
Copy the c:\publish directory to a test computer.
-
Run the Setup.exe program, or if the prerequisites are already installed on the test computer, double-click the ExcelWorkbook.vsto deployment manifest.
The Microsoft Office Customization Installer appears.
-
Click Install.
The Microsoft Office Customization Installer dialog box shows the following message: "The Microsoft Office customization was successfully installed." The Excel workbook is copied to the end user's desktop.
-
Open the ExcelWorkbook.xlsx file from the desktop.
- 7/7/2011
- Norm Estabrook - MSFT
I sort of got this working. I had a to make some tweaks to the Select code in FileCopyPDA:
SelectCase args.InstallationStatus
CaseAddInInstallationStatus.InitialInstall, AddInInstallationStatus.Update
2) System.IO.
File.Copy(sourceFile, destFile, True)
1) IfServerDocument.IsCustomized(destFile) Then
ServerDocument.RemoveCustomization(destFile)
EndIf
ServerDocument.AddCustomization(destFile, deploymentManifestUri)
Exit Select
CaseAddInInstallationStatus.Uninstall
If System.IO.File.Exists(destFile) Then
System.IO.
File.Delete(destFile)
EndIf
Exit Select
EndSelect
My main problem is that I set the update property to 'Check every time the customization runs'. So when you use the template, it checks for updates.
If you have changed the .dotx file, it tries to copy it onto your machine as an update, but obviously it is in use as you have just created a document from it.
Am I missing something obvious?
Many thanks for any help!!
- 3/11/2011
- Kel55
Here is a short(ish) PowerShell script that will modify the manifests and re-sign them as part of a post build event. This is only tested with VS2010. The PowerShell script will check the Assembly for the class that impliments the IAddInPostDeploymentAction interface add it to the manifest and then re-sign it and the vsto with the key file.
Add a "PostBuildEvent.ps1" file to your project and put the below script in it.
# This script is invoked via a post build event and is a script implimentation of http://msdn.microsoft.com/en-us/library/dd465291.aspx
# Startup Environment:
# $MSBuildProjectFullPath = The full path to the project file being built
# $OutputPath = The output path of the built project relative to $MSBuildProjectFullPath
# open project file as xmldocument and read key properties out of it
$ProjectFile = new-object System.Xml.XmlDocument;
$ProjectFile.Load($MSBuildProjectFullPath);
$ProjectFileNamespaceManager = new-object System.Xml.XmlNamespaceManager $ProjectFile.NameTable;
$ProjectFileNamespaceManager.AddNamespace('ns', 'http://schemas.microsoft.com/developer/msbuild/2003');
$AssemblyName = $ProjectFile.SelectSingleNode('/ns:Project/ns:PropertyGroup/ns:AssemblyName', $ProjectFileNamespaceManager).InnerText;
$ApplicationVersion = $ProjectFile.SelectSingleNode('/ns:Project/ns:PropertyGroup/ns:ApplicationVersion', $ProjectFileNamespaceManager).InnerText;
$ManifestKeyFile = $ProjectFile.SelectSingleNode('/ns:Project/ns:PropertyGroup/ns:ManifestKeyFile', $ProjectFileNamespaceManager).InnerText;
# create paths to key directories and files
$ProjectPath = [System.IO.Path]::GetDirectoryName($MSBuildProjectFullPath) + '\';
$BuildDir = $ProjectPath + $OutputPath;
$MainDll = $BuildDir + $AssemblyName + '.dll';
$VSTOFile = $BuildDir + $AssemblyName + '.vsto';
$ManifestFile = $BuildDir + $AssemblyName + '.dll.manifest';
$ManifestKeyFilePath = $ProjectPath + $ManifestKeyFile;
$windowsSdkKey = Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0A\";
$windowsSdkPath = $windowsSdkKey.InstallationFolder;
$mage = $windowsSdkPath + "bin\mage.exe";
$ildasm = $windowsSdkPath + "bin\ildasm.exe";
# open the manifest file and add post action to call IAddInPostDeploymentAction after the app has been deployed on the client machine
$manifestXmlDocument = new-object System.Xml.XmlDocument;
$manifestXmlDocument.Load($ManifestFile);
#find the class in the assembly that impliments the IAddInPostDeploymentAction
$decompiledMainDll = & $ildasm /text /nobar "$MainDll";
$classWithAction = ([regex]'\.class[^{]*IAddInPostDeploymentAction').matches($decompiledMainDll);
if($classWithAction.Count -gt 0)
{
$className = ([regex]'beforefieldinit([^\[]*)extends').matches($classWithAction[0].value)[0].groups[1].value.trim();
$manifestPostActionFragment = $manifestXmlDocument.CreateDocumentFragment();
$manifestPostActionFragment.InnerXml = '<vstav3:postActions xmlns:vstav3="urn:schemas-microsoft-com:vsta.v3"><vstav3:postAction><vstav3:entryPoint class="' + $className + '"><assemblyIdentity name="' + $AssemblyName + '" version="' + $ApplicationVersion + '" language="neutral" processorArchitecture="msil"
xmlns="urn:schemas-microsoft-com:asm.v2" /></vstav3:entryPoint><vstav3:postActionData></vstav3:postActionData></vstav3:postAction></vstav3:postActions>';
$namespaceManager = new-object System.Xml.XmlNamespaceManager $manifestXmlDocument.NameTable;
$namespaceManager.AddNamespace('vstav3', 'urn:schemas-microsoft-com:vsta.v3');
$updateNode = $manifestXmlDocument.SelectSingleNode('//vstav3:update', $namespaceManager);
$updateNode.ParentNode.InsertAfter($manifestPostActionFragment, $updateNode);
}
$manifestXmlDocument.Save($ManifestFile);
# re-sign the manifest file and vsto file because the manifest was changed
& $mage -sign $ManifestFile -certfile $ManifestKeyFilePath
& $mage -update $VSTOFile -appmanifest $ManifestFile -certfile $ManifestKeyFilePath
Lastly add a post build event with the below definition.
powershell.exe "$MSBuildProjectFullPath='$(MSBuildProjectFullPath)'; $OutputPath='$(OutputPath)'; $script = [System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\PostBuildEvent.ps1'); invoke-expression $script;"
- 7/29/2010
- theLuggage