How can I get the list of programs the same way that Programs and Features gets it?


A customer wanted to get the list of programs the same way that the Programs and Features folder gets it.

Here, here's an idea: Instead of trying to mimic the Programs and Features folder, just ask the Programs and Features folder for its contents! That way, no matter what changes are made to how the Programs and Features folder obtains its contents (and those changes occur pretty often), your program will always match it, because you're just showing the same thing.

Here's the basic idea, in scripting language since it's quicker:

var shell = new ActiveXObject("Shell.Application");
var programsFolder = shell.Namespace(
    "::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\" +
    "::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}");
var items = programsFolder.Items();
for (var i = 0; i < items.Count; i++) {
    var item = items.Item(i);
    WScript.StdOut.WriteLine(item);
    WScript.StdOut.WriteLine("Size = " + item.ExtendedProperty("System.Size"));
    WScript.StdOut.WriteLine("------------");
}

Okay, first of all, how did I get that magic string for the Programs and Features folder? I opened the Control Panel and dragged the Uninstall a program link onto the program from a few weeks ago.

The program itself is pretty straightforward. It's the standard enumerate everything in a folder and print it program we've seen before. The only trick was finding the folder.

As for the C++ version, it should also look familiar, because we've done it before more than once. The only difference is the way we create the folder and the details we choose to display. (For extra credit: Change this program to bind to the persisted pidl instead of the parsing name.)

int __cdecl wmain(int argc, wchar_t **argv)
{
 CCoInitialize init;
 CComPtr<IShellItem> spPrinters;
 SHCreateItemFromParsingName(
   L"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\"
   L"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}", nullptr,
   IID_PPV_ARGS(&spPrograms));
 CComPtr<IEnumShellItems> spEnum;
 spPrograms->BindToHandler(nullptr, BHID_EnumItems,
                              IID_PPV_ARGS(&spEnum));
 for (CComPtr<IShellItem> spProgram;
      spEnum->Next(1, &spProgram, nullptr) == S_OK;
      spProgram.Release()) {
  CComHeapPtr<wchar_t> spszName;
  spProgram->GetDisplayName(SIGDN_NORMALDISPLAY, &spszName);
  wprintf(L"%ls\n", spszName);
  PrintDetail(CComQIPtr<IShellItem2>(spProgram), &PKEY_Size, L"Size");
 }
 return 0;
}

Bonus script: You can even see what verbs are available.

var shell = new ActiveXObject("Shell.Application");
var programsFolder = shell.Namespace(
    "::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\" +
    "::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}");
var items = programsFolder.Items();
for (var i = 0; i < items.Count; i++) {
    var item = items.Item(i);
    WScript.StdOut.WriteLine(item);
    WScript.StdOut.WriteLine("Size = " + item.ExtendedProperty("System.Size"));
    var verbs = item.Verbs();
    for (var j = 0; j < verbs.Count; j++) {
       var verb = verbs.Item(j);
       WScript.StdOut.WriteLine("Action: " + verb.Name);
    }
    WScript.StdOut.WriteLine("------------");
}

And if you're really ambitious, you can even call verb.DoIt to carry out the action. Don't use this power for evil.

Note: Since we are working with the Programs and Features folder, we are necessarily targeting Windows Vista and later, since that was the version of Windows in which the Programs and Features folder was introduced. Therefore, I am free to use functionality introduced in Windows Vista.

I've been doing Little Programs for a year now. I kind of like it, so I'm going to continue for another year, but I'm going to relax the rules a bit: The Little Programs are now just programs that I think are interesting for whatever reason. They don't need to actually solve a problem.

Comments (25)
  1. crack tracker says:

    this folder view has a column for "last used on" which I imagine that links to System.Software.DateLastUsed, but it comes up blank, any ideas? That also reminds me of another useful property I never was able to fetch from any executable file:

    System.Software.TimesUsed

    happy holidays!

  2. nksingh says:

    How likely is it that the guid for programs and features might change or disappear in the future, with changes to the implementation?  Is this actually the kind of interface that would be treated as a compat surface of the shell, in your astimation?

  3. xpclient says:

    Adding shell:::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5} (shell:ChangeRemoveProgramsFolder) to Classic Shell's menu as a custom item cascading menu lets you uninstall programs faster than opening it from Explorer. :)

  4. Bob says:

    How about PowerShell scripts from now on instead of jscript?

  5. MV says:

    "Here, here's an idea: Instead of trying to mimic the Programs and Features folder, just ask the Programs and Features folder for its contents! That way, no matter what changes are made to how the Programs and Features folder obtains its contents (and those changes occur pretty often), your program will always match it, because you're just showing the same thing."

    – vs –

    "Note: Since we are working with the Programs and Features folder, we are necessarily targeting Windows Vista and later, since that was the version of Windows in which the Programs and Features folder was introduced. Therefore, I am free to use functionality introduced in Windows Vista. "

    So in other words, if someone had asked this some time between 95 and 2007(ish), you'd have given him a different "future-proof" solution which probably doesn't work any more.  The problem isn't that there's no standard way to do stuff, it's that the standards change every other release!

  6. Kevin says:

    @MV: Given Microsoft's religious adherence to backwards compatibility, I find that rather hard to believe.  The old way would still be supported, even if it was now "wrong."

  7. AndyCadley says:

    @MV: If someone had asked in the 95-2006(ish) era, Raymond would probably have scratched his head in confusion, since the question doesn't make any sense till Programs and Features existed. It's like asking how you enumerate all the Windows Store apps on Windows 95.

    They might have asked about getting the list of programs as seen in the Start Menu (relatively obvious) or as listed in Add/Remove Programs (the uninstall registry key mostly though, as Raymond has covered before, some of it is just heuristics)

    It's great to have a "standard" way of doing things, but it doesn't negate the need for a time machine if you want that to have existed before anyone thought of it.

  8. Fleet Command says:

    @Bob: Thumbs up! I second that.

  9. Cheong says:

    @nksingh: I don't know, it's the same as you write script to invoke "&Menuitem1" then "Menusubitem&3" of Foo application. If the names got changed for whatever reason, your program stops working and you need an update.

  10. Joshua says:

    I used the old "standard" way once, that is enumerate the registry key where uninstallers get written.

    The 1.x series of our product had a nasty bug where in-place-upgrades (the only supported kind) would litter the install list with all the old versions ever installed. Running the uninstaller on any of them would break the program's install rather badly. The 1.x installers used guid-looking names (one for each version installed) and we had no list of the assigned names. So I enumerated, matching on the display text and icon file name. Every one matching got uninstalled by the 2.x series installer (which would then repair the damage by virtue of upgrading to 2.x series).

  11. Gabe says:

    While it's great to ask the Programs and Features folder for its contents, my concern would be how to access the Programs and Features folder. The magic string contains the GUID for the Control Panel, then "8", and then the GUID for Programs and Features.

    It just so happens that Programs and Features is in category 8 of the Control Panel, but what if that changes? Well, it so happens that any string I used to replace the "8" still worked, so it doesn't matter at this point what category it's in. However, it's entirely possible that at some point in the future it will matter, and then the program may stop working.

  12. Anon says:

    Just to pile on here…

    @MV

    You can't future-proof beyond major OS releases, but Vista/7/8 all use the same method.

    If this was an iFruit, you couldn't even count on the same functionality being available over the course of a single year's upgrades.

    Linux, you can't even guarantee compatibility within the same month.

  13. Anon says:

    @Gabe

    Programs stop working all the time. Conveniently, code isn't a permanent document. When things stop working, you can fix them.

  14. George says:

    @Gabe – When I tried to get the name I got 0 (probably because I used the flat view of the Control Panel). Also slightly less hacky way is to use SHGetKnownFolderItem(FOLDERID_ChangeRemovePrograms).

    BTW, to get the version of the program you can use the undocumented property "{0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 8". It is weird because other similar properties are documented, like PKEY_Trademarks and PKEY_Software_ProductName.

  15. 640k says:

    How do a script perform this enumeration in windows nt/2k/xp, ie using add/remove programs?

    [Windows NT/2000/XP doesn't have a "Programs and Features" folder, so the question is moot. If you mean "enumerate all installed applications", there's no built-in way of doing this. You have to mimic what "Add/Remove Programs" does, which is messy because Add/Remove Programs enumerates apps from some places that may not be obvious (like the MSI store). -Raymond]
  16. 640k says:

    @Anon: Enumeration of the installed apps should be a basic OS feature? How hard could it be?

    [Since you can install an app via "copy foo.exe C:", would "enumerating installed apps" mean "enumerating all EXE files"? What about apps that run directly off the network? They aren't even copied anywhere! -Raymond]
  17. WndSks says:

    @George: {0CEF7D53-FA64-11D1-A203-0000F81FEDEE}, 8 = System.Software.ProductVersion but why a lot of these are missing from the "Property System ReferenceWindows PropertiesSystem" part of MSDN I don't know but it is listed @ msdn.microsoft.com/…/cc251929(v=prot.10).aspx and the legacy mapping table @ msdn.microsoft.com/…/bb776504%28v=vs.85%29.aspx (By name only)

  18. immibis_ says:

    @Raymond: the customer made a reasonable guess that there is some API underneath the Programs and Features folder, which is also accessible to the customer's program.

    Especially if they read this blog, they would not think that searching the P&F folder itself is a good idea. (What happens if the P&F folder is removed, and we go back to having a property window like on Win98?)

    @MV: how to enumerate all the Windows Store apps on Windows 95: (I have no idea how Windows Store apps are identified, but let's pretend)

    typedef void (*PFNENUMWINDOWSSTOREAPPSONWINDOWS95)(LPCTSTR pszAppIdentifier);

    void EnumWindowsStoreAppsOnWindows95(PFNENUMWINDOWSSTOREAPPSONWINDOWS95 callback)

    {

    }

  19. MV says:

    I'm just a dumb user (OK, I'm actually a dev, but I've got my "dumb user" hat on), and it seems to me that "the programs and features" folder is just a renaming/revamping of the old "add/remove programs".  I'm sure there's a million differences and it's far superior in a dozen different ways, but if I want to uninstall something I go to the control panel and find "add/remove programs" (or "programs and features" in vista+), find the program I want to uninstall, and uninstall it.  It serves the same basic function, so whatever it is people are wanting to do with "programs and features" today, they'd have wanted to do the exact same thing with "add/remove programs" a few years back.

  20. Marc says:

    I'm able to list the content of the Devices and Printer folder. But I'm not able to get the correct icons of the printers. The best I could get was the default icons for printers but not the real icons used for specific printers. Any hints on how to do this?

  21. Joshua says:

    [which is messy because Add/Remove Programs enumerates apps from some places that may not be obvious (like the MSI store). -Raymond]

    According to my tests, on XP these still show up if you enumerate the registry key for registering your own uninstallers. The uninstall action is corrupted though so you have to know it's an MSI action.

    [Since you can install an app via "copy foo.exe C:", would "enumerating installed apps" mean "enumerating all EXE files"?]

    No. It's not wired up to anything. However, (excluding software that chooses to misbehave) it should be able to find anything that is actually installed, that is, shows up as a program rather than the user accessing the EXE file directly to start it.

    [Then it all hinges upon your definition of "actually installed." Suppose a program is copied to your hard drive and a shortcut on the desktop is created to point to it. Is that "actually installed"? If so, how should an operating system detect it? -Raymond]
  22. Joshua says:

    [Then it all hinges upon your definition of "actually installed." Suppose a program is copied to your hard drive and a shortcut on the desktop is created to point to it. Is that "actually installed"? If so, how should an operating system detect it? -Raymond]

    Well since I hold xcopy install to be no install at all, I don't think we have any disagreement in fact.

    If you wanted to detect stuff like this anyway (and I don't as what you've describe so far sounds to me like user action), the way I'd do it is enumerate desktop, start menu, shell extensions, and any defined place where things can auto-start.

    [Okay, so xcopy is not installation. What counts as installation then? It would require applications to register somewhere as having been installed. Which is what we have today. That registration point is the Programs and Features control panel. -Raymond]
  23. Someone says:

    "That registration point is the Programs and Features control panel. -Raymond"

    Soory, but no. (1) A virtual shell view only shows, what is already known to Windows. The "registration point" is some place in the registry. (2) Without a dedicated SETUP/INSTALL program, there is no "installation". (3) If a SETUP/INSTALL program does not register with Windows, I consider such setup program to be broken.

    "You have to mimic what "Add/Remove Programs" does, which is messy because Add/Remove Programs enumerates apps from some places that may not be obvious (like the MSI store)"

    Why that? Regardless of the setup procedure, for each software part which can be configured or removed independently from other software parts, there have to be exactly one entry in the registry, and in turn one item shown to the user in "Add/Remove Programs" or "Programs and Features".

    [Whatever you call the registration point, it's the thing that Programs and Features uses. It sounds like your real problem is that Program and Features collects items from multiple locations. -Raymond]
  24. TT says:

    "Instead of trying to mimic the Programs and Features folder, just ask the Programs and Features folder for its contents!"

    I think the reason the "correct" way to solve this problem wouldn't occur to a lot of people is that it's a strange inversion of responsibilities to have the shell effectively providing an API.  In most systems, you'd build the shell ON TOP of the API, so the right way to get this list would be to go find out what API the shell special folder uses.  Microsoft seems to like to do it the other way around: they build UI which is then presented to the user, and because that's the abstraction that users are familiar with, the "API" becomes "query the UI that the user is familiar with."  I think most programmers who aren't familiar with the Microsoft way of doing things would expect you to be able to take a system with a bunch of installed programs and enumerate those installed programs even if there was no shell at all.

  25. Gabe says:

    TT: Do you have any other examples of APIs that some systems implement "under" their shells but that Microsoft has made available only as an API on top of the shell?

    Even so, the fact that MS made the shell accessible via nearly any programming language means it doesn't matter much that using the API invokes the shell.

    I would have to say that in general, MS tends to do a good job of making APIs available to do things that other systems only do via parsing text files. For example, IIS's configuration has been scriptable for as long as WSH has been around, while I've never seen any way to edit Apache configurations programmatically.

Comments are closed.