0

I've got a C++ software (not managed C++), the goal of which is to control certain actions of other applications on client's machine and send the alert to server. Once this action happens, software get application exe's path and sends. My modification task is to get application's DisplayName (like it is in the "Programs and Features" folder) and send it.

In the end, client machine's OS version is unknown, all I know is that it is Windows.

By reading Windows registry I could get display names of all those applications that can be seen in the "Programs and Features" folder. I used these keys:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_USERS\<User_sid>\Software\Microsoft\Windows\CurrentVersion\Uninstall

But there are few applications that have their installation location written in the registry. In my case, it is about 1 application of 6 has its installation location written. Using Vista's functions SHGetKnownFolderItem and SHGetKnownFolderPath is not helpful because "Programs and Features" is the virtual folder.

Is there any way to learn "Programs and Features" implementation? Or perhaps another available option?

Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
Serge
  • 43
  • 5
  • As far as I am aware, that is the only registry location Programs and Features looks at. – ChrisWard1000 Oct 22 '14 at 14:24
  • If the process is running, you can use: http://stackoverflow.com/questions/1933113/c-windows-how-to-get-process-path-from-its-pid – Brandon Oct 22 '14 at 14:26

2 Answers2

1

See this article on The Old New Thing: How can I get the list of programs the same way that Programs and Features gets it?

An example:

#include <Shobjidl.h>
#include <ShlGuid.h>
#include <atlbase.h>

CComPtr<IShellItem> programs;
if(SUCCEEDED(::SHCreateItemFromParsingName(
    L"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\"
    L"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}", 
    nullptr,
    IID_PPV_ARGS(&programs))))
{
    //
    //  Another super secret property, this time for the Version.
    //  See the link above and http://msdn.microsoft.com/en-us/library/cc251929(v=prot.10).aspx
    //
    const PROPERTYKEY PROP_KEY_VERSION = {
        {
            0x0cef7d53,
            0xfa64,
            0x11d1,
            0xa2, 0x03, 0x00, 0x00, 0xf8, 0x1f, 0xed, 0xee
        },
        8
    };

    CComPtr<IEnumShellItems> shellEnum;
    programs->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&shellEnum));
    for(CComPtr<IShellItem> prog; 
        S_OK == shellEnum->Next(1, &prog, nullptr);
        prog.Release())
    {
        CComHeapPtr<wchar_t> name;
        if(SUCCEEDED(prog->GetDisplayName(SIGDN_NORMALDISPLAY, &name))) {

            //  Do something with 'name'

            CComQIPtr<IShellItem2> shellItem2(prog);
            if(shellItem2) {
                LPWSTR ver;
                if(SUCCEEDED(shellItem2->GetString(PROP_KEY_VERSION, &ver))) {
                    //  Do something with 'ver'
                    ::CoTaskMemFree(ver);
                }
            }
        }   
    }
}

Additional Hints:

  • On Windows 8+, you have a mix of "legacy" desktop application and "Modern" aka Metro applications. Modern applications are a bit stricter in their management which makes it slightly easier to discover information about them.
  • Task Manager and similar tools often read the Version Information from binaries (e.g. PID -> binary -> Version Information) for display purposes
Community
  • 1
  • 1
NuSkooler
  • 5,391
  • 1
  • 34
  • 58
  • Tried your way. SHCreateItemFromParsingName fails in my case, returns E_INVALIDARG. I tried replacing "7B81BE6A-CE2B-4676-A29E-EB907A5126C5" with another (non-virtual) folder pidl, same deal. Tried replacing pidl with exe path, it returned "0x800401f0 CoInitialize has not been called" instead. Searched through my Windows SDKs, there is no CCoInitialize function in them. Will be looking for a way to straighten it. Thanks! – Serge Oct 22 '14 at 21:33
  • I am trying to get path of each installed application. But I am getting that error invalid property key. const PROPERTYKEY PROP_KEY_VERSION = { { 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac },2} – CrazyCoder Nov 28 '17 at 12:46
0

You won't get all the installed applications (user may even simply unzip an xcopy deployed application into a folder and run it) but you can do exactly what Programs and Features does using its own API: enumerate all installed applications using Windows Installer API.

Function you need is MsiEnumProducts(), it's pretty easy to use, in short:

char productCode[39];
int productIndex = 0;

while (true) {
    UINT result = MsiEnumProducts(productIndex++, productCode);
    if (result == ERROR_SUCCESS) {
        // Search information about this product
    } else if (result == ERROR_NO_MORE_ITEMS)
        break; // Finished

    cerr << "Error: " << result;
}

Information about a specific product (through its product code) can be obtained using MsiGetProductInfo() function. In your specific case you first need to check if it's the one you're looking for (comparing its installation directory obtained with INSTALLPROPERTY_INSTALLLOCATION). If it's the one you want then you can use INSTALLPROPERTY_INSTALLEDPRODUCTNAME to obtain its name. Something like this (pretty raw, not production code!):

char buffer[255];
DWORD bufferSize = sizeof(buffer);
result = MsiGetProductInfo(
    productCode,                     // Product we want to query a property
    INSTALLPROPERTY_INSTALLLOCATION, // Property we want to read
    buffer,                          // Buffer where property value will be stored
    &bufferSize);                    // Size of that buffer

if (result != ERROR_SUCCESS) {
    cerr << "Error: " << result;
} else {
    // Compare with path you're looking for and
    // if it matches then call MsiGetProductInfo
    // to obtain INSTALLPROPERTY_INSTALLEDPRODUCTNAME
}

Note that if you have executable path you may also do a simpler check StringFileInfo structures in the .exe version resources. First of all retrieve version information resources size with GetFileVersionInfoSize then read version information with GetFileVersionInfo. Now what you have to do is simply search for the VS_VERSIONINFO structure you need (but this may even be more complicate because such structure is localized).

As last resort you may also check another registry key (I'm not sure if this is portable): HKEY_CLASSES_ROOT\Installer\Products.

Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • I tried using the MsiGetProductInfo in the way you gave. The code is neat and clean, but the results are pretty much the same as when using the registry as the source. Everything that is returned is either in the **HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall** branch, or in the **HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall** branch. Same with the installation locations. – Serge Oct 22 '14 at 15:43
  • Yes, things (after all) come from registry (somewhere). You can't track home-made installations (because they don't write anywhere their info). You may also check HKEY_CLASSES_ROOT\Installer\Products (not sure, just monitored handles with ProcessExplorer for ProgramsAndFeatures). I don't know if it's portable... – Adriano Repetti Oct 22 '14 at 16:15
  • ProcessExplorer it seems contains only installation sources which is not exactly the solution. For now the only idea is to use UninstallString from the very same registry branches (when installation location is absent). If the application is in the ProgramsAndFeatures it usually has the uninstaller location in the registry. But this method is extremely unreliable. – Serge Oct 22 '14 at 16:45
  • ProcessExplorer to check which handles ProgramsAndFeatures opens (and to do the same). But. Well. Installing/Uninstalling **IS** something unreliable, even to list them all you have to scan your HD. If you just need "a name" you may check with GetFileVersionInfo (see answer) but even that is pretty unreliable (and it may be absent). Franly speaking if a multi-step approach doesn't work (registry, MSI, WMI, GetFileVersionInfo) then last resort is always...a huge dictionary (guessing with installation folder and executable name, file signature is too aleatory). – Adriano Repetti Oct 22 '14 at 17:54