UWP Features & Samples

Azure IoT with UWP

blogTitleRecently I moved to a new role within Microsoft and I am now working on the developer experience for Azure IoT. Going forward I will be shifting my blog activities to be more focused on IoT and Cloud development topics, across the diverse spectrum of technologies that is IoT today. To begin this transition I will start with a couple of introductory posts at the intersection of my previous and my current role: using the Azure IoT SDKs from UWP applications. In fact, if you are like me coming from a UWP/.NET background this is a great path to ramp up on Azure SDKs and to working with the cloud.

The Azure IoT SDKs for both the device and service side support a diverse set of programming languages and operating systems, including UWP on Windows 10. If you are using Windows IoT Core on your devices (“things”) then UWP is your choice for device app development. Also, if you are building a backend application for Windows 10 that connects to Azure IoT services, UWP can be the right tool for the task. In this first post I will focus on building a simple device application.

Building a Rating “Thing”

Simple goal for this first post: Using my Raspberry Pi 3, loaded with Windows IoT Core 17763 (aka 1809 update) I want to build a “thing” that lets customers rate their experience and sends that data to the cloud. You may have seen screens like this in airport bathrooms:

ratingux

What do I need?

  • Visual Studio 2017 with the latest updates
  • Azure subscription (free trial subscription will be sufficient for this example)
  • Raspberry Pi 3 (or any other device running Windows IoT Core)

If you don’t have an IoT device yet, you can still play along and use your PC as (simulated) IoT device. This is where the “universal” part of UWP comes in very handy because you will be able to run the same UWP app on both your PC and the device.

Preparation Steps

Azure Portal

Log into the Azure Portal and create an IoT Hub resource (choose the “Free Tier”)

iothub

Once the resource has been created, go to it and select “IoT Devices” from the left pane and click “Add”

adddevices

Now pick a name for your device and click “Save”

createDevice

Last step is to select the newly created device in the list and copy its connection string from the device details. Store this in notepad or somewhere so can get to it in a later step easily.

connstring

If you want to test with more than one device, please repeat the above steps for each device. I’d recommend you create at least two devices: one for your Raspberry Pi and one for your PC, so can test from both and observe the data flowing into Azure from more than one device.

Device

If you are using your PC as (simulated) device then there is nothing else to do here. For your Raspberry Pi follow the official steps from docs.microsoft.com to set up the latest Windows IoT Core build (at the time of this writing it was build 17763). Setup, configuration and maintenance of your Windows IoT Core device is a breeze using the Windows 10 IoT Dashboard and the Windows Device Portal.

Visual Studio 2017

Just make sure you have the latest updates and the Universal Windows Platform workload selected as part of your setup:

uwp

Now there are three options for you to get the device app running and connected to Azure:

1. Install the app from the Store and start running it from your PC (as simulated IoT  device)

You can find the pre-built sample app in the Microsoft Store at the below link. Install and launch it from there: https://www.microsoft.com/store/apps/9NGS2T2X166N

enterstring

The device app needs to know what the respective device’s connection string so can successfully connect to the Azure IoT Hub. So copy the string you had saved in the earlier step and paste it here. Click OK and once the connection is established the controls will be enabled and you can start submitting your ratings.

For an initial quick verification that the messages are arriving in your IoT Hub, navigate to the Azure Portal a go to your IoTHub resource. In the overview section, click on the “Device to cloud messages” chart and verify that your messages are arriving as expected. This view of Azure Monitor data may take a minute or two to get updated:

azmonitor

 

2. Download the source from GitHub, build and run it on your PC (as simulated IoT device)

To learn about the code, step through it in the debugger, make modifications etc. you can get the VS solution from here:

https://github.com/wickste/RatingThing

Open the RatingThing.sln solution in Visual Studio make sure you select x86 or x64 as target architecture and hit F5 to run with the debugger. Again you will need to enter your device’s connection string.

 

3. Deploy the app to your Raspberry Pi and connect it to Azure

Now the interesting way to run it of course is on your actual device (e.g. Raspberry Pi). For that you want to put the respective device connection string in the code (at the top of MainPage.xaml.cs. Note that in a production scenario you would not be doing it this way. Instead you would be using the Azure IoT Hub Device Provisioning Service that enables secure device provisioning at scale.

Next we set up VS for remote deployment and debugging for your Raspberry Pi. Select “ARM” as your architecture and “Remote Machine” as your target – then go into the Debug settings of your project properties to select the specific device you want to deploy to:

remotedebug

Now you can hit F5 to deploy and debug, just like you normally do for your desktop applications (just note that everything will be a bit slower …). So here we have it – our UWP app running on the Pi, connected to our Azure IoT Hub service:

 

Step-by-Step Code Review (full source here)

We start with the empty UWP app template and add a Nuget package for the Azure IoT Device Client SDK:

nuget

The XAML for our UI is extremely simply, thanks to the built-in RatingControl. Just stack the RatingControl on top of a TextBlock and a Button, and then wrap it all in a Viewbox for proper sizing on different display sizes:

<Viewbox Margin="24">
    <StackPanel>
        <RatingControl x:Name="ratingControl" IsEnabled="False"/>
        <TextBlock Text="Rate Your Experience"/>
        <Button x:Name="btnSubmit" Margin="12" IsEnabled="False"
                Content="Submit" Click="btnSubmit_Click"
                HorizontalAlignment="Center"/>
    </StackPanel>
</Viewbox>

Now we need to write the code to connect to our Azure IoT Hub, assuming we have obtained our device connection string (either from the dialog, or hard-coded in the source). Once our connection request has been processed we update the UI controls according to the connection status:

private void ConnectToAzure()
{
         
  iotHubClient = DeviceClient.CreateFromConnectionString(connectionString,
                                                         TransportType.Mqtt);
  iotHubClient.SetConnectionStatusChangesHandler(
    new ConnectionStatusChangesHandler(this.StatusChangesHandler));
  iotHubClient.OpenAsync();
}

private async void StatusChangesHandler(ConnectionStatus status,
                                        ConnectionStatusChangeReason reason)
{
  await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
  {  
    if (status == ConnectionStatus.Connected)
    {
      ratingControl.IsEnabled = true;
      btnSubmit.IsEnabled = true;
    }
    else
    {
      ratingControl.IsEnabled = false;
      btnSubmit.IsEnabled = false;
    }
  });
}

Last step to do is send the data whenever a customer provides their rating. In addition to the rating value, we also want to pass along some system health data to our backend. In this simple example I am adding the current memory usage to the telemetry message so we can build insights on the backend about our app’s health over time (subject of a future post):

private async void btnSubmit_Click(object sender, RoutedEventArgs e)
{
  ulong kiloBytes = MemoryManager.AppMemoryUsage / 1024;
  int rating = (int)ratingControl.Value;

  Message message = new Message();
  message.Properties.Add("rating", rating.ToString());
  message.Properties.Add("app-memory-usage", kiloBytes.ToString());

  await iotHubClient.SendEventAsync(message);
  ratingControl.ClearValue(RatingControl.ValueProperty);
}

That’s it. Pretty easy to get to this point thanks to Visual Studio, .NET & C# and the Universal Windows Platform. In the upcoming posts I will extend the solution on the cloud side and show several options for processing the data from our Rating Thing in Azure and taking action on the insights gained from our devices.

ablogTitle

Advertisements

App Elevation Samples – Part 3

Picture5

If you haven’t read part 1 and part 2 of this series yet, please start there first. I have covered the “hello world” version of a packaged app that requires elevation at launch time and showed how a packaged app can dynamically elevate itself. In this post we’ll get to the fun part: using the ‘allowElevation’ capability from a UWP app to execute code with elevated privileges on Windows 10 desktop with the 1809 update.

TL;DR

I already have the 1809 update and the Windows SDK 17763 (or later) installed. Just show me the code on GitHub …

https://github.com/StefanWickDev/AllowElevation-Samples/tree/master/Elevated%20Extension%20UWP

Step-by-Step Tutorial

1. Create a new Universal Windows App

Make sure you run Windows 10 with the 1809 update and have the Windows SDK 17763 (or later) installed. Now create a new C# Universal Windows application in Visual Studio 2017 (can use any supported programming language as well, e.g. C++, JS, …):

FileNewProject2

Be sure to set both “Minimum” and “Target” version to 17763 or higher as build number 17763 maps to the 1809 update for Windows 10. The ‘allowElevation’ capability we will be using has been introduced in 1809 and is not available in earlier versions of Windows 10.

setVersions

Now we need to add a ‘fullTrustProcess’ desktop extension to our UWP that will contain the code we want to run with elevated privileges. For a complete tutorial on UWP desktop extension read up here on all the details. In this post I will quickly move through the steps of setting up the desktop extension for our new UWP app:

2. Add the desktop extension project

For this example we want the desktop extension process to be headless (i.e. a background process without UI). It can have a UI as well if that’s what you need for your scenario. So let’s add a C# desktop console application (of course this could be C++, VB, etc. as well) and call it “Elevated Extension”:

addConsole2We don’t want a console box to pop up for our elevated background process, so we need to configure this project to run headless. For that go into the project settings and change the Output Type from “Console Application” to “Windows Application” (I know, not very intuitive …)

outputType

5. Packaging everything together

Next we need to add a “Windows Application Packaging Project” (look for it in the “Windows Universal” category). Name it “Package” and make it the startup project,  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:

addref3

6. Configuring the appx manifest

Now to connect the two projects we need to declare the “fullTrustProcess” desktop extension in the package manifest. For this open the Package.appxmanifest file in the Packaging project (“Package”) in XML editing mode and add the extension within the <Application> node. While we are in this file we are also adding the “appExecutionAlias” extension that we learned about in part 2, as we will need it to launch our extension with elevated privileges. Last but not least, you will also need to add namespace definitions for “uap3” and “desktop” in the root <Package> element per below snippet.

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="uap3 desktop ...">
  ...

  <Extensions>

    <desktop:Extension Category="windows.fullTrustProcess"
                       Executable="Elevated Extension\Elevated Extension.exe">
      <desktop:FullTrustProcess>
        <desktop:ParameterGroup GroupId="User" Parameters="/user" />
        <desktop:ParameterGroup GroupId="Admin" Parameters="/admin" />
      </desktop:FullTrustProcess>
    </desktop:Extension>

    <uap3:Extension Category="windows.appExecutionAlias"
                    Executable="Elevated Extension\Elevated Extension.exe"
                    EntryPoint="Windows.FullTrustApplication">
      <uap3:AppExecutionAlias>
        <desktop:ExecutionAlias Alias="ElevatedExtension.exe" />
      </uap3:AppExecutionAlias>
    </uap3:Extension>

  </Extensions> 
  ...
</Package>

Note that the the two parameter groups I am defining for the desktop extension are not mandatory. I am doing it here only for this sample to later demonstrate how we can launch the extension with either user privileges or admin privileges.

7. Adding the launcher code

Now that solution and manifest are set up and configured correctly, we are ready to add the code to launch the extension from the UWP – as either elevated or regular process. First lets add some XAML to MainPage.xaml:

<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Button Content="Run Backgroundprocess as User"
            Click="ButtonUser_Click"
            HorizontalAlignment="Stretch"
            FontSize="32" Margin="3"/>
    <Button Content="Run Backgroundprocess as Admin"
            Click="ButtonAdmin_Click"
            HorizontalAlignment="Stretch"
            FontSize="32" Margin="3"/>
</StackPanel>

Before we can add the code-behind for the button click handlers, we need to add a reference to the UWP desktop extension SDK, because that’s where the FullTrustProcessLauncher API is defined. This API, btw, is not new in the 1809 update. It has been around since the Creators Update (SDK build 14393), but it’s recommended you reference the Desktop extension SDK that matches your target version, i.e. 17763 in our case.

addDeskExte

Next we add the two button click handlers in MainPage.xaml.cs. The key piece is the FullTrustProcessLauncher API. We are passing in the parametergroup to indicate to the extension whether we need to run it as user or with admin privileges. The processing of this parameter is application logic as we will see in the next step. Wrapping this call into an “IsApiContractPresent” is an important practice to ensure our universal app can run without crashing also on non-desktop devices that don’t support the FullTrustProcessLauncher API:

private async void ButtonUser_Click(object sender, RoutedEventArgs e)
{
  if (ApiInformation.IsApiContractPresent(
      "Windows.ApplicationModel.FullTrustAppContract", 1, 0))
  {
    await
      FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("User");
  }
}

private async void ButtonAdmin_Click(object sender, RoutedEventArgs e)
{
  if (ApiInformation.IsApiContractPresent(
      "Windows.ApplicationModel.FullTrustAppContract", 1, 0))
  {
    await
      FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("Admin");
  }
}

The final piece of code we need to add now goes into Program.cs of the “Elevated Extension” project. This is the code that handles the “/user” vs “/admin” parameter. If the latter has been specified we want to do the same as in part 2, i.e. relaunch the process as admin using the AppExecutionAlias. In case you are wondering why the strings we are handling here are different from the strings being passed to the launch API (“/user vs “User”), take a look at the ParameterGroup entries we added to the manifest. It maps a friendly name (“User”) to what could be in some case a fairly convoluted long string of parameters (“/user” in our case).

static void Main(string[] args)
{
    if (args.Length > 2)
    {
        if (args[2] == "/admin")
        {
          string aliasPath =
      Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
      @"\microsoft\windowsapps\ElevatedExtension.exe";

          ProcessStartInfo info = new ProcessStartInfo();
          info.Verb = "runas";
          info.UseShellExecute = true;
          info.FileName = aliasPath ;
          Process.Start(info);
          return;
        }
    }

    AutoResetEvent areWeDone = new AutoResetEvent(false);
    // do some task, then exit
    areWeDone.WaitOne(10000); 
}

8. Run and test the solution

Now we are ready to run and test our solution, before hitting F5 make sure the “Package” project is set as startup project and you have added the “allowElevation” capability in its appx manifest:

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="allowElevation" />
  </Capabilities>

When the UWP is up-and-running use the buttons to launch the extension either as user or as admin. Observe the result in TaskManager, remember that the extension has been coded to exit itself after 10sec in this example:

uwpElevated

This concludes my 3-part tutorial about the new ‘allowElevation’ capability. Please leave your feedback and questions as comments and I will do my best to address them in a timely fashion.

App Elevation Samples – Part 2

Picture4

If you haven’t read part 1 of this series yet, please start there first. I have covered the “hello world” version of a packaged app that requires elevation at launch time. In this post I want to show how a packaged app can dynamically elevate itself after it has been launched normally.

TL;DR

I already have the 1809 update and the Windows SDK 17763 (or later) installed. Just show me the code on GitHub …
https://github.com/StefanWickDev/AllowElevation-Samples/tree/master/Self%20Elevation

Step-by-Step Tutorial

1. Create a new desktop application

Make sure you run Windows 10 with the 1809 update and have the Windows SDK 17763 (or later) installed. Now create a new C# WPF desktop application in Visual Studio 2017 (can be any type of desktop application really):

FileNewProject

2. Convert to a packaged app

Add a Windows Packaging Project to the solution and call it “Self Elevation”:

addPackage2

Be sure to set both “Minimum” and “Target” version to 17763 or higher as build number 17763 maps to the 1809 update for Windows 10:

setVersions

Reference your WPF application from the packaging project by right-clicking on the “Application” node under the “Package” project and selecting “Add Reference …” :

addRef2

Now set the “Self Elevation” package project as your startup project in the solutions settings per below screenshot, and then hit F5 to see your empty packaged WPF app running.

setStartup2

3. Add code to elevate dynamically

In contrast to the previous example, we are not adding an application manifest to require elevation. We want the application to run at normal user privileges by default, and only request elevation dynamically when it is indeed needed.

To demonstrate this, let’s add a button to the XAML user interface in MainPage.xaml:

<Grid>
    <Button Content="Elevate Me!"                
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="48"
            Click="Button_Click"/>
</Grid>

And add the following code-behind in MainPage.xaml.cs, to re-launch the app with elevated privileges:

private void Button_Click(object sender, RoutedEventArgs e)
{
  string aliasPath =
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) +
    @"\microsoft\windowsapps\SelfElevationWPF.exe";

  ProcessStartInfo info = new ProcessStartInfo();
  info.Verb = "runas";
  info.UseShellExecute = true;
  info.FileName = aliasPath;
  Process.Start(info);

  App.Current.Shutdown();
}

To re-launch as admin we need to call Process.Start (or CreateProcess), but packaged apps can’t just be launched via these APIs. Luckily, packaged apps have the concept of AppExecutionAlias, which is a 0-byte EXE that facilitates the packaged app launch for classic invokers – such as CMD or Process.Start.

The first line is about getting the full path to this 0-byte AppExecutionAlias EXE file for our WPF app. Then we are creating the parameters for the Process.Start call. Key piece here is the “runas” verb, that will trigger the request for elevation for the relaunched app.

Finally, once we have relaunched ourselves as elevated, we can shutdown our current instance.

4. Declare the AppExecutionAlias

Packaged apps don’t have an AppExecutionAlias by default – the developer needs to be explicit about supporting this additional launch contract in the appx manifest. So let’s add this extension to our Package.appxmanifest file inside the <Application> node.

<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="uap3 desktop ...">
  ...

  <Extensions>
    <uap3:Extension Category="windows.appExecutionAlias"
                    Executable="Self Elevation WPF\Self Elevation WPF.exe"
                    EntryPoint="Windows.FullTrustApplication">
      <uap3:AppExecutionAlias>
        <desktop:ExecutionAlias Alias="SelfElevationWPF.exe" />
      </uap3:AppExecutionAlias>
    </uap3:Extension>
  </Extensions> 
  ...
</Package>

Now we are almost done – last thing to add to our appx manifest is the same “AllowElevation” capability that we have already used in the previous post:

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="allowElevation" />
  </Capabilities>

That’s it. Now we can test via F5 or by deploying the app package on our test machine. Use TaskManager to observe how the “Elevation” status changes from ‘false’ to ‘true’ after clicking the button and accepting the elevation request.

taskman

In the next post in this series I will cover how to trigger elevated code execution from a pure UWP process running in the app container by combining the concepts of the “fullTrustProcess” desktop extension, the “allowElevation” capability and the “appExecutionAlias” extension.

Part 3 – UWP with elevated extension

App Elevation Samples – Part 1

Picture0

Elevate your packaged apps with the new ‘allowElevation’ capability. With the 1809 update for Windows 10, apps can now declare this new capability in order to require elevation – or elevate themselves dynamically when needed. I will explain the new capability with three samples in this mini-series of posts:

1 – “Hello Elevated World” – simple desktop app that requires elevation (this post)
2 – Desktop app self-elevates dynamically when needed
3 – Elevated extension to a UWP application

Hello Elevated World

TL;DR

I already have the 1809 update and the Windows SDK 17763 (or later) installed. Just show me the code on GitHub …
https://github.com/StefanWickDev/AllowElevation-Samples/tree/master/Hello%20Elevated%20World

Step-by-Step Tutorial

1. Create a new desktop application

Make sure you run Windows 10 with the 1809 update and have the Windows SDK 17763 (or later) installed. Now create a new C# desktop console application in Visual Studio 2017 (can be any type of desktop application really):
createApp

2. Add a few lines for testing

static void Main(string[] args)
{
    Console.Title = "Hello Elevated World!";
    Console.WriteLine("Press any key ...");
    Console.ReadKey();
}

3. Add app manifest and require elevation

Project | Add New Item … | Application Manifest File
addManifestOpen the app.manifest file and change the requested execution level from “asInvoker” to “highestAvailable”:

<requestedExecutionLevel level="highestAvailable" uiAccess="false" />

Now verify that running the unpackaged app will run as elevated after popping up the UAC prompt (unless you have turned this off).

4. Convert to a packaged app

Add a Windows Packaging Project to the solution and call it “Package”:addPackage

Be sure to set both “Minimum” and “Target” version to 17763 or higher as build number 17763 maps to the 1809 update for Windows 10:

setVersions

Reference your desktop console application from the packaging project by right-clicking on the “Application” node under the “Package” project and selecting “Add Reference …” :addRef

Now set the “Package” project as your startup project in the solutions settings:setStartup

5. Final Step: add the ‘allowElevation’ capability declaration

If you now hit F5 to run your packaged console app you will see the expected error stating that the launch failed because it requires elevation. This is the correct default behavior for packaged applications.

error

With the new ‘allowElevation’ capability apps can now properly disclose that they will/may request to run with elevated privileges. So let’s add this capability in Package.appxmanifest:

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="allowElevation" />
  </Capabilities>

Hit F5 again and you will see your packaged app requesting elevated privileges and then run as elevated process:elevated

And we are done! You have now implemented your first packaged app that runs with elevated privileges. To create the actual distribution package for your application you would follow the usual steps: Select the “Package” project in solution explorer, then go to Project | Store | Create App Packages …

In the next post I will cover how a packaged app can self-elevate itself dynamically, for example to access a file that requires administrative privileges.

AllowElevation Samples Part 2 – Dynamic Self Elevation

 

 

 

Global hotkey registration in UWP

screenshot_octo

As promised, I am starting now to share a number of concrete scenario samples where UWP apps can light up common desktop features using desktop extensions. In this post my UWP app will register for system-wide hotkeys and handle them – even when the app is suspended.

TL;DR;

architecture

Just show me the code on github …
https://github.com/StefanWickDev/ExtensionGallery/tree/master/GlobalHotkey

Let me run the demo app from the Microsoft Store …
https://www.microsoft.com/store/apps/9NJW3LBC2Z3Q

 

Implementation

If you have followed my tutorial for desktop extensions you will find this one pretty straight forward, here are the detailed steps to implement this feature from scratch:

1. Project Setup

We are using the same project setup as explained in part 1 of the tutorial, with a UWP project, a C# console project and a Packaging project to tie it all together:

solution

Since we don’t want the console project to actually pop up a console window (or any window for that matter), we need to switch the output type from Console to Windows Application (I know, not very intuitive …):

outputtype

2. Declaring & Launching the Desktop Extension

Now we can declare the Desktop Extension in the Package.appxmanifest file. Be sure to declare this in the Packaging project, not the UWP project! While we are at it, let’s also declare the AppService. We will need it later to communicate the WM_HOTKEY event back to the UWP process.

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

Now with this in place, we can launch the Desktop Extension from our UWP app. Before we launch it, we will store our ProcessId in the local settings, so the extension can pick it up and track the lifetime of the UWP. This will help us to clean up and shutdown when the UWP process goes away.

// MainPage.xaml.cs
if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{
 Process process = Process.GetCurrentProcess();
 ApplicationData.Current.LocalSettings.Values["processID"] = process.Id;

 await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
}

When the extension launches, it will get to work right away and register the hot keys for our application. Before it can do so though, it needs to create a (headless) window in order to handle the incoming WM_HOTKEY messages, whenever the hot keys get pressed:

// HotkeyWindow.cs
public HotkeyAppContext()
{
 int processId = (int)ApplicationData.Current.LocalSettings.Values["processId"];
 process = Process.GetProcessById(processId);
 process.EnableRaisingEvents = true;
 process.Exited += HotkeyAppContext_Exited;
 hotkeyWindow = new HotKeyWindow();
 hotkeyWindow.HotkeyPressed += new HotKeyWindow.HotkeyDelegate(hotkeys_HotkeyPressed);
 hotkeyWindow.RegisterCombo(1001, Modifiers.Alt, Keys.S); // Alt-S = stingray
 hotkeyWindow.RegisterCombo(1002, Modifiers.Alt, Keys.O); // Alt-O = octopus
}

 

3. Communicating the WM_HOTKEY event

Once we get a HotkeyPressed event (i.e. when we process WM_HOTKEY in our headless window), we create an AppServiceConnection back to the UWP process and send the ID of the hotkey:

// HotkeyWindow.cs
private async void hotkeys_HotkeyPressed(int ID)
{
 // send the key ID to the UWP
 ValueSet hotkeyPressed = new ValueSet();
 hotkeyPressed.Add("ID", ID);

 AppServiceConnection connection = new AppServiceConnection();
 connection.PackageFamilyName = Package.Current.Id.FamilyName;
 connection.AppServiceName = "HotkeyConnection";
 AppServiceConnectionStatus status = await connection.OpenAsync();
 connection.ServiceClosed += Connection_ServiceClosed;
 AppServiceResponse response = await connection.SendMessageAsync(hotkeyPressed);
}

Now on the UWP side we handle this request and kick-off off the respective animal animation:

// MainPage.xaml.cs
private async void AppServiceConnection_RequestReceived(
                     AppServiceConnection sender,
                     AppServiceRequestReceivedEventArgs args)
{
 AppServiceDeferral messageDeferral = args.GetDeferral();
 int id = (int)args.Request.Message["ID"];
 switch(id)
 {
  case 1001://stingray
   await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
   {
    stingrayMove.Begin();
   });
   break;
   ...

 

4. NativeWindow Implementation

The underlying Win32 magic is implemented in the HotkeyWindow class, derived from NativeWindow:

// HotkeyWindow.cs
public class HotKeyWindow : NativeWindow
{
 private const int WM_HOTKEY = 0x0312;
 private const int WM_DESTROY = 0x0002;

 [DllImport("user32.dll")]
 public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
 [DllImport("user32.dll")]
 public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

 private List<Int32> IDs = new List<int>();
 public delegate void HotkeyDelegate(int ID);
 public event HotkeyDelegate HotkeyPressed;

 // creates a headless Window to register for and handle WM_HOTKEY
 public HotKeyWindow()
 {
  this.CreateHandle(new CreateParams());
  Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
 }

 public void RegisterCombo(Int32 ID, Modifiers fsModifiers, Keys vlc)
 {
  if (RegisterHotKey(this.Handle, ID, (int)fsModifiers, (int)vlc))
  {
   IDs.Add(ID);
  }
 }

 private void Application_ApplicationExit(object sender, EventArgs e)
 {
  this.DestroyHandle();
 }

 protected override void WndProc(ref Message m)
 {
  switch (m.Msg)
  {
   case WM_HOTKEY: //raise the HotkeyPressed event
    HotkeyPressed?.Invoke(m.WParam.ToInt32());
    break;

   case WM_DESTROY: //unregister all hot keys
    foreach (int ID in IDs)
    {
     UnregisterHotKey(this.Handle, ID);
    }
    break;
   }
   base.WndProc(ref m);
  }
 }

5. Lifetime Considerations

There are a couple of interesting app lifetime/lifecycle considerations and options in this sample that I want to touch on:

a. Bringing the UWP back to the foreground

When the hotkey is processed in the sample, the UWP is brought to the foreground, so you can enjoy the animation. This is optional and for other scenarios you may chose to leave it in the background. At any rate, we accomplish this by calling AppListEntry.LaunchAsync() on ourselves. This API when called on a running app has the desired effect to bring the app into the foreground, even when the UWP process was suspended.

b. Lifecycle of the Desktop Extension

In this sample the extension’s lifecycle is coupled with the UWP app’s lifecycle. This makes sense here since we want the hotkeys to only be registered while the UWP is running (or suspended), not beyond that. That’s why we are tracking the UWP process and will unregister the hotkeys and shutdown the extension when the UWP goes away.

However, the extension lifetime is in your control and for a different scenario you may choose to keep the extension process alive even after the UWP process goes away.

c. Lifetime of the AppServiceConnection

On every WM_HOTKEY message we open an AppServiceConnection to the UWP, which we will then close once it has been processed. We could also choose to keep the connection around for the lifetime of the process, but this would have the unwanted effect that the UWP would never suspend when minimized and therefore use more system resources than it should.

 

Credits

C# Code for the headless window to handle WM_HOTKEY inspired my this Stackoverflow answer: https://stackoverflow.com/questions/17133656/global-hotkeys-in-windowless-net-app/17136050

Tile icon for the Store sample provided by https://creativeninjas.com/ 

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