Building a ClickOnce deployment outside of Visual Studio can be a difficult task. One point I want to make clear is that there is no magic going on to make your application deployable using ClickOnce. Well, unless you are using the tooling inside of Visual Studio, in which case there is a lot of magic happening. Much to its credit, Visual Studio does make it very easy to setup and publish a ClickOnce deployment for your application. That said, my suggestion would be to just use Visual Studio if it fits your workflow. However, if you need a fully automated solution to create a ClickOnce deployment outside of Visual Studio, then continue reading. In my environment, my builds are automated using NAnt, which are then built on a build server using CruseControl.Net.
My end goal with automating the ClickOnce deployment was to mimic the output as created by Visual Studio. I didn't have to do this, but I wanted to just incase I might ever need to resort back to using Visual Studio. I didn't want to get caught in a situation where my automated ClickOnce deployment files conflict with the files generated by Visual Studio.
In general, a ClickOnce deployment requires two files. An application manifest and a deployment manifest. The application manifest contains details of the application. Some of these details include dependencies, security privileges, and a complete listing of every file required by the application.
The deployment manifest contains details of, you guessed it, the deployment. For this file, my focus is primarily on the deployment strategy. It is also worth noting that this file will contain a dependency which is basically a pointer to the application manifest.
I know I'm only scratching the surface of what these two files actually contain. I'm calling out the details which are directly relevant here. I'm trying hard to avoid using the phrase, "beyond the scope of this article", but there it is. As much as I dislike that phrase, I'm using it anyway. Really, if you want to know more about these two files, look at the Microsoft documentation.
As I went through this process, I did find a walkthrough in the Microsoft documentation that you may find helpful. I followed the steps myself, but it didn't take me where I wanted to go and was hard to follow due to lack of detail. When I finished the walkthrough, I had more questions than when I started. But it did help to guide me in the right direction, so I want to point it out.
Steps to automate a ClickOnce application
1. Obtain or create a ".pfx" key file for signing the manifest files. The key file can also be used to sign the assembly/executable of the application if wanted, but not required. You can create a key file using the "Signing" tab of the project properties window. I think there are other kinds of keys that can be used for signing, but I am not an expert in this area so I'm saying as little about it as possible.
2. Add an "app.manifest" file to the project. This will give you a physical file that you can make custom edits to if needed. I personally didn't need to make any custom edits, but at least I have that option if I ever need to. This file will get updated post build using the Mage.exe utility.
3. Build the project/application to get all the files required for the application to run. Also, copy any extra files needed to deploy with the app such as the main app icon. The goal here is to create a folder containing your entire application. All your resource files, data files, help files, referenced dlls, everything. I use NAnt to automate this process.
4. Make any last changes to configuration files or whatever content you need to change for the deployment target. This is important. Do not change any application content after the application manifest has been updated because it will make the application manifest invalid. The application manifest contains hash codes for every file. This is a security measure to prevent any tampering with the files.
5. Use Mage.exe to update the application manifest. Note, if you are using the ".deploy" extension for your files, you'll want to do this step before appending the ".deploy" extension to the files. If this doesn't make sense right now, don't worry about it yet. I'll explain more about this with web hosted deployment. Below is an example of this command.
mage.exe -Update build\ClickOnceExample-Release\ClickOnceExample.exe.manifest
-ToFile "build\ClickOnceExample-Release\Application Files\220.127.116.1125\ClickOnceExample.exe.manifest"
-FromDirectory "build\ClickOnceExample-Release\Application Files\18.104.22.16825"
6. Use Mage.exe to sign the application manifest using your personal ".pfx" key file. Once the file is signed, your done with it. Leave it alone. Below is an example of this command.
mage.exe -Sign "build\ClickOnceExample-Release\Application Files\22.214.171.12425\ClickOnceExample.exe.manifest"
7. Create a deployment manifest file. This is the most troublesome part and I'll do my best to explain. I didn't find any single good solution to generate this file. One thing I can't explain is that once I added the app.manifest to the project, Visual Studio started generating a deployment manifest as well. I guess it's one of those magic tricks that Visual Studio performs. I didn't find a way to make this file not generate. I wouldn't mind, but I couldn't use the file as it was and I don't know of a way to set any properties for the generation of the file to make it useable. But in the end it doesn't really matter because it will get overwritten anyway.
This became a two-step process because not all the desired properties of the deployment manifest can be set using one method or the other. When using Mage.exe, you cannot set the update strategy to "beforeApplicationStartup", which is what I wanted. Someone else did some in-depth research on this issue and discovered a brick wall. I took his word for it, but you can see for yourself here. When using the "GenerateDeploymentManifest" task, you cannot properly set the "EntryPoint" because the final output of the application files will have a different directory structure than the default flat output from building inside of Visual Studio. So here's what to do.
7a. First, setup the "GenerateDeploymentManifest" task in the "AfterBuild" target of the main project file. This will generate a usable deployment manifest file.
1: <?xml version="1.0" encoding="utf-8"?>
2: <Project ToolsVersion="4.0" DefaultTargets="Build"
5: <!-- Other project content... -->
7: <Target Name="AfterBuild">
18: TargetFrameworkMoniker=".NETFramework,Version=v4.5" />
The most important properties to set in the "GenerateDeploymentManifest" task are the following:
7b. Next, use Mage.exe to update the deployment manifest file. This step assumes that the application directory structure has been setup either manually or as part of the build script. One important note to make for updating the deployment manifest with Mage.exe, Do not set the "Install" flag here because it will wipe out the settings set by the "GenerateDeploymentManifest" task and defeat the purpose of having used it in the first place. Below is an example of this command.
mage.exe -Update build\ClickOnceExample-Release\ClickOnceExample.application
-AppManifest "build\ClickOnceExample-Release\Application Files\126.96.36.19925\ClickOnceExample.exe.manifest"
-AppCodeBase "Application Files\188.8.131.5225\ClickOnceExample.exe.manifest"
-Publisher "Robolize Division"
The most important properties to set here are the following:
- Publisher (defaults to "Microsoft" if not set.)
8. Use Mage.exe to sign the deployment manifest using your personal ".pfx" key file. As with the application manifest, once you sign it, you're done with it. Leave it alone. Below is an example of this command.
mage.exe -Sign build\ClickOnceExample-Release\ClickOnceExample.application
9. Optionally copy the deployment manifest file to the location of the application manifest file for safe keeping. This is not required, but it may come in useful if you ever need to revert to an earlier version. You could just replace the current deployment manifest with an old version pointing to the old version of the application.
Web Hosted Deployment
I mentioned earlier that I would explain the ".deploy" extension. The ".deploy" extension is primarily used when the ClickOnce deployment is being hosted within a website. By default, the web server will not allow downloading files with specific extensions like ".exe", ".config", and others. The web server can be configured to allow these file extensions to be downloaded, but for security reasons, you really don't want to do that. It is better to just give every file a ".deploy" extension so there is only that one extension to be concerned with. If you need to use the ".deploy" extension, the setting MapFileExtensions="true" in the "GenerateDeploymentManifest" task should do the trick. I personally am using the file share deployment so I don't have the need to do this. I have not gone through the steps to verify that what I'm saying actually works, but I don’t see any reason why it wouldn't work.
Apparently there is a way to publish a ClickOnce deployment by executing MSBuild on a project file and specifying the "publish" target. This didn't work for me and I assume it's because I execute MSBuild on a solution file, not a project file. I always use solution files because everything I work on is made up of multiple projects. I didn't go far down this path, but you can have a look here and here.
The Mage.exe tool, on my system, is located here, C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\mage.exe
It could be located somewhere else on your system.
Looking at the entire process I describe here, it might be confusing to follow because the interweaving of steps between NAnt and MSBuild. To help with understanding, I created a simple demo solution utilizing the entire process. You can download and inspect my demo example here. This demo I created is actually a good example of using NAnt for build automation. It contains the core aspects which are present in all applications I manage.