rubrapack Manual←↑→

17 MSIX packages and bundles

Goal: build Hello as an MSIX package too - the newer package format of Windows - from the same source, then as one bundle that holds all three architectures.

17.1 MSI or MSIX?#

MSI (.msi)MSIX (.msix)
Installsanywhere the package says, and changes the computer as toldinto a sealed folder Windows owns; files and registry the app writes are kept apart
Removalas clean as the package makes italways complete: nothing is left behind
Signaturerecommendedrequired: Windows does not install an unsigned MSIX (except for testing, below)
Can doeverything in this tutorialfiles, shortcuts, file types, registry, INI files, fonts, services, a command-line alias, a start-at-sign-in task, environment variables for the application's own processes
Cannot do-custom actions, machine-wide environment variables, permissions, conditions, dialogs

Many products ship both. rubrapack builds either from one source: the output's extension decides.

17.2 The folder#

Three logos go next to the source, in assets\: PNG pictures of exactly 150x150, 44x44 and 50x50 pixels. The Start menu and the Settings app show them. Without them the package gets plain one-colour logos - give all three or none.

C:\work\hello\
    assets\
        Square150x150.png
        Square44x44.png
        StoreLogo.png
    dist\
        x64\hello.exe
        x86\hello.exe
        arm64\hello.exe
        docs\guide.txt
    hello.toml

17.3 The source#

# tutorial 17: hello.toml
format = 1

[package]
name = "Hello"
manufacturer = "Example Software"
version = "$(VERSION)"
arch = "x64"
upgrade-code = "{3F2A6C1D-8B4E-4F7A-9C2D-5E6F7A8B9C0D}"
upgrade-code-x86 = "{7D1C2B3A-4E5F-4061-9728-3A4B5C6D7E8F}"
upgrade-code-arm64 = "{0E9F8D7C-6B5A-4948-8372-6150F4E3D2C1}"

[define]
VERSION = "2.1.0"

[msix]
identity-name = "ExampleSoftware.Hello"
publisher = "CN=Example Software"
publisher-display-name = "Example Software"
min-version = "10.0.17763.0"

[msix-app.Hello]
executable = "Hello"
display-name = "Hello"
description = "Says hello."
logo-150 = "assets/Square150x150.png"
logo-44 = "assets/Square44x44.png"
store-logo = "assets/StoreLogo.png"

[msix-extension.Command]
kind = "alias"
alias = "hello.exe"

[msix-extension.AtSignIn]
kind = "startup-task"
display-name = "Hello"
enabled = false

[msix-extension.Web]
kind = "firewall"
direction = "in"
protocol = "tcp"
ports = "8080"
profile = "private"

[dir.INSTALLDIR]
path = "$(ProgramFiles)/Hello"

[file.Hello]
dir = "INSTALLDIR"
source = "dist/$(ARCH)/hello.exe"

[files.DocFiles]
dir = "INSTALLDIR"
glob = "dist/docs/**"

[shortcut.StartMenu]
dir = "Programs"
name = "Hello"
target = "file:Hello"

[assoc.HelloDoc]
extension = ".hello"
prog-id = "ExampleSoftware.HelloDocument"
description = "Hello document"
target = "file:Hello"

[registry.Greeting]
root = "HKMU"
key = "Software\\Example Software\\Hello"
name = "Greeting"
value = "Hello"

[env.HelloHome]
name = "HELLO_HOME"
value = "[INSTALLDIR]"
msi-only = true

17.4 The package's identity: [msix]#

KeyMeaning
identity-namethe package's name for Windows, 3-50 characters of A-Z a-z 0-9 . -; by custom Company.Product. Like the upgrade code of an MSI it must never change: a new version with the same name and publisher replaces the old one.
publisherthe subject of the certificate you will sign with, written as Windows writes it (next section)
publisher-display-namethe publisher's name people see; the default is [package] manufacturer
min-versionthe oldest Windows it installs on, as 10.0.<build>.0; the default 10.0.17763.0 is Windows 10 version 1809

The version is [package] version with a fourth part: 2.1.0 becomes 2.1.0.0.

17.5 The application: [msix-app.ID]#

An MSIX lists the applications it contains - the entries in the Start menu. executable names the [file.*] that starts it; the folder of that file (here INSTALLDIR) becomes the package's own folder. display-name and description default to the package's name. Several [msix-app.*] tables give several entries.

Names in several languages, logos in several sizes#

display-name-ko = "헬로" (and description-ko, and in [msix] display-name-ko and publisher-display-name-ko for the package) gives a name in another language; Windows shows the one that fits the user's language, and the text without a suffix everywhere else. For a logo, put sharper versions next to it - Square44x44.scale-200.png (88x88) beside Square44x44.png, with scale-125, -150 and -400 as you like - and screens set to 200% show those. rubrapack puts both into the package's resources.pri, the index Windows looks names and files up in, and writes it only when a package has them.

17.6 Extras: [msix-extension.ID]#

With several [msix-app.*] tables, app = "Hello" says which application an extension belongs to; the default is the first. An MSI build leaves these tables out.

17.7 What the package asks for: capabilities and dependencies#

An MSIX application asks Windows for what it uses beyond running as a desktop program, and names the framework packages it needs:

[msix]
identity-name = "ExampleSoftware.Hello"
publisher = "CN=Example Software, O=Example Software, C=KR"
capabilities = ["internetClient", "allowElevation"]

[msix-dependency.VCLibs]
name = "Microsoft.VCLibs.140.00.UWPDesktop"
publisher = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
min-version = "14.0.24217.0"

[msix-app.Helper]
executable = "HelperExe"
hidden = true                  # no Start menu entry
background-color = "#1E3A5F"   # the tile's colour

17.8 Optional and modification packages#

A package can belong to another one. An optional package adds content - more levels, a plug-in - to a main package, and installs only where that package is:

[msix]
identity-name = "ExampleSoftware.HelloExtras"
publisher = "CN=Example Software, O=Example Software, C=KR"
main-package = "ExampleSoftware.Hello"

A modification package changes how an installed application is set up - an enterprise's settings file and registry values - without touching the application's own package:

[msix]
identity-name = "ExampleSoftware.HelloSiteSettings"
publisher = "CN=Example Software, O=Example Software, C=KR"
main-package = "ExampleSoftware.Hello"
modification = true
min-version = "10.0.18362.0"

Neither needs [msix-app.*] tables; an optional package's files go to its own folder (the dir INSTALLDIR), a modification package's into the virtual file system, where the main application sees them. main-publisher names the main package's publisher when it is someone else's.

17.9 What the other tables become#

msi-only = true keeps a table in the MSI and leaves it out of the MSIX. What an MSIX cannot hold at all - a [require.*], an [action.*] - stops the build until you say what you want:

C:\work\hello> rubrapack build hello.toml -o hello.msix --unsigned-test
hello.toml:80:1: error[RP1605]: [require.Win81] cannot go into an MSIX; add msi-only = true to build the MSIX without it

rubrapack never drops something silently.

Features, properties, dialogs and [arp] concern Windows Installer only and are not used. rubrapack lint hello.toml --target msix checks a source for MSIX without building it.

17.10 Building for testing: --unsigned-test#

C:\work\hello> rubrapack build hello.toml -o hello.msix --unsigned-test
C:\work\hello> rubrapack build hello.toml -o hello.msi

Windows installs an unsigned MSIX only when the package says it is meant for testing and the installer says it accepts that. --unsigned-test adds that mark (it changes the publisher, so the package is a different package from the signed one). Install it in PowerShell, as administrator because it contains a program:

PS C:\work\hello> Add-AppxPackage -Path hello.msix -AllowUnsigned
PS C:\work\hello> hello.exe
PS C:\work\hello> Get-AppxPackage ExampleSoftware.Hello | Remove-AppxPackage

Such a package is for your own test computer only.

17.11 Signing it#

For people to install it, sign it (chapter 16) - the MSIX's publisher must be exactly the certificate's subject. Otherwise signing stops and prints the subject to use:

C:\work\hello> rubrapack build hello.toml -o hello.msix --key signer.pfx --pass-env SIGN_PASS
rubrapack: error[RP0011]: 'hello.msix': the package's publisher "CN=Example Software" is not the certificate's subject "O=Example Software Ltd, CN=Example Software"; set [msix] publisher to it (without --unsigned-test)

Copy that subject into publisher. Windows writes a subject's parts from the last to the first, so it differs from how some tools show it.

A signed package that holds programs also carries AppxMetadata\CodeIntegrity.cat, a catalog of their hashes signed with the same key, as Windows' own signer adds it. It is what Windows' code integrity checks the package's unsigned programs against where only signed code may run (S mode, application control); rubrapack's catalog was checked against Windows' own hashes and signtool, not on such a device.

17.12 All architectures in one file: a bundle#

An output ending in .msixbundle builds the source once for each architecture of --arch, as in chapter 15, and puts the packages in one file:

C:\work\hello> rubrapack build hello.toml -o hello.msixbundle --arch x64,x86,arm64 --unsigned-test
C:\work\hello> rubrapack inspect hello.msixbundle --files
AppxMetadata\AppxBundleManifest.xml	1616	deflate
ExampleSoftware.Hello_2.1.0.0_x64.msix	11119	stored
ExampleSoftware.Hello_2.1.0.0_x86.msix	11208	stored
ExampleSoftware.Hello_2.1.0.0_arm64.msix	11119	stored

Windows installs the package for its own processor from it. With --key the bundle and each package in it are signed.

When the source has names in other languages (display-name-ko and the like), the bundle also holds one resource package per language, ExampleSoftware.Hello_2.1.0.0_language-ko.msix, with that language's texts; the architecture packages keep only the source's own language. Windows installs the resource packages of the languages the user has set, so nobody downloads texts they never see, and a language added to Windows later is fetched with the next update. language-packs = false in [msix] keeps every language in the architecture packages instead.

17.13 Updates from a web site: .appinstaller#

Windows can keep an MSIX up to date by itself, from a web site (or a file share) you publish it on. Say where the package will be, and rubrapack writes an App Installer file beside it:

[msix]
...
appinstaller-uri = "https://example.com/hello/hello.appinstaller"
package-uri = "https://example.com/hello/hello.msixbundle"
update-hours = 24                 # how often Windows looks for a newer version (0: at every start)
C:\work\hello> rubrapack build hello.toml -o hello.msixbundle --arch x64,x86,arm64 --key ...

writes hello.msixbundle and hello.appinstaller. Put both at those addresses. People install from the .appinstaller (opening it, or Add-AppxPackage -AppInstallerFile), and from then on Windows checks that address when the app starts; publish the next version with its new .appinstaller at the same addresses and it updates itself. update-prompt = true asks the user first, update-blocks = true makes the app wait for the update, update-background = true also checks every eight hours in the background. App Installer takes signed packages only (chapter 16).

17.14 Compression#

Files are compressed with deflate; pictures and other files that are compressed already are stored as they are. --msix-compress store stores everything, which makes the package bigger (here 11119 to 30072 bytes) but quicker to open. An MSIX holds no date or time: the same source gives the same bytes on every computer.

17.15 What happened inside#

An MSIX is a ZIP archive. Look at its list of files:

C:\work\hello> rubrapack inspect hello.msix --files
hello.exe	17920	deflate
guide.txt	6	deflate
Assets\Square150x150.png	301	stored
Assets\Square44x44.png	111	stored
Assets\StoreLogo.png	117	stored
Registry.dat	8192	deflate
AppxManifest.xml	2960	deflate

Registry.dat is the virtual registry - a registry hive file. AppxManifest.xml describes the package; --manifest prints it:

C:\work\hello> rubrapack inspect hello.msix --manifest
...
  <Identity Name="ExampleSoftware.Hello" Publisher="CN=Example Software, OID.2.25.311729368913984317654407730594956997722=1" Version="2.1.0.0" ProcessorArchitecture="x64" />
...
    <Application Id="Hello" Executable="hello.exe" EntryPoint="Windows.FullTrustApplication">
...
          <uap3:FileTypeAssociation Name="examplesoftware.hellodocument" Parameters="&quot;%1&quot;">
...
          <desktop:StartupTask TaskId="AtSignIn" Enabled="false" DisplayName="Hello" />
...
            <desktop:ExecutionAlias Alias="hello.exe" />
...
    <rescap:Capability Name="runFullTrust" />

The OID.2.25...=1 part of the publisher is the test mark --unsigned-test added. runFullTrust means the program runs like any desktop program, not in the restricted sandbox of store apps. Two more entries are in every package, though --files does not list them: AppxBlockMap.xml, the hash of every 64 KB block of every file, which is what a signature covers, and [Content_Types].xml, the type of each file. A signed package also has AppxSignature.p7x, the signature. Part IV shows the ZIP layout and the block map byte by byte.