简体   繁体   English

如何在没有 WMI 的情况下可靠地找到应用程序安装位置

[英]How to reliably find app InstallLocation without WMI

I'm trying to find if an app is installed, and what the installed path is.我正在尝试查找是否安装了应用程序,以及安装路径是什么。 I have tried using WMI Win32_SoftwareElement and also enumerating the following registry keys我曾尝试使用 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

What I've found out is that although it's the fastest method (as opposed to WMI) it's not as reliable.我发现虽然它是最快的方法(与 WMI 相对),但它并不可靠。 Not all apps are listed, and out of those listed, not all have their InstallLocation properly set.并未列出所有应用程序,并且在列出的应用程序中,并非所有应用程序都正确设置了它们的InstallLocation Searching Win32_SoftwareElement yielded the best result, but it is sluggish, wondering if there is any better alternative, perhaps a reg location I failed to include in my search, or if there are other alternatives to WMI, like P/Invoke.搜索Win32_SoftwareElement产生了最好的结果,但它很慢,想知道是否有更好的替代方案,也许是我没有包含在我的搜索中的注册位置,或者是否有 WMI 的其他替代方案,如 P/Invoke。

The install location isn't always set because it's not automatic.安装位置并不总是设置的,因为它不是自动的。 Some installs might not be MSI-based installs and I don't know if the other tools that are used will all set the location.有些安装可能不是基于 MSI 的安装,我不知道使用的其他工具是否都会设置位置。 If it's an MSI-based install the location is there only if the package developer sets the ARPINSTALLOCATION property during the install to the actual location.如果是基于 MSI 的安装,则仅当软件包开发人员在安装期间将 ARPINSTALLOCATION 属性设置为实际位置时,该位置才存在。

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

The plain old Win32 Windows Installer API to enumerate installed products is MsiEnumProducts () to return the ProductCode values one at a time, then to call MsiGetProductInfo() passing that product code guid and asking for INSTALLPROPERTY_INSTALLLOCATION, but again that's only there if the MSI developer did the right thing.用于枚举已安装产品的普通旧 Win32 Windows Installer API 是 MsiEnumProducts () 一次返回一个 ProductCode 值,然后调用 MsiGetProductInfo() 传递该产品代码 guid 并要求 INSTALLPROPERTY_INSTALLLOCATION,但同样只有在 MSI 开发人员做了正确的事。

There's no reliable way to get installed products all at once because some are MSI-based, some are not, and they may or may not set the install location, and that install location is typically only the application directory and doesn't include files installed in any number of other places so it's value is minimal.没有可靠的方法可以一次性安装所有产品,因为有些是基于 MSI 的,有些不是,而且它们可能会或可能不会设置安装位置,并且该安装位置通常只是应用程序目录,不包括已安装的文件在许多其他地方,所以它的价值很小。 Trawling through the registry and making decisions on what you find seems to be the only way to get the list.浏览注册表并根据您找到的内容做出决定似乎是获取列表的唯一方法。

Having said all that, short answer is that if you know a ProductCode then call MsiGetProductInfo() as above and get the location!话虽如此,简短的回答是,如果您知道 ProductCode,则如上所述调用 MsiGetProductInfo() 并获取位置!

It looks like there's no clear answer on StackOverflow how to do this, even though it's possible to do it somewhat reliably in case you know that installer is using MSI .看起来 StackOverflow 上没有明确的答案如何做到这一点,即使在您知道安装程序正在使用MSI情况下可以稍微可靠地做到这一点。 I'll use C++, but it's simple to port it to C#:我将使用 C++,但将其移植到 C# 很简单:

  • First, we must obtain the ProductId .首先,我们必须获得ProductId You either know it already, use gwmi win32_product -filter "Name like '%<product_name_pattern>%'" -namespace root/cimv2 , or some other way to obtain it (eg with MsiEnumProductsW and MsiGetProductInfoW or by using upgrade_code and MsiEnumRelatedProductsW )您要么已经知道它,要么使用gwmi win32_product -filter "Name like '%<product_name_pattern>%'" -namespace root/cimv2 ,或者其他一些获取它的方式(例如使用MsiEnumProductsWMsiGetProductInfoW或使用 upgrade_code 和MsiEnumRelatedProductsW
  • we try getting the InstallLocation directly:我们尝试直接获取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;
  }

As you already know, that won't work for some installers, because setting ARPINSTALLLOCATION property is optional (interesting design choice).如您所知,这对某些安装程序不起作用,因为设置ARPINSTALLLOCATION 属性是可选的(有趣的设计选择)。 However, Windows still correctly uninstalls those, so it must know something that we don't.但是,Windows 仍会正确卸载它们,因此它必须知道一些我们不知道的信息。 MSI uses simple relational DB to organize its stuff, and Orca allows us to browse it. MSI 使用简单的关系数据库来组织它的东西,而Orca允许我们浏览它。 For example, here's node.js installer:例如,这里是 node.js 安装程序:

在此处输入图片说明 Component column is just a name node authors used/generated via some installer toolset, KeyPath is a file name (or registry key etc.) which this component installs, and finally ComponentId is a GUID and used by Windows to uniquely identify files to uninstall. Component列只是作者通过某些安装程序工具集使用/生成的名称节点, KeyPath是此组件安装的文件名(或注册表项等),最后ComponentId是 GUID,Windows 使用它来唯一标识要卸载的文件。

Since we have the ProductId , we can find path of the cached .msi installer and query its DB to get all ComponentIds and their actual paths using MsiGetComponentPathW :由于我们有ProductId ,我们可以找到缓存的 .msi 安装程序的路径并查询其数据库以使用MsiGetComponentPathW获取所有 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';
}

As @PhilDW mentioned, this probably crawls registry under the hood.正如@PhilDW 所提到的,这可能会在幕后抓取注册表。 Output for node.js: 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
...

As you can see, there's a lot of options to go from here:如您所见,从这里有很多选择:

  • Inferring the installation path using the all components (if you don't know anything about the software you uninstalling).使用所有组件推断安装路径(如果您对卸载的软件一无所知)。
  • If you know some file which is placed in the installation root, eg node.exe , just find take its prefix.如果您知道安装根目录中的某个文件,例如node.exe ,只需找到它的前缀即可。

Hope this helps.希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM