Archive

Archive for the ‘ClickOnce’ Category

Continuously Deploy Your ClickOnce Application From Your Build Server

January 10th, 2017 No comments

ClickOnce applications are a great and easy way to distribute your applications to many users, and have the advantage of offering automatic application updates out-of-the-box. Even though ClickOnce applications are super easy to deploy from Visual Studio (literally 3 clicks, just click Build –> Publish –> Finish), you may still want to have your build system publish the updates for various reasons, such as:

  1. You don’t want you (or your team members) to have to remember to manually publish a new version all the time; even if it is very quick and easy.
  2. If you are signing your ClickOnce application (to make your app more secure and avoid annoying Windows security warnings during every update), your team members may not have the certificate installed, or you may not want them to know the certificate password.
  3. All of the other benefits of deploying automatically from a build server; you can be sure the project was built in Release mode, all unit tests have run and passed, you are only publishing a new version of the application from a specific branch (e.g. master), etc.

In this post I’m going to show how to continuously deploy your ClickOnce application using Visual Studio Team Services (VSTS), but you can adopt what’s shown here to work on any build system, such as Team City or Jenkins.

 

Step 1 – Configure and publish your ClickOnce application manually

Before we can publish a new version of the application from the build server, we first have to build the project to create the artifacts to publish. And even before that, we have to make sure the project is setup properly with all of the required ClickOnce metadata. You will typically do this by going into your project Properties page and going to the Publish tab. There are several other websites and blog posts that discuss configuring a project for ClickOnce deployment, including the official MSDN documentation for configuring and publishing ClickOnce applications, so I won’t go into it any further here.

Basically you should have your project in a state where you can easily publish a new version manually, and it is configured the way that you want (includes the necessary files, has the destination to publish to specified, specifies if the application is only available online or not, etc.). Once you’ve published your project manually and confirmed that it’s configured the way you like, we can move onto the next step.

 

Step 2 – Setup the build on your build server

On your build server you will want to configure a new vanilla build that builds your project/solution. There are a couple modifications you will need to make that are different from building a regular console app or class library project.

The first difference is that you will need to provide the “/target:Publish” argument to MSBuild when it builds your project. Here is what this looks like in VSTS:

This will cause MSBuild to build the required artifacts into an “app.publish” directory. e.g. bin\Debug\app.publish.

The next difference is that you will want to copy that “app.publish” directory to your build artifacts directory. To do this, you will need to add a Copy Files step into your build process that copies the “app.publish” directory from the ClickOnce project’s bin directory to where the build artifacts are expected to be. You will want to do this before the step that publishes your build artifacts. Here is what this looks like in VSTS:

So we copy the files into the build artifacts directory, and then the Publish Build Artifacts step at the end will copy those files to wherever you’ve specified; in my case it’s a network share.

If you like you can now run the build and see if it succeeds. If the build fails with an error relating to an expired certificate or pfx file, see my other blog post on importing the required certificate on the build server at build-time, which involves adding one more “Import-PfxCertificate.ps1” build step before the MSBuild step.

For completeness sake, this is what my Publish Build Artifacts step looks like in VSTS, and you’ll also notice the “Import-PfxCertificate.ps1” step before the MSBuild step as well:

So we now have the ClickOnce artifacts being generated and stored in the appropriate directory. If you wanted, you could publish the build artifacts to the ClickOnce application’s final destination right now (instead of a file share as I’ve done here), but I’m going to follow best practices and separate the application “build” and “deployment” portions into their respective subsystems, as you may want separate control over when a build gets published, or maybe you don’t want to publish EVERY build; only some of them.

Hopefully at this point you are able to create a successful build, but we’re not done yet.

 

Step 3 – Publish the build artifacts to the ClickOnce application’s destination

Now that we have the build artifacts safely stored, we can publish them to the ClickOnce application’s destination. With VSTS this is done by using the Release subsystem. So create a new Release Definition and setup an Environment for it. By default it adds a “Copy and Publish Build Artifacts” step to the release definition. When configuring and using this default step, I received the error:

[error]Copy and Publish Build Artifacts task is not supported within Release

So instead I removed that default step and added a simple “Copy Files” step, and then configured it to grab the files from the file share that the build published the artifacts to, and set the destination to where the ClickOnce application was publishing to when I would do a manual publish from within Visual Studio. Here is what that step looks like in VSTS:

You should be able to run this release definition and see that it is able to post a new version of your ClickOnce application. Hooray! I setup my releases to automatically publish on every build, but you can configure yours however you like.

If you make changes and commit them, the build should create new artifacts, and the release should be able to publish the new version. However, if you launch your ClickOnce application, you may be surprised that it doesn’t actually update to the latest version that you just committed and published. This is because we need one additional build step to actually update the ClickOnce version so it can detect that a new version was published. If you launch another build and publish them, you’ll see that the files being published are overwriting the previous files, instead of getting published to a new directory as they are supposed to be.

You may be wondering why we need another build step to update the ClickOnce version. Part of the build artifacts that are created are a .application manifest file, and a directory that contains the applications files, both of which have the ClickOnce version in them. Can’t we just modify the directory name and .application manifest file to increment the old version number? This was my initial thought, but the .application manifest file contains a cryptography hash to ensure that nobody has tampered with it, meaning that it needs to contain the proper version number at the time that it is generated.

 

Step 4 – One more build step to update the ClickOnce version

The ClickOnce version is defined in your project file (.csproj/.vbproj) (as the <ApplicationVersion> and <ApplicationRevision> xml elements), and is different from the assembly version that you would typically set to version your .dll/.exe files via the AssemblyInfo.cs file. Unless you manually updated the ClickOnce version in Visual Studio, it will still have its original version. When publishing the ClickOnce version in Visual Studio, Visual Studio automatically takes care of incrementing ClickOnce version’s Revision part. However, because we’re publishing from our build system now, we’ll need to come up with an alternative method. To help solve this problem, I have created the Set-ProjectFilesClickOnceVersion GitHub repository. This repository houses a single Set-ProjectFilesClickOnceVersion.ps1 PowerShell script that we will want to download and add into our build process.

In order for the build system to access this Set-ProjectFilesClickOnceVersion.ps1 script you will need to check it into source control. Also, you need to make sure you add this build step before the MSBuild step. Here is what this step looks like in VSTS:

The Script Filename points to the location of the Set-ProjectFilesClickOnceVersion.ps1 script in my repository. Also note that I set the Working Folder to the directory that contains the .csproj project file. This is necessary since we are dealing with relative paths, not absolute ones.

For the Arguments I provide the name of the project file that corresponds to my ClickOnce application, as well as the VSTS Build ID. The Build ID is a globally unique ID that gets auto-incremented with every build, and most (all?) build systems have this. The script will translate this Build ID into the Build and Revision version number parts to use for the ClickOnce version, to ensure that they are always increasing in value. So you should never need to manually modify the ClickOnce version, and every build should generate a version number greater than the last, allowing the ClickOnce application to properly detect when a newer version has been published.

Maybe you update your application’s version number on every build, and want your ClickOnce version to match that version. If this is the case, then use the –Version parameter instead of –BuildSystemsBuildId. The other alternative is to use the –IncrementProjectFilesRevision switch to simply increment the ClickOnce Revision that is already stored in the project file. If you use this switch, you need to be sure to check the project file back into source control so that the Revision is properly incremented again on subsequent builds. I like to avoid my build system checking files into source control wherever possible, so I have opted for the BuildSystemsBuildId parameter here.

The last argument I have included is the UpdateMinimumRequiredVersionToCurrentVersion parameter, and it is optional. If provided, this parameter will change the ClickOnce application’s Minimum Required Version to be the same as the new version, forcing the application to always update to the latest version when it detects that an update is available. I typically use this setting for all of my ClickOnce applications. If you still plan on publishing your ClickOnce applications from Visual Studio, but want this functionality, simply install the AutoUpdateProjectsMinimumRequiredClickOnceVersion NuGet Package.

That’s it! Now if you launch another build it should create new artifacts with a larger ClickOnce version number, and once published your application should update to the new version.

 

Bonus – Displaying the ClickOnce version in your application

The ClickOnce application version number may be different than your assembly version number, or perhaps you don’t update your product’s version number on every publish; it’s easy to forget. I typically like to use semantic versioning for my projects, which only involves the first 3 version parts. This leaves the last version part, the Revision number, available for me to set as I please. I will typically use the ClickOnce Revision in my application’s displayed version number. This makes it easy to tell which actual release a client has installed in the event that the product version was not updated between releases. The code to do it is fairly simple.

public Version ApplicationVersion = new Version(&quot;1.11.2&quot;);

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
	// If this is a ClickOnce deployment, append the ClickOnce Revision to the version number (as it gets updated with each publish).
	if (ApplicationDeployment.IsNetworkDeployed)
		ApplicationVersion = new Version(ApplicationVersion.Major, ApplicationVersion.Minor, ApplicationVersion.Build, ApplicationDeployment.CurrentDeployment.CurrentVersion.Revision);

	// Display the Version as part of the window title.
	wndMainWindow.Title += ApplicationVersion;
}

Here I hard-code the product version that I want displayed in my application in a variable called ApplicationVersion. When the WPF application is launched, I obtain the ClickOnce Revision and append it onto the end of the version number. I then display the version in my application’s window title, but you might want to show it somewhere else, such as in an About window. If you want, you could even display both the full application version and full ClickOnce version.

 

I hope this blog has helped you further your automation-foo. Happy coding!

Creating A .PFX Certificate And Applying It On The Build Server At Build Time

December 1st, 2016 No comments

There are many project types that require a .pfx file in order to build and/or publish successfully, such as ClickOnce and UWP (Universal Windows Platform) applications. When you build these projects locally in Visual Studio everything is fine and dandy, but when you try to build them on a build server (such as part of a Continuous Integration build) the build server may fail with an error like:

Error APPX0108: The certificate specified has expired. For more information about renewing certificates, see http://go.microsoft.com/fwlink/?LinkID=241478.

error MSB4018: The “ResolveKeySource” task failed unexpectedly.
System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.

Cannot import the following key file: companyname.pfx. The key file may be password protected.

These errors will often occur when you are still using the temporary file that was generated automatically when the project was created in Visual Studio, or when using a .pfx file that is password protected.

If your .pfx file is password protected, we can import the certificate at build time, as shown further below. The nice thing about this approach is it does not require you (or one of your company admins) to manually install the certificate on every build server, as many other sites suggest. This way you don’t have to bother your admin or get unexpected broken builds when a new build server is added to the pool.

If your .pfx file has expired, you need to remove the current pfx file and add a new (password-protected) one in its place.

 

Creating a new .pfx file for a UWP application

To create a new password protected .pfx file in a UWP application,

1. open the Package.appxmanifest file that resides in the root of the project and

2. go to the Packaging tab.

3. In here, click the Choose Certificate… button.

4. In the Configure Certificate… dropdown, choose an existing certificate that you have, or create a new test certificate and provide a password for it. This should create the certificate and add the new .pfx file to your project’s root.

Create Pfx Certificate In UWP App

 

Creating a new .pfx file for a ClickOnce application

Creating a pfx file for a ClickOnce application is similar, but instead you want to

1. open up the project’s Properties window (by right-clicking on the project) and

2. go to the Signing tab.

3. Check off the box to Sign the ClickOnce manifests and then

4. choose the certificate you want to use, or create your own password-protected test certificate.

Create Pfx Certificate In ClickOnce App

 

Applying the .pfx file before building on the build server

Now that the project has a pfx certificate, we will need to update our build server to apply it before attempting to build the solution, otherwise a certificate related error will likely be thrown.

Before building the solution, we will want to apply the certificate using this PowerShell script, Import-PfxCertificate.ps1:

param($PfxFilePath, $Password)

$absolutePfxFilePath = Resolve-Path -Path $PfxFilePath
Write-Output &quot;Importing store certificate &#39;$absolutePfxFilePath&#39;...&quot;

Add-Type -AssemblyName System.Security
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($absolutePfxFilePath, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist &quot;MY&quot;, CurrentUser
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::&quot;ReadWrite&quot;)
$store.Add($cert)
$store.Close()

You can download the Import-PfxCertificate Powershell script from here.

To do this you will need to save this Import-PfxCertificate.ps1 file in your version control repository so that the build system has access to it. In my projects I have a root buildTools directory that I store these types of build scripts in. In your build system, you will need to configure a new step to run the PowerShell script before the step to actually build your solution. When you call the script you will need to pass it the path to the .pfx file (which should also be checked into source control), and the password for the .pfx file. If your build system supports using “hidden”/”private” variables, then it is recommended you use them to keep the .pfx file password a secret.

Here I am using VSTS (Visual Studio Team Services), but the same steps should generally apply to any build system (e.g. TeamCity). I have created a new build step called Apply Store Certificate that calls the Import-PfxCertificate.ps1 PowerShell script. This step is set to occur before the Build solution step, and provides the required parameters to the script; the path to the .pfx file and the certificates password (as a hidden variable that I configured in the build system). Notice that I also set the Working folder that the script will run from to the directory that the .pfx resides in, so that the script will be able to resolve the absolute file path.

VSTS Build Pfx Certificate Step

Your build system should now be able to apply the certificate before building the solution, avoiding the certificate errors and hopefully resulting in a successful build.

Hopefully this post has helped you out. Happy coding!

Force your ClickOnce app to update without prompting user – Now on NuGet!

March 8th, 2013 4 comments

A while ago I blogged about a powershell script I made that would automatically update your ClickOnce project file’s Minimum Required Version to the latest version of your application so that users would not be presented with the “There is an update for this application. Do you want to install it?” prompt; instead the application would just automatically download and install the update.  This is nice because it’s one less prompt the end-user has to see, and from a security standpoint it means that your users will be forced to always use the latest version of the app.

There was a bit of setup to get this to work.  You had to make sure the powershell script was in the proper directory and add a post-build event to your project.  I’m happy to announce that I’ve significantly updated the powershell script with more features and bug fixes, and you can now install it from Nuget, which will handle all of the setup for you.  So now making sure that your end-users are always using the latest version of your application is as easy as adding the AutoUpdateProjectsMinimumRequiredClickOnceVersion NuGet package to your ClickOnce project.

ManageNuget

Install

FileAdded

As you can see in the last screenshot, it adds a new “PostBuildScripts” folder to your project that contains the powershell script that is ran from the project’s post-build event.

A couple caveats to note.  Because this uses PowerShell it means that it can only be ran on Windows machines (sorry Mac and Unix).  Also, because the powershell script modifies the .csproj/.vbproj file outside of Visual Studio, the first time you do a build after creating a new ClickOnce version, if you have any files from that project open you will be prompted to reload the project.  In order to prevent this from closing your open tabs, I recommend installing Scott Hanselman’s Workspace Reloader Visual Studio extension.

I’ve also created a CodePlex site to host this open source project.

I hope you find this helpful.  Feel free to leave a comment.

Force ClickOnce applications to automatically update without prompting user – Automatically update MinimumRequiredVersion using PowerShell

August 15th, 2012 1 comment

Today I was thinking about using a ClickOnce application in my build process.  The problem is, when using an installed ClickOnce application (as opposed to an online one) if an update to the ClickOnce application is published, the application prompts the user to Accept or Skip downloading and applying the new update.  This would cause a problem for my automated builds as it would end up waiting forever for a user to click Accept.  This post lead me to the answer, which is:

“If your application is an installed application, you can force updates by using the MinimumRequiredVersion attribute. If you publish your application using Visual Studio, you can set this property from the Updates Dialog.”

Just for clarification, the dialog he mentions can be found in Visual Studio in Project Properties->Publish->Updates…  Ok, great.  This will allow the prompt to be suppressed, which is also useful if you don’t want to allow users to skip updates.

There is still a problem however.  Every time I publish a new version of the tool I have to remember to go in and update the MinimumRequiredVersion.  If I forget to do this and then publish another release, the prompt will be back and will ruin my automated builds.

To get around this I created a PowerShell script that keeps the MinimumRequiredVersion up to date, and I call it from a Post-Build event.  This allows me to never have to worry about manually setting the Minimum Required Version, since it gets updated automatically after every successful build.

<EDIT>

I’ve improved upon the powershell script below and created a NuGet package that handles all of the setup/installation for you, as described in my newer post.

</EDIT>

Here is the powershell script:

# Script finds the current ClickOnce version in a project's .csproj file, and updates the MinimumRequiredVersion to be this same version.
# This can be used to force a ClickOnce application to update automatically without prompting the user.

[Parameter(Position=0, HelpMessage="Comma separated paths of the .csproj files to process")]
Param([string]$projectFilePaths)

# If a path to a project file was not provided, grab all of the project files in the same directory as this script.
if (-not($projectFilePaths))
{
# Get the directory that this script is in.
$scriptDirectory = Split-Path $MyInvocation.MyCommand.Path -Parent

# Create comma-separated list of project file paths.
Get-Item "$scriptDirectory\*.csproj" | foreach { $projectFilePaths += "$_,"}
$projectFilePaths = $projectFilePaths.TrimEnd(',')
}

# Catch any unhandled exceptions, write its error message, and exit the process with a non-zero error code to indicate failure.
trap
{
[string]$errorMessage = [string]$_
[int]$exitCode = 1

# If this is one of our custom exceptions, strip the error code off of the front.
if ([string]$errorMessage.SubString(0, 1) -match "\d")
{
$exitCode = [string]$errorMessage.SubString(0, 1)
$errorMessage = [string]$errorMessage.SubString(1)
}

Write-Error $errorMessage
EXIT [int]$exitCode
}

Function UpdateProjectsMinimumRequiredClickOnceVersion
{
Param
(
[Parameter(Mandatory=$true, Position=0, HelpMessage="The project file (.csproj) to update.")]
[string]$projectFilePath
)
if (-not([System.IO.File]::Exists($projectFilePath))) { throw "2Cannot find project file to update at the path: '$projectFilePath'" }

# Build the regular expressions to find the information we will need.
$rxMinimumRequiredVersionTag = New-Object System.Text.RegularExpressions.Regex "\<MinimumRequiredVersion\>(?<Version>.*?)\</MinimumRequiredVersion\>", SingleLine
$rxApplicationVersionTag = New-Object System.Text.RegularExpressions.Regex "\<ApplicationVersion\>(?<Version>\d+\.\d+\.\d+\.).*?\</ApplicationVersion\>", SingleLine
$rxApplicationRevisionTag = New-Object System.Text.RegularExpressions.Regex "\<ApplicationRevision\>(?<Revision>[0-9]+)\</ApplicationRevision\>", SingleLine
$rxVersionNumber = [regex] "\d+\.\d+\.\d+\.\d+"

# Read the file contents in.
$text = [System.IO.File]::ReadAllText($projectFilePath)

# Get the current Minimum Required Version, and the Version that it should be.
$oldMinimumRequiredVersion = $rxMinimumRequiredVersionTag.Match($text).Groups["Version"].Value
$majorMinorBuild = $rxApplicationVersionTag.Match($text).Groups["Version"].Value
$revision = $rxApplicationRevisionTag.Match($text).Groups["Revision"].Value
$newMinimumRequiredVersion = [string]$majorMinorBuild + $revision

# If there was a problem constructing the new version number, throw an error.
if (-not $rxVersionNumber.Match($newMinimumRequiredVersion).Success)
{
throw "3'$projectFilePath' does not appear to have any ClickOnce deployment settings in it."
}

# If we couldn't find the old Minimum Required Version, throw an error.
if (-not $rxVersionNumber.Match($oldMinimumRequiredVersion).Success)
{
throw "4'$projectFilePath' is not currently set to enforce a MinimumRequiredVersion. To fix this in Visual Studio go to Project Properties->Publish->Updates... and check off 'Specify a minimum required version for this application'."
}

# Only write to the file if it is not already up to date.
if ($newMinimumRequiredVersion -eq $oldMinimumRequiredVersion)
{
Write "The Minimum Required Version of '$projectFilePath' is already up-to-date on version '$newMinimumRequiredVersion'."
}
else
{
# Update the file contents and write them back to the file.
$text = $rxMinimumRequiredVersionTag.Replace($text, "<MinimumRequiredVersion>" + $newMinimumRequiredVersion + "</MinimumRequiredVersion>")
[System.IO.File]::WriteAllText($projectFilePath, $text)
Write "Updated Minimum Required Version of '$projectFilePath' from '$oldMinimumRequiredVersion' to '$newMinimumRequiredVersion'"
}
}

# Process each of the project files in the comma-separated list.
$projectFilePaths.Split(",") | foreach { UpdateProjectsMinimumRequiredClickOnceVersion $_ }

The script was actually very small at first, but after commenting it and adding some proper error handling it is fairly large now.

So copy-paste the powershell script text into a new file, such as “UpdateClickOnceVersion.ps1”, and add this file to your project somewhere.

The next step now is to call this script from the Post Build event, so in Visual Studio go into your ClickOnce project’s properties and go to the Build Events tab.  In the Post-build event command line put the following:


REM Update the ClickOnce MinimumRequiredVersion so that it auto-updates without prompting
PowerShell set-executionpolicy remotesigned
PowerShell "$(ProjectDir)UpdateClickOnceVersion.ps1" "$(ProjectPath)"

The first line is just a comment.  The second line may be a security concern, so you might want to remove it.  Basically by default PowerShell is not allowed to run any scripts, so trying to run our script above would result in an error.  To fix this we change the execution policy to allow remotesigned scripts to be ran.  I’m not going to pretend to understand why powershell requires this (as I just started learning it today), but that line only needs to be ran on a PC once, so if you want to remove it from here and just run it from PowerShell manually instead (if you haven’t already ran it before), feel free to do so.  I just include it here so that other developers who build this tool in the future don’t have to worry about this setting.

The third line is where we are actually calling the powershell script, passing the path to the .csproj file to update as a parameter.  I added the powershell script to my project at the root level (so it sits right beside the .csproj file), but you can put the powershell script wherever you like.  Also, you don’t even have to include it in the project if you don’t want to, but I chose to so that it is easily visible for other developers when in Visual Studio, and so that it implicitly gets added to source control.  If you want to put the script in a folder instead of in the root of the project directory feel free; just remember to properly update the path in the post-build events.

So after going through and adding all of my nice error messages to the powershell script, I realized that if there is a problem with the script Visual Studio does not forward the error message to the Output window like I hoped it would; it just spits out the text in the Post-build event window and says an error occurred; which doesn’t really tell us anything.  So if you find you are getting errors, copy-paste that third line into PowerShell, replace the macro variables for their absolute values, and run it there.  Powershell should then give you a much more informative error message.

One last comment about this process is that because the powershell script modifies the .csproj outside of visual studio, after you publish a new version and build, the script will write to that .csproj file and visual studio will give you a prompt that the project was modified outside of visual studio and will want to reload it.  You can choose to reload it (which will close all file tabs for that project), or choose to ignore it; it’s up to you.  This is the one minor annoyance I haven’t been able to find a way around, but it’s still better than having to remember to update the Minimum Required Version manually after every new version of the tool I publish.

I hope you find this post useful, and I appreciate any comments; good or bad.  Happy coding!