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;
For my experiments, I thought I’d start small.
That’s “Hello World”-style small
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
and I even “Gold plated” it by adding a ViewBox around the TextBlock here
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;
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;
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;
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;
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;
and runs up as you’d expect;
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;
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;
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;
and that then switches on this section of the References dialog where I added references to Windows.media and Windows.foundation;
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;
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;
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;
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;
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;
and then made sure that this project referenced my other project so that it builds after that project;
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;
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;
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.
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;
and if I set the PackagingProject project to be my startup then I run a ‘UWP’ desktop app;
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;
versus;
and that makes it clearer that these two contexts are quite different.
man, I just wanna hang out with people like you and try new things in visual studio all the time. I would enjoy being paid for that.
Pingback: Windows 10 Anniversary Update, Desktop App Converter Preview– Experimenting with Open Live Writer – Mike Taulty
Will this work with apps in the system taskbar?
Thanks,
Ruslan
Hi,
Are you asking whether an app that has been packaged as a .APPX can then be added to the TaskBar? If so, the answer’s yes as I think that’s true for any UWP app.
Or…are you asking whether an app that has been packaged as a .APPX can display UI in the system tray?
Mike
Actually both. I’ve got an app, sort of a timer, which sits in the TaskBar but also has a popup/context menu. Some to the context menu item open a WPF UI.
I guess I will give it try and see how it behaves.
Thanks,
Ruslan