How To Push Updates to Raspberry Pi UWP Apps In Prod
Updating Raspberry Pi apps in the field can be tricky. This post covers the general problem and address some specific side-loading problems you are likely to run into.
The (Re)Deployment Problem
Imagine you have an IoT app like a kiosk, digital sign, or temperature reader that you want to productize and ship onto small (inexpensive) devices like Raspberry Pi's. If you're already in the Microsoft ecosystem or you want features like BitLocker, Automatic Updates, or Enterprise Data Protection (the ability to remote wipe a lost device), then the Windows IoT operating system is an obvious choice (plus the Windows IoT Core edition is free).
But once you've developed your app and are ready to ship, how do you quickly and consistently get your app (or more likely a set of foreground and background apps) onto devices? More importantly, when you have a bug fix or feature enhancement how do you push it out to devices in the field?
The Initial Image
In most regards, the Microsoft documentation on building Windows IoT images is excellent. After installing some prerequisite software, you use a set of provided command line tools to create numerous xml files that all together describe an image. You then compile those xml files into a multi-gigabyte .ffu image file that you can write to an SD Card using the Windows IoT Core Dashboard software (or better yet, the flashsd command line tool).
Now you can plug your newly flashed SD cards into a bunch of Raspberry Pi's and send them all over the world. However, unless you're Chuck Norris, you're going to need to ship bug fixes to those Raspberry Pi's, and visiting each one is out of the question. There are two primary options: store-based, and side loading.
Microsoft allows developers to publish Windows IoT apps to the Microsoft Store, at which point it will push app updates down onto device and install them automatically. This option is extremely secure and reliable. However, there are several downsides.
- There's a special sign up process that can be time consuming. I imagine it's gotten better, but it took me several weeks to get approved.
- Background apps require extra work to publish because each one requires a special empty Foreground UWP app that developers must submit to the store (see Special Instructions for Headless Apps).
- It's tricky to get multiple dependent apps to publish simultaneously as a package.
- Updates require an approval process and on top of that can take up to 24 hours to get pushed to devices.
Side Loading Updates
If you need complete control over the deployment process, for instance to ensure that multiple dependent packages upgrade in a certain order, then there is an additional option. You can manage your own app deployments using the PackageManager API along with a special .appxmanifest permission that allows side-loading app updates. Naturally, you'll need a server for hosting your app update files and informing your clients that an update is available. You then create a background UWP app that monitors your server and downloads and installs packages when it detects changes.
While this option has the benefit of more control, keep in mind the following disadvantages:
- Hand rolling the side loading code opens the attack surface for your app.
- You'll have to maintain a dedicated server and ssl certificate
- It may be more brittle as the entire process could break if your URL changes, SSL certificate expires, or something breaks in your upgrader background app
Matthijs Hoekstra beautifully documents one way to do this in his blog post Auto updater for my side loaded UWP Apps. His approach involves sending notifications to the updater background app using the UWP App-To-App Communication approach I described in a Visual Studio Magazine article I published last year.
If you prefer having a single app both monitor changes and install updates, thus isolating all deployment responsibilities into a single app, you may want to take a look at the SirenOfShame.Uwp.Maintenance project of the Siren of Shame UWP App. If you check out that code, you'll see I download the app to a temporary directory (perhaps a new UWP requirement), provide a lot of logging, and use certificate pinning for better security. I designed that project to be generic enough that you could copy-paste it into a new project and only make a few changes.
Regardless, if you combine side-loading and image creation you are likely to run into problems. Here are the problems and solutions I've found.
Package failed updates
ErrorCode = -2147009293 System.Runtime.InteropServices.COMException (0x80073CF3): Package failed updates, dependency or conflict validation.
You'll get this error when you include an appx in your image instead of an appxbundle. Including an appx is what they tell you to do in Step 4 of Lab 1b when they say "Generate app bundle: Never". The problem is you can only side-load an appxbundle if you originally installed an appxbundle. To get around this ignore the directions and set "Generate app bundle" to "Always" when you generate your app to include in your image. When you run the newAppxPkg command you can just reference your appxbundle instead of your appx and everything works exactly the same from there.
ErrorCode = -2147009286 System.Runtime.InteropServices.COMException (0x80073CFA): Removal failed. Please contact your software vendor.
You'll get this error if you try to uninstall a package via PackageManager.RemovePackageAsync() that had been installed into an image as an appx. Instead, always include an appxbundle instead of an appx when you build images (see above).
ErrorCode = -2147009287 System.Runtime.InteropServices.COMException (0x80073CF9): Install failed. Please contact your software vendor.
You'll get this error if you try to sideload via .UpdatePackageAsync() an app that you initially included in an image but then you subsequently F5 deployed over top of from Visual Studio. The solution here is to put a try/catch around your call to .UpdatePackageAsync() and if there's a COMException try to do an uninstall and then an install.
I've spent numerous hours getting this process right and I hope this document saves someone some time. If so please share your IoT deployment experience in comments or ping me @lprichar.