Skip to content

Conversation

@mulfapoeding-gif
Copy link

.github/workflows/dotnet-desktop.yml

.NET Desktop CI/CD workflow

Purpose

Build, test, sign, and package a WPF or Windows Forms desktop application on Windows runners.

Required repository secrets

- Base64_Encoded_Pfx : Base64 string of the signing .pfx file (do not commit the .pfx to the repo).

- Pfx_Key : Password for the .pfx file.

Required environment variables (set in the workflow env section)

- Solution_Name : Solution file name, e.g., MyApp.sln

- Test_Project_Path : Path to test project, e.g., MyApp.Tests/MyApp.Tests.csproj

- Wap_Project_Directory : Packaging project directory, e.g., MyApp.Package

- Wap_Project_Path : Packaging project file, e.g., MyApp.Package/MyApp.Package.wapproj

Signing instructions

1. Create or obtain a .pfx signing certificate locally.

2. Encode it to Base64 using PowerShell:

$pfx = Get-Content '.\SigningCertificate.pfx' -Encoding Byte

[System.Convert]::ToBase64String($pfx) | Out-File 'SigningCertificate_Encoded.txt'

3. Copy the Base64 string into the repository secret Base64_Encoded_Pfx and add the password as Pfx_Key.

Security notes

- Never commit the .pfx file or its password to the repository.

- Use repository secrets and restrict who can modify workflow files.

- Protect the main branch and require this workflow to pass before merging.

This workflow builds, tests, signs, and packages a .NET Core desktop application using GitHub Actions.
name: .NET Core Desktop CI/CD

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    strategy:
      matrix:
        configuration: [Debug, Release]

    runs-on: windows-latest

    env:
      Solution_Name: MyApp.sln
      Test_Project_Path: MyApp.Tests/MyApp.Tests.csproj
      Wap_Project_Directory: MyApp.Package
      Wap_Project_Path: MyApp.Package/MyApp.Package.wapproj

    steps:
    - name: Checkout source
      uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Install .NET 8 SDK
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 8.0.x

    - name: Setup MSBuild
      uses: microsoft/setup-msbuild@v2

    - name: Run unit tests
      run: dotnet test $env:Test_Project_Path

    - name: Restore solution
      run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration
      env:
        Configuration: ${{ matrix.configuration }}

    - name: Decode signing certificate
      run: |
        $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
        $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx
        [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte)

    - name: Build and package MSIX
      run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }}
      env:
        Appx_Bundle: Always
        Appx_Bundle_Platforms: x86|x64
        Appx_Package_Build_Mode: StoreUpload
        Configuration: ${{ matrix.configuration }}

    - name: Remove temporary certificate
      run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx

    - name: Upload MSIX artifacts
      uses: actions/upload-artifact@v4
      with:
        name: MSIX Package
        path: ${{ env.Wap_Project_Directory }}\AppPackages
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: windows-latest

    strategy:
      matrix:
        configuration: [Debug, Release]

    env:
      Solution_Name: MyApp.sln
      Test_Project_Path: MyApp.Tests/MyApp.Tests.csproj
      Wap_Project_Directory: MyApp.Package
      Wap_Project_Path: MyApp.Package/MyApp.Package.wapproj

    steps:
      - name: Checkout source
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x

      - name: Setup MSBuild
        uses: microsoft/setup-msbuild@v2

      - name: Run unit tests
        run: dotnet test ${{ env.Test_Project_Path }}

      - name: Restore solution
        run: msbuild ${{ env.Solution_Name }} /t:Restore /p:Configuration=${{ matrix.configuration }}

      - name: Decode signing certificate
        if: secrets.Base64_Encoded_Pfx != ''
        run: |
          $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
          $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx
          [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte)

      - name: Build and package MSIX
        run: msbuild ${{ env.Wap_Project_Path }} /p:Configuration=${{ matrix.configuration }} /p:UapAppxPackageBuildMode=${{ env.Appx_Package_Build_Mode }} /p:AppxBundle=${{ env.Appx_Bundle }} /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }}
        env:
          Appx_Bundle: Always
          Appx_Bundle_Platforms: x86|x64
          Appx_Package_Build_Mode: StoreUpload

      - name: Remove temporary certificate
        if: secrets.Base64_Encoded_Pfx != ''
        run: Remove-Item -Path "${{ env.Wap_Project_Directory }}\GitHubActionsWorkflow.pfx" -ErrorAction SilentlyContinue

      - name: Upload MSIX artifacts
        if: matrix.configuration == 'Release'
        uses: actions/upload-artifact@v4
        with:
          name: MSIX Package
          path: ${{ env.Wap_Project_Directory }}/AppPackages

not how github works
# .NET Desktop CI/CD workflow
#
# Purpose
#   Build, test, sign, and package a WPF or Windows Forms desktop application on Windows runners.
#
# Required repository secrets
#   - Base64_Encoded_Pfx : Base64 string of the signing .pfx file (do not commit the .pfx to the repo).
#   - Pfx_Key            : Password for the .pfx file.
#
# Required environment variables (set in the workflow env section)
#   - Solution_Name      : Solution file name, e.g., MyApp.sln
#   - Test_Project_Path  : Path to test project, e.g., MyApp.Tests/MyApp.Tests.csproj
#   - Wap_Project_Directory : Packaging project directory, e.g., MyApp.Package
#   - Wap_Project_Path   : Packaging project file, e.g., MyApp.Package/MyApp.Package.wapproj
#
# Signing instructions
#   1. Create or obtain a .pfx signing certificate locally.
#   2. Encode it to Base64 using PowerShell:
#        $pfx = Get-Content '.\SigningCertificate.pfx' -Encoding Byte
#        [System.Convert]::ToBase64String($pfx) | Out-File 'SigningCertificate_Encoded.txt'
#   3. Copy the Base64 string into the repository secret Base64_Encoded_Pfx and add the password as Pfx_Key.
#
# Security notes
#   - Never commit the .pfx file or its password to the repository.
#   - Use repository secrets and restrict who can modify workflow files.
#   - Protect the main branch and require this workflow to pass before merging.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a new GitHub Actions workflow for building, testing, signing, and packaging a .NET Desktop application (WPF or Windows Forms). The workflow includes certificate-based signing using repository secrets and produces MSIX packages for both Debug and Release configurations.

Changes:

  • Added a complete CI/CD workflow for .NET Desktop applications with matrix strategy for Debug and Release builds
  • Implemented secure certificate handling using Base64-encoded secrets for code signing
  • Configured automated testing, building, and MSIX package artifact uploads

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- name: Upload MSIX artifacts
uses: actions/upload-artifact@v4
with:
name: MSIX Package
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The artifact name is static and will cause conflicts when uploading artifacts from different matrix configurations. Since the workflow runs with both Debug and Release configurations, the second upload will overwrite the first. The artifact name should include the matrix configuration to distinguish between builds, for example: 'MSIX-Package-${{ matrix.configuration }}'.

Suggested change
name: MSIX Package
name: MSIX-Package-${{ matrix.configuration }}

Copilot uses AI. Check for mistakes.

- name: Decode signing certificate
run: |
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The certificate file is created but there's no error handling if the Base64_Encoded_Pfx secret is missing or invalid. If the secret is not set, this step will fail silently or create an invalid certificate file, leading to confusing build failures in subsequent steps. Consider adding validation or using the 'if' conditional to check if secrets are available.

Suggested change
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
if (-not "${{ secrets.Base64_Encoded_Pfx }}") {
Write-Error "The 'Base64_Encoded_Pfx' secret is not set or is empty. Please configure this secret in the repository settings."
exit 1
}
try {
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
}
catch {
Write-Error "Failed to decode 'Base64_Encoded_Pfx' secret. Ensure it contains a valid Base64-encoded PFX certificate."
exit 1
}

Copilot uses AI. Check for mistakes.
Configuration: ${{ matrix.configuration }}

- name: Remove temporary certificate
run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The certificate cleanup step uses a hardcoded backslash path separator which works on Windows but is inconsistent with the forward slash used in line 46. While both work on Windows, consider using consistent path separators throughout the workflow or using PowerShell's Join-Path for platform independence.

Suggested change
run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx
run: |
$certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx
Remove-Item -Path $certificatePath

Copilot uses AI. Check for mistakes.
uses: actions/upload-artifact@v4
with:
name: MSIX Package
path: ${{ env.Wap_Project_Directory }}\bin\${{ matrix.configuration }}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The artifact upload path uses backslash separators and may not work correctly if the directory structure changes. Consider using forward slashes or the format '${{ env.Wap_Project_Directory }}/bin/${{ matrix.configuration }}' for better cross-platform compatibility and consistency with GitHub Actions conventions.

Suggested change
path: ${{ env.Wap_Project_Directory }}\bin\${{ matrix.configuration }}
path: ${{ env.Wap_Project_Directory }}/bin/${{ matrix.configuration }}

Copilot uses AI. Check for mistakes.
Copy link
Owner

@cjmanca cjmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it's set to sign both release and debug builds. No reason to sign debug, just increases risk of key exposure.

I think a keyless signing method would be preferable to minimize risk.

Probably should only sign for releases with version numbers, to avoid leaking on possibly risky PR merges before final release review. Can split sign job out from build, so build can run for PRs without signing.

mulfapoeding-gif and others added 2 commits February 6, 2026 17:40
Co-authored-by: C.J. Manca <cjmanca@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants