Windows 10 Anniversary Update (1607) and UWP Apps – Connected Apps and Devices via Rome

I tried something out for the first time today that really caught my interest and so I thought I’d share.

I first heard reference to ‘Project Rome’ back at //build/ 2016 but I must admit that I hadn’t found time to watch the session about it properly until recently and that’s partly just due to ‘never having time’ and also partly because I had started to watch it and it didn’t seem to immediately leap into the topic and so I’d put it to one side.

I should have had more faith Smile because it’s a really good session and it’s absolutely worth watching – click the image below to jump into it (it’s short at around 30mins).

image

There’s two discrete parts to that session.

The first part about ‘Web to App Linking’. The core idea there is that an app can register itself (under certain circumstances and user control) to be a handler for what I’d call a ‘portion of the HTTP/HTTPS namespace’ such that if the device comes across a URI like “http://www.myAmazingApp.com” then it’s possible that when the system launches that URI the result ends up in the app from ‘My Amazing App’ rather than the standard system action which would be to launch the browser.

That’s written up really well here;

Support web-to-app linking with app URI handlers

But that //build/ 2016 session then switches at the 15m mark to talk about ‘Project Rome’ which feels like it is doing some of the legwork on the idea of an experience which spans devices rather than being confined to a single device.

There’s been lots of talk at Microsoft around ‘the mobility of the experience’ and it feels to me that ‘Project Rome’ has a part to play in that idea by providing some pieces of a framework which allows for asking questions about a user’s set of devices and then taking higher level actions across that set of devices.

I wanted to try it out for the first time so having watched the video above I went and got my developer laptop running 1607 and I started to see if the docs for classes like RemoteSystem that I spotted in the video were online (they are).

I then paused for a while as I hadn’t really considered whether I had multiple devices running Windows 10 1607 to test on until I remembered that I had a home all-in-one that I’d upgraded to 1607 which is sitting in another room on standby with some active logon sessions.

It’s worth saying that the set of devices that is being worked with here is the set of devices that you see when you go to;

http://account.microsoft.com

and then have a look at your list of Devices there;

Untitled

and I have this home office PC which shows up there.

Capture

As is often the case, I’d started to write a little bit of code when I found that there’s an article which brings some of these classes together;

Launch an app on a remote device

and that led me to the higher level documentation;

Connected apps and devices (Project “Rome”)

and my ‘Hello World’ is pretty much a duplicate of what happens in that former article in that I made simple XAML page which presents a data-bound ListView and a Button;

  <Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition
        Height="Auto" />
    </Grid.RowDefinitions>
    <ListView
      Margin="8"
      Header="Remote Systems"
      ItemsSource="{x:Bind RemoteSystems}"
      SelectionMode="Single"
      SelectedItem="{x:Bind SelectedSystem,Mode=TwoWay}">
      <ListView.ItemContainerStyle>
        <Style
          TargetType="ListViewItem">
          <Setter
            Property="HorizontalContentAlignment"
            Value="Stretch" />
        </Style>
      </ListView.ItemContainerStyle>
      <ListView.ItemTemplate>
        <DataTemplate
          x:DataType="remote:RemoteSystem">
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition />
              <ColumnDefinition />
              <ColumnDefinition />
              <ColumnDefinition />
              <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock
              Text="{x:Bind DisplayName}" />
            <TextBlock
              Text="{x:Bind Id}"
              Grid.Column="1" />
            <TextBlock
              Text="{x:Bind Kind}"
              Grid.Column="2" />
            <TextBlock
              Text="{x:Bind Status}"
              Grid.Column="3" />
            <TextBlock
              Text="{x:Bind IsAvailableByProximity}"
              Grid.Column="4" />
          </Grid>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    <Button
      Margin="8"
      Grid.Row="1"
      HorizontalContentAlignment="Center"
      HorizontalAlignment="Stretch"
      VerticalAlignment="Stretch"
      Click="{x:Bind OnLaunchUri}">
      <Button.Content>
        <StackPanel Orientation="Horizontal"
                    HorizontalAlignment="Center">
          <SymbolIcon
            Symbol="Map"/>
          <TextBlock
            Margin="4,0,0,0"
            Text="launch map of New York" />
        </StackPanel>
      </Button.Content>
    </Button>
  </Grid>

and then added some code-behind to see if I could get the basics of this to work;

namespace App24
{
  using System;
  using System.Collections.ObjectModel;
  using System.ComponentModel;
  using System.Linq;
  using System.Runtime.CompilerServices;
  using System.Threading.Tasks;
  using Windows.System;
  using Windows.System.RemoteSystems;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;

  public sealed partial class MainPage : Page, INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    public MainPage()
    {
      this.InitializeComponent();
      this.Loaded += OnLoaded;
      this.RemoteSystems = new ObservableCollection<RemoteSystem>();
    }
    public ObservableCollection<RemoteSystem> RemoteSystems
    {
      get; private set;
    }
    public RemoteSystem SelectedSystem
    {
      get
      {
        return (this.selectedSystem);
      }
      set
      {
        if (this.selectedSystem != value)
        {
          this.selectedSystem = value;
          this.FirePropertyChanged();
        }
      }
    }
    // Note, this is bound to from an x:Bind, just trying that out for the
    // first time.
    public async void OnLaunchUri()
    {
      if (this.SelectedSystem != null)
      {
        var request = new RemoteSystemConnectionRequest(this.SelectedSystem);

        await RemoteLauncher.LaunchUriAsync(
          request, 
          new Uri("bingmaps:?cp=40.726966~-74.006076&lvl=10&where=New%20York "));
      }
    }
    void FirePropertyChanged([CallerMemberName] string prop = "")
    {
      this.PropertyChanged?.Invoke(
        this, new PropertyChangedEventArgs(prop));
    }
    async void OnLoaded(object sender, RoutedEventArgs e)
    {
      var status = await RemoteSystem.RequestAccessAsync();

      if (status == RemoteSystemAccessStatus.Allowed)
      {
        this.watcher = RemoteSystem.CreateWatcher();
        this.watcher.RemoteSystemAdded += OnAdded;
        this.watcher.RemoteSystemRemoved += OnRemoved;
        this.watcher.RemoteSystemUpdated += OnUpdated;
        this.watcher.Start();
      }
    }
    async void OnUpdated(RemoteSystemWatcher sender, RemoteSystemUpdatedEventArgs args)
    {
      await this.DispatchAsync(
        () =>
        {
          // Laziness on my part not wanting to create a view model for a remote system
          // with property change notification etc.
          this.Remove(args.RemoteSystem.Id);
          this.Add(args.RemoteSystem);
        }
      );
    }
    async void OnRemoved(RemoteSystemWatcher sender, RemoteSystemRemovedEventArgs args)
    {
      await this.DispatchAsync(
        () =>
        {
          this.Remove(args.RemoteSystemId);
        }
      );
    }
    async void OnAdded(RemoteSystemWatcher sender, RemoteSystemAddedEventArgs args)
    {
      await this.DispatchAsync(
        () =>
        {
          this.Add(args.RemoteSystem);

        }
      );
    }
    void Add(RemoteSystem remoteSystem)
    {
      this.RemoteSystems.Add(remoteSystem);
    }
    async Task DispatchAsync(DispatchedHandler action)
    {
      await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, action);
    }
    void Remove(string remoteSystemId)
    {
      var entry = this.RemoteSystems.SingleOrDefault(system => system.Id == remoteSystemId);

      if (entry != null)
      {
        this.RemoteSystems.Remove(entry);
      }
    }
    RemoteSystem selectedSystem;
    RemoteSystemWatcher watcher;
  }
}

and so most of this code is just using the RemoteSystemWatcher class and, having asked for permission, this is going to deliver RemoteSystem information to me via events for Added/Removed/Updated. I use that to build up an ObservableCollection which is bound into the ListView on the screen and I also data-bind the SelectedSystem to the selected item in that ListView.

I liked that the RemoteSystemWatcher follows what feels like a pattern that’s consistent with (e.g.) looking for AllJoyn devices or looking for Bluetooth LE devices – there are quite a few ‘watcher’ objects within the UWP APIs and this one feels natural to use.

I hooked the button press to then construct a RemoteSystemConnectionRequest instance and to do a LaunchUriAsync on that remote system and I’m letting the default options stand here but it’s possible to specify the list of apps that I’d prefer handle the request remotely and also any launch parameters to be passed to the remote app.

I must admit that the first time that I ran this code, I was not expecting success. It seemed complex enough to be unlikely to work and I figured that there’d likely be some configuring and messing around to do.

I got success Smile 

The little UI listed the all-in-one office machine that had been sitting in another room on standby the whole time I’d been writing this code;

Capture

and I tapped on the button, went off to visit that machine and (sure enough) there was the Maps app displaying a map of New York.

I like this a lot Winking smile

Being an awkward type, I logged myself out of that machine to see if the status would go ‘Unavailable’ and it didn’t seem to and so I tried to launch the Maps app remotely again without an active logon session on that machine and this caused me to add a bit of code to display the return value from the LaunchUriAsync method;

        var response = await RemoteLauncher.LaunchUriAsync(
          request, 
          new Uri("bingmaps:?cp=40.726966~-74.006076&lvl=10&where=New%20York "));

        var messageDialog = new MessageDialog(response.ToString(), "return status");

        await messageDialog.ShowAsync();

and, sure enough, this now returned a RemoteSystemUnavailable status even though the RemoteSystem itself was still claiming availability so I’m not sure quite how that ‘availability’ status works and when it updates.

I also noticed a very interesting thing in that when I logged back in to that system the Maps app popped up with a map of New York – it seems like my request has been queued somewhere and delivered at a later point in time but I’d need to dig into that more to understand whether that’s by design or not.

Being an awkward type, I took the machine that is acting as a client here off the network that it shares with the ‘mthome’ PC and moved it to a 4G network to see what might happen there and it worked just the same way as you might expect.

I wondered how I might get these 2 PCs to be ‘proximally’ connected without a cloud connection and so I played with a few options there but have yet to figure out quite how that works for 2 PCs, maybe I’d have more luck with a phone for that scenario but it needs returning to.

Being able to launch an app on a remote machine with parameters opens up a tonne of scenarios which involve transferring a task from one device to another and especially when you think that those parameters might be some identifier to a piece of shared state in the cloud but there’s another part of this ‘Rome’ framework that interests me here too and that’s the idea of;

Communicate with a remote app service

and that gives us the possibility of an app on one device making use of app services from an app on another device.

That’s a topic for another post but it seems really quite powerful in that it has the potential to open up an opportunity to run the constituent pieces of an application on the machines that I think are best suited to the task at the time. I could see examples where I might choose to run some processing on a remote device because it has more processing power or a better (or cheaper, or more reliable) connection to the internet.

I want to dig into that but, again, that’s for another post…

Windows 10 Anniversary Update, Desktop App Converter Preview– Experimenting with Open Live Writer

Following on from my previous post;

Windows 10 Anniversary Update, Desktop App Converter Preview – Starting from Source

What I’d done in that previous post was to make a desktop application that really didn’t “do anything” and so it was fairly easy for me to create a .APPX package from the .EXE that it consisted of and deploy and run it.

That desktop application doesn’t depend on any specific file locations or any particular registry key and so it’s going to run ok both in a ‘regular desktop’ context and in a ‘UWP’ context.

As another, more realistic experiment, I wanted to take more of a real desktop application and see what it’s like to try and package that up into the .APPX format.

I used to build desktop applications for a living but I don’t any more and so I don’t have the source for a reasonably sized desktop application kicking around and so I had to be a little imaginative and have a think about where I might borrow a desktop application from.

Given that I use Live Writer to write my blog posts, my imagination didn’t have to wander too far to come up with the idea of using;

Open Live Writer

and so off I went to Github and cloned that repository and opened it up inside of Visual Studio “15” preview before making sure that I could actually build it and run/debug it as a regular-desktop-app which was fairly easy.

There’s a few projects in here;

Capture

and having built and run it, I then wanted to try and understand how Open Live Writer built up an installer for itself which it seems to drop out into this folder as part of the build process;

Capture

and I could see that there was the PostBuild.CreateInstaller project which has a post-build step to run a createinstaller.cmd file and the contents of that .cmd file are on the web here.

That .cmd file seems to use nuget.exe to pack a bunch of files listed in OpenLiveWriter.nuspec from the build output folder into a NuGet package.

It then uses squirrel to take that NuGet package and build the OpenLiveWriterSetup.exe from it that’s in the screenshot above.

It then wraps that up into a chocolatey package (I think Confused smile).

With those pieces, there are two (or more) ways that I could go to package this into a .APPX install.

  1. Run the Setup.exe into the desktop app converter and collect the output as per my first blog post.
  2. Add something to the build process to make a .APPX in the first place as per my second blog post.

Either way, I figured that I’d be needing a certificate so I started there.

Step 1 – Making Certificates

From my prior experiments, I know that I’m going to need a signing certificate and so I made one with;

MakeCert.exe -r -h 0 -n “CN=Open Live Writer” -eku 1.3.6.1.5.5.7.3.3 -pe -sv my.pvk my.cer

pvk2pfx.exe -pvk my.pvk -spc my.cer -pfx my.pfx

and I then installed the certificate into my local machine’s trusted roots certificate store.

I also dropped the various pieces I’d used in a top-level Certificates folder within the Open Live Writer folder structure so that I knew where to find them again.

Capture

Experiment 1 – Using the Desktop Converter

I took the OpenLiveWriterSetup.exe file that the build process emits and I ran it through the DesktopAppConverter.ps1 script with the command line below;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\apps\OpenLiveWriterSetup.exe -Version 1.0.0.0 -MakeAppx -Destination .\OpenLiveWriterOut -Publisher “CN=Open Live Writer” -PackageName “OpenLiveWriter” -Log out.txt

but I found that it seemed to run for a very long time and checking of the log file revealed that it was always waiting for the installation to complete and that never seemed to happen.

I wasn’t sure what the installer might be doing but one guess was that it might be related to the installer running the app at the end of the installation and so appearing to never end.

To see if I could mitigate that, I tried to use the “–silent” flag and in an interactive context that seemed to work (i.e. installation without app launch) but when I tagged it on to the desktop conversion process;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\OpenLiveWriterSetup.exe -Version 1.0.0.0 -MakeAppx -Destination .\OpenLiveWriterOut\ -Publisher “CN=Open Live Writer” -PackageName “OpenLiveWriter” -InstallerArguments “–silent”

I found that the tool would still sit there for a long time and a check of the log file revealed the last entry was always;

“Waiting for the installer process to complete inside Isolated Environment”

and it was tricky to know what was happening inside of that environment – was the installer trying to do something like install an earlier version of .NET? Did it relate to the installation targeting the user’s local folder rather than a central location? Was the installer showing a dialog or similar?

It’s possible that I’m just missing something around this particular type of installation but, so far, I haven’t managed to get this to work.

I moved on to the other approach.

Experiment 2 – Altering the Build Script

Manifests, Icons

I wondered if I could alter the build script that the Open Live Writer guys have built in order to package the app ‘automatically’ into a .APPX format as part of the build rather than taking the Setup.exe from the build and trying to record its output with the desktop converter tool.

I made a manifest in line with my previous post, changing a few names to ‘Open Live Writer’ where appropriate.

I’ve copied it below for completeness;

<?xml version="1.0" encoding="utf-8" ?>
<Package
   xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
   xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
   xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
  <Identity Name="c2030cf7-74d5-479f-aa19-f907e4145698"
    ProcessorArchitecture="x64"
    Publisher="CN=Open Live Writer"
    Version="1.0.0.0" />
  <Properties>
    <DisplayName>Open Live Writer</DisplayName>
    <PublisherDisplayName>Open Live Writer</PublisherDisplayName>
    <Description>Open source version of the classic Live Writer blogging application</Description>
    <Logo>assets\StoreLogo.png</Logo>
  </Properties>
  <Resources>
    <Resource Language="en-us" />
  </Resources>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.14316.0" />
  </Dependencies>
  <Capabilities>
    <rescap:Capability Name="runFullTrust"/>
  </Capabilities>
  <Applications>
    <Application 
      Id="OpenLiveWriter" 
      Executable="OpenLiveWriter.exe" 
      EntryPoint="Windows.FullTrustApplication">
      <uap:VisualElements
       BackgroundColor="#464646"
       DisplayName="Open Live Writer"
       Square150x150Logo="assets\Square150x150Logo.png"
       Square44x44Logo="assets\Square44x44Logo.png"
       Description="Open Live Writer Blogging App" />
    </Application>
  </Applications>
</Package>

and I made logos at size 50×50, 150×50 and 44×44 based on the image that I found in OpenLiveWriter.CoreServices\Images\SplashScreenLogo.jpg although I suspect there are better logos I could have chosen here.

I added a top level folder named AppxArtefacts to hold my assets and appxmanifest.xml;

Capture

and I added the certificate pieces into a top level folder named Certificates although I really only need the .pfx file to sign the .appx as part of the build script.

I then set about seeing if I could alter the script which creates the installer.

Hacking the Build Script

I’m no expert on anything and especially not on NuGet and I’m even more clueless when it comes to Squirrel so I tried to add something into the build script that wouldn’t destroy what was already there and just move a few things around rather than dramatically change anything.

With that in mind, I added these lines to the script in between the piece which makes the NuGet package and the piece which runs Squirrel.exe on it.

:: mtaulty1 - unpack that nuget package into a folder named UnpackedNuget
"%LocalAppData%\Nuget\Nuget.exe" install -ExcludeVersion OpenLiveWriter -Source %cd% -OutputDirectory UnpackedNuget 
ECHO Unpacked that package.

:: mtaulty2 - copy the AppxArtefacts and that unpacked content into a folder named AppxContents
:: hard-coding the lib\net451 pieces here isn't great.
mkdir AppxContents
xcopy /s "AppxArtefacts\*.*" .\AppxContents
xcopy /s "UnpackedNuget\OpenLiveWriter\lib\net451\*.*" .\AppxContents

:: mtaulty3 - make an appx package out of that. Hard-coding the location of the tool here isn't great.
"c:\program files (x86)\Windows Kits\10\bin\x86\makeappx.exe" pack /d AppxContents /p OpenLiveWriter.appx

:: mtaulty4 - sign that appx package
"c:\program files (x86)\Windows Kits\10\bin\x86\signtool.exe" sign -f Certificates\my.pfx -fd SHA256 -v OpenLiveWriter.appx 

:: mtaulty5 - tidy up
rmdir /s /q AppxContents
rmdir /s /q UnpackedNuget

The attempt here is to make a .APPX file out of the pieces that are in the AppxArtefacts folder that I’ve already pre-populated mixed with the contents of the build outputs that have just been added into the NuGet package by the existing script.

Sure enough, out of this drops a .APPX file and that file install the app just fine.

Capture

Capture

Trying it Out

The app that’s installed by this process runs ‘just fine’ but it’s not entirely surprising that it then hits upon a bit of a snag…

Capture

When installed through its regular installer, this app would end up in the equivalent of c:\users\mtaulty\appdata\local\OpenLiveWriter\app-0.6.0 and when it runs up for the first time within that folder it has the permission to create a folder named plugins if it wants to.

When packaged as a .APPX, the application doesn’t have the permission to create that Plugins folder and hence it fails at startup when it tries to do it.

I wondered why the code would think it was ok to create a ‘Plugins’ folder within its own installation folder and so I dug in and saw that the code in PostEditorPluginManager.cs seems to only attempt to create this folder in the DEBUG build of the app and so maybe this is more of a developer convenience than anything else.

Repackaging the Release Code, not the Debug Code

With that in mind, I went and built the released version and tried running that up instead and that seemed to work out pretty well in the sense that it looks like I can get Open Live Writer installed and running from that .APPX package. Whether all of the functionality then works, I’m not so sure but I tried a few dialogs and it all seemed to be hanging together and so I have 2 Open Live Writers on my Start Menu;

Capture

and the ‘Windows App’ launches and does the right thing.

Wrapping Up

It’s important to say that this is (clearly) just an experiment and I didn’t worry about making the app target .NET 4.6.x and I didn’t overly worry about 32-bit/64-bit or anything like that.

I also didn’t spend too much time concerning myself over whether the app ere does/doesn’t use squirrel to do some level of automatic updating because that could clearly perhaps clash with the app having been installed via .APPX and you perhaps don’t need both the Windows Store and Squirrel both trying to keep the app automatically up to date at the same time Smile

So, to do this properly would require more thought but I wanted to experiment with it and hence the write up here and, of course, if you know why the converter tool can’t get beyond the “waiting for the installer to finish” stage then do leave me a comment and let me know.

Windows 10 Anniversary Update, Desktop App Converter Preview – Starting from Source

Following up on this post;

Initial Experiments with the “Desktop App Converter” Preview

and continuing the theme of ‘experimentation’ with the Desktop App Converter I wanted to see what it was like to start from a place where I had the source code for a desktop application and wanted to build it out into a .appx installer that could potentially go into the Windows Store.

It’s worth flagging that there are official samples that you can look at around this area and you’d find those on github here;

Desktop app bridge to UWP Samples

For my experiments, I thought I’d start small.

That’s “Hello World”-style small Smile

Making Hello World

I’m a world-class expert when it comes to building “Hello World” and so I made a new WPF application and had it show “Hello World” on the screen.

I had to dig deep but within only a few days I had this working Winking smile

Capture

and I even “Gold plated” it by adding a ViewBox around the TextBlock here Winking smile

Ok, so I’ve got a WPF “Hello World” on .NET Framework 4.6.2 and Visual Studio knows how to build a .exe from it and so how do I package that into a .appx format given that I don’t have an installer for it right now?

Making a Blank App Manifest

There’s documentation to help on this topic so I followed it;

Manually convert your Windows desktop application to a Universal Windows Platform (UWP) app

and in doing so I had to think about a few things. Firstly, I made myself a new certificate and I did that by following the docs and using the specific command;

MakeCert.exe -r -h 0 -n “CN=mikedesktop” -eku 1.3.6.1.5.5.7.3.3 -pe -sv my.pvk my.cer

pvk2pfx.exe -pvk my.pvk -spc my.cer -pfx my.pfx

and that gave me a common name of mikedesktop for the publisher here and I put that certificate into my machine’s trusted roots.

I also dug into this document that I found useful;

App packages and deployment (Windows Runtime apps)

and I also found it useful to build a Blank UWP app in Visual Studio and look at the AppxManifest.xml file that gets build as part of that process by way of having a comparison that’s in a ‘known good state’.

I added a few files to my blank WPF project as below;

Capture

I should make it clear that I’m trying to make my life easy here rather than trying to do something smart and I wrote that appxmanifest.xml file to look like this;

<?xml version="1.0" encoding="utf-8" ?>
<Package
   xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
   xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
   xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
  <Identity Name="a2050516-ad7b-4170-ac1d-cb23a21de780"
    ProcessorArchitecture="x64"
    Publisher="CN=mikedesktop"
    Version="1.0.0.0" />
  <Properties>
    <DisplayName>Hello World Desktop App</DisplayName>
    <PublisherDisplayName>Mike Taulty</PublisherDisplayName>
    <Description>Amazing hello world desktop application</Description>
    <Logo>assets\StoreLogo.png</Logo>
  </Properties>
  <Resources>
    <Resource Language="en-us" />
  </Resources>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.14316.0" />
  </Dependencies>
  <Capabilities>
    <rescap:Capability Name="runFullTrust"/>
  </Capabilities>
  <Applications>
    <Application 
      Id="HelloWorldDesktopApp" 
      Executable="HelloWorldDesktopApp.exe" 
      EntryPoint="Windows.FullTrustApplication">
      <uap:VisualElements
       BackgroundColor="#464646"
       DisplayName="Hello World Desktop App"
       Square150x150Logo="assets\Square150x150Logo.png"
       Square44x44Logo="assets\Square44x44Logo.png"
       Description="This is my Hello World desktop app" />
    </Application>
  </Applications>
</Package>

and I made sure that the appxmanifest.xml, and the files within my assets folder all got copied to the output folder as illustrated on appxmanifest.xml below;

Capture

I should say that I copied the contents of the assets folder out of a regular, blank UWP project.

It’s also perhaps worth flagging that I set up both x86 and x64 platform targets and I worked solely within the x64 configuration as I think (i.e. not sure) that support for desktop apps packaged in .appx is 64-bit right now and I wasn’t sure how a ‘AnyCPU’ packaging would or wouldn’t work. I need to dig into that more to be sure of what does/doesn’t work.

Making a .APPX

With that set up, I went via what I thought might be the simplest route to getting a .APPX file created and I added the makeapp.exe tool to my project’s ‘post-build step’ (graphically rather than using the MSBuild AfterBuild task) in order to make that .appx;

“c:\program files (x86)\windows kits\10\bin\x86\makeappx.exe” pack /d “$(TargetDir).” /p “$(TargetDir)$(ProjectName).appx”

and that seemed to work reasonably well although it’s worth saying that I’m adding things like .pdb files into my .appx package here because they happen to be in the output folder from my build.

Signing the .APPX

I added a second post-build step to sign the .APPX using the certificate that I’d bundled into my Visual Studio project;

“c:\program files (x86)\windows kits\10\bin\x86\signtool.exe” sign -f “$(ProjectDir)Certificates\my.pfx” -fd SHA256 -v “$(TargetDir)$(ProjectName).appx”

and that also seemed to work out fine and I had a signed .APPX file.

Installing the .APPX

Once I’ve got a .APPX signed with a certificate that my machine trusts, I can simply ask the Windows shell to launch the .APPX (or use add-appxpackage) and it pops some UI;

Capture

and then it installs the app (which was too quick for me to capture as a screen shot) and then the app’s ready to go;

Capture

and runs up as you’d expect;

Capture

and that’s all fine.

Calling into UWP APIs

A desktop app can make use of some of the UWP APIs whether or not that desktop app has been packaged as a UWP app. There’s a good article here that talks about this;

UWP APIs callable from a classic desktop app

and that partitions APIs into;

  • APIs that need the calling app to have a package identity (which I read as “packaged as UWP”)
  • APIs that will work without a package identity.

beyond that, there’s a more detailed list on this page;

Supported UWP APIs for converted desktop apps

So in my desktop application I should be able to make use of an API like SpeechSynthesizer and so I thought I’d try that out and it wasn’t quite as simple as I might have hoped but it wasn’t too bad.

The steps that I took went something like this.

Edit the .csproj file to add TargetPlatformVersion

The first thing I did was to unload the project in visual studio (using the right mouse menu on the solution explorer) so that I could edit the XML and I used that to add;

Capture

and that then switches on this section of the References dialog where I added references to Windows.media and Windows.foundation;

Capture

That might work for some scenarios but I found that types like IAsyncOperation<> weren’t present and so I went out and referenced Windows.winmd instead as below;

Capture

and that reference is coming from c:\program files (x86)\Windows Kits\10\UnionMetadata\Windows.winmd.

However, once I started writing async code, this left me without definitions of GetAwaiter() on those pesky IAsyncOperation<> types and I then need to go off and reference System.Runtime.WindowsRuntime.dll as below;

Capture

and that reference is coming from c:\program files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\4.5\System.Runtime.WindowsRuntime.dll.

I’d stress that it’s the 4.5 version I took here rather than the 4.5.1 version as referencing that seemed to give me runtime problems.

And I made sure that I set “Copy Local” to false on that assembly;

Capture

And that got me to the point where I could compile some code and so I added a button to my UI and added this code behind it;


    async void OnSpeak(object sender, RoutedEventArgs e)
    {
      // This is a UWP API.
      SpeechSynthesizer synth = new SpeechSynthesizer();

      using (var stream = await synth.SynthesizeTextToStreamAsync("I am a desktop app"))
      {
        // We need a .NET stream
        using (var netStream = stream.AsStreamForRead())
        {
          // SoundPlayer is a .NET Framework API.
          SoundPlayer player = new SoundPlayer(netStream);

          player.Play();
        }
      }
    }

and, believe it or not, that actually worked although I’m somewhat unsure whether I should Dispose() both of those stream types above or just one of them.

I’ll admit that I had to chase down quite a few web references to get to the point where that code would compile and run correctly – the text above makes it look like it would take maybe 5 minutes whereas I’d say it took about 60 minutes.

I then used my post-build steps to repackage this as a UWP once again and verified that it would deploy and work in that context as well and it worked fine.

Using the Visual Studio 15 Preview Template

Having to keep running through that process of making the desktop app, building it into a .APPX and then installing/uninstalling it is a bit painful so there’s a Visual Studio packaging project template that helps in that area. I referenced it from my earlier blog post and it’s here;

Desktop to UWP Packaging Project for Visual Studio “15”

As far as I understand it, this project template supports a particular workflow where you already have a .APPX layout for your application on disk. Maybe you made that manually as I did in this post or maybe you made it via the Desktop App Converter as I did in my previous post.

Once you’ve got it, what you can then do is;

  • Configure a list of the outputs from your project that would need to be re-deployed into that .APPX layout whenever you build your projects in Visual Studio
  • Set the packaging project as your startup
  • Press F5 and have Visual Studio build, copy over the outputs and debug your UWP packaged app in its UWP context

which takes some of the manual steps out of installing/uninstalling packages and attaching debuggers and so on.

To try this out, albeit on my simplest of examples, I first followed the steps in the docs in that I added a packaging project to my solution;

Capture

and then made sure that this project referenced my other project so that it builds after that project;

Capture

and then I revisited my other project and removed the post-build steps that I had been using to make the .APPX file and sign it.

I then went and got the .APPX file and copied it into a folder c:\temp\PackageLayout on my system where I renamed it to .ZIP and extracted the contents before getting rid of both the .ZIP and the .APPX leaving just the files and folders that were within it;

Capture

Clearly, there are some extra pieces in there like the .vshost files but that comes from me building the .APPX from the output folder of the Debug build process in the first place.

With that in place, I changed the properties of the packaging project such that the “Package Layout” option pointed to my artefacts as below;

Capture

And then, as per the instructions, I edited the AppXPackageFileList.xml which the project uses to copy files from the build output into the package layout as those files get re-built.

My file ended up looking like this;

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  
  <PropertyGroup>
    <MyProjectOutputPath>c:\temp\helloworlddesktopapp\helloworlddesktopapp\</MyProjectOutputPath>
  </PropertyGroup>
  <ItemGroup>
    <LayoutFile Include="$(MyProjectOutputPath)bin\x64\Debug\HelloWorldDesktopApp.exe">
      <PackagePath>$(PackageLayout)\HelloWorldDesktopApp.exe</PackagePath>
    </LayoutFile>
  </ItemGroup>

</Project>

and, while there’s a couple of embedded, hard-coded paths in there, it does seem to work and I can build and set this packaging project as my start up project and hitting F5 runs it up.

Capture

Whose Desktop App Is It Anyway?

I’ve now got these 2 projects with 2 views of the same thing. If I set the HelloWorldDesktopApp project to be my startup project then I run a ‘regular’ desktop app;

Capture

and if I set the PackagingProject project to be my startup then I run a ‘UWP’ desktop app;

Capture

and they look pretty similar to me and they’re running the same code and it works in both contexts so how can I be convinced that there’s anything different going on here?

I thought that perhaps I could try one of those UWP APIs that require a package identity in order to function and see what it returns in the two different contexts here and the first API that I could think of was the API that would return the current package identity.

So, I modified my code such it was trying to make use of Package.Current.Id as below;

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      var packageId = "package id is not set";

      try
      {
        packageId = $"package id is {Package.Current.Id.FullName}";
      }
      catch (InvalidOperationException)
      {
      }
      this.txtPackageId.Text = packageId;
    }

and now I get 2 different results based on whether I run the HelloWorldDesktopApp project or the PackagingProject;

Capture

versus;

Capture

and that makes it clearer that these two contexts are quite different.

Windows 10, UWP and Composition–More Light, Less Shade

Just a quick follow up on the post that I published where I took some baby steps in using lighting and shadows with the Visual Layer on Windows 10 1607.

Windows 10, UWP and Composition–Light and Shade

In that post, I’d failed to get a simple XAML scene lit with a PointLight and then James pinged me on Twitter saying;

image

and I’d actually seen the sample in question which shimmers some text;

Capture

and I hadn’t realised that it was a XAML based sample so I stared at it and it took me maybe 2-3 minutes to figure out what’s different about it from my attempt and I thought it was worth sharing what it taught me.

My attempt to do lighting with XAML involved a piece of ‘UI’ like this;

  <Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    x:Name="grid">
    <Image
      x:Name="image"
      Source="assets\img102.jpg"
      Stretch="UniformToFill" />
  </Grid>

and then some code-behind which looks like this;

namespace App23
{
  using System.Numerics;
  using Windows.UI;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Hosting;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
      this.Loaded += OnLoaded;
    }
    void OnLoaded(object sender, RoutedEventArgs e)
    {
      var gridVisual = ElementCompositionPreview.GetElementVisual(this.grid);

      var compositor = gridVisual.Compositor;

      var pointLight = compositor.CreatePointLight();
      pointLight.CoordinateSpace = gridVisual;
      pointLight.Color = Colors.Yellow;
      pointLight.Offset = new Vector3(
          (float)this.grid.ActualWidth / 2,
          (float)this.grid.ActualHeight / 2,
          50);

      pointLight.Targets.Add(gridVisual);
    }
  }
}

Now, this doesn’t work for me and the fact that it didn’t work caused me to take the classic programmer’s thinking path of “Somebody Else’s Problem”;

“ah, this hasn’t worked so I have to assume that there must be some limitation in the composition layer which means that you can’t apply lighting to XAML elements”

and it was pretty easy for me to put it to one side and go and do something else instead.

However, if I’d looked at the ‘Text Shimmer’ sample beforehand I’d have been confident that the composition layer worked fine and it was my own code that was broken.

What was broken about it? Object lifetimes. Here’s code that works for me. Hopefully it’s not a ‘red-herring’ Smile

namespace App23
{
  using System.Numerics;
  using Windows.UI;
  using Windows.UI.Composition;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;
  using Windows.UI.Xaml.Hosting;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
      this.Loaded += OnLoaded;
    }
    void OnLoaded(object sender, RoutedEventArgs e)
    {
      var gridVisual = ElementCompositionPreview.GetElementVisual(this.grid);

      var compositor = gridVisual.Compositor;

      this.pointLight = compositor.CreatePointLight();
      this.pointLight.CoordinateSpace = gridVisual;
      this.pointLight.Color = Colors.Yellow;
      this.pointLight.Offset = new Vector3(
          (float)this.grid.ActualWidth / 2,
          (float)this.grid.ActualHeight / 2,
          50);

      this.pointLight.Targets.Add(gridVisual);
    }
    PointLight pointLight;
  }
}

and all I had to do was promote the PointLight variable to be a member of my Page and life is good and the light does what I expect it to.

Now, to be honest, I’ve hit this type of issue working with the composition APIs before and so I should have expected it and fixed my code but I think my intuitive expectation was that having created a light and handed it ‘to the system’ I’d expect the system to keep hold of it rather than asking me to keep hold of it.

Maybe that expectation comes from me applying XAML-like ‘retained graphics’ thinking that doesn’t necessarily apply here and this is a slight odd scenario because;

  • I ask the Compositor to create the PointLight
  • I ask the light to target a Visual
  • I don’t have to ‘insert’ the light into any kind of scene or anything like that

and so I hadn’t assumed that I was the owner of the PointLight and needed to keep it around although it’s worth saying that the PointLight is disposable so maybe that should have made it clear that the ownership lives with me as a consumer of the light.

That’s my learning for this post Smile

In looking around this area of ownership, I did spot that others are asking questions around object lifetimes and disposing of some of these pieces so it’s something that docs/guides could perhaps do more to help users of the APIs here understand how the lifetimes are meant to work;

image

but at least on this simplest of samples I got resolution as to why my code didn’t work when the sample code did!

Windows 10, UWP and Composition–Light and Shade

One of the functional areas that the composition APIs offer are the abilities to add both lighting and shadowing effects to UI and I was interested in trying them out after some of my efforts with general effects like blur.

It’s worth saying that the official samples have a DropShadow sample and some lighting samples within them but I wanted to experiment for myself and especially around how I might create this kind of effect that the UI team shared on one of their newsletters – this picture;

image

had me thinking “how do you do that?” and it got my interest.

Shadows and Fog

I’ll admit that there’s another version of this blog post where I fumbled around a bit trying to understand what the Shadow property was for on a SpriteVisual and trying to make use of it in a way that really didn’t work out for me and caused me to go looking for the manual.

Fortunately, at the time that I was writing the post, I did a search and I found this brand new article on MSDN;

Using the Visual Layer with XAML

which has a recipe for drop shadows and that helped a lot and caused me to more or less start over and rework the post entirely as I had spent a little time barking up the wrong tree.

I also realised that I should have watched this video from //Build as well around effects, lighting and shadows before trying to make it up on my own Smile 

image

Armed with some of what I saw in these materials, my first experiment with shadows was to make a blank UI.

  <Grid
    x:Name="grid"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

  </Grid>

and to write a tiny bit of code which set up a SpriteVisual which did not paint itself at all but which did have a Shadow set for it as below;

      var compositor = ElementCompositionPreview.GetElementVisual(this.grid).Compositor;
      var spriteVisual = compositor.CreateSpriteVisual();
      spriteVisual.Size = new Vector2(100, 100);
      var dropShadow = compositor.CreateDropShadow();
      dropShadow.Offset = new Vector3(10, 10, 0);
      dropShadow.Color = Colors.Orange;
      spriteVisual.Shadow = dropShadow;
      ElementCompositionPreview.SetElementChildVisual(this.grid, spriteVisual);

and the interesting thing to me was that this Visual does indeed paint a shadow even though it doesn’t paint any content.

My earlier attempts had assumed something quite different and that’s why I’d fumbled around quite a lot before getting to what now seems like a simple realisation – a SpriteVisual can paint both content and a shadow and, by default, the shadow is rectangular;

Capture

The other key part of what I learned in that recipe is that the DropShadow has a Mask property that you can use to shape the shadow.

I’m unsure how to get hold of such a mask for arbitrary content but, from the recipes post, a new method has been added to Image, TextBlock and ShapeGetAlphaMask() which makes this very easy for those specific element types.

So, if my challenge was to add a shadow to a TextBlock then my UI can become;

  <Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
      <Grid
        x:Name="grid" />
      <TextBlock
        x:Name="txtBlock"
        Text="Drop Shadow"
        FontSize="48"
        HorizontalAlignment="Center"
        VerticalAlignment="Center" />
    </Grid>
  </Grid>

and my code can become;

      var compositor = ElementCompositionPreview.GetElementVisual(this.grid).Compositor;
      var spriteVisual = compositor.CreateSpriteVisual();
      spriteVisual.Size = this.grid.RenderSize.ToVector2();

      var dropShadow = compositor.CreateDropShadow();
      dropShadow.Mask = this.txtBlock.GetAlphaMask();
      dropShadow.Offset = new Vector3(10, 10, 0);
      spriteVisual.Shadow = dropShadow;

      ElementCompositionPreview.SetElementChildVisual(this.grid, spriteVisual);

and that works out well;

Capture

but I think the heavy lifting here is really being done by TextBlock.GetAlphaMask() and I wondered how I might do this for arbitrary content in the absence of a helper method?

For instance, TextBox doesn’t have a GetAlphaMask() property and neither does Slider and so on.

It’s worth noting that in the official samples, there’s a use of a circular image to mask a shadow in the “Shadow Playground” such that you can have either a rectangular shadow;

Capture

or a circular one;

Capture

and that makes it fairly clear that if you know the shape of the mask up-front then you can represent it by an image and use that to mask the shadow.

But, let’s say that I do have arbitrary content in XAML, how do I apply a shadow to it? Something like a Slider?

I searched around a little and found a similar question here;

Are shadows only for images?

and that was useful to me for 2 reasons.

One was that my local samples clone had got out of step with the remote (and I hadn’t noticed) and so I’d been staring at out of date samples thinking that my git pull had been updating them when it hadn’t been.

The other was that it seemed to answer my question with the supplied CompositionShadow user control until I took a look into it and realised that it’s hard-wired to assume that the content of the control is one of the three that have had a GetAlphaMask() method added to them – i.e. either a TextBlock, Image or Shape.

This then led me back to the same question that I started with which would be how I could apply a shadow to an arbitrary piece of UI which isn’t;

  • Image, TextBlock, Shape
  • Rectangular
  • Defined by a pre-defined shape that can be masked by an image of that shape

Now, I must admit that maybe this question isn’t particularly important given that this list probably covers 95%+ of the useful cases anyway but it still left me curious and I’m not sure how I’d do it at the time of writing other than to possibly try and render the XAML element as an image and then use that as a mask of some sort.

I gave it a quick try even though it’s likely to be prohibitively expensive to do it and I use the CompositionImageBrush class that I wrote in this post and with this UI;


  <Grid
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid
      HorizontalAlignment="Center"
      VerticalAlignment="Center">
      <Grid
        x:Name="grid"
        HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch" />
      <Slider
        x:Name="slider"
        Width="284" />
    </Grid>
  </Grid>

and this chunk of code-behind;

    async void OnLoaded(object sender, RoutedEventArgs e)
    {
      var gridVisual = ElementCompositionPreview.GetElementVisual(this.grid);

      var spriteVisual = gridVisual.Compositor.CreateSpriteVisual();
      spriteVisual.Size = this.slider.RenderSize.ToVector2();

      var bitmap = new RenderTargetBitmap();

      await bitmap.RenderAsync(
        this.slider,
        (int)this.slider.ActualWidth,
        (int)this.slider.ActualHeight);

      var pixels = await bitmap.GetPixelsAsync();

      using (var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
        pixels,
        BitmapPixelFormat.Bgra8,
        bitmap.PixelWidth,
        bitmap.PixelHeight,
        BitmapAlphaMode.Premultiplied))
      {
        var brush = CompositionImageBrush.FromBGRASoftwareBitmap(
          gridVisual.Compositor,
          softwareBitmap,
          new Size(bitmap.PixelWidth, bitmap.PixelHeight));

        var dropShadow = gridVisual.Compositor.CreateDropShadow();
        dropShadow.Mask = brush.Brush;
        dropShadow.Offset = new Vector3(50, 50, 0);
        spriteVisual.Shadow = dropShadow;
      }
      ElementCompositionPreview.SetElementChildVisual(this.grid, spriteVisual);
    }

then it “kind of worked”;

Capture

although, clearly, that work would need re-doing every time the value changed and the slider resized and that kind of thing so it’s probably not a very “practical” solution Smile

No doubt there’s a better way and if you know of one, let me know in the comments below and I’ll update the post but it feels to me like the addition of the 3 GetAlphaMask() methods to Image, TextBlock and Shape signals the idea that there’s not perhaps a simple, one-method-fits-all approach that the UI team could easily add (otherwise, they’d almost certainly have done so Smile).

Lights

In terms of experimenting with lights, I spun up a UI with an image in it that looked like this;

 <Grid
    Background="Black"
    x:Name="grid">
    <Image
      Stretch="UniformToFill"
      Source="ms-appx:///Assets/cat.jpg" />
  </Grid>

and some code that tried to create a PointLight and point it at that UI;

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      var gridVisual = ElementCompositionPreview.GetElementVisual(this.grid);
      var compositor = gridVisual.Compositor;

      var pointLight = compositor.CreatePointLight();
      pointLight.CoordinateSpace = gridVisual;
      pointLight.Color = Colors.White;

      pointLight.Offset = new Vector3(
        (float)this.grid.ActualWidth / 2,
        (float)this.grid.ActualHeight / 2,
        100);

      pointLight.Targets.Add(gridVisual);
    }

and it didn’t seem to have much of an effect so I thought that I’d create the visual to display the image myself using the ImageLoader class that’s part of the Windows UI Labs samples. So, I took the Image element out of the UI altogether and wrote this code instead;

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      var gridVisual = ElementCompositionPreview.GetElementVisual(this.grid);
      var compositor = gridVisual.Compositor;

      var imageLoader = ImageLoaderFactory.CreateImageLoader(compositor);

      var image = imageLoader.LoadImageFromUri(new Uri("ms-appx:///Assets/cat.jpg"));
      var surfaceBrush = compositor.CreateSurfaceBrush(image);

      var spriteVisual = compositor.CreateSpriteVisual();
      spriteVisual.Brush = surfaceBrush;
      spriteVisual.Size = this.grid.RenderSize.ToVector2();

      var pointLight = compositor.CreatePointLight();
      pointLight.CoordinateSpace = gridVisual;
      pointLight.Color = Colors.White;

      pointLight.Offset = new Vector3(
        (float)this.grid.ActualWidth / 2,
        (float)this.grid.ActualHeight / 2,
        100);

      pointLight.Targets.Add(gridVisual);

      ElementCompositionPreview.SetElementChildVisual(this.grid, spriteVisual);
    }

and that gave me more like the desired effect;

Capture

so I’m not sure what went wrong in my trying to apply this to an existing XAML element. Once the light is created, it’s not too difficult to animate it around a little and give it that sort of ‘roaming spotlight’ effect. I added in a little bit of code;

      var centreX = (float)this.grid.ActualWidth / 2.0f;
      var centreY = (float)this.grid.ActualHeight / 2.0f;
      var deltaX = centreX * 0.9f;
      var deltaY = centreY * 0.9f;
      var upperZ = 250;
      var lowerZ = 100;

      var animation = compositor.CreateVector3KeyFrameAnimation();
      animation.Duration = TimeSpan.FromSeconds(5);
      animation.InsertKeyFrame(0.0f, new Vector3(centreX - deltaX, centreY, upperZ));
      animation.InsertKeyFrame(0.25f, new Vector3(centreX, centreY - deltaY, lowerZ));
      animation.InsertKeyFrame(0.5f, new Vector3(centreX + deltaX, centreY, upperZ));
      animation.InsertKeyFrame(0.75f, new Vector3(centreX, centreY + deltaY, lowerZ));
      animation.InsertKeyFrame(1.0f, new Vector3(centreX - deltaX, centreY, upperZ));
      animation.IterationBehavior = AnimationIterationBehavior.Forever;

      pointLight.StartAnimation("Offset", animation);

and that creates a reasonable animated effect with very little code;

and it’s pretty easy to get that spun up and working without writing tonnes of code around it. There’s 3 other types of light to experiment with here, AmbientLight, DistantLight and SpotLight and I’d intend to try replacing my PointLight with the latter two to see how that works out.

But that’s for another post…

Windows 10 1607/Anniversary Update and the Device Portal

One of the things that I noticed yesterday on my dev PCs running Windows 10 1607 or Anniversary Update was that the Windows Device Portal has appeared and (I think) in a new incarnation on these PC builds.

I was in the ‘developer settings’ for this particular PC and I saw the option to switch on the Device Portal with or without authentication for local network connections;

Capture

and so I did just that and then pointed a browser from another PC on the same network at that endpoint and got the updated UI;

image

and it’s kind of fun to take some of the installed apps here and use the “Start” button to launch one of those apps on the remote PC.

This was possible on phone and/or IoT but there’s something about doing it on a PC build that feels a bit different. It’s also possible (as on the other devices) to use this as a way to deploy .appx packages and their dependencies.

The new UI offers drag/drop/dock tabs so that you can remove areas;

image

or drag/drop them around;

image

to reposition;

image

and it’s also possible to create your own workspaces with whatever tools you want via the new workspace option;

image

which lets you name it;

image

and then add tools to it, including the tool named ‘Coming Soon’ Winking smile

image

Beyond the UI changes, the other things that I noticed here which feel new are…

The ‘Xbox Live’ section although that’s currently in a ‘Coming Soon’ state.

The ‘File explorer’ section looks like it would be useful for working with files that live within the part of the file system which is devoted to storage for Store apps and if you followed my last post here you’d know that I’d installed the InkScape app as a UWP packaged desktop app onto this machine and you can see its section of the file system here;

image

It feels like the ‘Running Processes’ tab is getting more like ‘Remote Task Manager’ with each build that comes out but it’s similar to what’s been there before

image

whereas the other part of the Task-Manager-like-UI is provided by the ‘Performance’ tab which looks to have had an update;

image

and most of the rest of the UI is as I remember it, albeit factored into these new, resizable panels.

I’d have to say that while I was trying this out I did find that the UI could become a bit unresponsive and I’d have to refresh the browser page from time to time. I’d also say that those graphs on that ‘Performance’ tab above don’t seem to be showing me actual graphs so I’m not sure whether that’s a bug or not.

The other thing to remember with this portal is that it surfaces not only a UI but also set of REST APIs which means that you can do things like deploy or undeploy packages using these APIs.

As an example, I might try and find a particular package on this machine…

$response = Invoke-RestMethod http://192.168.0.14:50080/api/app/packagemanager/packages
$packages = $response.InstalledPackages
$calculator = $packages | Where {$_.Name -eq ‘Calculator’}
echo $calculator.PackageFamilyName

and that works quite nicely and I can go on to uninstall that package should I want to try and get rid of the Calculator and so on.

Initial Experiments with the “Desktop App Converter” Preview

Big Note: this post is a bunch of experimentation with the Desktop App Converter. I reference the official documents which should be your guide to what can/can’t be done and what’s supported and so on. These notes are really just the results of me experimenting with a few things as I find that’s the best/only way to learn anything and I find writing it down as I’m going along quite useful both at the time and afterwards.

Even in this modern world of mobile-first, cloud-first computing and a heterogenous client computing environment it’s fair to say that desktop applications on Windows (and on Mac) are still hugely important and billions of people are using them every day.

And, very importantly, continuing to develop them.

One of the first set of questions that I got back in 2012 when the Windows Store launched were along the lines of;

  • How do I put my existing desktop application into that Store? ( you could, but only to ‘advertise it’ )
  • How do I make use of those new WinRT APIs from my existing desktop application? ( no great answer there )
  • If I rewrite my desktop application to be ‘WinRT’ (now expanded to UWP) then what do I do about the APIs that I use which are missing? ( brokered components added something here )

These were all fair questions and, at the time, I mostly found myself having to say “not sure”.

Here in 2016, the picture is a bit clearer in that;

  1. The “Desktop App Converter” means that, with the Anniversary Update, desktop applications can truly go into the Windows Store for discovery, purchase, installation, updating, etc.
  2. Desktop applications can make use of a lot of the UWP APIs available.
  3. It’s still very fair to say that the set of APIs available to a desktop app is huge in comparison to those available to a UWP app but the UWP API set has grown a lot in the past 4 years and so includes some of those areas that developers were asking for back in 2012.

I wanted to dig into (1) and (2) above and the arrival of the Anniversary Update seemed like the perfect time to do that and that’s what this post (and maybe some follow-on-posts) is about.

If you’ve not looked at the “Desktop App Converter” then I’d suggest the //build 2016 session as a starting point.

image

and then you can go and read the docs over here;

Desktop App Converter Preview

which includes a link to go and get the preview converter tool itself.

I downloaded both the converter and the base image (3.3GB in total) and then ran through the setup instructions which all worked out fine although my machine did seem to reboot at the;

PS C:\> .\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-1XXXX.wim -Verbose

stage but, apart from this, it all seemed to work out.

As a note, the first time I downloaded I got presented with only a file for 14352.wim whereas I think I need a 14393.wim to work on the Anniversary Update. A revisit to the page seemed to sort that out for me so take care if you see something similar because the .wim file has to match the version of the OS you’re running on.

From here, it seems to me that there’s at least two routes that I could go with this;

  1. I have a desktop application which I build and would like to package as a .appx in order to be able to deploy that way and put in the Store etc.
  2. I have a desktop application which I have some kind of installer for and I would like to “play its installer actions” into a .appx or manually create a .appx in order to be able to deploy that way and put in the Store etc.

In the former case, a .appx file is just a fancy .zip file with some structure to it but it’s probably easier to have a tool make one if possible and Visual Studio “15” (Preview 3) does have one of those things and you can grab it from here;

Desktop to UWP Packaging Project for Visual Studio “15”

but, in the first instance I thought I’d try out the latter case and see if I could take existing installers and “play them” into a .appx using the converter.

Which desktop app should I try this out with? I tried a few.

Trial 1 – Spotify

I went to Spotify.com, downloaded their Windows installer and then wondered whether it would work or not because it’s one of those installers that goes on to download more bits from the web.

Regardless, I thought that I’d give it a whirl as everything’s a learning experience so I executed the command;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\SpotifySetup.exe –PackageName Spotify -Publisher “CN=mtaultyTest” -Version 1.0.0.0 -Destination .\SpotifyOut -MakeAppx

You can perhaps see that I have a folder for the converter, another for Apps and another for output and I’m asking the converter to make a .appx file.

Out of this popped a .appx file Smile and the tool also emitted a registry.dat, a AppxManifest.xml, some basic imagery and a VFS folder which looked like it contained the contents of all the files that the installer had tried to put down onto my system.

So far, so good! I then went through the process of signing the .appx file as described in the documentation as I can never, ever remember how to use makecert and signtool.

One thing I’d flag here – you need to make sure that the common name of your certificate here is exactly the same as the –Publisher argument that you specified to DesktopAppConverter.ps1 otherwise you’ll find that you get the “SignerSign()” error.

The other thing I’d flag is to not forget that you need to trust the certificate that you just made via makecert or the package deployment will fail.

At that point, I had a .appx file and so I went about trying to use add-appxpackage to install it and it worked.

I was actually expecting it to fail because the tool had given me a warning about not being able to figure out my app’s installation folder but, no, it worked.

pic1

followed by silence…

pic2

and then it’s installed…

pic3

and it works…

pic4

and, no, I mean it really works Smile

pic5

so that was fairly impressive.

Trial 2 – InkScape

I use InkScape quite a lot for playing with vector graphics and so I thought I’d give that a try and downloaded their 64-bit MSI and wondered whether I’d have success with it.

I was initially encouraged as the .msi has a /quiet option so I figured that might help me out and so I tried that out and hit an error;

error

Something relating to the installer returning 1639 rather than 0, 1641, 3010.

I wondered what that might mean and so I made sure that I could install the app on my system manually using the “/quiet /n” switches and that seemed to work ok.

I tried making use of the –LogFile option to see if that would help me figure out what the problem was. The log file was pretty decent but I couldn’t figure out where the 1639 was coming from based on its contents.

I wondered if the 1639 might be a red herring and so I tried the –InstallerValidExitCodes option to see what that would do for me. I was glad to see that it was an option Smile although I doubted that the installer was setting this return value for no reason.

This got me here;

Capture

Now, I actually got the W_INSTALL_PATH_NOT_DISCOVERED warning when I did Spotify so I figured that I may be able to ignore that but it seems that the W_EXE_NOT_DISCOVERED was going to cause me a problem.

I rooted around the VFS folders that the tool had made for InkScape and I couldn’t see any .exe in there myself and this was in contrast to the VFS folder that I’d seen when I’d processed Spotify.

So, for some reason the InkScape installation wasn’t working out for me here.

I wondered whether I could use a different option for the InkScape installer and so I tried the /passive flag rather than the /quiet flag. That makes the command line that I used;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\inkscape-0.91-x64.msi -PackageName InkSpace -Publisher “CN=mtaultyTest” -Version 1.0.0.0 -Destination .\InkSpaceOut -MakeAppx -InstallerArguments “/passive”

and out popped an .appx file with no warnings. I signed it. Around this time I realised that I’d called it InkSPACE rather than InkSCAPE – my mistake.

I installed it;

Capture

and I ran it…

Capture

and it seemed to work just fine Smile

Capture

I suspect that my trials/errors here were more related to me not passing the right flags to the installer rather than anything that the Desktop App Converter was doing.

Trial 3 – Paint.NET

I use Paint.NET at all the time and (as the name suggests) it’s clearly a .NET application and so I thought I’d try that out as I suspected that I might hit a snag if it isn’t currently built on .NET 4.6.1 plus I also suspected that I might have to do something to deal with the x86/x64/AnyCPU nature of the code.

I downloaded the installer for Paint.NET – unlike InkScape, it’s a .exe rather than a .msi and when you run it, it seems to extract a bunch of files somewhere.

I first off ran with this command prompt;

PS C:\temp\dac> .\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\paint.net.4.0.10.install.exe -PackageName PaintNet -Version 1.0.0.0 -Publisher “CN=mtaultyTest” -MakeAppx -InstallerArguments “/skipConfig” -Destination ./PaintNetOut

and I hit the same snag that I’d hit when trying out InkScape – the two warnings around W_EXE_NOT_DISCOVERED and W_INSTALL_PATH_NOT_DISCOVERED leading to the error E_CANNOT_MAKEAPPX_WITHOUT_EXE which seems entirely reasonable.

I wondered whether there was a tweak that I could make to the parameters again and I tried the “/auto” option to the installer but got the same result.

It felt like the Paint.NET installer wasn’t installing itself into the containerised environment.

However, the installer does have an option /createMsi which makes a .MSI for Paint.NET so I wondered whether that might give different results and I asked it to make that .MSI (it makes both an x86 and x64 variant) and then took the 64-bit version and tried to see if I could use the Desktop App Converter to make a .appx from that.

If I ran the .MSI that I’d created interactively then it worked fine and Paint.NET got installed whereas, typically, different combinations of parameters to both the Desktop App Converter and the Paint.NET .MSI seemed to cause the Desktop App Converter to error in different ways but the error generally related to either;

  • The .MSI has exited with a code other than 0. Now, I could try and hack around this by telling the converter that the error code was “ok to ignore” but that didn’t help because I’d then get…
  • The converter can’t find the EXE inside of the bits that the .MSI has installed.

And I’d find that if I dug around in the VFS folder that the converter had created then I couldn’t find the .EXE file either so I could understand why the converter was having trouble but why was the installer not installing the .EXE file in the first place?

This had me poking around in the log file that the Desktop App Converter produces and it’s pretty decent with the revelant bit for me here seeming to be;

Running Installer in Isolated Environment

[2016-08-05T11:10:42] No installer valid exit codes specified. Using defaults: 0 1641 3010
[2016-08-05T11:10:42] Replace any occurence of <log_folder> in -InstallerArguments with C:\shared\logs
[2016-08-05T11:10:42] Creating Isolated Environment
[2016-08-05T11:10:43] Starting the Isolated Environment
[2016-08-05T11:10:45] Logging known folder locations in Isolated Environment
[2016-08-05T11:10:45] Command line: “C:\shared\dac\bin\KnownFolderDump.exe” “C:\shared\dac\out\KnownFolders.xml”
[2016-08-05T11:10:45] Waiting for known folder mapping process to finish in Isolated Environment
[2016-08-05T11:10:56] Running installer command in Isolated Environment
[2016-08-05T11:10:56] Command line: “C:\WINDOWS\system32\msiexec.exe” /quiet DISABLEADVTSHORTCUTS=1 INSTALLSTARTMENUSHORTCUTS=1 /norestart /i “C:\shared\installer\PaintDotNet_x64.msi” /l*v “C:\shared\logs\install_PaintDotNet_x64.log”
[2016-08-05T11:10:56] Waiting for installer process to complete inside Isolated Environment
[2016-08-05T11:11:41] Stopping the Isolated Environment
[2016-08-05T11:11:42] Exporting changes made by your installer inside the Isolated Environment
[2016-08-05T11:11:48] Disposing of the Isolated Environment
[2016-08-05T11:11:48] Moving any logs in C:\DesktopAppConverter\021d9821-ef35-4641-95dd-0f3f4c1411ed\shared\logs to .
[2016-08-05T11:11:48] Moving any dac output in C:\DesktopAppConverter\021d9821-ef35-4641-95dd-0f3f4c1411ed\shared\dac\out to C:\DesktopAppConverter\021d9821-ef35-4641-95dd-0f3f4c1411ed\output\dac
[2016-08-05T11:11:48] Cleaning up by removing isolated environment shared folder C:\DesktopAppConverter\021d9821-ef35-4641-95dd-0f3f4c1411ed\shared
[2016-08-05T11:11:48] Isolated Install Complete. Moving output from C:\DesktopAppConverter\021d9821-ef35-4641-95dd-0f3f4c1411ed\output\export to C:\DesktopAppConverter\021d9821-ef35-4641-95dd-0f3f4c1411ed\output\packageRoot
[2016-08-05T11:11:48] Checking installer exit code 0 to ensure it is indicative of success
[2016-08-05T11:11:48] Isolated Installer Run Complete!
[2016-08-05T11:11:48] ————————————————–

and that all looked pretty good and I can see that it copies the log that the installer makes out of the isolated environment for me so I had a look in there and there was something that caught my eye amongst all of the spew which was as far as I could tell (and I’m not good at reading .MSI logs) the installer did seem to be laying down the .EXE file;

MSI (s) (28:20) [11:10:59:161]: Executing op: FileCopy(SourceName=PAINTD~1.EXE|PaintDotNet.exe,SourceCabKey=_BC1135F9DC4A8788EA40968E8DE47726,DestName=PaintDotNet.exe,Attributes=512,FileSize=1764048,PerTick=65536,,VerifyMedia=1,,,,,CheckCRC=0,Version=4.10.6033.29846,Language=0,InstallMode=58982400,,,,,,,)
MSI (s) (28:20) [11:10:59:161]: File: C:\Program Files\paint.net\PaintDotNet.exe;    To be installed;    Won’t patch;    No existing file

and it seemed to be putting it into C:\Program Files\paint.net which didn’t seem to be unreasonable but I wondered about this installation into c:\program files\ as there’s a comment in the Desktop App Converter help page which sounds ‘similar’ if not the same to this;

“A .NET app compiled with “AnyCPU” build option will fail to install if the main executable or any of the dependencies were placed under “Program Files” or “Windows\System32”. As a workaround, please use your architecture specific desktop installer (32 bit or 64 bit) to successfully generate an AppX package.”

Whether this was my issue or not, I wasn’t sure but I thought I’d see if I could alter that behaviour and see if it made any difference and so I first asked the Paint.NET installer to make me an .MSI;

.\paint.net.4.0.10.install.exe /createMsi /auto /skipConfig TARGETDIR=”c:\paintnet”

and I was hoping that this .MSI would install into c:\paintnet\ rather than Program Files. I then took the 64-bit .MSI package that made and ran it through the converter;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\PaintDotNet_x64.msi -Publisher “CN=mtaultyTest” -Version 1.0.0.0 -Destination .\PaintNetOut -MakeAppx -PackageName PaintNet -InstallerArguments “/passive” -LogFile .\log.txt

and that produced me a .appx file albeit once again with a warning around finding binaries. I then signed that .appx as before and installed it.

Capture

and, ran it;

Capture

and, sure enough, there’s Paint.NET;

Capture

Now, I must admit that I’m pretty sure that Paint.NET does some NGEN’ing as part of its initial installation and I’m very unsure whether this code that I’m running here as been NGEN’d or not – I suspect not but I haven’t dug in and tried to figure that out.

But, after quite a bit of fumbling (and some learning around where log files go and what they look like) I got it working Smile

Trial 4 – Skype

On the Windows 10 Anniversary Update, there’s a preview of a UWP version of Skype already installed which I really like the look of but I wondered if I could get the Skype desktop offline installer to package itself up as a .appx and install that desktop application that way because I still use that desktop Skype app on my main Surface Pro 3 system today (running 1511).

I went off to Skype and got their installer;

Skype Full Installer

From a bit of web searching I found that this installer supports arguments /VERYSILENT and /NOLAUNCH and so I wanted to see if I could pass them to the app converter. My initial attempt produced this;

PS C:\temp\DAC> .\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\skypesetupfull.exe -Publisher “CN=mtaultyTest” -Version 1.0.0.0 -Destination .\SkypeOut -MakeAppx -PackageName SkypeDesktop -InstallerArguments “/VERYSILENT”, “/NOLAUNCH”, “/SP-“,”/SUPPRESSMSGBOXES”,”/NOCANCEL”,”/NORESTART” -LogFile .\log.txt

WARNING: DesktopAppConverter : warning ‘W_DISCOVERED_EXE_NOT_FOUND’: Discovered shortcut indicates the app executable
should be ‘\WINDOWS\Installer\{FC965A47-4839-40CA-B618-18F486F042C6}\SkypeIcon.exe’, but this file cannot be found.
MakeAppx will fail until you fix the Application Executable property in the AppxManifest.xml.

and so it feels like the converter is assuming that SkypeIcon.exe is the application’s executable here but I don’t think it is. I think that should, perhaps, be Skype.exe and I poked around the outputted folder structure within the VFS directory and could see that this seems to end up in ProgramFilesX86\Skype\Phone\Skype.exe.

So…maybe the .EXE is there but the converter isn’t finding it and there’s the –AppExectuable parameter which, I think, tells the converter to stop trying to work this out for itself. So, I tried that;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\skypesetupfull.exe -Publisher “CN=mtaultyTest” -Version 1.0.0.0 -Destination .\SkypeOut -MakeAppx -PackageName SkypeDesktop -InstallerArguments “/VERYSILENT”, “/NOLAUNCH”, “/SP-“,”/SUPPRESSMSGBOXES”,”/NOCANCEL”,”/NORESTART” -LogFile .\log.txt -AppExecutable “c:\program files (x86)\Skype\Phone\Skype.exe”

and I can’t say that every one of those flags to the Skype installer is required as such but it worked out and I got an .appx out of it and got past my error.

I signed the .appx and installed Skype from the .appx package but I realised that my victory here had been short-lived as I saw;

Capture

So I think I’d need to revisit this one and try a bit harder if I wanted to get this one to work fully.

Trial 5 – VLC

I use VLC quite a bit and so I went and grabbed their installer;

VLC Installer

which seemed to support a /S parameter for silent mode. I ran that first, interactively, on my system to see if it worked and it did seem so and so I uninstalled it and then set about running that installer through the app converter;

.\DesktopAppConverter\DesktopAppConverter.ps1 -Installer .\Apps\vlc-2.2.4-win32.exe -Publisher “CN=mtaultyTest” -Version 1.0.0.0 -Destination .\VLCOut -MakeAppx -PackageName VLC -InstallerArguments “/S” -LogFile .\log.txt

and that all worked out fine as far as I could tell. However, when I ran the signtool on the resulting .appx file I got an error which I checked by setting the APPXSIP_LOG environment variable to 1 and then re-running;

C:\temp\DAC\VLCOut>signtool sign -f ..\my.pfx -fd SHA256 -v .\VLC.appx
The following certificate was selected:
    Issued to: mtaultyTest
    Issued by: mtaultyTest
    Expires:   Sat Dec 31 23:59:59 2039
    SHA1 hash: AB1863ED8ABB6280978B9D96A4690B8E50DC5634

Done Adding Additional Store
ERROR: [AppxSipCustomLoggerCallback] File has malformed certificate: VLC\uninstall.exe

So, I get a malformed certificate on this uninstall.exe which, ironically, I guess doesn’t really need to be there given that installation is now handled by .appx.

I figured that I could maybe address this for this particular instance by taking off the –MakeAppx parameter and re-running the command to make the output folder without the .appx file.

Then, I deleted the offending uninstall.exe so that it wouldn’t be packaged into the .appx before running;

MakeApp.exe /d VLCOut /p VLC.appx

to make the .appx myself and then signing it with the signing tool.

That seemed to work ok and so I ran the .appx file;

Capture

and let the app install and in so much as I could tell I got VLC installed;

Capture

Final Scores

Ok, from my initial experiments here it looks like it’s a 4-1 win to me in the sense that I got 4 out of 5 of these installers to play nice with me and the desktop app converter and I learned quite a lot along the way about the process and the sorts of things that I might look for in taking an installer like this and trying to convert it to .appx format.

There’s a lot more to learn but it’s a start Smile