Reading a contract from the other side: Application publishers


In an earlier article, I gave an example of reading a contract from the other side. Here's another example of how you can read a specification and play the role of the operating system.

I chose this particular example because somebody wanted to do this and didn't realize that everything they needed was already documented; they just needed to look at the documentation in a different light.

The goal today is to mimic the list of programs that appears on the "Add New Programs" page of the Add or Remove Programs control panel. Note that in order for this list to appear at all, you need to be on a domain, and for the list to be nonempty, your domain controller needs to publish applications for domain members to install. Consequently, I suspect many of my readers won't get to see any interesting results from this exercise, but then again, the point of exercise is not the result but merely the doing of it.

The documentation for the IAppPublisher interface describes how a publisher can register itself to appear in the list of programs that can be installed. All you have to do is read the documentation from the other side: Instead of reading documentation about a method and asking, "How would I implement this method?", read it and ask, "How would I call this method?"

The documentation says that an app publisher registers its CLSID under the specified registry key. Therefore, if you want to find all the app publishers, you need to enumerate that key.

#define REGSTR_PATH_PUBLISHERS  \
    L"Software\\Microsoft"      \
    L"\\Windows\\CurrentVersion\\App Management\\Publishers"

    HKEY hkPub;
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REGSTR_PATH_PUBLISHERS,
                      0, KEY_READ, &hkPub) == ERROR_SUCCESS) {
      WCHAR szKey[MAX_PATH];
      for (DWORD dwIndex = 0;
           RegEnumKeyW(hkPub, dwIndex, szKey, MAX_PATH) == ERROR_SUCCESS;
           dwIndex++) {
          ...
      }
      RegCloseKey(hkPub);
    }

The documentation says that the subkeys have the CLSID in REG_SZ format, so that's what we read out.

        WCHAR szCLSID[MAX_PATH];
        LONG l = sizeof(szCLSID) - sizeof(WCHAR);
        if (RegQueryValueW(hkPub, szKey, szCLSID, &l) == ERROR_SUCCESS)
          szCLSID[l/sizeof(WCHAR)] = 0;
          CLSID clsid;
          if (SUCCEEDED(CLSIDFromString(szCLSID, &clsid))) {
            ...
          }
        }

Notice the extra care we take to avoid the problem of registry strings that aren't null-terminated, as discussed in an earlier entry.

The documentation quite explicitly states how this CLSID is used.

Add/Remove Programs creates an instance of your object by calling CoCreateInstance for your object and requests the approprite [sic] IAppPublisher interface when the Add New Programs view is populated.

Not much choice, now, is there. So we do what it says.

            IAppPublisher *ppub;
            if (SUCCEEDED(CoCreateInstance(clsid, NULL,
                            CLSCTX_ALL, IID_IAppPublisher,
                          (void**)&ppub))) {
              ...
              ppub->Release();
            }

Okay, now that we have an app publisher, we can invoke the various methods on it to get information from that publisher. If we were more ambitious, we could ask for the categories but today we're just going to be happy with enumerating the programs so we can print their names.

              IEnumPublishedApps *penum;
              if (SUCCEEDED(ppub->EnumApps(NULL, &penum))) {
                IPublishedApp *papp;
                while (penum->Next(&papp) == S_OK) {
                  ...
                  papp->Release();
                }
                penum->Release();
              }

The enumerator gives us an application interface, and we can use that interface to get information about the application and print it out.

                  APPINFODATA info = { sizeof(info) };
                  info.dwMask = AIM_DISPLAYNAME;
                  if (SUCCEEDED(papp->GetAppInfo(&info)) &&
                      (info.dwMask & AIM_DISPLAYNAME)) {
                    wprintf(L"%ls\n", info.pszDisplayName);
                    CoTaskMemFree(info.pszDisplayName);
                  }

We ask only for the display name, since that's all we're interested in today. In a more complicated program, we may ask for other data and would probably not release the IPublishedApp interface immediately, but rather hang onto it so we could invoke some other more interesting method like IPublishedApp::Install.

(Note that we have to use the correct memory allocator to free the memory.)

Okay, let's assemble all this into a simple console program.

#include <windows.h>
#include <ole2.h>
#include <shappmgr.h>
#include <stdio.h>

#define REGSTR_PATH_PUBLISHERS  \
    L"Software\\Microsoft"      \
    L"\\Windows\\CurrentVersion\\App Management\\Publishers"

int __cdecl main(int argc, char **argv)
{
  if (SUCCEEDED(CoInitialize(NULL)) {
    HKEY hkPub;
    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, REGSTR_PATH_PUBLISHERS,
                      0, KEY_READ, &hkPub) == ERROR_SUCCESS) {
      WCHAR szKey[MAX_PATH];
      for (DWORD dwIndex = 0;
           RegEnumKeyW(hkPub, dwIndex, szKey, MAX_PATH) == ERROR_SUCCESS;
           dwIndex++) {
        WCHAR szCLSID[MAX_PATH];
        LONG l = sizeof(szCLSID) - sizeof(WCHAR);
        if (RegQueryValueW(hkPub, szKey, szCLSID, &l) == ERROR_SUCCESS)
          szCLSID[l/sizeof(WCHAR)] = 0;
          CLSID clsid;
          if (SUCCEEDED(CLSIDFromString(szCLSID, &clsid))) {
            IAppPublisher *ppub;
            if (SUCCEEDED(CoCreateInstance(clsid, NULL,
                            CLSCTX_ALL, IID_IAppPublisher,
                          (void**)&ppub))) {
              IEnumPublishedApps *penum;
              if (SUCCEEDED(ppub->EnumApps(NULL, &penum))) {
                IPublishedApp *papp;
                while (penum->Next(&papp) == S_OK) {
                  APPINFODATA info = { sizeof(info) };
                  info.dwMask = AIM_DISPLAYNAME;
                  if (SUCCEEDED(papp->GetAppInfo(&info)) &&
                      (info.dwMask & AIM_DISPLAYNAME)) {
                    wprintf(L"%ls\n", info.pszDisplayName);
                    CoTaskMemFree(info.pszDisplayName);
                  }
                  papp->Release();
                }
                penum->Release();
              }
              ppub->Release();
            }
          }
        }
      }
      RegCloseKey(hkPub);
    }
    CoUninitialize();
  }
  return 0;
}

When you run this program, a list of all programs published by your domain controller should go scrolling past. (As I noted at the beginning of this entry, you won't see much if your computer is not on a domain or if your domain controller doesn't publish any programs.)

Yes, this program is not very pretty, because prettiness was not my goal. In real life, a lot of the mess would be moved out into helper functions, and you can clean it up even more by using a smart pointer library, but the goal here was not to write a pretty program; it was to show how something could be done by reading the specification from the other side.

(Why don't I use a smart pointer library? Because I try to write in "raw" C++ in order to avoid arguments about whose smart pointer library is best, or why smart pointers are evil... It's easy to convert "raw" C++ to use a smart pointer library, but it's harder to convert from one smart pointer library to another.)

Comments (7)
  1. Michael says:

    What a great article! I am going to sit down and run some tests and really sink my teeth into this subject for some work I have to do later next week. What a great resource your site has become. Thanks! Finally, for one am glad you do not use smart pointers in you examples. Not that I do not use raw pointers in my work, it just makes it easier to follow.

  2. Filip says:

    You may call me pedantic … but I tried to compile your program:

    if (SUCCEEDED(CoInitialize(NULL))) { <– Missing parenthesis

    and

    if (RegQueryValueW(hkPub, szKey, szCLSID, &l) == ERROR_SUCCESS)

    { <== Missing bracket

  3. Norman Diamond says:

    Seriously, a lower-case l is a terrible name for an identifier, whether or not you intend for your code to be read by humans.

    Now just to be snarky, having observed cases of "if ([…] == ERROR_SUCCESS)" and "if (SUCCEEDED([…]))" in the same code sample, I can’t help wondering. Shouldn’t the latter macro have been ERROR_SUCCEEDED? Or ERRORED_SUCCESSFULLY?

    Too bad this one doesn’t have a screenshot:

    http://www.sonic.net/~ezzell/Error%20Messages/Nothing_Goes_Wrong_Like_Success.htm#Nothing_Like_Success

    but this one does:

    http://home.comcast.net/~anon4321/niceone.gif

  4. Raymond Chen says:

    It’s called ERROR_SUCCESS because it’s an error code (and therefore must be named ERROR_something) but no error occurred. What would you call it?

  5. darwou says:

    Now just to be snarky, having observed

    > cases of "if ([…] == ERROR_SUCCESS)"

    > and "if (SUCCEEDED([…]))" in the same

    > code sample, I can’t help wondering.

    > Shouldn’t the latter macro have been

    > ERROR_SUCCEEDED? Or ERRORED_SUCCESSFULLY?

    SUCCEEDED() is unrelated to ERROR_SUCCESS.

    HRESULTS are not Win32 errors.

    ERROR_SUCCESS is the Win32 error code indicating success. It is defined as 0.

    Win32 API errors are positive integers. For example, ERROR_NOT_ENOUGH_MEMORY is defined as 8.

    On the other hand, an HRESULT is an error returned from a COM interface or API.

    S_OK is the error code that indicates success. It is also defined as 0.

    However, HRESULT codes representing errors are all negative. For example, E_OUTOFMEMORY is defined as 0x8007000E.

    SUCCEEDED() and FAILED() are both macros designed to work only with HRESULT. They check to see if the error code is negative to determine whether to return TRUE or FALSE.

    Hence, you must NEVER use either macro with the error code returned from a Win32 API like RegEnumKeyW or RegQueryValueW().

  6. Norman Diamond says:

    8/31/2004 6:46 PM Raymond Chen

    > It’s called ERROR_SUCCESS because it’s an

    > error code […] What would you call it?

    I would have defined return codes or status codes instead of error codes. I would have allowed the possibility of a successful return code or successful status code. I would probably call the thing SUCCESS or STATUS_SUCCESS. Despite my cynicism, I think there were known cases of operations performing as intended even before the codes here were designed as error codes.

    For comparison: When programs under VMS returned a value whose meaning meant a warning level but success, the fault was in those programs not the OS. The OS did define a value whose meaning meant a success level together with success. The OS came with impeccable documentation of this fact. Some programs even obeyed it.

  7. Raymond Chen says:

    And that’s what the NT team did – with STATUS and HRESULT codes. But the registry stuff predates NT. The only thing the NT team could do with the registry stuff was come up with names for things that already existed.

Comments are closed.

Skip to main content