In the previous post, I described how to use Windows Packaging Project in Visual Studio to build a MSIX package manually.

I also mentioned the difference between running the application normally (when your WPF/WinForms app project is marked as a startup project) and running the application from a package (when the package project is the startup project).

For normal development, you’d ll be probably running the application normally, but it is useful to have the opportunity to test the package directly from the Visual Studio – there may be tiny differences between these two modes.


Continuous everything

Once your application is ready, you’ll want to distribute it to the end users. Commonly there is also a small group of “beta testers” or “early adpoters” who’ll want to get preview releases. You may also want to have internal builds.

Therefore, we can create several release channels:

  • Dev (not automated, built directly from Visual Studio)
  • Preview (built and published automatically for each commit in master)
  • Release (built automatically, published manually)

Since the Dev version will not go through the CI/CD process, let’s update the Package manifest to be the manifest for the Dev version.

  1. Set the Package Identity to “yourappuniquename-dev”.
  2. Add a “[DEV]” suffix to the Display Name so you can easily recognize the installed versions.

When you set the Package project as a startup project and run it, the application should install automatically in the Start menu.


Automated builds of MSIX packages

I am using Azure DevOps which is a natural choice for most .NET developers. However, if you prefer any other CI/CD solution, don’t worry – everything we’ll do can be automated using command line tools, and Azure DevOps only provides UI for these operations.

Creating the new Build pipeline

I have created a new Build pipeline using the new YAML pipelines. They don’t have such comfortable UI as the classic pipelines, but they store the entire pipeline together with the source code and can be versioned with full utilization of branches, which is super-important in larger projects because the structure of the solution evolves over time, there are always some new components and so on.

If you store your source codes within Azure Repos, it is the easiest way. If your repo is elsewhere (External Git, SVN), choose the appropriate option.

My sample project is on GitHub, so I am choosing it.

Creating the new pipeline

On the next step, I’ve selected the repository and authenticated with my GitHub account.

Now select .NET Desktop even if your app is in .NET Core – we’ll remove most of the code anyway:

Choosing starter template

Azure DevOps now opens the YAML editor you can use to define your build process.

YAML editor


Buidling the packages

Our process consists of 3 steps:

  • Udpdating the package manifest to match the correct channel. Since the manifest is a XML file, we can do this using PowerShell.
  • Building the solution (with some MSBuild parameters added so the package is produced)
  • Publishing the MSIX and related files as build artifacts

Since we want to have the Preview and Production consistent, we’ll build both within the same build from the same sommit. Thanks to this, when you make sure that the Preview version is working, you can publish the same source code as a stable version.

The YAML file starts with the following code:

trigger:
- master

pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'x86'
  buildConfiguration: 'Release'
  msixInstallUrl: https://wugdaysdemoapp.azurewebsites.net
  packageVersion: 1.0.0
  packageName: 'WUG Days Demo App'

jobs: 
- job: BuildMSIX
  strategy: 
    matrix: 
      Preview:
        channelName: 'preview'
        packageIdSuffix: '-preview'
        packageNameSuffix: ' [PREVIEW]'
      Production:
        channelName: 'production'
        packageIdSuffix: ''
        packageNameSuffix: ''
  • The first section says that this pipeline is triggered on any commit made to the master branch.
  • The second section says that we’ll be using the latest version of the built-in Windows agents. These built-in agents come with Azure DevOps and you’ll probably have some free build minutes. If you’d like to use you own VMs, make sure they have the latest Visual Studio and Windows 10 SDK installed.
  • The third section defines variables for the entire build pipeline.
  • Since we want to repeat the same steps for two channels (Preview and Production), I’ve added the strategy element. It defines additional three variables (channelName, packageIdSuffix and packageNameSuffix) for the two runs (Preview and Production).
  steps:
  - task: PowerShell@2
    inputs:
      targetType: 'inline'
      script: |
        [xml]$manifest= get-content ".\src\WpfCoreDemo.Package\Package.appxmanifest"
        $manifest.Package.Identity.Version = "$(packageVersion).$(Build.BuildId)"
        $manifest.Package.Identity.Name = "demo-864d9095-955f-4d3c-adb0-6574a5acb88b$(packageIdSuffix)"
        $manifest.Package.Properties.DisplayName = "$(packageName)$(packageNameSuffix)"
        $manifest.Package.Applications.Application.VisualElements.DisplayName = "$(packageName)$(packageNameSuffix)"
        $manifest.save(".\src\WpfCoreDemo.Package\Package.appxmanifest")

  - task: VSBuild@1
    inputs:
      solution: '$(solution)'
      msbuildArgs: '/restore /p:AppInstallerUri=$(msixInstallUrl)/$(channelName) /p:AppxPackageDir="$(Build.ArtifactStagingDirectory)/$(channelName)" /p:UapAppxPackageBuildMode=SideLoadOnly /p:GenerateAppInstallerFile=true'
      platform: '$(buildPlatform)'
      configuration: '$(buildConfiguration)'

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'drop'
      publishLocation: 'Container'

In the rest of the file, we define the build tasks:

  • The first is a PowerShell script that opens the manifest file as XML and updates the package identity (version, name) and the display name (it’s there twice). The version number is composed from the static packageVersion variable (1.0.0) with added Build.BuildId built-in variable that is a numeric sequence representing the number of builds.
  • The second task is the MSBuild with a few parameters:
    • /restore says that we want to do NuGet restore during the build
    • /p:AppInstallerUri specifies the URL where the MSIX package will be published
    • /p:AppxPackageDir is the path where we want to have the package outputs (the default is projectDir/AppPackages) – I am putting in in the staging directory for the artifacts task
    • /p:UapAppxPackageBuildMode=SideLoadOnly means that the package won’t go the the Windows Store and will be side-loaded
    • /p:GenerateAppInstallerFile tells MSBuild to generate the .appinstaller file – it is a simple file that defines the latest version of the package and can be used to check whether theare are new versions of the app
  • The third task just takes the artifacts staging directory and publishes it as a result of the build.

Build progress

When the build finishes, your packages will be published in the drop artifact:

Published build artifacts

You can see that there are two folders in the artifact, and each holds the index.html page, the app installer file and a versioned folder with the MSIX package itself.

image


That’s for building the packages. In the next post, I’ll show how to release the packages.

For a few last years, my company was building mostly web applications. The demand for desktop applications was very low, and even though we had some use cases in which building a desktop app would be less costly, our customers preferred the web solutions. The uncertain future of WPF, together with low interest in UWP, indicated that the web is practically the only way to go.

The situation changed a bit when Microsoft announced that .NET Core 3.0 would support WPF and WinForms. At about the same time, we got a customer who wanted us to build a large custom point-of-sale solution, and from all the choices we had, WPF sounded like the most viable option. There were many desktop-specific requirements in the project, for example printing different kinds of documents (invoices, sales receipts) using different printers, an integration with credit card terminals, and more.


Deployment of desktop apps

Together with WPF and WinForms on .NET Core, Microsoft also started pushing a new technology of deployment desktop apps: MSIX.

It is conceptually similar to ClickOnce (simple one-click installation process, automatic updates, and more), but it is very flexible. The most severe pain we had with ClickOnce was when we used it for large and complicated apps. We were hitting many issues and obstacles during the installation and upgrade process of the apps. The users were forced to uninstall the app and re-install it again. Sometimes, they had to delete some folder on the disk or clean something in the registry to make ClickOnce install the app.

MSIX should be more reliable as it was designed with respect to a wide range of Windows applications. It is a universal application packaging format that can be used for classic Win32 apps as well as for .NET and the new UWP applications.

It is also secure as the app installed from a package runs in a container - it cannot change system configuration, and all writes in the system folders or registry are virtualized. When you uninstall the app, no garbage should remain in your system.

You can choose to distribute the app packages manually, or you can use Windows Store for that. The nice thing is that you can avoid Windows Store entirely and use any way you want to distribute the MSIX. The most natural way is to publish the package on a network share or on some internal web server so they can be accessed using HTTPS.

Every step in the package building process can be automated using command-line tools, which allows us to embrace DevOps practices we got used to from the web world.


Our WPF project started when .NET Core 3.0 was in an early preview, but we have decided to try both WPF on .NET Core and the new MSIX deployment model.


The Simplest Scenario: Building a package to the WPF app

In my sample project, I have a simple WPF app that uses .NET Core 3.0.

The first step is to add a Windows Application Packaging Project in the solution:

Adding a Windows Application Packaging Project


The Windows Package project contains a manifest file. It is an XML file, but when you open it in Visual Studio, there is an editor for it.

Application Manifest editor


The most important fields are Display Name on the first tab, and Package Name and Version on the Packaging tab.

There are also several image files with the app icon, Windows Store icon, splash screen image, and so on.

Don't forget to make sure the WPF app is referenced in the Applications folder of the Windows Package project.

Application referenced in the Packaging project


Now, when you set the WPF project as a startup project and run it, the application will run in a classic, non-package mode. It's the same as it always worked in Windows. The app can do anything that your user has permissions.

However, when you set the Package project as a startup project and run it, the application will run from the package.

There is a NuGet package called Microsoft.Windows.SDK.Contracts that contains the Package class - you can use Package.Current to access the information about the application package - the name, version, identity, and so on. There is even an API to check or download the updates of the package.

public static PackageInfo GetPackageInfo()
{
    try
    {
        return new PackageInfo()
        {
            IsPackaged = true,
            Version = Package.Current.Id.Version.Major + "." + Package.Current.Id.Version.Minor + "." + Package.Current.Id.Version.Build + "." + Package.Current.Id.Version.Revision,
            Name = Package.Current.DisplayName,
            AppInstallerUri = Package.Current.GetAppInstallerInfo()?.Uri.ToString()
        };
    }
    catch (InvalidOperationException)
    {
        // the app is not running from the package, return and empty info
        return new PackageInfo();
    }
}


When you right-click the Package project and choose Publish > Create App Packages, there is a wizard that helps you with building the MSIX package.

Create App Packages


image


image


Update the installer location to either a UNC path, or to a web URL.

image


The process also creates a simple web page with information about the app and a button to install it. Aside from the MSIX package, there is also an App Installer file holding information about the app, its latest version, and a path to its MSIX package.

You can copy these files on a network share, or publish them at the URL you specified in the wizard.

Package with web page and app installer file




Installation page


When you change something in the project, you can publish the package again and upload the files on a web server or to the UNC share.

The application will check for the updates when it is started, and when you launch it next time, the update will install automatically.


Summary

I've just got through the most straightforward scenario for MSIX. In larger projects, you will need to have multiple release channels (preview and stable), you will need to sign your packages with a trusted certificate, and you will want to build the packages automatically in the DevOps pipeline.

I'll focus on all these topics in the next parts of this series. Stay tuned.