Skip to content

Instantly share code, notes, and snippets.

@LanceMcCarthy
Last active December 20, 2023 13:28
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save LanceMcCarthy/0fa5c1af7c2b73f5c51b703db72da144 to your computer and use it in GitHub Desktop.
Save LanceMcCarthy/0fa5c1af7c2b73f5c51b703db72da144 to your computer and use it in GitHub Desktop.
VS Live Episode: Resources for Publishing .NET MAUI Projects

.NET MAUI Publishing Resources

So you're done building your .NET MAUI application, congrats! Now it's time to publish them, This can be a tricky topic to navigate, even to dedicated DevOps engineers who do this regularly because of the number of moving pieces in the puzzle.

To help you, I have combined several resources that are a great place to get started. These are not only a good Getting Started experience, but it's also a good thing to bookmark as a quick look-up reference.

Table of Contents

Documentation

First, let's go through some links to the Microsoft docs. The MAUI team has put a lot of effort in making these articles helpful and relevant packaging and publishing your application. These docs are born out of the efforts of many repo issue conversations and internal experiences on the team. they saw the difficulties devs were having and refined the documentation to tailor to those topics.

There are many more article in this section, but I wanted to combine the ones you want to definitely visit.

Understanding project Confguration and various flags

Demos and Tools

Although I cannot share exact details due to the sensitive nature of code signing certificates and publisher identities, but we can still review the general setup where you can plug in your details.

The microsoft docs above explain what most of this is, and how you can create the prerequisites, the demos are intended to show you how you can set it up in your CI-CD workflows

Videos

Gerald Versluis has an amazing set of videos about .NET MAUI on YouTube. Several of them have been about publishing your project. I highly recommend checking out his channel, there is a ton of great MAUI content there. I'll list the publishing-specific topics ones here:

Commands

To help jumpstart your workflow commands, here are some publish commands that use environment secrets.

Windows

Example secrets and thier values (important, these should not be plain-text env vars). See how I prepare a Windows signing certificate file from a GitHub secret here.

  • PFX_Certificate_Thumbprint: "MyMauiApp"
  • PFX_FilePath: "MyCert.pfx"
  • PFX_Password: "MyCertPassword"
dotnet publish MyApp.csproj -c Release -f:net6.0-windows10.0.19041.0 \
  /p:GenerateAppxPackageOnBuild=true \
  /p:AppxPackageSigningEnabled=true \
  /p:PackageCertificateKeyFile=${{ secret.PFX_FilePath }} \
  /p:PackageCertificatePassword=${{ secret.PFX_Password }} \
  /p:PackageCertificateThumbprint=${{ secret.PFX_Certificate_Thumbprint }}

Android

Example secrets and thier values (important, these should not be plain-text env vars)

  • AndroidKeyStore: "john.keystore"
  • AndroidKeyAlias: "john"
  • AndroidKeyPass: "myKeyPassword"
  • AndroidStorePass: "myStorePassword"

The publish command

dotnet publish -f net8.0-android -c Release \
  -p:AndroidKeyStore=true \
  -p:AndroidSigningKeyStore="${{ secrets.AndroidKeyStore }}" \
  -p:AndroidSigningKeyAlias="${{ secrets.AndroidKeyAlias }}" \
  -p:AndroidSigningKeyPass="${{ secrets.AndroidKeyPass }}" \
  -p:AndroidSigningStorePass="${{ secrets.AndroidStorePass }}"

iOS

Example secrets and thier values (important, these should not be plain-text env vars). You can learn how to set your p12 and provisioing profile as secrets here, also see how you can import these items to the runner easily here.

  • AppleCodesignProvisioningProfile: "MyMauiApp"
  • AppleCodesignKey: "Apple Distribution: John Doe (ABCDE1234)"
  • MacIpAddress: "192.168.1.100"
  • MacPort: "58181"
  • MacUsername: "JohnDoe"
  • MacUserPassword: "JohnsMacPassword"

The publish command form a Windows machine, connecting to a Mac host (you can omit the server parameters if building on a Mac)

dotnet publish -f net8.0-ios -c Release \
	-p:ArchiveOnBuild=true \
	-p:RuntimeIdentifier=ios-arm64 \
	-p:CodesignKey="${{ secrets.AppleCodesignKey }}" \
	-p:CodesignProvision="${{ secrets.AppleCodesignProvisioningProfile }}" \
	-p:ServerAddress="${{ secrets.MacIpAddress }}" \
	-p:ServerUser="${{ secrets.MacUsername }}" \
	-p:ServerPassword="${{ secrets.MacUserPassword }}" \
	-p:TcpPort="${{ secrets.MacPort }}" \
	-p:_DotNetRootRemoteDirectory="/Users/${{ secrets.MacUsername }}/Library/Caches/Xamarin/XMA/SDKs/dotnet/"

MacCatalyst

Example secrets and thier values (important, these should not be plain-text env vars). You can learn how to set your p12 and provisioing profile as secrets here, also see how you can import these items to the runner easily here.

  • AppleCodesignProvisioningProfile: "MyMauiApp"
  • AppleCodesignKey: "Apple Distribution: John Doe (ABCDE1234)"
  • MacPackageSignKey: "3rd Party Mac Developer Installer: John Doe (ABCDE1234)"

The publish command form a Windows machine, connecting to a Mac host (you can omit the server parameters if building on a Mac)

dotnet publish -f net8.0-maccatalyst -c Release \
    -p:MtouchLink=SdkOnly \
	-p:CreatePackage=true \
	-p:EnableCodeSigning=true \
	-p:EnablePackageSigning=true \
	-p:PackageSigningKey="${{ secrets.MacPackageSignKey }}" \
	-p:CodesignKey="${{ secrets.AppleCodesignKey }}" \
	-p:CodesignProvision="${{ secrets.AppleCodesignProvisioningProfile }}" \
	-p:CodesignEntitlements="Platforms\MacCatalyst\Entitlements.plist"

Alternate Approach

If you are on a local device machine and have your certificates available, then you can also have conditional statements in the csproj file that will automatically apply these settings when in Release mode.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
    <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
    ... app stuff

    <!-- Display name -->
    <ApplicationTitle>MyApp</ApplicationTitle>

    <!-- ****** Important App Identifiers ****** -->
    <!-- Windows -->
    <ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">com.lancelotsoftware.MyApp</ApplicationId>
    <ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">61FFFFAC6-FFFFF-4830-FFFFF-78DFFF85CC</ApplicationIdGuid>

    <!-- MacCatalyst -->
    <ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">com.lancelotsoftware.MyApp</ApplicationId>
    <ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">61FFFFC6-FFFF-4830-FFFF-78DFFFF85CC</ApplicationIdGuid>

    <!-- iOS -->
    <ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">com.lancelotsoftware.MyApp</ApplicationId>
    <ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">61717AC6-FFFF-4830-FFFF-78DD35E085CC</ApplicationIdGuid>
    <ProvisioningType Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">manual</ProvisioningType>
    <CodesignKey Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">Apple Distribution: Lancelot Software, LLC (XXXXXXXXXX)</CodesignKey>

    <!-- Android -->
    <ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">com.lancelotsoftware.MyApp</ApplicationId>
    <ApplicationIdGuid Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">617FFAC6-FFFF-4830-B715-78DFFFF85CC</ApplicationIdGuid>
    <ProvisioningType Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">manual</ProvisioningType>
    <CodesignKey Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">Apple Distribution: Lancelot Software, LLC (XXXXXXXXXX)</CodesignKey>

    ... target platform stuff
  </PropertyGroup>

  <ItemGroup>
    ... icons and assets
  </ItemGroup>

  <ItemGroup>
    ... nuget packages
  </ItemGroup>

  <!-- ******** Android ******** -->
  
  <PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'">
        <AndroidKeyStore>True</AndroidKeyStore>
        <AndroidSigningKeyStore>lance.keystore</AndroidSigningKeyStore>
        <AndroidSigningStorePass>$(android_keystore_password)</AndroidSigningStorePass>
        <AndroidSigningKeyAlias>lance.alias</AndroidSigningKeyAlias>
        <AndroidSigningKeyPass>$(android_keystore_alias_password)</AndroidSigningKeyPass>
    </PropertyGroup>


  <!-- ******** iOS ******** -->
  
    <PropertyGroup Condition="$(TargetFramework.Contains('-ios')) and '$(Configuration)' == 'Release'">
        <RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
        <CodesignKey>Apple Distribution: Lancelot Software, LLC (XXXXXXXXX)</CodesignKey>
        <CodesignProvision>MyApp_AdHoc_2023.mobileprovision</CodesignProvision>
		or for store
        <CodesignProvision>MyApp_AppStore_2023.mobileprovision</CodesignProvision>
    </PropertyGroup>


  <!-- ******** MacCatalyst ******** -->

  <PropertyGroup Condition="$(TargetFramework.Contains('-maccatalyst')) and '$(Configuration)' == 'Release'">
        <RuntimeIdentifier>maccatalyst-x64</RuntimeIdentifier>
        <CodesignKey>Apple Distribution: Lancelot Software, LLC (XXXXXXXXX)</CodesignKey>
        <CodesignProvision>MyApp_MacStore_2023.provisionprofile</CodesignProvision>
    </PropertyGroup>

  <!-- ******** Windows (use VS2022 Publish tooling or CLI) ******** -->

</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment