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, 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…

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