uwp.net-6.0desktop-bridge

How to distribute arm64 app in the Microsoft Store as a WAP project?


My WPF app was running on .NET Framework 4.8 and being distributed as anyCPU to the Store. With the migration to .NET 6, I'm going to redistribute the app as x64, x86 and arm64.

When trying to publish it as an appxbundle, I noticed that arm64 is not available:

Older WAPP

When publishing as anyCPU, I get this error:

'Project.csproj' was resolved using '.NETCoreApp,Version=v6.0' instead of the project target framework '.NETFramework,Version=v4.5.1'. This project may not be fully compatible with your project.


I thought that the WAP project was an older version and that's why it had no arm64 variant, so I tried to update it somehow. Without finding any way to update it, I created a new one, referencing the same binary (my project) and got this:

New

But when publishing, I get this error:

The specified RuntimeIdentifier 'win-ARM64' is not recognized.


What's the migration path for my case? Also, with the new Windows 11 Store, is there any way to use the same app name and use MSIX or just MSI for distribution?


Trying to add msix packages when the latest package was an appxbundle gives me this error:

A previous submission for this app was released with a Windows 10/11 .msixbundle or .appxbundle. Subsequent submissions must continue to contain a Windows 10/11 .msixbundle or .appxbundle.


Solution

  • It worked by publishing the app as a msixbundle instead.

    At first, I tried to publish as three separated msix (x86, x64 and arm64), but since I had published as appxbundle, I needed to continue publishing as a bundle, per Microsoft Store's rules.

    The process to build a msix and msixbundle was not straight-forward as I had no experience with it and as there's not much info available, besides some cryptic documentation from Microsoft.


    The first step was trying to understand a msix package file structure and what's needed to build one.

    My initial idea was to take an installer and use the MSIX Packing Tool to generate a msix package. Unfortunately, there's little control of what's set up or not and the process is quite tiresome (building a special installer just to convert using the tool).

    I opted for building my own msix using command line, via a set of loose files instead.

    The process is somewhat simple, discovering what to do at first was not. I needed to copy all my files to a folder, then set a manifest file called AppxManifest.xml like so:

    The steps are:

    1. Copy all files to a folder. Since my app was made with .NET 6, I exported as self-contained and copied all files to it (~70MB).
    2. Create a manifest file, name as AppxManifest.xml, just changing version and architecture as needed:
    <?xml version="1.0" encoding="utf-8"?>
    <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6" xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap uap6 uap10 rescap">
      <Identity Name="App ID" Publisher="Publisher ID" Version="0.0.0.0" ProcessorArchitecture="x64"/>
    
      <Properties>
        <DisplayName>App Name</DisplayName>
        <PublisherDisplayName>Publisher Name</PublisherDisplayName>
        <Description>App Description</Description>
        <Logo>Assets\StoreLogo.png</Logo>
    
        <uap10:PackageIntegrity>
          <uap10:Content Enforcement="on" />
        </uap10:PackageIntegrity>
      </Properties>
    
      <Resources>
        <Resource Language="en-us" />
      </Resources>
    
      <Dependencies>
        <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22000.1" />
      </Dependencies>
    
      <Applications>
        <Application Id="A Simple ID" Executable="AppName.exe" EntryPoint="Windows.FullTrustApplication">
          <uap:VisualElements BackgroundColor="transparent" DisplayName="AppName" Square150x150Logo="Assets/AppName-Square150x150Logo.png" Square44x44Logo="Assets/AppName-Square44x44Logo.png" Description="App Description">
            <uap:DefaultTile Wide310x150Logo="Assets/AppName-Wide310x150Logo.png" Square310x310Logo="Assets/AppName-Square310x310Logo.png" Square71x71Logo="Assets/AppName-Square71x71Logo.png" />
          </uap:VisualElements>
        </Application>
      </Applications>
    
      <Capabilities>
        <rescap:Capability Name="runFullTrust" />
      </Capabilities>
    </Package>
    
    1. Generate a PRI file, indexing the resources. This one was tough as I was having lots of issues. I wanted to index my image assets, without the other files. That was the only way to make the logo to appear in the msix installer UI.

    I used a config file by calling the MakePri CLI app:

    .\makepri.exe new
    /v
    /cf '.\Resources\PriConfig.xml' --The config file bellow
    /pr '.\Output' --The output folder where all files are located
    /mn '.\Output\AppxManifest.xml' --The manifest file created before.
    /o
    /of '.\Output\Resources.pri'
    
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <resources targetOsVersion="10.0.0" majorVersion="1">
        <packaging>
            <autoResourcePackage qualifier="Scale"/>
        </packaging>
    
        <index root="\" startIndexAt="\">
            <default>
                <qualifier name="Language" value="en-us"/>
                <qualifier name="Contrast" value="standard"/>
                <qualifier name="Scale" value="100"/>
                <qualifier name="HomeRegion" value="001"/>
                <qualifier name="TargetSize" value="256"/>
                <qualifier name="LayoutDirection" value="LTR"/>
                <qualifier name="Theme" value="dark"/>
                <qualifier name="AlternateForm" value=""/>
                <qualifier name="DXFeatureLevel" value="DX9"/>
                <qualifier name="Configuration" value=""/>
                <qualifier name="DeviceFamily" value="Universal"/>
            </default>
    
            <indexer-config type="folder" foldernameAsQualifier="true" filenameAsQualifier="true" qualifierDelimiter=".">
                <exclude type="extension" value=".dll" doNotTraverse="true" doNotIndex="true"/>
                <exclude type="extension" value=".exe" doNotTraverse="true" doNotIndex="true"/>
                <exclude type="extension" value=".xml" doNotTraverse="true" doNotIndex="true"/>
                <exclude type="extension" value=".txt" doNotTraverse="true" doNotIndex="true"/>
                <exclude type="extension" value=".json" doNotTraverse="true" doNotIndex="true"/>
            </indexer-config>
        </index>
    </resources>
    

    It's important to erase your previous *.pri files when you generate another one, or else it would index it too.

    1. Generate each msix package, one for each architecture. I just had to call makeappx.exe passing this command:
    makeappx.exe pack /d ".\Output" /p ".\AppName.Version.Architecture.msix" /v /o
    

    Replacing the version and architecture as needed.

    1. (Optional) Singing the package:

    This step I had a slowdown. I had the idea to sign the package using a code signing certificate (as I so with my normal installer releases), but I found out that I could not, at least not for the Store version, since I need to use the same publisher ID in the AppxManifest.xml as it's set in the Microsoft Store configuration for my user.

    Store Identity

    And to be able to sign the package, I needed to set the publisher ID to be the same as the subject name of the certificate.

    In the end, the msix/msixbundle package that I publish to the Store is not code signed, but the one that I will distribute outside the store will be.

    To sign, I just need use this command:

    signtool.exe sign /f CERTIFICA-PATH /p PASSWORD /tr TIMESTAMP-URL /v /td sha256 /fd sha256 /du WEBSITE /a PATH-TO-MSIX-FILE
    

    It will fail if the publisher ID is different from the certificate subject.

    1. Since I was publishing as bundle before (as appxbundle), I needed to continue publishing as bundle (as msixbundle) now.

    To create a bundle, just copy all msix (three in my case, x86, x64 and arm64) to a folder with nothing else inside and call this command:

    makeappx.exe bundle /bv 0.0.0.0 /d "PATH-TO-FOLDER-WITH-MISX" /p "PATH-TO-OUTPUT-MSIXBUNDLE"
    
    1. (Optional) You can also sign the bundle if you plan in publishing outside the store. Same way as step 5.