We recently discussed External Location
and how it lets Windows find files referenced in AppxManifest.xml but installed by some other
(not-MSIX) installer.
Many people call this kind of package a “Sparse package”. Many even think sparse packages are the important feature, and External Location is a minor implementation detail.
But…what is a sparse package?
First, let’s create one.
HOWTO create a sparse package
At its simplest, a sparse package contains AppxManifest.xml and other
footprint files,
and no payload files.1 For example, here’s a simple manifest for a HelloWorld application:
<?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:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap uap10 rescap">
<Identity Name="HelloWorld"
Publisher="CN=Contoso LLC, O=Contoso LLC, L=Paris, C=France"
Version="1.2.3.4"
ProcessorArchitecture="x64" />
<Properties>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
<DisplayName>Hello World!</DisplayName>
<PublisherDisplayName>Contoso</PublisherDisplayName>
<Logo>logo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.28000.0" />
</Dependencies>
<Applications>
<Application Id="app" Executable="helloworld.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="packagedClassicApp">
<uap:VisualElements DisplayName="Hello World!" Square150x150Logo="logo.png" Square44x44Logo="logo.png" Description="Say hello" BackgroundColor="#222222">
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
The manifest declares <uap10:AllowExternalContent>true</uap10:AllowExternalContent>, indicating it
is intended for installation with an External Location.
Let’s create a PowerShell script to make a signed package with our manifest:
# Where to find inputs, a temporary workspace and save output
$sourcedir = 'C:\Projects\HelloWorld'
$workdir = "$env:TEMP\HelloWorldFiles"
$msix = 'C:\Projects\HelloWorld\Packages\HelloWorld.msix'
$cert = 'C:\Secrets\Contoso.pfx'
# Create the temporary workspace containing our input files
mkdir $workdir
copy "$sourcedir\AppxManifest.xml" $workdir
copy "$sourcedir\logo.png" $workdir
# Create and sign the package
makeappx.exe pack /o /h SHA256 /d $workdir /p $msix /nv
signtool.exe sign /fd SHA256 /f $cert /p "CertPasswordShhhDontTell" $msix
Notice the makeappx.exe command includes the /nv option. This command would normally fail as the
manifest references helloworld.exe but no such file is provided. The /nv parameter disables that
file existence check. This allows us to create a package without those payload files, such as
helloworld.exe.
Looking inside the .msix file via PowerShell…
$zip = [System.IO.Compression.ZipFile]::OpenRead($msix)
try {
$zip.Entries | Select-Object FullName
}
finally {
$zip.Dispose()
}
Shows us…
FullName
--------
logo.png
AppxManifest.xml
AppxBlockMap.xml
[Content_Types].xml
AppxSignature.p7x
This is effectively a signed manifest and logo with no helloworld.exe (as intended).
MakeAppx.exe
validates the contents, including that package-relative files referenced in AppxManifest.xml are
provided in the content directory (/d parameter). Sparse packages are unusual because they
intentionally bypass that particular validation using /nv, allowing package-referenced payload to
exist only in the External Location.
Do I have to omit payload files?
No. You can continue including helloworld.exe and other payload files in your .msix as usual. But
Windows won’t use them.
Since Windows won’t use those files you can safely omit them and shrink your otherwise much larger package down to a few KB.
So…why “sparse”?
When External Location was first described, this “install payload files via (whatever) installer and omit them from the .msix” behavior was described as similar to NTFS sparse files. The file exists, but some of its contents wouldn’t be used, so that content can safely be omitted. This makes the package rather tiny (usually a few KB) and simple to create.
But never underestimate the power of a catchy name.
People heard “it’s like a sparse package” and thought “Sparse package” was the feature.
It wasn’t.
External Location is the key feature. A sparse package is simply an optimization.
And not always desirable.
When to be less ‘sparse’
Earlier I mentioned you can include payload files in your .msix, but Windows won’t use them.
“Windows” being the operative word.
Payload files included in your .msix will be staged in a new package directory as usual, with the package’s footprint files – and with the usual integrity protections and options.
Windows won’t look for package referenced files in the package’s staged directory, but you can – and for files not referenced in the manifest.
For example, include a DSCv3 resource definition in your sparse package and enable Package Integrity Content Enforcement to make your DSC resource publisher-controlled and tamper-evident. This provides a higher degree of protection than storing your DSC configuration file in your External Location or elsewhere on the system.
In the helloworld.exe example, let’s assume you name your DSCv3 resource file helloworld.dsc.resource.json.
You could include the file helloworld.dsc.resource.json in your .msix, install the rest of your
payload via your other (not-MSIX) installer, install the package with an External Location pointing
to that location, and use Windows APIs to locate the DSC file in your package’s install location.
For example:
string GetDscConfigFilename(string packageFamilyName)
{
var packageManager = new Windows.Management.Deployment.PackageManager();
List<Package> packages =
packageManager.FindPackagesForUserWithPackageType(
"",
packageFamilyName,
PackageTypes.Main)?
.ToList()
?? [];
if (packages.Count == 1)
{
return Path.Combine(packages[0].InstalledPath, "helloworld.dsc.resource.json");
}
else
{
return null;
}
}
It doesn’t matter if a malicious actor drops a helloworld.dsc.resource.json file into your External Location.
You ignore it, as you only look at the file in the package’s Installed location.
Sparse packages can intentionally contain selected payload that benefits from staging, integrity protection, and publisher control.
TL;DR
External Location is the feature; a sparse package is merely one way of packaging for that feature, not the feature itself.
1 An exception to the rule is a sparse package must contain the payload file referenced by the
<Package><Properties><Logo> element in AppxManifest.xml.
0 comments
Be the first to start the discussion.