MSIX 打包 Win 程序技术文档
一个简单项目的演示的Demo
MsixAppDemo.zip
1. 对比 MSIX 和 EXE 上架 Microsoft Store 的优缺点。
1.1. 使用 MSIX 打包 Win 程序上架 Microsoft Store 的优点
(1) 可触达的用户更多。 Windows 操作系统中新版本的 Microsoft Store 能够直接下载安装 exe 程序,但是很多 Windows 10 、 Windows 11 的用户都是不会手动更新 Microsoft Store ,这些用户都无法通过 Windows 10 、 Windows 11 自带的 Microsoft Store 下载安装开发者提交的 exe 程序。 MSIX 打包 Win 程序能够支持 Windows 10 1809 及以后版本操作系统自带的 Microsoft Store 下载安装。
MSIX 特性支持情况
https://docs.microsoft.com/zh-cn/windows/msix/supported-platforms
(2) 支持检查安装状态。目前所有版本的 Microsoft Store 都不支持检查从 Microsoft Store 安装的 exe 程序是否已经安装,但是MSIX 打包 Win 程序能够支持检查是否已经安装、是否可以更新。
1.2. 使用 MSIX 打包 Win 程序上架 Microsoft Store 的缺点。
以下缺点都在 MSIX 满足上架 Microsoft Store 需要满足的受限功能的场景下,进行介绍。
受限功能:https://docs.microsoft.com/zh-cn/windows/uwp/packaging/app-capability-declarations#restricted-capabilities
(1) MSIX 部署原生不支持创建快捷方式。部署的可执行程序不支持直接被调用,需要借助其他程序在沙箱中运行对应的程序。
示例:
explorer.exe shell:appsFolder\MsixAppDemo_y21qvjamj4ssj!MsixAppDemo
需要注意的是,直接借助explorer.exe 启动 MSIX 程序,是无法像常规程序的快捷方式那样传递参数的。
(2) MSXI 的程序图标不支持透明,如果你的图标中有透明的部分,透明部分只可以根据开发者的设置,选择使用系统主题色填充,或者指定的颜色填充。
(3) MSXI 不支持 .NET 编写的文件资源管理器缩略图COM组件、预览COM组件,不过它支持 原生C++ 编写的文件资源管理器缩略图COM组件、预览COM组件。
(4) 在 MSIX 满足 上架 Microsoft Store 的场景下, MSIX 不支持自定义安装、卸载程序。如果 MSIX 不上架 Microsoft Store ,使用受限功能的的场景下是支持定义安装、卸载程序。
(5) 在 MSIX 满足 上架 Microsoft Store 的场景下, 程序都被放在类似沙盒的环境中,通用库的以外的文件夹、文件、注册表都存放在虚拟的文件夹中。其中,通用库包括:公用视频、公用图片、公用文档、公用下载、公用音乐、公用视频、公用图片、公用文档、公用下载、公用音乐、我的视频、我的图片、我的文档、我的下载、我的音乐、我的视频、我的图片、我的文档、我的下载、我的音乐。%appdata% 文件夹在有些版本的 Windows 系统上会放在虚拟的文件夹,在有些版本的 Windows 系统上会直接放在真实文件夹。
2. 创建 MSIX 包的方法
创建 MSIX 包的方法大致上有3种,其中前2种的无法充分发挥 MSIX 能力,但是很适合新手入门 MSIX 打包,第3种方法能够充分发挥 MSIX 能力,并且支持打包服务器创建 MSIX 包。
2.1. 使用 MSIX Packaging Tool 将已有安装程序创建 MSIX 包
微软文档:
https://docs.microsoft.com/zh-cn/windows/msix/packaging-tool/create-an-msix-overview
这是最简单的创建 MSIX 包的方法,不过也是最无法发挥 MSIX 能力的方法。
最简单的原因:
你只要从Windows 10 1809 及以后版本操作系统中的 Microsoft Store 中下载安装 MSIX Packaging Tool 工具,执行这个工具,运行已有安装程序,就可以通过录制安装行为,创建 MSIX 包。如果你向减少录制多余的内容,可以先安装一个虚拟机,并在这虚拟机执行前面的步骤。
安装虚拟机的教程:
https://docs.microsoft.com/zh-cn/windows/msix/packaging-tool/quick-create-vm
2.2. 使用 Visual Studio 中创建 MSIX 包
微软文档:
https://docs.microsoft.com/zh-cn/windows/msix/desktop/vs-package-overview
在 Visual Studio 2017 15.5 及更高版本中,通过 “工具” > “获取工具和功能”,打开 “正在修改”弹窗。
勾选 “通用 Windows 平台开发”,勾选 “Windows 10 SDK(10.0.19041.0)”,点击 “下载时安装”,等待安装完成。
可以 通过 “新建” > “项目” ,打开 “创建新项目” 弹窗,选择 “Windows 应用程序打包项目” 。
在 “依赖项” 上右键,点击 “添加项目引用”,添加你的主程序对应的项目。
需要注意的是,这里只支持添加 .NET Framework 4.6.1 及以上版本的项目。
在打包项目上右键,选择 “Publish” > “创建应用程序包§”,就可以进入创建 MSIX包流程。
2.3. 使用命令行创建 MSIX 包
微软文档:
https://docs.microsoft.com/zh-cn/windows/msix/package/manual-packaging-root
2.3.1 准备打包环境
确认 makeappx.exe 是否存在。根据 SDK 的安装路径,以下是makeappx.exe 在 Windows 10 电脑上的位置:
C:\Program Files (x86)\Windows Kits\10\bin<build number><architecture>\makeappx.exe
其中 = x86、x64、arm、arm64 或 chpe。
示例:
C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x86\makeappx.exe
C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\makeappx.exe
如果没有找到makeappx.exe,也可以通过安装 “2.2. 使用 Visual Studio 中创建 MSIX 包” 中介绍的环境。如果为了节约空间,也可以只安装 Windows 10 SDK(10.0.19041.0) 。
2.3.2 MSXI打包的主要内容
MSIX打包最主要的内容:被打包的文件夹的结构、被打包的文件夹中的AppxManifest.xml 文件。
MSIX安装的时候,会直接复制文件夹中所有内容,到分配给程序的虚拟目录;根据AppxManifest.xml 文件的声明,注册程序和组件。所以,AppxManifest.xml 文件能够声明的哪一些信息,就代表 MSIX 打包支持哪一些能力。
AppxManifest.xml 支持的能力文档:
https://docs.microsoft.com/zh-cn/uwp/schemas/appxpackage/uapmanifestschema/root-elements
使用 MSIX 打包与使用 EXE 打包最大区别是,MSIX 只能够向被打包的文件夹添加文件、文件夹,在AppxManifest.xml 文件中声明程序信息。 EXE 打包能够向 Windows 系统任何文件夹添加文件、文件夹,调用可执行程序,执行安装部署流程,调用 Windows 的组件注册程序、驱动注册程序等等。
2.3.3 一个简单项目的演示
2.3.3.1 需要被打包的文件夹
详细演示见后面的Demo。MsixAppDemo.zip
MsixAppDemo.zip
需要打包的文件夹为ProjectSource,主程序为 MsixAppDemo.exe ,虚拟注册表为Registry.dat
程序的图标资源位于文件夹 Images
产品程序、组件、右键多语言资源位于文件夹 Strings
AppxManifest.xml 支持的能力文档:
https://docs.microsoft.com/zh-cn/uwp/schemas/appxpackage/uapmanifestschema/root-elements
最简单的 AppxManifest.xml 文件的内容
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
<Identity
Name="MsixAppDemo"
Publisher="CN="Msix App Demo Co.,Ltd", OU=RD, O="Wondershare Technology Co.,Ltd", C=CN"
Version="1.0.0.1" ProcessorArchitecture="x86" />
<Properties>
<DisplayName>ms-resource:Resources/PackageDisplayName</DisplayName>
<PublisherDisplayName>ms-resource:Resources/PublisherDisplayName</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource uap:Scale="200" />
<Resource Language="en-US" />
</Resources>
<Applications>
<Application Id="MsixAppDemo" Executable="MsixAppDemo.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="ms-resource:Resources/ApplicationDisplayName"
Description="ms-resource:Resources/ApplicationDescription"
BackgroundColor="#101010"
Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile ShortName="ms-resource:Resources/TileShortName"
Wide310x150Logo="Images\Wide310x150Logo.png"
Square71x71Logo="Images\Square71x71Logo.png">
<uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo"/>
</uap:ShowNameOnTiles>
</uap:DefaultTile>
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
元素 Identity 的属性 Name
属性 Name 的内容会做为你 msixbundle 的唯一标记的前缀,需要注意的是,唯一标记的后缀,无法由开发者在签名前固定,因为后缀受版本号和签名证书影响。除版本号和架构以外的后缀,在签名证书修改的情况下,一定会改变;否则,一定不会改变。
演示项目的唯一标记示例:
MsixAppDemo_1.0.0.1_x86__y21qvjamj4ssj
MsixAppDemo_y21qvjamj4ssj
如果没有修改 WindowsApp 安装位置,包内的资源都会复制到文件夹
C:\Program Files\WindowsApps\MsixAppDemo_1.0.0.1_x86__y21qvjamj4ssj
如果修改 WindowsApp 安装位置为D盘,包内的资源都会复制到文件夹
D:\WindowsApps\MsixAppDemo_1.0.0.1_x86__y21qvjamj4ssj
如果使用了控制台可用的别名,支持别名的虚拟程序同时存在于以下2个文件夹:
%AppData%…\Local\Microsoft\WindowsApps
%AppData%…\Local\Microsoft\WindowsApps\MsixAppDemo_y21qvjamj4ssj
程序运行期间对非通用库的文件夹读写、注册表,实际上都是在读写以下文件夹中的文件夹、文件、虚拟注册表
%AppData%…\Local\Packages\MsixAppDemo_y21qvjamj4ssj
元素 Identity 的属性 Publisher
属性 Publisher 的内容必须与你将要使用的签名证书相对应,否则会签名失败。如果要验证点击 msixbundle 文件就进入安装,那边就必须要对 msixbundle 进行签名
元素 Application
元素Applications中可以包含多个元素Application,但是每个元素Application的属性 Id 必须不同,属性 Id 作为程序入口的唯一标记,可以支持使用文件资源管理器启动 MSIX 中的程序。
示例:
explorer.exe shell:appsFolder\MsixAppDemo_y21qvjamj4ssj!MsixAppDemo
其中,
MsixAppDemo_y21qvjamj4ssj为项目的唯一标记。
MsixAppDemo为元素 Application 的属性 Id 。
2.3.3.2 准备签名证书
演示项目中,已经提供了1个测试签名证书,证书文件中包含2个证书。
点击 Msix App Demo Cert CA.pfx,进入安装
选择存储位置为“当前用户”,多次点击“下一步”。
输入密码 123456,勾选“标志此密钥可导出的密钥(M)”。
选择“根据证书类型,自动选择证书存储”,继续点击“下一步”,直至点击“完成”。
2.3.3.3 开始打包
点击演示项目中的 make_fat_msixbundle.bat ,即可开始自动打包。
make_fat_msixbundle.bat 文件的内容
@echo off
set CurrentDir=%~dp0
cd %CurrentDir%
set AppName=MsixAppDemo
::Msix包中要合并的多语言资源,要合并的多倍贴图资源。
set dq=/dq en-US_de-DE_fr-FR_scale-200
set ProcessorArchitecture=x86
set TargetSystemVersion=10.0
::被打包的文件夹的位置。
set ProjectSource=%CurrentDir%ProjectSource
::完成打包后,包输出文件夹。
set ProjectOutput=%CurrentDir%Output
::签名证书颁发者名称的一部分。
set CertificateLssuer=Msix App Demo Cert
::签名证书名称的一部分。
set CertificateName=Msix
::时间戳服务器。
set CertificateTime=http://timestamp.digicert.com
set Makepri="C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\makepri.exe"
set Makeappx="C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\makeappx.exe"
set Signtool="C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\signtool.exe"
set IntermediateSplit=%CurrentDir%Temp\IntermediateSplit
set IntermediateBundle=%CurrentDir%Temp\IntermediateBundle
if exist Temp (RMDIR /S /Q Temp)
IF not exist %IntermediateSplit%. (mkdir %IntermediateSplit%)
IF not exist %IntermediateBundle%. (mkdir %IntermediateBundle%)
IF not exist %Output%. (mkdir %Output%)
set appNameXml=%IntermediateSplit%\%AppName%.xml
echo making config
%Makepri% createconfig /cf %appNameXml% %dq% /pv %TargetSystemVersion% /o
IF ERRORLEVEL 1 GOTO GenericError
set resourcesPri=%IntermediateSplit%\resources.pri
echo making pri
%Makepri% new /pr %ProjectSource% /cf %appNameXml% /of %resourcesPri% /mf appx /o
IF ERRORLEVEL 1 GOTO GenericError
set appxManifestXml=%ProjectSource%\AppxManifest.xml
set resourcesMapTxt=%IntermediateSplit%\resources.map.txt
set mainMsix=%IntermediateBundle%\%AppName%_%ProcessorArchitecture%.msix
echo making msixe
%Makeappx% pack /m %appxManifestXml% /f %resourcesMapTxt% /p %mainMsix% /o
IF ERRORLEVEL 1 GOTO GenericError
echo signing msixe
%Signtool% sign /i "%CertificateLssuer%" /fd sha256 /s my /a /n "%CertificateName%" /tr "%CertificateTime%" /td sha256 /d "%AppName%" "%mainMsix%"
IF ERRORLEVEL 1 GOTO SigningError
set mainMsixbundleName=%AppName%__%ProcessorArchitecture%.msixbundle
set mainMsixbundleNamePath=%ProjectOutput%\%mainMsixbundleName%
set mainMsixbundle=%ProjectOutput%\%mainMsixbundleName%
echo making msix bundle
%Makeappx% bundle /d %IntermediateBundle% /o /p %mainMsixbundle%
IF ERRORLEVEL 1 GOTO GenericError
echo signing bundle
%Signtool% sign /i "%CertificateLssuer%" /fd sha256 /s my /a /n "%CertificateName%" /tr "%CertificateTime%" /td sha256 /d "%AppName%" "%mainMsixbundleNamePath%"
IF ERRORLEVEL 1 GOTO SigningError
GOTO End
:GenericError
ECHO.
ECHO ******
ECHO Error %ERRORLEVEL%; stopping batch file.
ECHO ******
ECHO.
pause
:SigningError
ECHO.
ECHO ******
ECHO Error %ERRORLEVEL%; stopping batch file.
ECHO Ensure that the Publisher attribute of the Identity element
ECHO in the AppX manifest matches the Issuer in the signing certificate.
ECHO ******
ECHO.
pause
:End
ECHO.
ECHO Done!
pause
常见问题
创建能够带参数的快捷方式
主要思路就是使用了控制台可用的别名,支持别名的虚拟程序同时存在于以下2个文件夹:
%AppData%…\Local\Microsoft\WindowsApps
%AppData%…\Local\Microsoft\WindowsApps\MsixAppDemo_y21qvjamj4ssj
在AppxManifest.xml 文件中,将以下元素添加到元素Application中的元素Extensions中,安装完成后,就会创建别名程序在相应的文件夹。
<!--使得程序支持在包外通过快捷方式打开。-->
<uap5:Extension Category="windows.appExecutionAlias">
<uap5:AppExecutionAlias>
<uap5:ExecutionAlias Alias="MsixAppDemo.exe" uap8:AllowOverride="true" />
</uap5:AppExecutionAlias>
</uap5:Extension>
创建一个快捷方式指向别名程序,就可以传参数了;不过,别名程序是没有图标的,需要给快捷方式添加图标。
如何观察 .msix .msixbundle 文件中的内容
可以直接使用 7-Zip 程序解压 .msix .msixbundle 文件,观察被打包的文件夹,在打包完成后,添加了什么内容。
如何仅在第1次启动程序时,执行一些自定义的部署行为
使用这个方法的意义:
在 MSIX 满足 上架 Microsoft Store 的场景下, MSIX 不支持自定义安装、卸载程序。如果需要执行一些部署行为,可以用这个方案替代。
利用卸载MSIX时,会删除所有虚拟文件夹的特性,在每个版本的程序的第1次启动时,执行一些自定义的部署行为,并将这个版本已经启动过的信息添加到特定的虚拟文件夹,之后启动这个版本时,检查前面写入的信息,如果发现已经启动过,就不再执行这些行为。在程序卸载时,这些信息会因为位于虚拟文件夹,而自动被删除。
示例可写入文件夹:
%AppData%…\Local\Packages\MsixAppDemo_y21qvjamj4ssj\LocalCache
注册文件资源管理器缩略图COM组件、预览COM组件
需要注意,MSXI 不支持 .NET 编写的文件资源管理器缩略图COM组件、预览COM组件,不过它支持 原生C++ 编写的文件资源管理器缩略图COM组件、预览COM组件。
在AppxManifest.xml 文件中,将以下元素添加到元素Application中的元素Extensions中,其中Id、AppId使用COM的Clsid,Path使用包含COM的dll文件相对于被打包的文件夹的路径。
<!--注册COM。txt资源管理器预览插件PrevewHandler。-->
<!--Id="AAABAF99-0C5D-4FA8-8CCD-1129EE6D25B9"-->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer AppId="815BAF99-0C5D-4FA8-8CCD-1129EE6D25B9">
<com:Class Id="AAABAF99-0C5D-4FA8-8CCD-1129EE6D25B9" Path="App32\PEPreview4.dll" ThreadingModel="Both">
</com:Class>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<!--注册COM。txt资源管理器缩略图插件ThumbnailHandler。-->
<!--Id="AAA3C4E9-D71A-4411-A9CD-1130412C5FC0"-->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer AppId="AAA3C4E9-D71A-4411-A9CD-1130412C5FC0">
<com:Class Id="AAA3C4E9-D71A-4411-A9CD-1130412C5FC0" Path="App32\ThumbnailHandler.dll" ThreadingModel="Both">
</com:Class>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="txt">
<uap:SupportedFileTypes>
<uap:FileType>.txt</uap:FileType>
</uap:SupportedFileTypes>
<!--使得程序出现在文件的右键菜单的 打开方式 的列表中。-->
<uap2:SupportedVerbs>
<uap3:Verb Id="open" Parameters=""%1"">open</uap3:Verb>
</uap2:SupportedVerbs>
<!--注册txt资源管理器预览插件PrevewHandler。-->
<desktop2:DesktopPreviewHandler Clsid="AAABAF99-0C5D-4FA8-8CCD-1129EE6D25B9"/>
<!--txt资源管理器缩略图插件ThumbnailHandler。-->
<desktop2:ThumbnailHandler Clsid="AAA3C4E9-D71A-4411-A9CD-1130412C5FC0" />
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions>
注册虚拟打印机
MSIX的虚拟打印机和EXE注册虚拟打印机完全不一样,本质上是添加一个处理.xps文件的exe程序,并在AppxManifest.xml 文件中声明这个程序。
详细演示见后面的Demo。MsixPrinterDemo.zip
<Application Id="Printer"
Executable="MsixPrinter.exe"
EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
AppListEntry="none"
DisplayName="MsixPrinterDemo"
Description="MsixPrinterDemo"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
</uap:VisualElements>
<Extensions>
<desktop2:Extension
Category="windows.appPrinter"
Executable="MsixPrinter.exe"
EntryPoint="Windows.FullTrustApplication">
<desktop2:AppPrinter DisplayName ="MsixPrinterDemo" Parameters=""%1"" />
</desktop2:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="pdf">
<uap:SupportedFileTypes>
<uap:FileType>.pdf</uap:FileType>
<uap:FileType>.txt</uap:FileType>
<uap:FileType>.doc</uap:FileType>
<uap:FileType>.docx</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions>
</Application>
使用 MSIX Hero 查看已安装 .msix .msixbundle 的注册表
文档:
https://msixhero.net/documentation/viewing-virtual-registry-keys-from-an-msix-app/