HockeyApp and VSTS – #nothinbutnet.1 Creating a publish build task

It’s been a while between posts but the latest focal point for my attention has been HockeyApp. In this multi-part series I will detail my exposure to the framework and identify the solutions to any challenges I faced along the way. As HockeyApp currently doesn’t quite have the coverage across all application types (Android, IOS, Win81, WinRT, UWP, etc) there are some unique obstacles that need to be overcome depending on your application architecture.

In this series of posts we will be addressing the deployment and the collection of telemetry from a WinForms application. If we quickly jump over to the HockeySDK we will rapidly discover that there currently isn’t much support for WinForms as shown in the following feature matrix:


Fortunately Don Kackman is hard at work at implementing the missing components in the HockeySDK as illustrated by the this pull request. We’ll have a look at utilising these changes in upcoming articles.

Let’s start with a little bit about HockeyApp. In 2014, Microsoft acquired the company and positioned the tool as a core part of their solution framework for application distribution, crash reporting, user metrics and feedback collection. Up until that time there had been support for device apps in Application Insights but this year Microsoft announced their plans to transition all mobile and desktop applications to HockeyApp. This was met with some concerns in regards to feature parity between the two platforms and was addressed in this update from Microsoft. As of August, the HockeyApp Bridge was also delivered which allowed integration back to Application Insights and this is when I started to get excited.

Motivated by that excitement I attempted to use HockeyApp with a WinForms application that is a core product delivered by the company I work for. The steps required to get the application from a build on VSTS to a customer deployment (with telemetry collection) are the backbone of this story. I have currently achieved the first four and I’m rolling the dice on being able to deliver the last one by the time I need to write the blog post. The steps are as follows:

  1. Create a VSTS build task to publish an MSI to a HockeyApp application.
  2. Use the HockeyApp REST services to download and install the published MSI via Powershell.
  3. Integrate the new WinForm support if available or update the SDK ourselves to publish custom events (telemetry) to HockeyApp.
  4. Demonstrate the HockeyApp to Application Insights bridge for surfacing telemetry retrieved from all installations of the application.
  5. Finally we will attempt to incorporate the application distribution functionality into the WinForms application itself.

Let’s get into it…

Create a VSTS build task to publish an MSI to a HockeyApp application.

So I’ve got this wizz bang ALM tool and I want to release my application to HockeyApp. First stop, the Visual Studio Marketplace. Boom!! There’s a HockeyApp extension. Buckle up…

Not so fast… As I quickly find out, its really cool if you want to deploy mobile apps but its not the silver bullet in my case:

[blockquote source=””]You can integrate HockeyApp directly in Visual Studio Team Services (VSTS) and in Team Foundation Server (TFS) to upload your Android, iOS, and Mac OS X builds as well as Windows Phone apps. Support file types are .ipa, .apk,, .appx, .appxbundle, .zip (if it contains an .appx or .appxbundle),, and mapping.txt for Android symbols. If you need support for Windows builds that are packaged as a .zip file, please check out this 3rd-party task.[/blockquote]

Alright, not quite the solution I was looking for but my app is a “Windows Build” and I could just ship a zip file as mentioned in the introduction. I then checked out the aformentioned 3rd-party task and was presented with the following project description:

[blockquote source=””]


VSO Build vNext Script for pushing AppX Packages to HockeyApp


Looking at the description it says AppX pretty clearly but if we look at the Powershell script, it basically just puts the location into an archive and sends it up:

[syntax type=”html|php|js|css”]$path = dir $packageDirectory -Directory | Select-Object -first 1
$zipPath = “$($buildSourcesDirectory)/”
Write-Host “Zipping ” + “$($packageDirectory)$($path.Name) into $($zipPath)”

[IO.Compression.ZipFile]::CreateFromDirectory(“$packageDirectory$($path.Name)”, $zipPath)

$zipFile = dir “$($buildSourcesDirectory)/” -File | Select-Object -first 1
Write-Host “Zipped file: $($, Size: $($zipFile.length)”[/syntax]

I decided against using this task and wrote my own for a few reasons:

  1. In a previous post I had created a node.js build task that was pretty close so I thought I may as well leverage that.
  2. I preferred to handle any compression as a build step rather than always created
  3. Having more control over the process was an advantage if I needed flexibility moving forward.

I threw together a new build task using the following code:

var tl = require('vsts-task-lib');

var appID = tl.getInput("appID", true);
var apiToken = tl.getInput("apiToken", true);
var installPackage = tl.getInput("installPackage", false);
var buildNumber = tl.getVariable("Build.BuildNumber");

console.log("Build Number: " + buildNumber);
console.log("Install Package: " + installPackage);

var request = require('request');

var options = {
  url: "" + appID + "/app_versions",
  headers: {
    "X-HockeyAppToken": apiToken

var nextVersion = 1;

request(options, function (error, response, body) {
   if (!error && response.statusCode == 200) {
	  var options = {
		url: "" + appID + "/app_versions/new",
		headers: {
		  "X-HockeyAppToken": apiToken
		form:  { bundle_version: buildNumber }

	  request(options, function (error, response, body) {
		if (!error) {
				console.log("Response: " + body);
				var response = JSON.parse(body);
			var fs = require("fs");
			var glob = require("glob");
			glob(installPackage, function(err,files){
				if (err) throw err;
				var item = files[0];
				var req = request.put(
					{ url: "" + appID + "/app_versions/" +, headers: {"X-HockeyAppToken": apiToken }}, 
						function (error, response, body) {
				console.log(item + " found");
				var form = req.form();
				form.append('ipa', fs.createReadStream(item));
				form.append('status', 2);
				form.append('notify', 1);        

This code performed the following functions:

  1. Created a new version of the application in HockeyApp using the build number as the identifier.
  2. Uploads the first file found that matches the mask specified to HockeyApp against that newly created version.

Refer to my post, From TeamCity to VSTS – My DevOps Journey – Part 2 for more information on creating custom build tasks in this fashion.

Once published the build task can be added to a release and both the API token and App ID for HockeyApp specified.


Specify the file or file mask to use for specifying the file to upload and this will be published to HockeyApp. The output of the hosted build controller should look like so:

[syntax type=”html|php|js|css”]2016-11-10T05:27:53.1809456Z 2 downloads remaining.
2016-11-10T05:27:58.1941619Z 2 downloads remaining.
2016-11-10T05:28:03.9427590Z Download complete.
2016-11-10T05:28:03.9447505Z 191 placed file(s): 191 downloaded, 0 empty
2016-11-10T05:28:03.9447505Z 392 MB downloaded at 5254 KB/sec. Download time: 00:01:16.2506997. Parallel download limit: 4.
2016-11-10T05:28:03.9447505Z Downloaded linked artifact Staging
2016-11-10T05:28:03.9447505Z Finished artifacts download
2016-11-10T05:28:03.9497499Z ##[section]Finishing: Download Artifacts
2016-11-10T05:28:03.9527502Z ##[section]Starting: Publish to HockeyApp
2016-11-10T05:28:04.0017505Z ==============================================================================
2016-11-10T05:28:04.0017505Z Task : HockeyApp Publish
2016-11-10T05:28:04.0017505Z Description : Publish Package to HockeyApp 1.0.14
2016-11-10T05:28:04.0017505Z Version : 1.0.14
2016-11-10T05:28:04.0017505Z Author : Simon Lamb
2016-11-10T05:28:04.0017505Z ==============================================================================
2016-11-10T05:28:05.3446535Z Build Number: 1.0.16315.3
2016-11-10T05:28:05.3446535Z Install Package: C:ar1aDeployment.msi
2016-11-10T05:28:08.2754262Z Response: {“version”:”1.0.16315.3″,”shortversion”:””,”title”:”Deployment.msi”,”timestamp”:1478755688,”appsize”:0,”notes”:””,”mandatory”:false,”external”:false,”device_family”:null,”id”:5,”app_id”:405593,”config_url”:”″,”restricted_to_tags”:false,”status”:1,”tags”:[],”expired_at”:null,”created_at”:”2016-11-10T05:28:08Z”,”updated_at”:”2016-11-10T05:28:08Z”,”sdk_version”:null,”block_crashes”:false,”app_owner”:”Apps”}
2016-11-10T05:28:08.3064270Z C:/a/r1/a/Deployment.msi found
2016-11-10T05:28:19.5976251Z {“version”:”1.0.16315.3″,”shortversion”:””,”title”:”Deployment MSI”,”timestamp”:1478755699,”appsize”:82125161,”notes”:””,”mandatory”:false,”external”:false,”device_family”:null,”id”:5,”app_id”:405593,”public_url”:””,”build_url”:””,”config_url”:”″,”restricted_to_tags”:false,”status”:2,”tags”:[],”expired_at”:null,”created_at”:”2016-11-10T05:28:08Z”,”updated_at”:”2016-11-10T05:28:19Z”,”sdk_version”:null,”block_crashes”:false,”app_owner”:”Apps”}
2016-11-10T05:28:19.6196211Z ##[section]Finishing: Publish to HockeyApp
2016-11-10T05:28:19.6206208Z ##[section]Finishing: Release

The artifacts will now show as published in HockeyApp.


The published version will also have a download page that will allow manual distribution of the release.


With this process in place we have been able to automatically release a version for testing using the QA application on HockeyApp whenever a build is performed on our integration branch. After validation of the QA build the same set of artifacts can be released to the production application on HockeyApp for distribution to our customers.

In further posts in this series I’ll show how we deployed the release we just published and how we can instrument the application to collect metrics from the deployed applications.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s