通过使用外部位置进行打包来授予包标识

如果你有一个现有的桌面应用,并且该应用有自身的安装程序,你只需做出极少量的更改即可从包标识中受益。

仅当桌面应用在运行时具有程序包标识符时,该应用才能使用后台任务、通知、实时磁贴、自定义上下文菜单扩展和共享目标等多项 Windows 扩展性功能。 这是因为,操作系统 (OS) 需要能够识别相应 API 的调用方。 请参阅需要包标识的功能

只有打包的应用在运行时具有包标识。 有关打包、解包的应用,以及使用外部位置打包的应用的定义,请参阅部署概述

  • 在 Windows 10 版本 2004 和更低版本中,向应用授予包标识的唯一方法是将其打包到已签名的 MSIX 包中(请参阅从代码生成 MSIX 包)。 在这种情况下,将在程序包清单中指定标识,并由 MSIX 部署管道根据清单中的信息处理标识注册。 程序包清单中引用的所有内容都存在于 MSIX 包中。
  • 但从 Windows 10 版本 2004 开始,只需生成一个具有外部位置的包并将其注册到应用,即可向应用授予包标识。 这会将该应用转变成打包的应用;具体而言,是具有外部位置的打包应用。 这是因为,某些桌面应用的某些内容尚未准备好在 MSIX 包中提供。 因此,此项支持能使此类应用获得包标识,从而能够使用需要包标识的 Windows 扩展性功能。 有关更多背景信息,请参阅博客文章 Identity, Registration and Activation of Non-packaged Win32 Apps(非打包 Win32 应用的标识、注册和激活)。

若要使用外部位置生成和注册包(向应用授予包标识),请执行以下步骤。

  1. 为具有外部位置的包创建程序包清单
  2. 生成具有外部位置的包并为其签名
  3. 将程序包标识符元数据添加到桌面应用程序清单
  4. 在运行时注册具有外部位置的包

重要概念

以下功能可使非打包桌面应用获取包标识。

具有外部位置的包

具有外部位置的包包含程序包清单,但不包含其他应用二进制文件和内容。 具有外部位置的包的清单可以在预定的外部位置引用包外部的文件。 如前所述,此项支持使得其某些内容尚未准备好在 MSIX 包中提供的应用,能够使用需要包标识的 Windows 扩展性功能。

注意

使用具有外部位置的包的桌面应用不会获得通过 MSIX 包完全进行部署所带来的某些好处。 这些优势包括:防篡改、在锁定位置安装,以及在部署、运行时间和卸载时由 OS 进行全面管理。

允许外部内容

为了支持具有外部位置的包,程序包清单架构现在已支持 Properties 元素下的可选元素 uap10:AllowExternalContent。 这使程序包清单可以在磁盘上的特定位置引用包外部的内容。

例如,如果你的现有非打包桌面应用在 C:\Program Files\MyDesktopApp 中安装应用可执行文件和其他内容,则可以创建一个在清单中包含 uap10:AllowExternalContent 元素的具有外部位置的包。 在应用安装过程中或应用首次运行时,可以安装具有外部位置的包并将 C:\Program Files\MyDesktopApp\ 声明为应用将使用的外部位置。

为具有外部位置的包创建程序包清单

在生成具有外部位置的包之前,必须先创建一个程序包清单(一个名为 AppxManifest.xml 的文件),该文件声明桌面应用的包标识元数据和其他所需的详细信息。 为具有外部位置的包创建程序包清单的最简单方法是使用以下示例,并使用架构引用为应用自定义它。

确保程序包清单包含以下项:

  • 一个 Identity 元素,用于描述桌面应用的标识属性。
  • Properties 元素下的 uap10:AllowExternalContent 元素。 应为此元素分配 true 值,该值允许程序包清单在磁盘上的特定位置引用包外部的内容。 在后面的步骤中,在从安装程序或应用中运行的代码注册具有外部位置的包时,将指定外部位置的路径。 在清单中引用的不属于包本身的所有内容都应安装到外部位置。
  • 应将 TargetDeviceFamily 元素的 MinVersion 属性设置为 10.0.19000.0 或更高版本。
  • Application 元素的 TrustLevel=mediumIL 和 RuntimeBehavior=Win32App 属性声明了与具有外部位置的包关联的桌面应用的运行方式与标准未打包桌面应用类似,无需注册表和文件系统虚拟化以及其他运行时更改。

以下示例演示了具有外部位置的包清单 (AppxManifest.xml) 的完整内容。 此清单包含需要程序包标识符的 windows.sharetarget 扩展。

<?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:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
  IgnorableNamespaces="uap uap2 uap3 rescap desktop uap10">
  <Identity Name="ContosoPhotoStore" ProcessorArchitecture="x64" Publisher="CN=Contoso" Version="1.0.0.0" />
  <Properties>
    <DisplayName>ContosoPhotoStore</DisplayName>
    <PublisherDisplayName>Contoso</PublisherDisplayName>
    <Logo>Assets\storelogo.png</Logo>
    <uap10:AllowExternalContent>true</uap10:AllowExternalContent>
  </Properties>
  <Resources>
    <Resource Language="en-us" />
  </Resources>
  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.19000.0" />
  </Dependencies>
  <Capabilities>
    <rescap:Capability Name="runFullTrust" />
    <rescap:Capability Name="unvirtualizedResources"/>
  </Capabilities>
  <Applications>
    <Application Id="ContosoPhotoStore" Executable="ContosoPhotoStore.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App"> 
      <uap:VisualElements AppListEntry="none" DisplayName="Contoso PhotoStore" Description="Demonstrate photo app" BackgroundColor="transparent" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png">
        <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square310x310Logo="Assets\LargeTile.png" Square71x71Logo="Assets\SmallTile.png"></uap:DefaultTile>
        <uap:SplashScreen Image="Assets\SplashScreen.png" />
      </uap:VisualElements>
      <Extensions>
        <uap:Extension Category="windows.shareTarget">
          <uap:ShareTarget Description="Send to ContosoPhotoStore">
            <uap:SupportedFileTypes>
              <uap:FileType>.jpg</uap:FileType>
              <uap:FileType>.png</uap:FileType>
              <uap:FileType>.gif</uap:FileType>
            </uap:SupportedFileTypes>
            <uap:DataFormat>StorageItems</uap:DataFormat>
            <uap:DataFormat>Bitmap</uap:DataFormat>
          </uap:ShareTarget>
        </uap:Extension>
      </Extensions>
    </Application>
  </Applications>
</Package>

生成具有外部位置的包并为其签名

创建程序包清单后,请使用 Windows SDK 中的 MakeAppx.exe 工具生成具有外部位置的包。 由于具有外部位置的包不包含清单中引用的文件,因此必须指定 /nv 选项,该选项将跳过对包的语义验证。

以下示例演示如何从命令行创建具有外部位置的包。

MakeAppx.exe pack /d <path to directory that contains manifest> /p <output path>\MyPackage.msix /nv

在目标计算机上成功安装具有外部位置的包之前,必须使用目标计算机上受信任的证书对其进行签名。 可以出于开发目的创建新的自签名证书,并使用 Windows SDK 中提供的 SignTool 为具有外部位置的包签名。

以下示例演示如何从命令行为具有外部位置的包签名。

SignTool.exe sign /fd SHA256 /a /f <path to certificate>\MyCertificate.pfx /p <certificate password> <path to package with external location>\MyPackage.msix

将程序包标识符元数据添加到桌面应用程序清单

还必须在桌面应用中包含并行应用程序清单。 请参阅应用程序清单(该文件声明了 DPI 感知等内容,并且在生成过程中被嵌入到应用的 .exe)。 在该文件中,包含一个 msix 元素,其中包含声明应用的标识属性的属性。 OS 将使用这些属性的值在启动可执行文件时确定应用的标识。

下面的示例展示了包含 msix 元素的并行应用程序清单。

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="Contoso.PhotoStoreApp"/>
  <msix xmlns="urn:schemas-microsoft-com:msix.v1"
          publisher="CN=Contoso"
          packageName="ContosoPhotoStore"
          applicationId="ContosoPhotoStore"
        />
</assembly>

msix 元素的属性必须与具有外部位置的包的程序包清单中的以下值匹配:

  • packageNamepublisher 属性必须分别与程序包清单中 Identity 元素中的 NamePublisher 属性相匹配。
  • applicationId 属性必须分别与程序包清单中 Application 元素的 Id 属性相匹配。

在运行时注册具有外部位置的包

若要向桌面应用授予包标识,应用必须使用 PackageManager 类的 AddPackageByUriAsync 方法注册具有外部位置的包。 从 Windows 10 版本 2004 开始已提供此方法。 可以在首次运行应用时向应用添加代码以注册具有外部位置的包,也可以在安装桌面应用时运行代码以注册该稀疏包(例如,如果你使用 MSI 安装桌面应用,可以通过自定义操作运行此代码)。

以下示例演示如何注册具有外部位置的包。 此代码创建一个 AddPackageOptions 对象,该对象包含程序包清单可以引用包外部内容的外部位置的路径。 然后,代码将此对象传递给 AddPackageByUriAsync 方法以注册具有外部位置的包。 此方法还会接收已签名的具有外部位置的包的位置(一个 URI)。 有关更完整的示例,请参阅相关示例应用中的 StartUp.cs 代码文件(参阅本主题中的“示例应用”部分)

private static bool registerPackageWithExternalLocation(string externalLocation, string pkgPath)
{
    bool registration = false;
    try
    {
        Uri externalUri = new Uri(externalLocation);
        Uri packageUri = new Uri(pkgPath);

        Console.WriteLine("exe Location {0}", externalLocation);
        Console.WriteLine("msix Address {0}", pkgPath);

        Console.WriteLine("  exe Uri {0}", externalUri);
        Console.WriteLine("  msix Uri {0}", packageUri);

        PackageManager packageManager = new PackageManager();

        // Declare use of an external location
        var options = new AddPackageOptions();
        options.ExternalLocationUri = externalUri;

        Windows.Foundation.IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deploymentOperation = packageManager.AddPackageByUriAsync(packageUri, options);

        // Other progress and error-handling code omitted for brevity...
    }
}

示例应用

有关演示如何使用具有外部位置的包向桌面应用授予包标识的功能齐全的示例应用,请参阅 SparsePackages 示例。 博客文章 Identity, Registration and Activation of Non-packaged Win32 Apps(非打包 Win32 应用的标识、注册和激活)中提供了有关生成和运行该示例的详细信息。

此示例包含以下内容:

  • 名为 PhotoStoreDemo 的 WPF 应用的源代码。 在启动过程中,应用将检查并确定它是否使用标识运行。 如果它没有使用标识运行,则会注册具有外部位置的包,然后重启应用。 有关执行这些步骤的代码,请参阅 StartUp.cs
  • 名为 PhotoStoreDemo.exe.manifest 的并行应用程序清单。
  • 名为 AppxManifest.xml 的程序包清单。