Automating WPF Deployment - Take 1

Automating deployment tasks is one of the pivotal operations in DevOps, lifting the repetitive tasks will allow the developers to focus on the thing they are good at, which is writing code, and will yield a deployment mechanism that is repetitive and error-prone.

And the area we want to automate today is the deployment of a WPF application, specifically the creation and publishing of a ClickOnce package, in this post I will demo one way of accomplishing this task, and in a future one, another method will be used.

A small note first, this method here is adapted from a build a deploy definition I implemented on my previous work, while it accomplishes the task, it doesn’t check all the boxes but it serves as a good starting point, also watch for the second iteration of this series, where I demo a better and more complete solution

Demo application

Our demo application will be a small WPF application taken from a Microsoft Azure sample on Github, you can find a fork here, which have the small modifications needed for the deployment. The repository houses two examples, our target app is housed in the folder called “1. Desktop app calls Web API” ,the app itself is a WPF Todo app, interfacing with a ASP.NET core API to store todo lists in memory, and using AAD to authenticate and get permission, in our scenario we have two environments we want to deploy the app to, Staging and Production, with each one having a different endpoint (The API will be deployed to an app service instance).

if we enumerate our deployment steps algorithm

  1. update the app.config file and inject environment specific configurations
  2. generate the ClickOnce package
  3. copy the generated package into the publish destination (which in this case is a container on Azure storage account)

for the first step we will use the SlowCheetah tool which allows us to transform the app.config file on build, first you define a transformation file for each build configuration you have on the solution, and when building the project while enabling a build configuration, the corresponding build configuration will be picked and the tool will transform the app.config based on the defined transformation. To satisfy our two environments requirements we will add two new build configurations (release-staging, release-production)

Added Configurations

for more info on the transformation syntax follow this link and for more info on the tool give a visit to the repository at Github

For the second step, we as WPF developers are used to generate the ClickOnce package using the publish tab on visual studio, but behind the scene visual studio instrument msbuild to generate the package, msbuild has a build target called publish, when building a WPF project and specifying this target, msbuild will first compile the project and its dependencies (this target depends on the default build target), and will generate the ClickOnce package and place it in an app.publish folder under the bin directory, which what we will use, the command we want to run is


msbuild TodoListClient.csproj /t:publish /p:PublishUrl="$(StorageUrl)" /p:InstallUrl="$(StorageUrl)" /p:Install=true /p:ApplicationVersion=$(Build.BuildNumber) /p:MinimumRequiredVersion=$(Build.BuildNumber) /p:PublisherName="Mohammed Kamil" /p:ProductName="AAD Sample" /p:BootstrapperEnabled=true  /p:IsWebBootstrapper=true /p:SolutionDir="1. Desktop app calls Web API/Desktop-App-calls-Web-API.sln"

AS you can see,we are making use of variables either predefined on azure build pipline -Build.BuildNumber for example -, or our own custom variables, like $(StorageUrl) which points to the blob storage that will serve the ClickOnce installer.

Added Configurations

finally, we copy the ClickOnce bits to the storage account, DevOps pipeline has a ready-made azure copy step we can use for the purpose, the full configuration of the step is as follows

image here

the container name option in the azure copy step could refer to an already created container, otherwise it will be created on the copy, you need to make sure to set the access level to blob (anonymous read access for blobs only), to allow both the user and the ClickOnce installer from accessing and downloading the app bits.

Blob Access Level

what we have now is a build pipeline to create a ClickOnce package configured for the staging environment, the story for the production is similar, with a similar pipeline the difference is only on the pipeline variables, which will bring in the required behavior needed in the production version.

Variable Staging Production
BuildConfiguration Release-Staging Release-Production
BuildPlatform AnyCPU AnyCPU
Container staging production