Setting up CI for your .NET MAUI Windows app with GitHub Actions

In my previous post I talked about how to set up CI for your .NET MAUI Windows app in Azure DevOps. This post will focus on how you can do the same with GitHub Actions.

This post is heavily inspired by Gerald Versluis’s video on just this, along with this blog post from Microsoft on DevOps with .NET MAUI, so I highly recommend that you check those out. This is meant to be a written reference specifically for DevOps with .NET MAUI Windows.

Create your workflow

Create a new workflow under the Actions tab in your repository. Select the .NET workflow, which should be suggested to you (search it up if it isn’t suggested) and hit “Configure”.

Workflow for .NET.

Out of the box, the template will look like this:

The template now uses .NET 6 by default, so we don’t need to edit that anymore.

Select your VM image

The first thing we want to change is what kind of virtual image this action will run on. The template sets it to ubuntu-latest, but we’ll want to change this to a Windows image. Let’s change it to windows-latest:

    runs-on: windows-latest

Install the MAUI workload

Next we’ll remove the dotnet restore and dotnet test commands. In place of the dotnet restore command we’ll add a run where we will install the .NET MAUI workload:

    - name: Install MAUI workload
      run: dotnet workload install maui

Build it

Finally we’ll modify the existing dotnet build command to build the .NET MAUI app for Windows. We’ll have to make sure that the target framework matches the one that’s set in your csproj file:

    - name: Build
      run: dotnet build -c Release -f:net6.0-windows10.0.19041.0

Sign it

If you want to publish the resulting MSIX file to Windows Store, you can use this without having to sign your package. However, if you plan on sideloading your app, you’ll need to sign it. You can do this with a self-signed certificate (mostly used for testing) or a certificate issued by a trusted source. For instructions on how to create a self-signed certificate, follow this guide.

Tell me your secrets

Once you have your certificate, you’ll first have to create some Actions secrets. You can create these from the Settings tab in your repository and selecting Secrets -> Actions from the menu on the left. The first two we’ll create secrets for are the certificate password and the certificate thumbprint. You should be able to retrieve these after having created your certificate. We’ll name these WINDOWS_PFX_PASSWORD and WINDOWS_PFX_THUMBPRINT, respectively.

The last secret we need to add is the base64-encoded certificate. To encode our certificate, we can use the Windows utility certutil to encode our certificate.

In the terminal on a local Windows computer, navigate to the folder where your certificate resides and use the following command:

certutil -encode .\mycert.pfx mycert.pfx.asc

To print out the newly encoded value, use this command:

cat .\mycert.pfx.asc

Copy the printed out content and create a new Actions secret. Paste the content in here and name the secret WINDOWS_PFX_FILE.

Decode PFX file

Since we’ve only uploaded the encoded PFX file, we’ll have to decode it during our action workflow in order to use it for signing. In our workflow, above the dotnet build step, add this code:

- name: Decrypt PFX File
      run: |
        echo "${{ secrets.WINDOWS_PFX_FILE }}" > cert.pfx.asc
        certutil -decode cert.pfx.asc cert.pfx

This will decode the content into a cert.pfx that we can use further on in our workflow.

Install certificate

Next we’ll have to install the certificate to the Windows agent. We have to do this in a specific way – please refer to Gerald’s video for a full explanation on this.

    - name: Add Cert to Store
      run: certutil -user -q -p ${{ secrets.WINDOWS_PFX_PASSWORD }} -importpfx cert.pfx NoRoot

Build it (take 2)

Now we’ll have to edit the dotnet build step to actually sign the package with the installed certificate. Edit the step to now look like this:

    - name: Build
      run: dotnet publish -c Release -f:net6.0-windows10.0.19041.0 /p:GenerateAppxPackageOnBuild=true /p:AppxPackageSigningEnabled=true /p:PackageCertificateThumbprint="${{ secrets.WINDOWS_PFX_THUMBPRINT }}"

The first thing to note is that we switched from build to publish. This is so that the step actually produces an MSIX file. Some MSBuild arguments are also passed here to make sure an Appx package is created and signed with the specified certificate thumbprint. The build agent will look for an installed certificate with the specified thumbprint and will use the certificate that way.


Your workflow should now have created and signed a distributable file for your .NET MAUI Windows application. To use the generated file in a further step, you can use the Upload Artifact action (actions/upload-artifact@v3.1.0).

Here’s a look at the final YAML file:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.