按名称与发布者枚举 Windows 应用包:基于 PackageManager.FindPackages 的 C++/C 双语言实战

  • 示例工程

【免费下载链接】Windows-classic-samples

This repo contains samples that demonstrate the API used in Windows classic desktop applications.

项目地址: https://gitcode.com/gh_mirrors/wi/Windows-classic-samples
点击查看 免费下载

本文以 Windows-classic-samples 仓库中的 Enumerate app packages by name and publisher sample 为蓝本,深入讲解如何通过 Windows.Management.Deployment.PackageManager.FindPackages(String, String) 按"包名 + 发布者"组合条件枚举系统上已安装的应用程序包(app package),并进一步枚举每个包的安装用户、解析其 SID 对应的账户名。读完本文,你将掌握该 API 的调用约定、命令行参数设计、C++(CX/WinRT)与 C# 两套完整实现,以及构建、运行与排错的全部要点。

背景:应用包与按用户安装机制

Windows 以"应用包"(app package)为单位向用户分发和安装应用。包内既包含构成应用的文件,也包含一个描述应用的包清单(package manifest)。Windows 依据清单信息将应用以"按用户"(per-user)方式安装——每个用户各自拥有一份安装记录;当所有安装过该应用的用户都卸载后,系统才会彻底清除该应用在设备上的全部痕迹。这意味着,同一台机器上可能存在"同一应用包被多个不同用户各自安装"的情况,因此"枚举某个包被哪些用户安装"(FindUsers)与"按名称和发布者过滤包"(FindPackages)往往需要配合使用。

核心 API:PackageManager.FindPackages(String, String)

本示例的核心调用是双参数重载 PackageManager.FindPackages(String packageName, String publisherName)。它返回一个可枚举的 Package 集合,其中每个元素代表一个同时匹配指定名称与指定发布者的已安装应用包:

  • packageName:包名(Package Name),例如 microsoft.help;
  • publisherName:发布者名称(Publisher),通常以证书主体(subject)字符串形式给出,例如 CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US。

在 C++ 实现 中调用方式如下:

auto packageManager = ref new Windows::Management::Deployment::PackageManager();
auto packages = packageManager->FindPackages(inputPackageName, inputPublisherName);

在 C# 实现 中则为:

Windows.Management.Deployment.PackageManager packageManager = new Windows.Management.Deployment.PackageManager();
IEnumerable<Windows.ApplicationModel.Package> packages =
    (IEnumerable<Windows.ApplicationModel.Package>)packageManager.FindPackages(inputPackageName, inputPublisherName);

两个版本都把返回的集合强类型化为 Windows.ApplicationModel.Package(C++ 侧为 Windows::ApplicationModel::Package^),随后逐个输出包信息。

示例工程结构

仓库中该示例同时提供 C++ 与 C# 两种实现,各自包含解决方案文件、工程文件和命令行说明:

Samples/PackageManagerFindPackagesByNameAndPublisher/
├── cpp/
│   ├── FindPackagesByNameAndPublisherSample.cpp   # C++/CX 实现
│   ├── FindPackagesByNameAndPublisherSample.sln   # VS 解决方案
│   ├── FindPackagesByNameAndPublisherSample.vcxproj
│   └── README.txt
├── cs/
│   ├── FindPackagesByNameAndPublisherSample.cs    # C# 实现
│   ├── FindPackagesByNameAndPublisherSample.csproj
│   ├── FindPackagesByNameAndPublisherSample.sln
│   ├── README.txt
│   └── app.config                                 # .NET Framework 4.5 运行时声明
└── README.md                                      # 主文档

C++ 工程(vcxproj)面向 Win32 控制台应用,使用 VS2013 工具集(PlatformToolset 为 v120)、Unicode 字符集,并开启 WinRT 编译支持(CompileAsWinRT 为 true,附加参数 AdditionalOptions 中包含 /ZW:nostdlib /FUplatform.winmd /FUWindows.winmd)——这正是它可以直接 #using <Windows.winmd> 并调用 WinRT API 的原因。C# 工程(csproj)目标框架为 .NET Framework 4.5、目标平台为 Windows 8.1(TargetPlatformVersion 为 8.1),并显式引用了 Windows 程序集;app.config 声明了 .NETFramework,Version=v4.5 运行时支持。

C++ 实现要点解析

命令行参数校验

C++/CX 控制台入口使用 main(Platform::Array<String^>^ args),其中 args[0] 是程序自身路径,因此可用的用户参数从 args[1] 开始。代码在 第 91-95 行 校验:

if (args->Length < 3)
{
    wcout << L"Usage: FindPackagesByNameAndPublisherSample.exe packageName publisherName";
    return 1;
}

即要求至少传入 packageName 与 publisherName 两个参数,否则打印用法说明并返回非零退出码。

输出包信息

DisplayPackageInfo 函数(第 23-34 行)逐字段打印 PackageId 与包属性:

void DisplayPackageInfo(Windows::ApplicationModel::Package^ package)
{
    wcout << L"Name: " << package->Id->Name->Data() << endl;
    wcout << L"FullName: " << package->Id->FullName->Data() << endl;
    wcout << L"Version: " << package->Id->Version.Major << "." <<
        package->Id->Version.Minor << "." << package->Id->Version.Build <<
        "." << package->Id->Version.Revision << endl;
    wcout << L"Publisher: " << package->Id->Publisher->Data() << endl;
    wcout << L"PublisherId: " << package->Id->PublisherId->Data() << endl;
    wcout << L"Installed Location: " << package->InstalledLocation->Path->Data() << endl;
    wcout << L"IsFramework: " << (package->IsFramework ? L"True" : L"False") << endl;
}

各字段含义与取值说明:

输出字段来源属性说明
NameId->Name包名,即 FindPackages 第一参数匹配对象
FullNameId->FullName包的完整名称,通常为 Name_PublisherId_Version_arch__hash 形式,也是 FindUsers 的入参
VersionId->Version主版本.次版本.构建号.修订号(四段)
PublisherId->Publisher发布者显示名称(证书主体)
PublisherIdId->PublisherId从发布者派生的短标识
Installed LocationInstalledLocation->Path包在磁盘上的安装路径
IsFrameworkIsFramework是否为框架包,输出 True/False

枚举安装用户并把 SID 转成账户名

DisplayPackageUsers(第 69-83 行)调用 packageManager->FindUsers(package->Id->FullName) 获取安装了该包的所有用户的 PackageUserInformation 集合,每个元素的 UserSecurityId 是一个 SID 字符串。随后 SidToAccountName(第 36-67 行)借助经典 Win32 函数 ConvertStringSidToSid + LookupAccountSid 把 SID 翻译为 DOMAIN\UserName 形式;若翻译失败(如本地不存在该账户),则回退输出原始 SID 字符串。这是本示例在 WinRT 枚举之外引入原生 Win32 API 的一个典型细节。

异常与权限处理

主流程(第 100-130 行)在 try 块内完成枚举;对每个包输出信息、输出用户并累加计数。若最终计数为 0,打印 No packages were found.。异常分支中:

  • 捕获 AccessDeniedException^:提示"必须以提升权限(elevated)的命令提示符运行",返回退出码 1;
  • 捕获通用 Exception^:打印错误消息,返回退出码 1。

这与 README.txt 中"本示例必须在提升的命令提示符下运行"的说明完全对应——因为枚举全部用户的包安装信息需要相应权限。

C# 实现要点解析

C# 版本(FindPackagesByNameAndPublisherSample.cs)与 C++ 版本逻辑一一对应:

  • 入口 Main(string[] args)(第 14-68 行)校验 args.Length < 2(C# 的 args 不含程序路径本身),然后调用 FindPackages 并将结果强转成 IEnumerable<Windows.ApplicationModel.Package>;
  • DisplayPackageInfo(第 99-115 行)输出与 C++ 版完全一致的字段集;
  • DisplayPackageUsers(第 70-82 行)调用 packageManager.FindUsers(package.Id.FullName);
  • SidToAccountName(第 84-96 行)使用 .NET 的 SecurityIdentifier 与 NTAccount 完成 SID→账户名转换,捕获 IdentityNotMappedException 时回退输出原 SID;
  • 异常分支捕获 UnauthorizedAccessException(对应 C++ 的 AccessDeniedException)与通用 Exception,提示信息一致:必须以提升权限的命令提示符运行。

两种语言实现共享同一命令行契约,便于对照学习同一 WinRT API 在不同语言投影下的写法差异。

构建示例

方式一:命令行构建(msbuild)

  1. 打开"命令提示符"(Command Prompt)窗口;
  2. 切换到示例所在目录 Samples/PackageManagerFindPackagesByNameAndPublisher/cpp 或 .../cs;
  3. 执行:
msbuild FindPackagesByNameAndPublisherSample.sln

方式二:Visual Studio 构建

  1. 启动 Visual Studio,选择 文件 > 打开 > 项目/解决方案;
  2. 定位到 FindPackagesByNameAndPublisher 示例目录,选择对应的 .sln 文件;
  3. 按 F7(Visual Studio 2013 为 F6),或使用 生成 > 生成解决方案。

工程默认生成到 Debug 或 Release 输出目录(C# 工程在 csproj 中配置为 bin\Debug\ 与 bin\Release\)。

工具链要求

  • 需要 Microsoft Visual Studio 2013 或更高版本(任意 SKU);
  • 不能使用 Visual Studio Express 2013 for Windows 编译(原文档中的明确警告);
  • 从工程配置看,C++ 侧要求 v120 工具集并开启 WinRT 编译选项(CompileAsWinRT=true、/ZW),C# 侧要求 .NET Framework 4.5 与 Windows 8.1 SDK,因此应确保安装了 Windows Runtime SDK。

运行示例

  1. 打开一个提升权限的命令提示符窗口(以管理员身份运行);
  2. 切换到包含 FindPackagesByNameAndPublisherSample.exe 的目录;
  3. 执行:
FindPackagesByNameAndPublisherSample packageName publisherName

例如:

FindPackagesByNameAndPublisherSample microsoft.help "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"

参数格式要点:packageName 与 publisherName 缺一不可;发布者名称(证书主体)包含空格与逗号,必须用双引号整体包裹(cpp/README.txt 中明确说明 "Arguments containing whitespace must be surrounded by double quotes")。C++ 版退出码 1 同时用于参数缺失、权限不足与运行时错误三种情形;成功时返回 0。

运行后控制台将为每个匹配包输出上述字段,并在末尾追加一行 Users: DOMAIN\user1 DOMAIN\user2 ...(若 SID 无法解析则显示原始 SID)。若没有任何匹配包,输出 No packages were found.。

运行环境与注意事项

  • 客户端:Windows 8.1;
  • 服务器:Windows Server 2012 R2;
  • 示例创建于 Windows 8.1 / Windows Server 2012 R2(Visual Studio 2013)时代,按仓库主 README 的说明,多数情况下可在更高版本系统上不加修改地运行,但个别平台、版本或 SKU 特定的要求仍可能影响编译与运行;
  • 必须以提升权限的命令提示符运行,否则会因权限不足抛出 AccessDeniedException(C++)/ UnauthorizedAccessException(C#)并给出对应提示。

与仓库内其他枚举样本的关联

本示例是 Windows-classic-samples 仓库中"Package Manager 枚举"系列之一,可与同类样本对照阅读:

三者共同覆盖了 PackageManager 在包枚举场景下的三种典型过滤维度(不过滤 / 按名称与发布者 / 按用户 SID),本示例恰是其中"按名称与发布者精确匹配"的一环,也是理解 PackageId 各字段与 FindUsers 返回结构的最佳入口。

  • 示例工程

【免费下载链接】Windows-classic-samples

This repo contains samples that demonstrate the API used in Windows classic desktop applications.

项目地址: https://gitcode.com/gh_mirrors/wi/Windows-classic-samples
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值