Create .NET 4 ClickOnce applications from the command line
March 28, 2012 -ClickOnce packages have always been complicated to build from the command line with if you want to go beyond msbuild /t:Publish
. Given that ClickOnce doesn’t seem as common these days, there’s a dearth of information regarding building .NET 4 applications, so I’ll record some of the issues I encountered in the hope of saving others time.
- Use the correct version of mage.exe: This is located in the Windows SDK (Program Files\MicrosoftSDKs\Windows\7.0A or 7.1) under ‘bin\NETFX 4.0 Tools’. Don't use the mage.exe directly under bin - this is the 3.5 version and you'll end up getting “Assembly is incorrectly specified as a file” errors.
- Select ‘Create application without a manifest’ under the executable project properties Application tab: If you embed a manifest ClickOnce seems to get confused and you’ll get “Reference in the manifest does not match the identity of the downloaded assembly”.
- Untick the ‘Enable ClickOnce security settings’ checkbox in the project properties Security tab: This also causes issues if you are building external to Visual Studio.
- Specify the ‘AppCodeBase’ parameter on -New Deployment: I found if the AppCodeBase is not specified, it defaults the value specified by AppManifest but mangles (truncates) it.
- Add .deploy extension to files after -New Application: you’ll need a .deploy extension on the files to serve them from IIS, but if you add this before creating the application manifest, you'll get “mismatched identity, expected file name: '
.deploy>” warnings in the build. You need to create the manifest, change the filename, then re-sign the manifest.
You'll also need to modify your deployment manifest xml directly - you can’t set all the values via mage.exe (in particular mapFileExtensions). It will need to be re-signed afterwards.
Below is listed a sample psake build script that generates a ClickOnce package. It assumes the correct mage is available on the path, and the certificate doesn’t have a password.
Task Build {
#Build your solution...
$files = "$build_artifacts_dir\Release\MyApp", "$build_artifacts_dir\Release\MyApp.exe.config"
Create-ClickOnce $files -App_Name "MyApp" -Version "1.0.0.0" -Output_Dir "$build_artifacts_dir\ClickOnce" -Cert $certificate -Deployment_Url "http://example.com/MyApp"
}
function Create-ClickOnce {
param($app_name, $version, $output_dir, $cert, $deployment_url)
$files = $args[0]
$version_dir = "$output_dir\$app_name_$($version.Replace(".", "_"))"
mkdir $version_dir
$relative_version_dir = [System.IO.Path]::GetFileName($version_dir)
#Copy files into the output folder and generate the .manifest and .application files.
Copy-Item $files -Destination $version_dir
Exec {mage -New Application -ToFile "$version_dir\$app_name.exe.manifest" -Name $app_name -Version $version -Processor X86 -CertFile "$cert" -FromDirectory $version_dir -TrustLevel FullTrust }
Exec {mage -New Deployment -ToFile "$output_dir\$app_name.application" -Name $app_name -Version $version -Processor X86 -AppManifest "$version_dir\$app_name.exe.manifest" -AppCodeBase "$deployment_url/$relative_version_dir/$app_name.exe.manifest" -CertFile $cert -IncludeProviderURL true -ProviderURL "$deployment_url/$app_name.application" -Install true }
#Append .deploy to files for web server deployment, then re-sign the manifest. No idea why mage can't do this.
Get-ChildItem $version_dir | Foreach-Object { if (-not $_.FullName.EndsWith(".manifest")) { Rename-Item $_.FullName "$($_.FullName).deploy" } }
Exec {mage -Sign "$version_dir\$app_name.exe.manifest" -CertFile $cert }
#Set parameters in deployment xml, then re-sign the deployment. Why can't we do this from the command line, Microsoft?
$xml = [xml](Get-Content "$build_artifacts_dir\ClickOnce\NaplanDB.application")
$deployment_node = $xml.SelectSingleNode("//*[local-name() = 'deployment']")
$deployment_node.SetAttribute("minimumRequiredVersion", $version)
$deployment_node.SetAttribute("mapFileExtensions", "true")
$xml.Save("$output_dir\$app_name.application")
Exec {mage -Sign "$output_dir\$app_name.application" -CertFile $cert }
}