Software Inventory on Windows using C++

Introduction

For local and remote queries, you can use the Windows Management Instrumentation (WMI) class Win32_Product but this will only provide you with a list of applications that were installed using Microsoft Installer (MSI) packages.

Applications deployed with their own installer force you to parse the registry manually and weed out the entries you’re not interested in. The control panel applet appwiz.cpl works by parsing the registry but unfortunately does not work for a remote system.

Although I don’t provide a tool to list applications on a remote system, the information here should be enough for you to implement by yourself using the RegConnectRegistry API or somthing similar using StdReg in VBScript or powershell.

Registry keys

Applications installed which are only accessible to the user get stored in NTUSER.DAT located inside the user’s profile which is a registry hive and gets loaded by the system when user logs on. You can find it under HKEY_CURRENT_USER or in HKEY_USERS under the user’s Security Identifier (SID) if you open up regedit.exe

System wide applications available to all users get stored in the SOFTWARE registry hive which you’ll find under HKEY_LOCAL_MACHINE

Since the introduction of 64-bit Windows, there are 2 locations required to store information about applications installed on the system.

The native key is

SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

For 32-bit or legacy mode applications installed on 64-bit long mode Windows, the entries are stored under

SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

Bear in mind, the code here will only list system wide applications and not those which only appear in the user’s registry hive NTUSER.DAT

Avoiding hot patches

This might not work in all scenarios but what I’d do for older XP systems is query the key name for application entry and if it contained “KB” which is abbreviation for Knowledge Base, I would assume it to be a hot patch and ignore it.

The other check for “KB” is in the DisplayName property.

Even though it’s a potentially unreliable method to use, it worked well enough for me in most situations.

Avoiding System Components

The SystemComponent property is a DWORD(32-bit) which is either 0 or 1 to indicate a hot patch or update you may not be interested in. Our code avoids listing these entries in the same way appwiz.cpl does.

/**
 *
 *  Enumerate all 32 and 64-bit products installed
 *
 */
void Products::enumEntries(HKEY hRegistry)
{
  HKEY  hApplications;
  DWORD dwEntries[2];
  
  getTotalEntries(dwEntries);

  // enumerate 32 and 64-bit applications
  for (DWORD i=0; i<2; i++) 
  {
    // open key for enumerate and query access
    dwError = RegOpenKeyEx (hRegistry, lpszAppKeys[i], 0,
        KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_WOW64_64KEY,
        &hApplications);
        
    if (dwError == ERROR_SUCCESS)
    {                
      // loop through each entry for this key
      for (DWORD dwKeyIndex=0; i<dwEntries[i]; dwKeyIndex++) 
      {
        // get key name
        DWORD   cbName = MAX_KEY_LEN;
        wchar_t wszKeyName[MAX_KEY_LEN];

        dwError = RegEnumKeyEx (hApplications, dwKeyIndex, wszKeyName, 
            &cbName, NULL, NULL, NULL, NULL);
        
        // nothing left?
        if (dwError == ERROR_NO_MORE_ITEMS)
          break;

        HKEY hEntry;
        std::wstring keyName = wszKeyName;

        // if not a name with "KB" in it
        // not incredibly reliable but will do for now
        if (keyName.find(L"KB") == std::wstring::npos) 
        {
          // open to query values
          dwError = RegOpenKeyEx (hApplications, wszKeyName, 
              0, KEY_QUERY_VALUE,&hEntry);
              
          if (dwError == ERROR_SUCCESS) 
          {            
            // exclude SystemComponents
            DWORD dwValue = 0;
            DWORD dwSize = sizeof(DWORD);
            
            RegQueryValueEx (hEntry, L"SystemComponent", NULL, 
                NULL,(LPBYTE)&dwValue,&dwSize);
            
            // if not a system component
            if (dwValue != 1) 
            {              
              ProductEntry entry;
            
              // clear string values incase set from previous entry
              entry.name.clear();
              entry.publisher.clear();
              entry.version.clear();

              // query display name, if we don't get this value, 
              // we don't save anything and continue to next entry
              wchar_t wszDisplayName[MAX_KEY_LEN];
              dwSize = MAX_KEY_LEN;

              dwError = RegQueryValueEx (hEntry, L"DisplayName", NULL,
                  0, (LPBYTE)&wszDisplayName, &dwSize);
                  
              if (dwError == ERROR_SUCCESS) 
              {              
                // if display name doesn't have "KB" in it
                // could be a problem since other applications could have KB in it
                entry.name = wszDisplayName;
                
                if (entry.name.find(L"KB") == std::wstring::npos) {

                  // query the version
                  wchar_t wszVersion[MAX_KEY_LEN];
                  dwSize = MAX_KEY_LEN;
                
                  // just continue if we don't get it
                  dwError = RegQueryValueEx (hEntry, L"DisplayVersion", NULL, 0,
                      (LPBYTE)&wszVersion, &dwSize);
                  
                  if (dwError == ERROR_SUCCESS)    
                    entry.version = wszVersion;

                  // if not listed get the publisher and save
                  if (!isListed(entry)) {

                    // query the publisher value
                    wchar_t wszPublisher[MAX_KEY_LEN];
                    dwSize = MAX_KEY_LEN;

                    if (RegQueryValueEx (hEntry, L"Publisher", NULL, 0,
                        (LPBYTE)&wszPublisher, &dwSize) == ERROR_SUCCESS)
                      entry.publisher = wszPublisher;

                    // find the max length of strings so far
                    nName      = max(entry.name.length(),     nName);
                    nPublisher = max(entry.publisher.length(),nPublisher);
                    nVersion   = max(entry.version.length(),  nVersion);
                  
                    // add to list
                    entries.push_back(entry);
                  }
                } // we found "KB" in display value..don't want these.
              } // end query of display
            } // SystemComponent
            RegCloseKey(hEntry);
          } // end if of open entry
        } // we found "KB" in key name, don't want these
      } // end for          
      RegCloseKey(hApplications);
    } // end open
  } // end for
  
  sortEntries();
}

appwiz.cpl listed 213 applications whereas the above code listed 217. Didn’t thoroughly check where the differences were but it’s good enough for me 🙂

See products.cpp here.

Advertisements
This entry was posted in programming, windows and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s