原文: 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:
1
2
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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");">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.