0

I work on a plugin-based application that is currently scanning the Windows registry for compatible COM servers that expose certain "Implemented Categories" entries. This works well for "regular" COM servers installed through MSI installers.

However, I'm now facing a problem with COM servers installed through MSIX installers that expose COM extension points through the "Packaged COM" catalog as described in https://blogs.windows.com/windowsdeveloper/2017/04/13/com-server-ole-document-support-desktop-bridge/ . These COM servers can still be instantiated through CoCreateInstance, but RegOpenKey/RegEnumKey searches aren't able to detect their presence.

I'm not sure how to approach this problem. The best outcome would be some sort of Windows API for querying the "Packaged COM" catalog for installed COM servers that I can run in addition to the registry search. However, I don't know if that even exist? I'm also open for other suggestions, as long as they still allows my application to dynamically detect the presence of new COM-based plugins.

  • Are you looking for this information : https://stackoverflow.com/questions/50802129/how-to-get-appx-full-name-from-a-string-with-wildcard (package list is in "%ProgramFiles%\WindowsApps") – Simon Mourier Oct 13 '21 at 18:35
  • 1
    You can also use WinRT API PackageManager.FindPackages: https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.packagemanager.findpackages – Simon Mourier Oct 13 '21 at 18:43
  • Thanks for a very helpful clue @SimonMourier. I've now managed to retrieve a list of `Windows.ApplicationModel.Package` objects for all packages installed on my computer. However, I also need to retrieve the corresponding COM extensions for these packages. The XML schema is documented on https://learn.microsoft.com/nb-no/uwp/schemas/appxpackage/uapmanifestschema/root-elements , but I have so far not been able to find the corresponding API. – Fredrik Orderud Oct 15 '21 at 08:24
  • You don't need an API for a documented schema. – Simon Mourier Oct 15 '21 at 08:56
  • Ok. So what you're proposing is that I for each detected `Windows.ApplicationModel.Package` object open & parse the associated `AppxManifest.xml` file to detect associated COM extension points? This sounds a bit clunky and inefficient, but I think it could work. – Fredrik Orderud Oct 15 '21 at 10:22
  • 1
    Maybe you can post this on the MSIX community (https://techcommunity.microsoft.com/t5/MSIX/ct-p/MSIX) and hopefully, somebody from Microsoft will help. I tried asking around online but no luck so far. Our team is not aware of any API either (and I skipped recommending parsing the manifest because it's not the most elegant solution) – Bogdan Mitrache Oct 15 '21 at 13:56
  • Thanks for the tip @BogdanMitrache. I've now posted the same question on https://techcommunity.microsoft.com/t5/msix-deployment/how-to-query-for-installed-packaged-com-extension-points/td-p/2854090 – Fredrik Orderud Oct 17 '21 at 09:48

2 Answers2

1

PLEASE DISREGARD THIS ANSWER. There's a better answer based on ICatInformation::EnumClassesOfCategories below.

Answering myself with sample code to query the "Packaged COM" catalog for installed COM servers. Based on suggestion from @SimonMourier.

using System.Collections.Generic;
using System.IO;

/** Use Target Framework Moniker as described in https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance */
class PackagedComScan {
    static void Main(string[] args) {
        var packageManager = new Windows.Management.Deployment.PackageManager();
        // this call require the "packageQuery" capability if called from a UWP app (add <rescap:Capability Name="packageQuery" /> to the appxmanifest)
        IEnumerable<Windows.ApplicationModel.Package> my_packages = packageManager.FindPackagesForUser("");

        foreach (var package in my_packages) {
            try {
                ParseAppxManifest(package.InstalledLocation.Path + @"\AppxManifest.xml");
            } catch (FileNotFoundException) {
                // Installed package missing from disk. Can happen after deploying UWP builds from Visual Studio.
            }
        }
    }

    static void ParseAppxManifest(string manifest_path) {
        var doc = new System.Xml.XmlDocument();
        using (var fs = new FileStream(manifest_path, FileMode.Open, FileAccess.Read, FileShare.Read))
            doc.Load(fs);

        var nsmgr = new System.Xml.XmlNamespaceManager(doc.NameTable);
        nsmgr.AddNamespace("a", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); // default namespace
        nsmgr.AddNamespace("com", "http://schemas.microsoft.com/appx/manifest/com/windows10");

        // detect exported COM servers
        var nodes = doc.SelectNodes("/a:Package/a:Applications/a:Application/a:Extensions/com:Extension/com:ComServer/com:ExeServer/com:Class/@Id", nsmgr);
        foreach (System.Xml.XmlNode node in nodes)
            System.Console.WriteLine("Exported COM CLSID: {0}", node.Value);
    }
}

This is admittedly a bit ad-hoc since it relies on parsing the AppxManifest.xml files. Still, it seems to get the job done. Please note that UWP applications that runs within sandboxed AppContainer processes only seem to have read access to some of the AppxManifest.xml files, and not all. The code therefore only works for "regular" Win32 or .Net processes.

0

Answering myself with sample code to query all installed COM servers, including the "Packaged COM" catalog, using ICatInformation::EnumClassesOfCategories. Based on suggestion by Aditi_Narvekar:

#include <atlstr.h>
#include <vector>

static void CHECK(HRESULT hr) {
    if (FAILED(hr))
        abort(); // TODO: More graceful error handling
}

/** Return COM classes that implement any of the provided "Implemented Categories". */
inline std::vector<CLSID> GetClassesWithAnyOfCategories(std::vector<CATID> impl_categories) {
    CComPtr<ICatInformation> cat_search;
    CHECK(cat_search.CoCreateInstance(CLSID_StdComponentCategoriesMgr));

    CComPtr<IEnumGUID> class_list;
    CHECK(cat_search->EnumClassesOfCategories((ULONG)impl_categories.size(), impl_categories.data(), -1, nullptr, &class_list));

    std::vector<CLSID> app_clsids;
    app_clsids.reserve(64);
    for (;;) {
        CLSID cur_cls = {};
        ULONG num_read = 0;
        CHECK(class_list->Next(1, &cur_cls, &num_read));
        if (num_read == 0)
            break;

        // can also call ProgIDFromCLSID to get the ProgID
        // can also call OleRegGetUserType to get the COM class name

        app_clsids.push_back(cur_cls);
    }

    return app_clsids;
}