Jump to content
Nytro

Finding Installed Applications with VC++

Recommended Posts

Posted

Finding Installed Applications with VC++

Marius Bancila C++, COM, Windows Programming 2011-05-01

Finding applications installed on a machine (the ones that you see in Control Panel Add/Remove programs) could be a little bit tricky, because there isn’t a bulletproof API or method. Each of the available methods has its own weak points. WMI is slow and can actually be disabled on a machine. MSI API only shows applications installed with an MSI, and reading directly from the Windows Registry is not an officially supported alternative. Thus it is an open point which one is the most appropriate, though the official answer will probably be MSI API.

In this post I will go through all of these three methods and show how to query for the installed applications and display the name, publisher, vendor and installation location (if available). Notice these are just some samples, and if you want to use this in your applications you’ll probably want to do additional things like better error checking. Because I want the code to work both with ANSI and UNICODE I will use the following defines

#include < iostream >
#include < string>

#ifdef _UNICODE
#define tcout wcout
#define tstring wstring
#else
#define tcout cout
#define tstring string
#endif

WMI

Win32_Product is a WMI class that represents a product installed by Windows Installer. For fetching the list of installed applications with WMI I will reuse the WMIQuery class I first shown in this post. You need to include Wbemidl.h and link with wbemuuid.lib.

In the code shown below WmiQueryValue() is a function that reads a property from the current record and returns it as an STL string (UNICODE or ANSI). WmiEnum() is a function that fetches and displays in the console all the installed applications.

class WMIQuery
{
IWbemLocator* m_pLocator;
IWbemServices* m_pServices;

public:
WMIQuery():
m_pLocator(NULL),
m_pServices(NULL)
{
}

bool Initialize()
{
// Obtain the initial locator to WMI
HRESULT hr = ::CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &m_pLocator);

if (FAILED(hr))
{
cerr << "Failed to create IWbemLocator object. Err code = 0x" << hex << hr << endl;
return false;
}

// Connect to WMI through the IWbemLocator::ConnectServer method
// Connect to the root\cimv2 namespace with the current user
hr = m_pLocator->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (e.g. Kerberos)
0, // Context object
&m_pServices // pointer to IWbemServices proxy
);

if (FAILED(hr))
{
cerr << "Could not connect. Error code = 0x" << hex << hr << endl;
m_pLocator->Release();
m_pLocator = NULL;
return false;
}

// Set security levels on the proxy
hr = ::CoSetProxyBlanket(
m_pServices, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);

if (FAILED(hr))
{
cerr << "Could not set proxy blanket. Error code = 0x" << hex << hr << endl;
m_pServices->Release();
m_pServices = NULL;
m_pLocator->Release();
m_pLocator = NULL;
return false;
}

return true;
}

IEnumWbemClassObject* Query(LPCTSTR strquery)
{
IEnumWbemClassObject* pEnumerator = NULL;
HRESULT hr = m_pServices->ExecQuery(
bstr_t("WQL"),
bstr_t(strquery),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);

if (FAILED(hr))
{
cerr << "Query for operating system name failed. Error code = 0x" << hex << hr < endl;
return NULL;
}

return pEnumerator;
}

~WMIQuery()
{
if(m_pServices != NULL)
{
m_pServices->Release();
m_pServices = NULL;
}

if(m_pLocator != NULL)
{
m_pLocator->Release();
m_pLocator = NULL;
}
}
};

tstring WmiQueryValue(IWbemClassObject* pclsObj,
LPCWSTR szName)
{
tstring value;

if(pclsObj != NULL && szName != NULL)
{
VARIANT vtProp;

HRESULT hr = pclsObj->Get(szName, 0, &vtProp, 0, 0);
if(SUCCEEDED(hr))
{
if(vtProp.vt == VT_BSTR && ::SysStringLen(vtProp.bstrVal) > 0)
{
#ifdef _UNICODE
value = vtProp.bstrVal;
#else
int len = ::SysStringLen(vtProp.bstrVal)+1;
if(len > 0)
{
value.resize(len);
::WideCharToMultiByte(CP_ACP,
0,
vtProp.bstrVal,
-1,
&value[0],
len,
NULL,
NULL);
}
#endif
}
}
}

return value;
}

void WmiEnum()
{
HRESULT hres;

// Initialize COM.
hres = ::CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
return;
}

// Set general COM security levels
hres = ::CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);

if (FAILED(hres))
{
cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
::CoUninitialize();
return;
}
else
{
WMIQuery query;
if(query.Initialize())
{
IEnumWbemClassObject* pEnumerator = query.Query(_T("SELECT * FROM Win32_Product"));

if(pEnumerator != NULL)
{
// Get the data from the query
IWbemClassObject *pclsObj;
ULONG uReturn = 0;

while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

if(0 == uReturn)
{
break;
}

// find the values of the properties we are interested in
tstring name = WmiQueryValue(pclsObj, L"Name");
tstring publisher = WmiQueryValue(pclsObj, L"Vendor");
tstring version = WmiQueryValue(pclsObj, L"Version");
tstring location = WmiQueryValue(pclsObj, L"InstallLocation");

if(!name.empty())
{
tcout << name << endl;
tcout << " - " << publisher << endl;
tcout << " - " << version << endl;
tcout << " - " << location << endl;
tcout << endl;
}

pclsObj->Release();
}

pEnumerator->Release();
}
}
}

// unintializa COM
::CoUninitialize();
}

A sample from the output of this WmiEnum() function looks like this:

Java? 6 Update 25

– Oracle

– 6.0.250

– C:\Program Files\Java\jre6\

Java? SE Development Kit 6 Update 25

– Oracle

– 1.6.0.250

– C:\Program Files\Java\jdk1.6.0_25\

Microsoft .NET Framework 4 Client Profile

– Microsoft Corporation

– 4.0.30319

-

Microsoft Sync Framework Services v1.0 SP1 (x86)

– Microsoft Corporation

– 1.0.3010.0

-

Microsoft ASP.NET MVC 2 – Visual Studio 2010 Tools

– Microsoft Corporation

– 2.0.50217.0

-

Adobe Reader X (10.0.1)

– Adobe Systems Incorporated

– 10.0.1

– C:\Program Files\Adobe\Reader 10.0\Reader\

One can notice that the code is relatively long, but most important it is very slow.

MSI API

Two of the MSI API functions can help fetching the list of installed applications:

* MsiUnumProductsEx: enumerates through one or all the instances of products that are currently advertised or installed (requires Windows Installer 3.0 or newer)

* MsiGetProductInfoEx: returns product information for advertised and installed products

In order to use these functions you need to include msi.h and link to msi.lib.

In the code below, MsiQueryProperty() is a function that returns the value of product property (as a tstring as defined above) by calling MsiGetProductInfoEx. MsiEnum() is a function that iterates through all the installed applications and prints in the console the name, publisher, version and installation location.

tstring MsiQueryProperty(LPCTSTR szProductCode,
LPCTSTR szUserSid,
MSIINSTALLCONTEXT dwContext,
LPCTSTR szProperty)
{
tstring value;

DWORD cchValue = 0;
UINT ret2 = ::MsiGetProductInfoEx(
szProductCode,
szUserSid,
dwContext,
szProperty,
NULL,
&cchValue);

if(ret2 == ERROR_SUCCESS)
{
cchValue++;
value.resize(cchValue);

ret2 = ::MsiGetProductInfoEx(
szProductCode,
szUserSid,
dwContext,
szProperty,
(LPTSTR)&value[0],
&cchValue);
}

return value;
}

void MsiEnum()
{
UINT ret = 0;
DWORD dwIndex = 0;
TCHAR szInstalledProductCode[39] = {0};
TCHAR szSid[128] = {0};
DWORD cchSid;
MSIINSTALLCONTEXT dwInstalledContext;

do
{
memset(szInstalledProductCode, 0, sizeof(szInstalledProductCode));
cchSid = sizeof(szSid)/sizeof(szSid[0]);

ret = ::MsiEnumProductsEx(
NULL, // all the products in the context
_T("s-1-1-0"), // i.e.Everyone, all users in the system
MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED | MSIINSTALLCONTEXT_MACHINE,
dwIndex,
szInstalledProductCode,
&dwInstalledContext,
szSid,
&cchSid);

if(ret == ERROR_SUCCESS)
{
tstring name = MsiQueryProperty(
szInstalledProductCode,
cchSid == 0 ? NULL : szSid,
dwInstalledContext,
INSTALLPROPERTY_INSTALLEDPRODUCTNAME);

tstring publisher = MsiQueryProperty(
szInstalledProductCode,
cchSid == 0 ? NULL : szSid,
dwInstalledContext,
INSTALLPROPERTY_PUBLISHER);

tstring version = MsiQueryProperty(
szInstalledProductCode,
cchSid == 0 ? NULL : szSid,
dwInstalledContext,
INSTALLPROPERTY_VERSIONSTRING);

tstring location = MsiQueryProperty(
szInstalledProductCode,
cchSid == 0 ? NULL : szSid,
dwInstalledContext,
INSTALLPROPERTY_INSTALLLOCATION);

tcout << name << endl;
tcout << " - " << publisher << endl;
tcout << " - " << version << endl;
tcout << " - " << location << endl;
tcout << endl;

dwIndex++;
}

} while(ret == ERROR_SUCCESS);
}

And this is a sample for the WmiEnum() function.

Java? 6 Update 25

- Oracle

- 6.0.250

- C:\Program Files\Java\jre6\

Java? SE Development Kit 6 Update 25

- Oracle

- 1.6.0.250

- C:\Program Files\Java\jdk1.6.0_25\

Microsoft .NET Framework 4 Client Profile

- Microsoft Corporation

- 4.0.30319

-

Microsoft Sync Framework Services v1.0 SP1 (x86)

- Microsoft Corporation

- 1.0.3010.0

-

Microsoft ASP.NET MVC 2 - Visual Studio 2010 Tools

- Microsoft Corporation

- 2.0.50217.0

-

Adobe Reader X (10.0.1)

- Adobe Systems Incorporated

- 10.0.1

- C:\Program Files\Adobe\Reader 10.0\Reader\

Windows Registry

Installed applications are listed in Windows Registry under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall. The KB247501 article explains the structure of the information under this Registry key. Make sure you read it if you decide to use this approach.

In the code shown below, RegistryQueryValue() is a function that queries the value of a name/value pair in the registry and returns the value as a tstring. RegistryEnum() is a function that prints to the console all the installed application as found in the registry.

tstring RegistryQueryValue(HKEY hKey,
LPCTSTR szName)
{
tstring value;

DWORD dwType;
DWORD dwSize = 0;

if (::RegQueryValueEx(
hKey, // key handle
szName, // item name
NULL, // reserved
&dwType, // type of data stored
NULL, // no data buffer
&dwSize // required buffer size
) == ERROR_SUCCESS && dwSize > 0)
{
value.resize(dwSize);

::RegQueryValueEx(
hKey, // key handle
szName, // item name
NULL, // reserved
&dwType, // type of data stored
(LPBYTE)&value[0], // data buffer
&dwSize // available buffer size
);
}

return value;
}

void RegistryEnum()
{
HKEY hKey;
LONG ret = ::RegOpenKeyEx(
HKEY_LOCAL_MACHINE, // local machine hive
_T("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"), // uninstall key
0, // reserved
KEY_READ, // desired access
&hKey // handle to the open key
);

if(ret != ERROR_SUCCESS)
return;

DWORD dwIndex = 0;
DWORD cbName = 1024;
TCHAR szSubKeyName[1024];

while ((ret = ::RegEnumKeyEx(
hKey,
dwIndex,
szSubKeyName,
&cbName,
NULL,
NULL,
NULL,
NULL)) != ERROR_NO_MORE_ITEMS)
{
if (ret == ERROR_SUCCESS)
{
HKEY hItem;
if (::RegOpenKeyEx(hKey, szSubKeyName, 0, KEY_READ, &hItem) != ERROR_SUCCESS)
continue;

tstring name = RegistryQueryValue(hItem, _T("DisplayName"));
tstring publisher = RegistryQueryValue(hItem, _T("Publisher"));
tstring version = RegistryQueryValue(hItem, _T("DisplayVersion"));
tstring location = RegistryQueryValue(hItem, _T("InstallLocation"));

if(!name.empty())
{
tcout << name << endl;
tcout << " - " << publisher << endl;
tcout << " - " << version << endl;
tcout << " - " << location << endl;
tcout << endl;
}

::RegCloseKey(hItem);
}
dwIndex++;
cbName = 1024;
}
::RegCloseKey(hKey);
}

And a sample output of the RegistryEnum() function:

Java? SE Development Kit 6 Update 25

- Oracle

- 1.6.0.250

- C:\Program Files\Java\jdk1.6.0_25\

Microsoft Visual Studio 2005 Tools for Office Runtime

- Microsoft Corporation

- 8.0.60940.0

-

MSDN Library for Visual Studio 2008 - ENU

- Microsoft

- 9.0.21022

- C:\Program Files\MSDN\MSDN9.0\

Microsoft SQL Server Compact 3.5 SP2 ENU

- Microsoft Corporation

- 3.5.8080.0

- C:\Program Files\Microsoft SQL Server Compact Edition\

Microsoft .NET Framework 4 Client Profile

- Microsoft Corporation

- 4.0.30319

Sursa: Finding Installed Applications with VC++ | Marius Bancila's Blog

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...