Blog Post

Modern Work App Consult Blog
5 MIN READ

Create a Jenkins pipeline to deploy Desktop Apps as MSIX - Part 2: Packaging a Visual Studio solutio

luisdem's avatar
luisdem
Icon for Microsoft rankMicrosoft
May 31, 2024

Parts:

  1. Setup the Jenkins environment: install Jenkins and the required tools.
  2. Packaging a Visual Studio solution: for applications that use Visual Studio IDE, like Windows Forms and WPF.
  3. Packaging a solution developed outside Visual Studio: for applications developed outside VS, i.e., in others IDEs like Eclipse or Visual Studio Code, for Java GUI application.
  4. Packaging using the VB6RegistryTool: despite of the name, the tool can be used by any technology.

1. Visual Studio Solution

In this section it will be demonstrated how to create the Visual Studio solution with a Windows Forms application and the Windows Application Packaging Project project used to generate the MSIX file.

In case you prefer, you can skip this step, since the solution is already available on jenkins_msix repo.

Create the Windows Form application

On Visual Studio 2019, select Create a new project, select the Windows Forms app project template and click Next:

 

Provide the project name WinForms.App and click Next:

 

 

I am choosing the .NET 6.0 framework, but feel free to use any other version. Click on Create:

 

 

 

 

 

This is the WinForms project structure. I am using only a PictureBox and a button, but it could be an empty project, as the idea is just to show how to package a WinForm application:

 

 

 

 

Follows the code-behind:

 

 

 

 

 

 

 

namespace WinForms.App
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

 

 

 

 

 

 

 

So far, the WinForm project is a .NET 6.0 application that depends of the .NET runtime available in the client machine. To remove this dependency, it is possible to publish the project as self-contained where the .NET runtime and runtime libraries are bundled together with the application and third-party assemblies.

Add the following lines to the SelfContained and RuntimeIdentifier to the WinForms.App project file:

 

 

 

 

 

 

<PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>enable</ImplicitUsings>
    
    <SelfContained>true</SelfContained>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    
  </PropertyGroup>

 

 

 

 

 

 

More details at: Trim self-contained deployments and executables

Build the application to ensure that no errors appear.

Create the the Windows Application Packaging project

The next step is adding the Windows Application Packaging Project to the solution.

Right-click on the solution, select Add and click on New Project...:

 

 

Select the Windows Application Packaging Project and click Next:

 

 

Provide the project name WinForms.Packaging and click Create:

 

 

 

The next step is to provide the target and minimum platform version supported by your application. I am selecting both versions to 19041 to keep it simple, as I need to install the same SDKs on my Jenkins server environment:

 

 

In the WinForms.Packaging project, right-click on Dependencies node and click Add Project Reference...:

 

 

Select the Windows Application Packaging Project and click Next:

 

 

Observe that the WinForms.App project was added to the WinForms.Packaging project:

 

 

The Windows Application Packaging Project don't accept the target Any CPU. Therefore, we need to change the processor target to x86 or x64 for both projects.

 

Open Configuration Manager, change the Active solution platform to x86, change the WinForms.App and WinForms.Packaging projects to x86:

 

 

Build the application to ensure that no errors appear.

Build the application using MSBuild command line

Before creating a Jenkins pipeline, let's make sure that the MSBuild command line that will be used to build our application is working.

Open the Package Manager Console (you can press CTRL+Q and type package manager console):

 

 

 

Before running the following command, make sure to provide the MSBuild.exe PATH available in your environment:

 

 

 

 

 

 

&"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" /p:AppxBundlePlatforms=X86 /p:AppxBundle=Never /p:UapAppxPackageBuildMode=Sideloading  /p:AppxPackageSigningEnabled=false

 

 

 

 

 

 

 

 

 

 

In my case, the msix package file was generated on:

 

C:\github\msixdemos\01_VisualStudio\WinForms.App\WinForms.Packaging\AppPackages\WinForms.Packaging_1.0.0.0_AnyCPU_Debug_Test

 

 

In the next section, it will demonstrate how to build a Jenkins Pipeline for this project.

2. Jenkins Pipeline

Pipeline is a series of tasks required to build, test, and deploy an application.

Create a new job

In the Jenkins Dashboard, click on the Create a job option:

 

 

Provide a name for the job, check the Pipeline type of job and click on OK to proceed.

 

 

 

In the pipeline configuration page, check the GitHub Project to specify that this is a GitHub project and provide a GitHub URL:

 

 

 

Scroll down under the Pipeline section and change the definition to Pipeline script from SCM

 

 

Provide the Repository URL as well. Because this is a public project, we can skip the credentials:

 

 

 

Scroll-down to the Branches to build section, change the branch name to */main, the Jenkins script path to Jenkinsfile01 and click on Save:

 

 

Those actions were needed as we want to use the Jenkins pipeline file available in the main branch of the following repo:

 

 

Jenkins Pipeline File

In the previous section it was demonstrated how to setup the Jenkins pipeline to use a Jenkins script file available on our GitHub repository.

 

Follows the Jenkinsfile01 content:

 

 

 

 

 

 

 

pipeline {
  
  agent any

  environment {
    MSBUILD = "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Msbuild\\Current\\Bin\\MSBuild.exe"
    CONFIG = 'Release'
    PLATFORM = 'x86'
  }
  
  stages {
    
    stage('Update manifest version') {
      steps {
          powershell '''
            $manifest = "01_VisualStudio\\WinForms.App\\WinForms.Packaging\\Package.appxmanifest"     
            [xml]$xmlDoc = Get-Content $manifest
            $version = $xmlDoc.Package.Identity.Version
            $trimmedVersion = $version -replace '.[0-9]+$', '.'
            $xmlDoc.Package.Identity.Version = $trimmedVersion + ${env:BUILD_NUMBER}
            $xmlDoc.Save($manifest)
          '''
      }
    }
    
    stage('Build') {
      steps {
        bat "dotnet restore 01_VisualStudio\\WinForms.App\\WinForms.App\\WinForms.App.csproj"

        bat "\"${MSBUILD}\" 01_VisualStudio\\WinForms.App\\WinForms.app.sln /p:Configuration=${env.CONFIG} /p:AppxBundlePlatforms=${env.PLATFORM}  /p:AppxBundle=Never /p:UapAppxPackageBuildMode=Sideloading  /p:AppxPackageSigningEnabled=false"
        
      }
      post{
          always {
           archiveArtifacts artifacts: '**/*.msix', followSymlinks: false
          }
      }
    }
  }
}

 

 

 

 

 

 

The pipeline directive is the complete script from beginning to end.

 

The agent directive instructs Jenkins to allocate an executor and workspace for the entire Pipeline. In our case, we are justing saying it can run on any agent. For example, it could be specified that it could run in a Docker container or run on a specific node.

 

The environment directive specifies a sequence of key-value pairs which will be defined as environment variables for all steps, or stage-specific steps, depending on where the environment directive is located within the Pipeline.

 

In our case, it is defined the variables MSBUILD that contains the MSBUILD path, the CONFIG with the value Release and PLATFORM with the value x86. Those variables will be used in the command line used to build our application.

 

The stages block contains on or more stage block, and each stage is going to have one or more steps. In our case, we have only one stage named Build, that has two steps to restore the dotnet WinForms.App project and to build the solution.

 

The post section defines the additional step needed to keep the msix file artifact available in our build, as workspace is a temporary directory.

 

You can find more details about the Jenkins pipeline syntax in the post Getting started with Pipeline.

 

In addition, there is a great post about how to Creating a Jenkins pipeline for a .NET Core application.

 

Switch back to Jenkins, click on Dashboard and click on the Visual Studio Solution pipeline:

 

 

Click on Build Now to start the build:

 

 

The job starts by checking out the source code to next restore and build our solution as defined in the Jenkinsfile01.

 

 

After the build is done, the build icon will be green and the msix artifact will be available:

 

 

The next post demonstrate how to package an application that not uses the Visual Studio IDE, but that uses the Windows Application Packaging Project to generate the MSIX file.

 

Updated May 31, 2024
Version 3.0
No CommentsBe the first to comment

Share