.net, Cake, Continuous Integration, Raspberry Pi 3, UWP

Deploy a UWP application to a Windows 10 device from the command line with Cake

I’ve wanted to improve my continuous integration process for building, testing and deploying UWP applications for a while. For these UWP apps, I’ve been tied to using VS2017 for build and deploy operations – and VS2017 is great, but I’ve felt restricted by the ‘point and click’ nature of these operations in VS2017.

Running automated tests for any .NET project is well documented, but until relatively recently I’ve not had a really good way to use a command line to:

  • build my UWP project and solution,
  • run tests for the solution,
  • build an .appxbundle file if the tests pass, and
  • and deploy the appxbundle to my Windows 10 device.

Trying to find out what’s going on under the hood is the kind of challenge that’s catnip for me, and this is my chance to share what I’ve learned with the community.

This is part of a series of posts about building and deploying different types of C# applications to different operating systems.

If you follow this blog, you’ll probably know I normally deploy apps to my Raspberry Pi 3, but the principles in here could be applied to other Windows 10 devices, such as an Xbox or Windows Phone.

Step 1 – Create the demo UWP and test projects.

I’ll keep the description of this bit quick – I’ll just use the UWP template in Visual Studio 2017 – it’s only a blank white screen but that’s ok for this demonstration.

screenshot.1500076564

I’ve also created an empty unit test project – again the function isn’t important for this demonstration, we just need a project with a runnable unit test.

screenshot.1500076646

I’ve written a simple dummy ‘test’, shown below – this is just created for the purposes of demonstrating how Cake can run a Unit Test project written using MSTest:

using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace UnitTestProject2
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            Assert.IsTrue(true);
        }
    }
}

Step 2: Let’s build our project and run the tests using Cake

Open a powershell prompt (I use the package manager console in VS2017) and navigate to the UWP project folder. Now get the Cake bootstrapper script and example Cake build file using the commands below:

Invoke-WebRequest http://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1

Invoke-WebRequest https://raw.githubusercontent.com/cake-build/example/master/build.cake -OutFile build.cake

I edited the build.cake file to have the text below – this script cleans the binaries, restores the NuGet packages for the projects, builds them and runs the MSTests we created.

#tool nuget:?package=NUnit.ConsoleRunner&version=3.4.0
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
//////////////////////////////////////////////////////////////////////

var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");

//////////////////////////////////////////////////////////////////////
// PREPARATION
//////////////////////////////////////////////////////////////////////

// Define directories.
var buildDir = Directory("./App3/bin") + Directory(configuration);

//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////

Task("Clean")
    .Does(() =>
{
    CleanDirectory(buildDir);
});

Task("Restore-NuGet-Packages")
    .IsDependentOn("Clean")
    .Does(() =>
{
    NuGetRestore("../App3.sln");
});

Task("Build")
    .IsDependentOn("Restore-NuGet-Packages")
    .Does(() =>
{
    if(IsRunningOnWindows())
    {
      // Use MSBuild
      MSBuild("../App3.sln", settings =>
        settings.SetConfiguration(configuration));
    }
    else
    {
      // Use XBuild
      XBuild("../App3.sln", settings =>
        settings.SetConfiguration(configuration));
    }
});

Task("Run-Unit-Tests")
    .IsDependentOn("Build")
    .Does(() =>
{
    MSTest("../**/bin/" + configuration + "/UnitTestProject2.dll");
});

//////////////////////////////////////////////////////////////////////
// TASK TARGETS
//////////////////////////////////////////////////////////////////////

Task("Default")
    .IsDependentOn("Run-Unit-Tests");

//////////////////////////////////////////////////////////////////////
// EXECUTION
//////////////////////////////////////////////////////////////////////

RunTarget(target);

Cake’s built in benchmarking shows the order in which the tasks are executed

Task Duration 
--------------------------------------------------
Clean                  00:00:00.0124995 
Restore-NuGet-Packages 00:00:03.5300892 
Build                  00:00:00.8472346 
Run-Unit-Tests         00:00:01.4200992 
Default                00:00:00.0016743 
--------------------------------------------------
Total:                 00:00:05.8115968

And obviously if any of these steps had failed (for example if a test failed), execution would stop at this point.

Step 3: Building an AppxBundle in Cake

If I want to build an appxbundle for a UWP project from the command line, I’d run the code below:

MSBuild ..\App3\App3.csproj /p:AppxBundle=Always /p:AppxBundlePlatforms="x86|arm" /Verbosity:minimal

There’s four arguments have told MSBuild about:

  • The location of the csproj file that I want to target
  • I want to build the AppxBundle
  • I want to target x86 and ARM platforms (ARM doesn’t work on its own)
  • And that I want to minimise the verbosity of the output logs.

I could use StartProcess to get Cake to run MSBuild in a task, but Cake already has methods for MSBuild (and many of its parameters) baked in. For those parameters which Cake doesn’t know about, it’s very easy to use the WithProperty fluent method to add the argument’s parameter and value. The code below shows how I can implement the command to build the AppxBundle in Cake’s C# syntax.

var applicationProjectFile = @"../App3/App3.csproj";
 
// ...

MSBuild(applicationProjectFile, new MSBuildSettings
    {
        Verbosity = Verbosity.Minimal
    }
    .WithProperty("AppxBundle", "Always")
    .WithProperty("AppxBundlePlatforms", "x86|arm")
);

After this code runs in a task, an AppxBundle is generated in a folder in the project with the path:

AppPackages\App3_1.0.0.0_Debug_Test\App3_1.0.0.0_x86_arm_Debug.appxbundle

The path and file name isn’t massively readable, and is also likely to change, so I wrote a short method to search the project directories and return the path of the first AppxBundle found.

private string FindFirstAppxBundlePath()
{
    var files = System.IO.Directory.GetFiles(@"..\", @"*.appxbundle", SearchOption.AllDirectories);
    
    if (files.Count() > 0)
    {
        return files[0];
    }
    else
    {
        throw new System.Exception("No appxbundle found");
    }
}

Now that I have the path to the AppxBundle, I’m ready to deploy it to my Windows device.

Step 4: Deploying the AppxBundle

Microsoft have provided a command line tool in the Windows 10 SDK for deploying AppxBundles – this tool is called WinAppDeployCmd. The syntax used to deploy an AppxBundle is:

WinAppDeployCmd install -file "\MyApp.appxbundle" -ip 192.168.0.1

It’s very straightforward to use a command line tool with Cake – I’ve blogged about this before and how to use StartProcess to call an executable which Cake’s context is aware about.

But what about command line tools which Cake doesn’t know about? It turns out that it’s easy to register tools within Cake’s context – you just need to know the path to the tool, and the code below shows how to add the UWP app deployment tool to the context:

Setup(context => {
    context.Tools.RegisterFile(@"C:\Program Files (x86)\Windows Kits\10\bin\x86\WinAppDeployCmd.exe");
});

If you don’t have this tool on your development machine or CI box, it might be because you don’t have the Windows 10 SDK installed.

So with this tool in Cake’s context, it’s very simple to create a dedicated task and pull the details of this tool out of context for use with StartProcess, as shown below.

Task("Deploy-Appxbundle")
	.IsDependentOn("Build-Appxbundle")
	.Does(() =>
{
    FilePath deployTool = Context.Tools.Resolve("WinAppDeployCmd.exe");
 
    Information(appxBundlePath);
 
    var processSuccessCode = StartProcess(deployTool, new ProcessSettings {
        Arguments = new ProcessArgumentBuilder()
            .Append(@"install")
            .Append(@"-file")
            .Append(appxBundlePath)
            .Append(@"-ip")
            .Append(raspberryPiIpAddress)
        });
 
    if (processSuccessCode != 0)
    {
        throw new Exception("Deploy-Appxbundle: UWP application was not successfully deployed");
    }
});

And now we can run our Cake script to automatically build and deploy the UWP application – I’ve pasted the benchmarking statistics from Cake below.

Task                     Duration
--------------------------------------------------
Clean                    00:00:00.0821960
Restore-NuGet-Packages   00:00:09.7173174
Build                    00:00:01.5771689
Run-Unit-Tests           00:00:03.2204312
Build-Appxbundle         00:01:09.6506712
Deploy-Appxbundle        00:02:13.8439852
--------------------------------------------------
Total:                   00:03:38.0917699

And to prove it was actually deployed, here’s a screenshot of the list of apps on my Raspberry Pi (from the device portal) before running the script…

screenshot.1500907026

…and here’s one from after – you can see the UWP app was successfully deployed.

screenshot.1500907690

I’ve uploaded my project’s build.cake file into a public gist – you can copy this and chan-ge it to suit your particular project (I haven’t uploaded a full UWP project because sometimes people have issues with the *.pfx file).

Wrapping up

I’ve found it’s possible to build and deploy a UWP app using the command line, and beyond that it’s possible to integrate the build and deployment process into a Cake script. So even though I still create my application in VS2017 – and I’ll probably keep using VS2017 – it means that I have a much more structured and automated integration process.


About me: I regularly post about .NET – if you’re interested, please follow me on Twitter, or have a look at my previous posts here. Thanks!

Continuous Integration, Powershell

How to create and run a Powershell script

Presently my Visual Studio 2015 instance is out of commission because I’m installing Update 2 ahead of the Hololens Dev Kit and Emulator general release tomorrow, so I’ll write a simple post about something that doesn’t need VS2015.

I’ve spoken to a few other C# developers and recommended using Powershell scripts to assist with their continuous deployment. This post is meant to explain one of the simplest things with powershell scripting – how to run a script.

I normally run powershell scripts directly from an instance of Powershell. However, it’s normally necessary to change the computer’s execution policy to be able to run powershell scripts that I’ve created.

As an example, I created a trivial deployment script – the code below copies binaries from a project directory to a deployment directory.

Get-Childitem C:\ProjectDirectory\bin -recurse -filter "*.dll" | %{ Copy-Item -Path $_.FullName -Destination C:\DeploymentDirectory\root}

I saved this to a script called CopyBinaries.ps1.

When I open a powershell window, navigate to the script’s parent directory and run the command “./CopyBinaries.ps1“, I get an error telling me that the file “cannot be loaded because running scripts is disabled on this system“.

This is because the Execution Policy on my machine is set to Restricted, which means I’m not allowed to run any powershell scripts.

To change this, I need to change the execution policy to RemoteSigned. This changes my system to allow me to run powershell scripts that I’ve created locally (which I’m happy to do, because I trust these scripts), and scripts that I’ve downloaded which are digitally signed with the publisher’s identity. There’s more information on execution policies here.

I can change my local policy by running a powershell window as an administrator, and run the command:

Set-ExecutionPolicy RemoteSigned

This asks me to confirm that I intend to do this and understand the consequences – I just type Y to confirm and hit Enter. I can confirm my update to my local execution policy has succeeded by typing:

Get-ExecutionPolicy

Now if I repeat the process of navigating to the script’s parent directory and run the command “./CopyBinaries.ps1“, all binary DLL files are successfully copied from the “C:\ProjectDirectory\bin” to the “C:\DeploymentDirectory\root” directory.

Accessibility, Continuous Integration, Non-functional Requirements

Accessibility and Continuous Integration

There are some great tools out there already to test if your page conforms to accessibility standards – HTML_CodeSniffer is one of the best I’ve seen – but I can’t run this as part of my automated CI process.

There are some tools that allow you to submit a URL, such as the WAVE tool on the WebAIM site, but if you’re developing a site on an intranet, or you’re working with confidential data, this isn’t useful either.

From the W3C WAI Tools list, I discovered AccessLint, which audits your code using javascript from Google’s Accessibility Developer Tools. This posting is a quick recipe for how to run this against a web page from the command line using Windows.

  1. Download PhantomJS, extract to somewhere on your hard drive, and add the binary’s path to your environment variables.
    • Grab Phantom JS from here.
    • I extracted the zip file to C:\PhantomJS_2.0.0 so the actual PhantomJS.exe sits in C:\PhantomJS_2.0.0\bin. I created an environment variable called PHANTOM_JS, and then added “%PHANTOM_JS\bin” to the end of my PATH.
  2. Download the installer for Ruby and run it.
  3. Download the Access Lint code.
    • Grab the Access Lint code from here. Pull using Git, or download the zip – either way works.
    • I have the code in C:\Access_Lint so I can see the access_lint ruby file in C:\Access_Lint\bin.
  4. Install the rubygem.
    • Open a command prompt, browse to where the access_lint ruby file is saved (as above, I have it in C:\Access_Lint\bin), and enter:
gem install access_lint

And we’re ready to go!

Now you can open a command prompt, and enter a command like:

access_lint audit http://w3.org

The audit output will render to the command prompt window as JSON.

You can now check the accessibility of a web page on your integration server as part of a CI process.

Criticisms

The process isn’t perfect.

  • To test each page, you’d have to audit it individually – it would be better if we could crawl the site. (We could work around this by running a batch of audit commands, specifying each page to be audited in a separate line).
  • The JSON output has some odd artefacts – it uses “=>” instead of “:”, and the first and last lines in the file are console logs. (We could work around this by doing some simple post processing on the output).
  • JSON isn’t particularly readable. (We could work around this by using the JSON as a data source, and using another tool to render results in a more readable format)
  • And most significantly, if this tool doesn’t report failures, it doesn’t mean your page is accessible. (No real workarounds beyond manually checking the page.)

But this is the starting point on a journey. Accessibility can sometimes be an afterthought. It should be a natural part of development and process, and part of a team’s definition of done.