Nytro Posted May 16, 2011 Report Posted May 16, 2011 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#endifWMIWin32_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 APITwo 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 productsIn 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 RegistryInstalled 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.30319Sursa: Finding Installed Applications with VC++ | Marius Bancila's Blog Quote