Introduction
Recently, I answered a question in the Visual C++ Forum concerning how you would duplicate the list of installed programs seen when you select Add/Remove Programs in Control Panel, and I thought the solution would make a nice (if basic) article.
To fetch the list is very easy - first you need to open the following registry key:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
Then simply enumerate all the entries and fetch the DisplayName value from each one.
MFC/WTL/ATL
In order to cater for all tastes, I have included versions of my code for the following environments:
- MFC
- WTL
- ATL7 (included with Visual Studio 2002/2003)
The differences between the MFC and WTL versions are subtle - the MFC version stores the list of programs in a CStringArray
and the WTL version uses a CSimpleArray<CString>
.
The ATL7 version makes use the of the handy CRegKey
class.
Using the CInstalledSoftware Class
To use this class, simply declare an instance of the CInstalledSoftware
class in your application, and the constructor will fill the public m_aPrograms
member with the list of installed programs. For example, to fill an MFC listbox, you may have code like this:
CInstalledSoftware apps;
for (int i = 0; i < apps.m_aPrograms.GetCount(); i++)
m_wndList.AddString(apps.m_aPrograms[i]);
That's it! Rocket science it ain't!
Building the List - MFC/WTL
The MFC/WTL versions of the code build the list using Win32 registry calls:
void BuildList(void)
{
HKEY hKey;
if (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, IS_KEY, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return;
DWORD dwIndex = 0;
LONG lRet;
DWORD cbName = IS_KEY_LEN;
TCHAR szSubKeyName[IS_KEY_LEN];
while ((lRet = ::RegEnumKeyEx(hKey, dwIndex, szSubKeyName, &cbName, NULL,
NULL, NULL, NULL)) != ERROR_NO_MORE_ITEMS)
{
if (lRet == ERROR_SUCCESS)
{
HKEY hItem;
if (::RegOpenKeyEx(hKey, szSubKeyName, 0, KEY_READ, &hItem) != ERROR_SUCCESS)
continue;
TCHAR szDisplayName[IS_KEY_LEN];
DWORD dwSize = sizeof(szDisplayName);
DWORD dwType;
if (::RegQueryValueEx(hItem, IS_DISPLAY, NULL, &dwType,
(LPBYTE)&szDisplayName, &dwSize) == ERROR_SUCCESS)
{
m_aPrograms.Add(szDisplayName);
}
::RegCloseKey(hItem);
}
dwIndex++;
cbName = IS_KEY_LEN;
}
::RegCloseKey(hKey);
}
The ATL7 version uses the much more readable CRegKey
class:
void BuildList(void)
{
CRegKey reg;
if (reg.Open(HKEY_LOCAL_MACHINE, IS_KEY, KEY_READ) != ERROR_SUCCESS)
return;
DWORD dwIndex = 0;
DWORD cbName = IS_KEY_LEN;
TCHAR szSubKeyName[IS_KEY_LEN];
LONG lRet;
while ((lRet = reg.EnumKey(dwIndex, szSubKeyName, &cbName)) != ERROR_NO_MORE_ITEMS)
{
if (lRet == ERROR_SUCCESS)
{
CRegKey regItem;
if (regItem.Open(reg, szSubKeyName, KEY_READ) == ERROR_SUCCESS)
{
ULONG ulChars;
if (regItem.QueryStringValue(IS_DISPLAY, NULL, &ulChars) == ERROR_SUCCESS)
{
CString strName;
regItem.QueryStringValue(IS_DISPLAY, strName.GetBuffer(ulChars), &ulChars);
strName.ReleaseBuffer();
m_aPrograms.Add(strName);
}
}
}
dwIndex++;
cbName = IS_KEY_LEN;
}
}
OK, this class isn't going to win any prizes, but hopefully others will find it useful. Comments welcome!