Auto updater for my side loaded UWP apps

As I described before, i have a few tasks to solve for my home automation project. One of the things I wanted to do is to be able to update my application on the different devices easily. I could publish my home automation app to the store, but it’s hard to provide a test environment for the store testers and it takes a while when the app gets certified before i can roll it out to all my devices. Of course I can sign up for an Intune account and use regular MDM to manage my apps, but 1st i need to have this Intune environment which I think is a bit over the top, and 2nd I would be depending on an internet connection. This is also a topic which comes up with some of my customers so I thought I could solve this differently.

Since the app is going to be side loaded I am allowed to use restricted capabilities. The store app uses these capabilities to be able to download and update apps from the store so what APIs are used by this app? It uses (among others) the PackageManager class. This API is protected by the restricted capability packageManagement.

So the first experiment I tried was to build an update button in a test app and I tried to update myself. I created a version 1.0.0.0 (Project, Store, Create App Package and set the version to 1.0.0.0, I also created a version 2.0.0.0) In my code I added the following line in the click event:

PackageManager packagemanager = new PackageManager();
await packagemanager.UpdatePackageAsync(new Uri(packageLocation), null, DeploymentOptions.ForceApplicationShutdown);

Where the packageLocation pointed to a file on my disk (but could also be a URL to a website). But updating yourself didn’t work. So the next idea I had was to write a little app which I could call with app2app communication and the only thing that little app would do was update the calling app. This works perfectly. So I created a new project called AppUpdate (Blank App). I added another project to the solution called AppUpdaterService (Windows Runtime Component).  I defined 2 parameters (I only use 1 currently) to be passed to this background service and that’s the package family name and the package location.

The entire code of this class looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
using Windows.Management.Deployment;

namespace AppUpdaterService
{
    public sealed class BackGroundUpdaterTask : IBackgroundTask
    {
        BackgroundTaskDeferral serviceDeferral;
        AppServiceConnection connection;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            //Take a service deferral so the service isn't terminated
            serviceDeferral = taskInstance.GetDeferral();
            taskInstance.Canceled += OnTaskCanceled;

            //initialize my stuff

            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            connection = details.AppServiceConnection;

            //Listen for incoming app service requests
            connection.RequestReceived += OnRequestReceived;
        }

        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (serviceDeferral != null)
            {
                //Complete the service deferral
                serviceDeferral.Complete();
            }
        }

        async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            //Get a deferral so we can use an awaitable API to respond to the message
            var messageDeferral = args.GetDeferral();

            ValueSet message = args.Request.Message;
            ValueSet returnData = new ValueSet();

            try
            {
                string packageFamilyName = message["PackageFamilyName"] as string;
                string packageLocation = message["PackageLocation"] as string;
                PackageManager packagemanager = new PackageManager();
                await packagemanager.UpdatePackageAsync(new Uri(packageLocation), null, DeploymentOptions.ForceApplicationShutdown);

                //Don't need to send anything back since the app is killed during updating but you might want this if you ask to update another app instead
                //of yourself.

                //returnData.Add("Status", "OK");
                //await args.Request.SendResponseAsync(returnData);
            }
            finally
            {
                //Complete the message deferral so the platform knows we're done responding
                messageDeferral.Complete();
            }
        }
    }
}

Make sure you add the reference to this project to your AppUpdater project.

Inside my dashboard app I call the following code to update myself through the updater app:

private AppServiceConnection updaterService;
private async void button_Click(object sender, RoutedEventArgs e)
{
    if (this.updaterService == null)
    {
        this.updaterService = new AppServiceConnection();
        this.updaterService.AppServiceName = "net.hoekstraonline.appupdater";
        this.updaterService.PackageFamilyName = "be122157-6d1a-47b8-a505-4d6b276b1973_f6s3q6fqr0004";

        var status = await this.updaterService.OpenAsync();
        if (status != AppServiceConnectionStatus.Success)
        {
            txtOutput.Text = "Failed to connect!";
            updaterService = null;
            return;
        }
    }
    try
    {
        //Uri updatePackage = new Uri("C:\\Users\\matth\\Documents\\Visual Studio 15\\Projects\\MyAppForUpdateTest\\MyAppForUpdateTest\\AppPackages\\MyAppForUpdateTest_2.0.0.0_Debug_Test\\MyAppForUpdateTest_2.0.0.0_x86_Debug.appxbundle");
        Uri updatePackage = new Uri("<a href="http://192.168.2.250/MyAppForUpdateTest_2.0.0.0_Debug_Test/MyAppForUpdateTest_2.0.0.0_arm_Debug.appxbundle&quot;);">http://192.168.2.250/MyAppForUpdateTest_2.0.0.0_Debug_Test/MyAppForUpdateTest_2.0.0.0_arm_Debug.appxbundle");</a>
        var message = new ValueSet();
        message.Add("PackageFamilyName", Windows.ApplicationModel.Package.Current.Id.FamilyName);
        message.Add("PackageLocation", updatePackage.ToString());

        AppServiceResponse response = await this.updaterService.SendMessageAsync(message);

        if (response.Status == AppServiceResponseStatus.Success)
        {
            txtOutput.Text = "Update started, time to say goodbye" + response.Message["Status"];

        }
    }
    catch (Exception ex)
    {
        txtOutput.Text = ex.Message;
    }

}

The cool thing that happens is my app is closed when the update starts and automatically restarted after the update. This works on both Desktop as Mobile. I still need to test this on my RPI3 and in combination with Assigned Access/Kiosk mode on mobile.

So what’s left? I need to implement something which checks for a new version regularly. I am still debating if I am going to use the updater app to get registrations from other apps and checks if there is an update and if it finds one starts updating the app, it is more generic this way. On the other hand, the app might know better when it should be updating or not and implement the logic itself and just uses the appupdater to do the update.

I was also thinking about using some kind of broadcast in the app to send an update command to the apps on all devices to be able to force an update after I published a newer version on my internal website.

How I need to check for a newer version could be as simple as providing a little text file on the webserver with the latest version number. If the app has a lower version it will update. Or perhaps I should test what happens if I call the update command and the version of the app on the server is the same. If the update is ignored i don’t need to write any specific code to handle this. It might not be the most optimal solution, although we only download the changes in packages.

Comments

Comment by Jasper on 2017-01-08 07:31:00 -0800

Thanks, this really helped me a lot!

I made some changes to the implementation with guidance from an msdn article. This implementation provides more detailed error description upon update failure.

async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)

{

// Get a deferral so we can use an awaitable API to respond to the message

var messageDeferral = args.GetDeferral();

// Set return message and instantiate vars

string returnMessage = “”;

DeploymentResult deploymentResult;

PackageManager packagemanager = null;

ValueSet returnData = new ValueSet();

// Get parameters

ValueSet message = args.Request.Message;

string packageFamilyName = message[“PackageFamilyName”] as string;

string packageLocation = message[“PackageLocation”] as string;

Debug.WriteLine($”Upgrading {packageFamilyName} from {packageLocation}”);

// Construct packagemanager

try

{

packagemanager = new PackageManager();

}

catch (Exception e)

{

Debug.WriteLine($”Kon geen PackageManager class instantieren, app capability beschikbaar?”, e);

throw e;

}

// Update procedures

try

{

Debug.WriteLine(“Installing package {0}”, packageLocation);

Debug.WriteLine(“Waiting for installation to complete…”);

IAsyncOperationWithProgress deploymentOperation = packagemanager.UpdatePackageAsync(new Uri(packageLocation), null, DeploymentOptions.ForceApplicationShutdown);

ManualResetEvent opCompletedEvent = new ManualResetEvent(false); // this event will be signaled when the deployment operation has completed.

deploymentOperation.Completed = (depProgress, status) => { opCompletedEvent.Set(); };

opCompletedEvent.WaitOne();

deploymentResult = deploymentOperation.GetResults();

if (deploymentOperation.Status == AsyncStatus.Error)

{

Debug.WriteLine(“Installation Error: {0}”, deploymentOperation.ErrorCode);

Debug.WriteLine(“Detailed Error Text: {0}”, deploymentResult.ErrorText);

returnMessage = $”Fout bij updaten, foutdetails: {Environment.NewLine}{deploymentResult.ErrorText}{Environment.NewLine}{deploymentResult.ExtendedErrorCode}{Environment.NewLine}{deploymentOperation.ErrorCode}”;

}

else if (deploymentOperation.Status == AsyncStatus.Canceled)

{

returnMessage = $”Fout bij updaten, updateproces geannuleerd”;

Debug.WriteLine(“Installation Canceled”);

}

else if (deploymentOperation.Status == AsyncStatus.Completed)

{

returnMessage = $”Update voltooid”;

Debug.WriteLine(“Installation succeeded!”);

}

else

{

returnMessage = $”Kon niet vaststellen of updateprocedure gelukt is”;

Debug.WriteLine(“Installation status unknown”);

}

returnData.Add(“Status”, returnMessage);

await args.Request.SendResponseAsync(returnData);

}

catch (Exception e)

{

Debug.WriteLine($”Onafgehandelde fout tijdens updateproces.”, e);

returnData.Add(“Status”, e.Message);

await args.Request.SendResponseAsync(returnData);

throw e;

}

finally

{

//Complete the message deferral so the platform knows we’re done responding

messageDeferral.Complete();

}

}

Comment by Andrey on 2017-01-08 09:05:35 -0800

Hi!

What capabilities have you setup there?

Comment by Nick on 2017-01-13 08:01:57 -0800

Nice post, thank you for sharing this. I followed your implementation but I came across an issue while trying to update my sideload uwp app. I am receiving an System.UnauthorizedAccessException in the AppUpdaterService.winmd when the PackageManager is being initialized. I have under the same solution the app I would like to have updated, another project which is an blank app which is referencing the AppUpdaterService. Is this the correct way of approaching this or I should use only AppUpdaterService and reference it from the app that I want to have updated ? Thank you in advance.

Comment by Felipe on 2017-02-01 06:02:23 -0800

Hi, I’m trying to put to work your example is still not working. The status returns success but it does nothing else. Can you help me?

Comment by Alex on 2017-03-13 06:09:00 -0800

Hi,

for using the PackageManager class you have to grant following capability:

the this capability can be found in following xml namespace:

xmlns:rescap=”http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities”

You have to add the namespace and the capability manually in the Package.appxmanifest file.

Then you should be able to run the code.

Comment by Gert on 2017-03-31 00:03:45 -0800

Hi Matthijs,

I have tried to repeat your implementation, but I get the same issue as reported by Nick on January 13, 2017. it seems that it is not possible to instantiate the PackageManager class within the Windows Runtime Component (AppUpdaterService). Even though I am Admin on my computer. Do I have to change some (security) settings in the registry or DCOM?

Hope you can point me in the right direction.

Thanks in advance.

Comment by Dennis Stanoev on 2017-07-14 01:13:24 -0800

Hey Matthijs,

Thanks for the insightful post – it’s been very helpful for me trying to build an auto-updater for a UWP app.

I am just wondering – did you ever manage to get this working with Assigned Access/Kiosk mode?

Thanks

Comment by Matthijs Hoekstra on 2017-07-22 19:00:19 -0800

Hmm good one, nope I never tried that.

Comment by TS on 2017-08-25 00:13:31 -0800

Hi, did you ever end up testing – and getting this working on the raspberry pi 3?

Comment by Sohel on 2017-08-28 04:40:34 -0800

How are you running the Service?

AppUpdate – is your blank app?

AppUpdater – is your service app?

Comment by Simon on 2017-08-31 08:34:47 -0800

Hey Matthijs,

very nice tutorial, just what I was searching for. But I am getting an Deployment Error:

The current user has already installed an unpackaged version of this app. A packaged version cannot replace this. The conflicting package is 82fa41fc-70b2-468f-a1aa-cba3404c3429 and it was published by CN=Administrator, CN=Users, DC=…., DC=com.

System.Exception: Install failed. Please contact your software vendor. (Exception from HRESULT: 0x80073CF9)

System.Runtime.InteropServices.COMException (0x80073CF9): Install failed. Please contact your software vendor.

Dou you maybe know, what I am doing wrong? The error is thrown by packagemanager.Update(), wich is odd, as i want to update and not install the app :/

Comment by Andy on 2017-09-06 00:26:06 -0800

Nice. Thanks for sharing.

I also wonder if you managed to get it working on Assigned Acces Mode?

Comment by Caleb on 2017-11-13 15:52:02 -0800

One more person wondering about this working in Assigned Access mode. I wish there was a better way to handle updating Assigned Access apps that didn’t involve disabling the mode, switching users, updating the app, switching users, re-enabling the mode, switching users. If this could handle auto-updates, everything could be automatic.

However, since Assigned Access mode allows only one app, I doubt it will work. ):

Comment by Ramesh Dabhi on 2017-11-17 04:55:35 -0800

Same here. Any solution to this, yet?

Comment by Ramesh Dabhi on 2017-11-20 22:59:04 -0800

It finally worked! No changes in code. It simply did not work in debugging mode, it worked after I deployed both, the main package, and the updater app.

One problem I faced, after the app is updated, it does automatically restart. But after the restart, it crashes. When I start the app again from the menu, it works properly without crashing.

Comment by Andy on 2018-02-25 03:42:05 -0800

For me the UpdatePackageAsync() call does work on foreground app without any problems on Windows 10 Pro (Desktop) Build 16299.248 and also on Windows 10 IoT Core (Raspberry Pi 3) with Build 16299.248. So it seems that we don’t need a Windows Runtime Component for any BackgroundTask anymore. One Thing to note is that it is required to install the initial version of the app from the AppPackage installer. When the app is deployed directly from Visual Studio 2017, then the package update does not work, at least for me it doesn’t.

Comment by Dan Neely on 2018-03-06 11:39:05 -0800

I managed to get this working, the big piece that was missing from the blog was setting up the second app to expose the service. If you follow along in this MS document it should fill in most of the blanks.

https://docs.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service

Comment by Matthijs Hoekstra on 2018-03-23 21:54:52 -0800

Yes I did 🙂

Comment by Matthijs Hoekstra on 2018-03-23 21:55:02 -0800

Yes I did 🙂

Comment by Matthijs Hoekstra on 2018-03-23 21:57:10 -0800

I thought that was what I explained in this post. Updating yourself doesn’t work so I created another project with the app2app service I call from my dashboard app.

Comment by Matthijs Hoekstra on 2018-03-23 21:58:22 -0800

You can’t mix and match packaged and unpackaged. So if you install the initial app through visual studio you cant update with a packaged application and vice versa.

Comment by Prashant Srivastava on 2018-06-26 02:13:17 -0800

I getting “InstallFailed” error in AppService while updating the main app.

Comment by suraj soni on 2018-11-22 22:33:30 -0800

Sir can you Please provide me the zip file of the whole solution or the Restricted capability. This would great help. thanks in advanced

Regards

suraj soni.

Comment by suraj soni on 2018-11-23 04:03:34 -0800

Sir can you Please send the solution project at suraj.soni401@gmail.com. It would be a help. thanks in advanced

Comment by suraj soni on 2018-11-26 01:39:18 -0800

Have you got any solution Regarding the same

Comment by suraj soni on 2018-11-26 01:40:56 -0800

Have you got the solution?

Comment by Jonathan Berent on 2019-01-08 09:34:23 -0800

Matt,

Great! Very similar to a situation I am in (pre build 17120 over USB) so I’m going this route instead of .appinstaller for now. I’m wondering how you would handle initial installation since this will be in production and not just for testing (I currently ask the technicians to run a PowerShell script, but now having TWO apps might lead to more confusion than this seemingly simple task already causes). Also, what about auto-updating the auto-updater?

Comment by Matthijs Hoekstra on 2019-01-13 12:20:10 -0800

Powershell, or you could use Intune/SCCM/Login scripts etc. Depends on your admins.

Comment by Niko on 2019-03-29 05:28:39 -0800

Hi! Thank you for this post.

I did not fully understand how you get this values?

this.updaterService.AppServiceName = “net.hoekstraonline.appupdater”;

this.updaterService.PackageFamilyName = “be122157-6d1a-47b8-a505-4d6b276b1973_f6s3q6fqr0004”;

Comment by Parth Rathore on 2019-05-27 02:45:03 -0800

Hey, how did you get it working? I mean Assigned Access allows only one app right, so how did the other app start. Or it being a service can be started by the app?

Comment by YZahringer on 2020-02-17 11:09:03 -0800

I get a 0x80040904 error when I launch the application in Assigned Access mode. Did you have success to launch it in Kiosk mode? Anything in particular to do?