如何在没有WMI的情况下可靠地找到应用程序InstallLocation

时间:2014-04-09 01:12:23

标签: c# .net windows registry windows-installer

我试图查找是否安装了应用,以及安装的路径是什么。我已尝试使用WMI Win32_SoftwareElement并枚举以下注册表项

  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
  • HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
  • HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall
  • HKCU\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

我发现的是,虽然它是最快的方法(而不是WMI),但它并不可靠。并非列出所有应用,并且列出的所有应用都未正确设置InstallLocation。搜索Win32_SoftwareElement产生了最好的结果,但它很迟钝,想知道是否有更好的替代方案,也许是我未能在搜索中包含的注册位置,或者是否有其他WMI替代方案,如P / Invoke。

2 个答案:

答案 0 :(得分:1)

安装位置并非始终设置,因为它不是自动的。某些安装可能不是基于MSI的安装,我不知道使用的其他工具是否都将设置位置。如果它是基于MSI的安装,则仅当包开发人员在安装期间将ARPINSTALLOCATION属性设置为实际位置时,该位置才存在。

http://msdn.microsoft.com/en-us/library/aa367589(v=vs.85).aspx

用于枚举已安装产品的普通旧版Win32 Windows Installer API是MsiEnumProducts(),一次返回一个ProductCode值,然后调用MsiGetProductInfo()传递该产品代码guid并请求INSTALLPROPERTY_INSTALLLOCATION,但再次说明'只有在MSI开发人员做正确的事情时才会这样做。

没有可靠的方法可以同时获得已安装的产品,因为有些是基于MSI的,有些则不是,它们可能会也可能不会设置安装位置,而且安装位置通常只是应用程序目录并且不包括安装在任何其他地方的文件,因此它的价值很小。浏览注册表并对所发现的内容做出决定似乎是获取列表的唯一方法。

说了这么多,简短的回答是如果你知道一个ProductCode然后按上面的方式调用MsiGetProductInfo()并获取位置!

答案 1 :(得分:0)

即使在知道安装程序正在使用MSI的情况下,即使有可能可靠地做到这一点,在StackOverflow上似乎也没有明确的答案。我将使用C ++,但是将其移植到C#很简单:

  • 首先,我们必须获得ProductId。您要么已经知道它,就使用gwmi win32_product -filter "Name like '%<product_name_pattern>%'" -namespace root/cimv2或其他某种方式来获取它(例如,使用MsiEnumProductsWMsiGetProductInfoW或使用upgrade_code和MsiEnumRelatedProductsW
  • 我们尝试直接获取InstallLocation
  DWORD buf_size = MAX_PATH;
  wchar_t buf[MAX_PATH];
  if(ERROR_SUCCESS != MsiGetProductInfoW(Product_ID, INSTALLPROPERTY_INSTALLLOCATION, buf, &buf_size) || !buf_size)
  {
    return buf;
  }

您已经知道,这对于某些安装程序不起作用,因为设置ARPINSTALLLOCATION property是可选的(有趣的设计选择)。但是,Windows仍然可以正确卸载这些文件,因此它必须知道一些我们不知道的内容。 MSI使用简单的关系数据库来组织其内容,Orca允许我们对其进行浏览。例如,这是node.js安装程序:

enter image description here Component列只是通过某些安装程序工具集使用/生成的节点作者的名称,KeyPath是此组件安装的文件名(或注册表项等),最后ComponentId是GUID,Windows使用它来唯一标识要卸载的文件。

由于我们拥有ProductId,因此我们可以使用MsiGetComponentPathW找到缓存的.msi安装程序的路径并查询其DB以获取所有ComponentId及其实际路径:

using unique_msi_handle = wil::unique_any<MSIHANDLE, decltype(&::MsiCloseHandle), ::MsiCloseHandle>;

if(ERROR_SUCCESS != MsiGetProductInfoW(Product_ID, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size))
{
  return;
}
std::wstring package_path(++package_path_size, L'\0');

if(ERROR_SUCCESS != MsiGetProductInfoW(Product_ID, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size))
{
  return;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW

unique_msi_handle db_handle;
if(ERROR_SUCCESS != MsiOpenDatabaseW(package_path.data(), (wchar_t *)MSIDBOPEN_READONLY, &db_handle))
{
  return;
}

unique_msi_handle view;
if(ERROR_SUCCESS != MsiDatabaseOpenViewW(db_handle.get(), L"select ComponentId from Component", &view))
{
  return;
}

if(ERROR_SUCCESS != MsiViewExecute(view.get(), 0))
{
  return;
}

unique_msi_handle record;
while(ERROR_SUCCESS == MsiViewFetch(view.get(), &record))
{
  wchar_t component_id[guid_length];
  DWORD component_id_len = guid_length;
  MsiRecordGetStringW(record.get(), 1, component_id, &component_id_len);
  wchar_t path[256];
  DWORD path_size = 256;
  MsiGetComponentPathW(Product_ID, component_id, path, &path_size);

  std::wcout << component_id << L": " << path << '\n';
}

正如@PhilDW所提到的,这很可能会在注册表中抓取注册表。 node.js的输出:

{BE71D092-38E4-5DED-B176-D60079E51615}: C:\Program Files\nodejs\node.exe
{1A357DF6-3AF1-5A76-AE55-3676CCFA4513}: 22:\SOFTWARE\Node.js\InstallPath
{26837A22-55BA-5207-B2DE-0F7366E8FB8E}: C:\Program Files\nodejs\nodevars.bat
{EE1FC8BD-57FF-514B-8950-359974C2C6B9}: C:\Program Files\nodejs\install_tools.bat
{8B344AC8-9B54-5327-9D16-CE528884AC7E}: C:\Program Files\nodejs\node_etw_provider.man
{A194E0CC-E739-5C8B-947E-BD9463D8341A}: 21:\SOFTWARE\Node.js\Components\NodeStartMenuShortcuts
{B466EFC0-4255-51C5-8E26-3D8258F1AB57}: C:\Program Files\nodejs\npm.cmd
{CA3A5DB4-0430-533D-83BC-7044D32BAE9E}: C:\Program Files\nodejs\npm
{1BDBC761-41B0-51F4-8D42-42AD24077B91}: C:\Program Files\nodejs\npx.cmd
{F4384E08-1315-5F41-B086-F78BD6C0EE84}: C:\Program Files\nodejs\npx
{781AE8C4-C809-5E2B-A2C8-132EE6210483}: C:\Program Files\nodejs\node_modules\npm\npmrc
...

如您所见,这里有很多选择:

  • 使用所有组件来推断安装路径(如果您对卸载的软件一无所知)。
  • 如果您知道放置在安装根目录中的某个文件,例如node.exe,只需找到其前缀即可。

希望这会有所帮助。