UWP with Desktop Extension – Part 4

extensions4

If you haven’t read part 1 , part 2 and part 3 of this series yet, please start there first. In this final part I will explain how to submit a UWP with desktop extension to the Microsoft Store.

The .appxupload File

The Store accepts .appx, .appxbundle and .appxupload files (as well as .xap files for Silverligt phone apps). For UWP apps with desktop extension components the best way to go is the .appxupload file. The VS Packaging Project produces this file for you and ensures it includes all the right binaries, assets and symbols. It’s an important thing to remember to create the .appxupload off the packaging project, not the UWP project in your solution. The latter will not be valid for Store submission.

To produce the .appxupload file, right-click on the packaging project in Solution Explorer, and select “Create App Packages …”

screenshot1

Now follow the instructions in the wizard and select the appropriate options like version number, architecture etc.

screenshot2

Once you hit “Create”, VS will produce the .appxupload file as well as test packages that can be used for local testing. At this point it is recommended to perform the following test activities:

  1. Launch the Windows App Certification Kit (from the dialog) to run all the applicable certification tests. Any failures in the mandatory certification tests will block your Store submission, so you will want to get on top of those right away. Note that there are also optional tests in the suite. Those point to potential problems in the app that may lead to problems on certain devices/configurations. Those failures won’t block the submission, but are rather warnings for you to take a look and double-check. A typical optional failure we are seeing with desktop extensions is the 4.2 Blocked Executable test. This test warns you if it detects code that potentially launches unsigned EXEs or utilities such as PowerShell, cmd.exe or reg.exe, as those are not allowed in Windows 10 S mode. Note that these may be false positives and won’t block your Store submission.
  2. Perform a final test pass of the packaged app, outside of Visual Studio. What I typically do is copy the *_Test folder under AppPackages to a clean test machine or VM (i.e. one that doesn’t have a VS development environment) and run the Add-AppDevpackage powershell script to install it in a way that is nearly identical to how your customers will install it from the Store. Then sanity check all important features of your app, including your desktop extension.

screenshot3

 

Adding ARM Configuration

If you are targeting Mobile with your UWP, you will need to provide ARM packages. In the current build of Visual Studio 2017 (Update 15.7) the packaging project does not create ARM packages by default. You will have to edit the .wapproj and add ARM support explicitly:

screenshot4

Now replace “AnyCPU” with “ARM”, as you won’t need the neutral configuration for the packaging project:

<ProjectConfiguration Include="Debug|AnyCPUARM">
 <Configuration>Debug</Configuration>
 <Platform>AnyCPUARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|AnyCPUARM">
 <Configuration>Release</Configuration>
 <Platform>AnyCPUARM</Platform>
</ProjectConfiguration>
...
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPUARM'">
 <AppxBundle>Always</AppxBundle>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPUARM'">
 <AppxBundle>Always</AppxBundle>
</PropertyGroup>

Once you have done those edits save the changes and reload the full solution (not just the project). Now you are set for building and submitting ARM packages (alongside your x86 and/or x64 packages).

It’s a good idea at this point to double-check the configuration properties in the solution for ARM. It should look like this:

screenshot5

 

Requesting ‘runFullTrust’ Approval

If you are submitting your UWP with desktop extension for the first time you will receive an error because you don’t have permission to submit apps with the ‘runFullTrust’ capability. To request access you are redirected to this form:
https://developer.microsoft.com/en-us/windows/projects/campaigns/desktop-bridge

Once you have submitted your info a Microsoft engineer will contact you and work with you through the onboarding process for your app. Once that is done you will be able to submit your app and any future updates just like for any other Windows Store application. We are working on making this initial onboarding process smoother and I appreciate your feedback. If you see any hiccups or delays going through the process please don’t hesitate to contact me and I will do my best to get you unblocked.

In Closing

This is it for my 4-part quick tutorial on how to make your UWP apps even better on PC by lighting up desktop extensions. What’s next? I have started to put together a number of concrete examples of activities that Windows Desktop apps typically need to perform outside of the sandbox, like integrating with legacy systems in LOB scenarios. Those I will blog about next and publish the samples in this repo – stay tuned for more on this coming soon …

https://github.com/StefanWickDev/ExtensionGallery

UWP with Desktop Extension – Part 3

desktop_yellow

If you haven’t read part 1 or part 2 of this series yet, please start there first. I have covered the “Hello World” version of a UWP with Desktop extension and explained how to launch different process types and pass in arguments. Now the next set of questions I want to answer in this post are:

  • How to establish a communication channel between the components?
  • How to send requests and receive responses from either side?
  • How to handle process exit scenarios?

TL; DR;

Short answers: An AppServiceConnection can be used to connect between the components and to allow bi-directional communication via ValueSets. Typically you want to host the AppService in-proc with your UWP app in this type of scenario.

Just show me the code on github …
https://github.com/StefanWickDev/UWP-FullTrust/tree/master/UWP_FullTrust_3

Let me run your sample from the Microsoft Store …
https://www.microsoft.com/store/apps/9NR0G35Q2F65

Declaring the AppService

The first step is to declare the AppService extension in our manifest, which is what will provide us with a two-way communication pipe between our components:

<Extensions>
 <uap:Extension Category="windows.appService">
  <uap:AppService Name="SampleInteropService" />
 </uap:Extension>
 <desktop:Extension Category="windows.fullTrustProcess" Executable="FullTrust\FullTrust.exe" />
</Extensions>

For full details on AppServices in the Universal Windows Platform our developer documentation has this good read: https://docs.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service

Establishing the Connection

Now in order to establish the connection, let’s take a look at our WPF app. One of the first things it does during launch is to establish the connection to the AppService, and then retain the instance of the AppServiceConnection for the lifetime of the connection. We are also hooking up the event handlers here to receive requests and to get notified when the connection gets closed:

// MainWindow.xaml.cs in WPF project
private async void InitializeAppServiceConnection()
{
 connection = new AppServiceConnection();
 connection.AppServiceName = "SampleInteropService";
 connection.PackageFamilyName = Package.Current.Id.FamilyName;
 connection.RequestReceived += Connection_RequestReceived;
 connection.ServiceClosed += Connection_ServiceClosed;

 AppServiceConnectionStatus status = await connection.OpenAsync();
 if (status != AppServiceConnectionStatus.Success)
 {
  // something went wrong ...
  MessageBox.Show(status.ToString());
  this.IsEnabled = false;
 } 
}

On the other side in the UWP, this will now trigger the OnBackgroundActivated event, and we’ll grab a reference to the AppServiceConnection instance there as well and hold on to it.

In our case the UWP is already running in the foreground, but note that it doesn’t have to: Client processes can also connect to the AppService if the foreground app is not running. It would activate the UWP in the background and establish the AppServiceConnection. The UWP process would then continue running in the background for as long as the connection is active.

// App.xaml.cs in UWP project
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
 base.OnBackgroundActivated(args);

 if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
 {
  // only accept connections from callers in the same package
  if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
  {
   // connection established from the fulltrust process
   AppServiceDeferral = args.TaskInstance.GetDeferral();
   args.TaskInstance.Canceled += OnTaskCanceled;

   Connection = details.AppServiceConnection;
   AppServiceConnected?.Invoke(this, args.TaskInstance.TriggerDetails as AppServiceTriggerDetails);
  }
 }
}

Sending Requests and Responses

Now that the connection has been established and either side keeps a reference to the AppServiceConnection instances, we can send requests from either side, and respond to them accordingly.

Scenario 1 – UWP sends request to desktop extension

UWP apps don’t have access to the registry. Let’s assume for an LOB enterprise scenario I need to read a certain reg key. The UWP can use a desktop extension to get the job done, by sending an AppServiceRequest that contains the requested key. The desktop extension can then query the registry and send the name/value pairs back to the UWP as an AppServiceResponse.

screenshot8

// MainPage.Xaml.cs in UWP project
private async void btnClick_ReadKey(object sender, RoutedEventArgs e)
{
 ValueSet request = new ValueSet();
 request.Add("KEY", tbKey.Text);
 AppServiceResponse response = await App.Connection.SendMessageAsync(request);

// display the response key/value pairs
 tbResult.Text = "";
 foreach (string key in response.Message.Keys)
 {
 tbResult.Text += key + " = " + response.Message[key] + "\r\n";
 }
}

This triggers the registry reading code in the WPF app. The implementation can be found here on github in the ‘Connection_RequestReceived’ event handler. In a real production scenario you wouldn’t implement this extension as a windowed WPF app of course, but instead use a headless background process. But since we need UI for scenario #2, I went with WPF …

Scenario 2 – Desktop extension sends request to UWP

To show the reverse communication flow I am just picking a dummy scenario: I will send two double values from WPF to UWP and let the UWP app respond with the sum of both:

screenshot9

// MainWindow.xaml.cs in WPF project
private async void Button_Click(object sender, RoutedEventArgs e)
{
 // ask the UWP to calculate d1 + d2
 ValueSet request = new ValueSet();
 request.Add("D1", d1);
 request.Add("D2", d2);
 AppServiceResponse response = await connection.SendMessageAsync(request);
 double result = (double)response.Message["RESULT"];
 tbResult.Text = result.ToString();
}
// in MainPage.Xaml.cs in UWP project
private async void AppServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
 double d1 = (double)args.Request.Message["D1"];
 double d2 = (double)args.Request.Message["D2"];
 double result = d1 + d2;
 
 ValueSet response = new ValueSet();
 response.Add("RESULT", result);
 await args.Request.SendResponseAsync(response);

// log the request in the UI for demo purposes
 await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
 {
 tbRequests.Text += string.Format("Request: {0} + {1} --> Response = {2}\r\n", d1, d2, result);
 });
}

 

Handling Process Exit Scenarios

Now what happens if one side of the communication pipeline goes away, for whatever reason? It’s up to the app to handle this and decide what should happen. Generally speaking there are three options: try to relaunch the companion component, or keep going without the companion (if it’s non-essential), or failfast to terminate yourself. For our demo I will implement two scenarios:

Scenario 1 – UWP will relaunch closed WPF

// MainPage.xaml.cs in UWP project
private async void MainPage_AppServiceDisconnected(object sender, EventArgs e)
{
 await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, ()=>
 {
  // disable UI to access the connection
  btnRegKey.IsEnabled = false;

  // ask user if they want to reconnect
  Reconnect();
 }); 
}

private async void Reconnect()
{
 if (App.IsForeground)
 {
  MessageDialog dlg = new MessageDialog("Connection to desktop process lost. Reconnect?");
  UICommand yesCommand = new UICommand("Yes", async (r) =>
  {
   await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
  });
  dlg.Commands.Add(yesCommand);
  UICommand noCommand = new UICommand("No", (r) => { });
  dlg.Commands.Add(noCommand);
  await dlg.ShowAsync();
 }
}

Scenario 2 – WPF will terminate itself when UWP closes

// MainWindow.xaml.cs in WPF project
private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
 // connection to the UWP lost, so we shut down the desktop process
 Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
 {
  Application.Current.Shutdown();
 })); 
}

This is it for part 3 of this series. Hopefully I answered most of the question around communication between the app and its extension in this sample. If not, please leave comments here, hit me up on Twitter or post your questions on Stackoverflow. If they are tagged with ‘UWP’ they will show on my radar.

Next up: Part 4 – Submitting to the Microsoft Store

UWP with Desktop Extension – Part 2

desktop_blue

If you haven’t read part 1 of this series yet, please start there first. I have covered the basic project setup in VS and walked through the “Hello World” version of a UWP with Desktop extension. Now the next set of questions I want to answer in this post are:

  • What type of processes can I use as extensions?
  • What about EXEs that are not in my package?
  • Can I have multiple Desktop extensions for my app?
  • How do I pass parameters into those EXEs?

TL;DR;

Short answers: Any type of Win32 or .NET desktop project type can be used. Can be background-only or with UI. Can come from the package or from elsewhere (read the fine print below), and you can launch with custom command line parameters.

Just show me the code on github …
https://github.com/StefanWickDev/UWP-FullTrust/tree/master/UWP_FullTrust_2

Let me run your sample from the Microsoft Store …
https://www.microsoft.com/store/apps/9PLMLGKSCZQW

Multiple Desktop Processes

If you have studied the ‘windows.fullTrustProcess’ extension schema and corresponding API surface you have noticed that there is only one extension per <Application> node in your package. For multi-process scenarios, you implement them in the declared extension process. There you can use the Process class (.NET) or CreateProcess API (Win32) to create and manage your child processes. To see how this is done, take a look at the “Launcher” project in my sample #2 , which launches several different process types based on input from the UWP.

// determine the package root, based on own location
string result = Assembly.GetExecutingAssembly().Location;
int index = result.LastIndexOf("\\");
string rootPath = $"{result.Substring(0, index)}\\..\\";

// process object to keep track of your child process
Process newProcess = Process.Start(rootPath + @"Win32\Win32.exe");

screenshot3

In-package vs out-of-package

Launching EXEs that come with your package is easy, as the sample demonstrates. You know where they are located and how to launch them as they are your binaries. You can also launch EXEs from other locations on the system. The sample shows this by launching mstsc.exe (the system’s remote desktop app). Be careful here though: In Windows 10 S mode, only Microsoft-signed code can run (OS binaries & apps from the Microsoft Store), so you can’t just CreateProcess/Process.Start an EXE that you’ve downloaded from a share or website. That will fail in S mode. Launching mstsc or notepad will work of course as they are Microsoft-signed.

Another detail to be aware of is process identity: EXEs from your package will be launched with your package’s identity. EXEs from other locations will run under their own identity. Running with package identity also means those desktop processes have the same state separation for Registry and AppData as Desktop Bridge apps (learn all the details here).

You can easily visualize the identity grouping in the task bar and task manager, as the processes are grouped together – whereas the Remote Desktop app is separate from the UWP app:

screenshot6

Background Processes

As you can see in the sample, the desktop extension can run as a headless background process. This can be very powerful as you are in control of the lifetime of this process. It can run for the entire duration of the user session. However, with great power comes great responsibility and you should make sure you don’t burn CPU without delivering user value, especially when run on battery. In my sample I am using a 60 sec timeout on my background process (even though it is not adding any value), just to give folks enough time to find it in task manager to verify its existence.

Also note that running a fulltrust background process for long periods of time may delay your app from getting updated.

Passing in Launch Parameters

From the extension schema, the API surface and/or the MSDN snippet you may have already gathered that the FullTrustProcessLauncher only accepts a finite list of predefined command line arguments that get passed to the desktop extension process. This design is based on the principle, that a less trusted process should not be able to send arbitrary input to a more trusted process. They should communicate via well-defined protocols. Here is how I am using predefined parameter groups in my sample to launch the various types of processes:

<desktop:Extension Category="windows.fullTrustProcess" Executable="Launcher\Launcher.exe">
 <desktop:FullTrustProcess>
 <desktop:ParameterGroup GroupId="Background" Parameters="/background" />
 <desktop:ParameterGroup GroupId="WPF" Parameters="/wpf" />
 <desktop:ParameterGroup GroupId="WinForms" Parameters="/winforms" />
 <desktop:ParameterGroup GroupId="Win32" Parameters="/win32" />
 <desktop:ParameterGroup GroupId="RemoteDesktop" Parameters="/mstsc" />
 <desktop:ParameterGroup GroupId="Parameters" Parameters="/parameters" />
 </desktop:FullTrustProcess>
</desktop:Extension>

In the UWP app code, I now use the FullTrustProcessLauncher overload that takes the ParameterGroupID as string:

if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
 await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Background");
}

If you own the code on both sides you can use the local AppData to share complex data from the calling app to the desktop process. Furthermore, from your desktop extension process you can launch other desktop process with arbitrary command line arguments, as those processes have the same integrity, as shown in the sample as scenario 2.

UWP side (MainPage.cs):

if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
 // store command line parameters in local settings
 // so the Lancher can retrieve them and pass them on
 ApplicationData.Current.LocalSettings.Values["parameters"] = tbParameters.Text;

 await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Parameters");
}

Win32 side (Program.cs)

string parameters = ApplicationData.Current.LocalSettings.Values["parameters"] as string;
Process newProcess = Process.Start(rootPath + @"WPF\WPF.exe", parameters);

screenshot7

This is it for part 2 of this series. Hopefully I answered most of the question around process launching and parameters in this sample. If not, please leave comments here, hit me up on Twitter or post your questions on Stackoverflow. If they are tagged with ‘UWP’ they will show on my radar.

Next up: Part 3 – Bi-directional communication between components

UWP with Desktop Extension – Part 1

desktop_purple

Adding a desktop extension component to your UWP app package is a great way to make your universal app even better on PC by lighting up PC-specific capabilities. Recently on Stackoverflow I have seen a number of questions coming up around this topic, so I decided to write up a quick tutorial to help de-mystify various aspects around this useful feature:

Part 1 – Getting started, hello world (this post)
Part 2 – Launching multiple processes, passing in parameters
Part 3 – Communicating between components
Part 4 – Submitting the package to the Microsoft Store

TL;RD;

Including a desktop extension (‘windows.fullTrustProcess’) in your UWP app package lets your UWP app light up PC-specific capabilities when run on the Windows 10 desktop SKU (including Windows 10 S mode). The extension code runs at the full privileges of the user and has access to all public APIs on the machine. Your app will still be able to run on all SKUs of Windows 10 (e.g. Hololens) – it just won’t light up the desktop extension on those devices. This feature has been introduced with the Windows 10 Anniversary Update 1607 (build 14393, aka Redstone 1).

Show me the code for “Hello World” on github
https://github.com/StefanWickDev/UWP-FullTrust/tree/master/UWP_FullTrust_1

Let me try your “Hello World” app from the Microsoft Store
https://www.microsoft.com/store/apps/9ND9W9HWSWNS

Project Setup

With Visual Studio 2017 Update 5 (and later), creating a UWP solution with desktop extension is fairly straight forward now. Here is a step-by-step guide to create your first UWP app that lights up powerful desktop code:

Add extension and packaging projects

In your UWP VS solution, add a “Windows Classic Desktop” project to implement your extension. This could be any type of Win32 or .NET project in any language. For “Hello World” I am adding a C# Console project. Next you need to add a “Windows Application Packaging Project” (look for it in the “Windows Universal” category).

So your solution setup looks like in the screenshot below. In order to package both the UWP and the desktop code in one app package, add both as Application references to the Packaging project:

AddAppRefs

Making the code work together

Now to connect the two projects we need to declare the extension in the package manifest. For this open the Package.appxmanifest file in the Packaging project (“Package”) in XML editing mode and add the following section within the <Application> node:

<Extensions>
 <desktop:Extension
   xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
   Category="windows.fullTrustProcess"
   Executable="DesktopExtension\DesktopExtension.exe" />
</Extensions>

Note that the Packaging project by default adds the ‘runFullTrust’ capability to your manifest, which needs to be declared for this extension. Note that this is a restricted capability and will require an additional on-boarding review for the public Microsoft Store – more details on this in part 4 of this series.

To invoke the extension, we are using the FullTrustProcessLauncher API from the UWP Desktop Extension SDK (which is part of the Windows SDK and should already be on your machine). So let’s add a reference to this SDK from our UWP project (“Hello World”):

AddSDKref

Now in your UWP app code, add a line of code to invoke the desktop extension component. It is very important to wrap this into an if-statement as shown, to make sure the app is still universal and can run on all current and future SKUs of Windows 10. We want to launch the extension only on devices that support this contract, in this case on Desktop devices.

if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
  await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
}

In our desktop extension code (in Program.cs), we just add a quick piece of code for testing/demo purposes:

class Program
{
 static void Main(string[] args)
 {
  Console.Title = "Hello World";
  Console.WriteLine("This process has access to the entire public desktop API surface");
  Console.WriteLine("Press any key to exit ...");
  Console.ReadLine();
 }
}

Run, test and debug

First, double check the configuration properties for solution and make sure it looks like this picture for all configurations you care about (architecture & debug/release). Specifically, you want to uncheck the “Deploy” action for the UWP, since deploy will done from the Packaging Project.

screenshot10

Now make sure you have set the “Package” project as the solution’s start up project in VS, and “Hello World” is its application entry point. Now hit F5 and test out your project. Debugging the UWP code continues to work as before. In order to debug the desktop code, launch without debugging and manually attach to the desktop process in VS. The latter should become more seamless in an upcoming update to VS (I believe the work is scheduled for Update 8), which will let you debug across the UWP/Desktop boundary seamlessly.

screenshot5

This is it for my first post in this tutorial series. Now that you have seen “Hello World” I hope you can imagine some of the possibilities this enables. Keep in mind the desktop extension can be any type of project, e.g. a C++ background process, a VB Winforms app, a C# WPF app, etc. – or any combination of those and it has access to the full public API surface on the machine. More on this in the next post.

Also see my earlier blog posts that use the same pattern to implement some concrete desktop scenarios – UWP with Systray Extension & Office Interop with UWP.

Next up: Part 2 – Launching multiple processes, passing in parameters