在通过本地x64二进制文件使用PowerShell实例化Microsoft.Update.Session时,出现空的CLSID {00000000-...},错误码0x80040154 (REGDB_E_CLASSNOTREG)

后端开发 2026-07-08

我正在编写一个原生C++的 Windows桌面应用程序,作为驱动更新器。更新程序会扫描本地硬件,然后创建一个后台线程,通过管道异步启动PowerShell (powershell.exe),以便从Microsoft Update Catalog通过Windows Update Agent (WUA) API的 COM对象下载/安装匹配的驱动程序包。

尽管将二进制编译为原生64位应用程序,并在PowerShell执行时强制使用单一线程公寓(-Sta)标志,但WUA COM对象的实例化仍然失败,出现一种极为特殊的 REGDB_E_CLASSNOTREG 错误变体:它返回一个完全为空/为null的 CLSID {00000000-0000-0000-0000-000000000000}

The Error Payload Received in C++ Standard Output:

Status: Failed (Retrieving the COM class factory for component with CLSID {00000000-0000-0000-0000-000000000000} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).)

Minimal Reproducible Example

1. The Background C++ Execution Code (driver_updater.cpp)

该应用会生成一个临时脚本文件并执行它。为保险起见,我们显式绕过WoW64文件系统重定向,并使用Visual Studio的 MSVC编译工具链,目标为原生 x64

c++ code

// Compiled using: cl.exe /EHsc /DUNICODE /D_UNICODE main.cpp driver_updater.cpp ...
bool ExecutePowerShellScript(const std::wstring& scriptContent, std::vector<std::wstring>& outputLines) {
    // ... [Standard Pipe Initialization code omitted for brevity] ...

    // Target native 64-bit PowerShell and force it into Single-Threaded Apartment (-Sta) mode
    std::wstring cmd = L"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -Sta -NoProfile -ExecutionPolicy Bypass -File \"" + scriptPath + L"\"";
    std::vector<wchar_t> cmdBuf(cmd.begin(), cmd.end());
    cmdBuf.push_back(L'\0');

    // Pause 32-bit File System Redirection just to be absolutely certain we call native x64 components
    PVOID oldWow64Value = NULL;
    BOOL isWow64 = FALSE;
    IsWow64Process(GetCurrentProcess(), &isWow64);
    if (isWow64) { Wow64DisableWow64FsRedirection(&oldWow64Value); }

    BOOL success = CreateProcessW(NULL, cmdBuf.data(), NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);

    if (isWow64) { Wow64RevertWow64FsRedirection(oldWow64Value); }
    // ... [Pipe Reading / Output collection stream code] ...
}

2. The Injected PowerShell Payload Block

在字符串块生成过程中,崩溃在第一行操作就立即发生:

PowerShell

try {
    # CRASH OCCURS RIGHT HERE:
    $Session = New-Object -ComObject Microsoft.Update.Session

    $Searcher = $Session.CreateUpdateSearcher()
    $Query = "IsInstalled=0 and Type='Driver'"
    $SearchResult = $Searcher.Search($Query)
    # ... [Filtering and item installation iteration] ...
} catch {
    $CleanedMessage = $_.Exception.Message
    Write-Output "STATUS:Failed ($CleanedMessage)"
}

What I Have Already Verified & Ruled Out:

  1. Architecture Alignment: The C++ app is verified x64 via cl.exe. The targeted executable path is the native C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe.
  2. UAC Context: The application manifest forces a requireAdministrator run level. The processes are confirmed to be executing with elevated full admin split tokens.
  3. Threading Model: The -Sta flag is supplied directly to the CLI argument array to comply with the COM apartment expectations of wuapi.dll.
  4. The Empty CLSID Anomaly: Normally, a typical registration error or architecture mismatch reveals the legitimate WUA CLSID ({4CA3A67D-4B26-4A91-B430-EAE2D96AFCCB}). The fact that the thrown exception reports {00000000-0000-0000-0000-000000000000} heavily implies that the .NET interop factory inside PowerShell is generating/instantiating a completely uninitialized null tracking context pointer instead of throwing a clean registration failure。

为什么一个完全原生的、具有管理员权限的x64生命周期会在生成的PowerShell实例中彻底使得 Microsoft.Update.Session 组件的CLSID注册路径失效?我该如何在不通过手动遍历注册表的情况下解决?

compile command in x64 Native Tool Command Promp for vs :

cl.exe /EHsc /DUNICODE /D_UNICODE main.cpp driver_scanner.cpp driver_updater.cpp app.res /Fe:DriverUpdater.exe urlmon.lib comctl32.lib setupapi.lib newdev.lib winhttp.lib cfgmgr32.lib user32.lib gdi32.lib advapi32.lib***

解决方案

特别感谢 RbMm 指出正确方向!完全放弃外部脚本执行器,改为通过COM原生接口直接与Windows Update Agent (WUA) API交互,是绝对正确的做法。

The Problem with the PowerShell Approach

尝试通过管道传输或调用PowerShell来处理底层设备和系统驱动更新是非常脆弱的。它带来巨大的开销,在目标机器上因严格的执行策略而容易失效,并且在尝试从外部脚本跟踪异步下载和安装状态时,会遇到微妙的进程间COM注册错误。

The Solution: Native C++ WUA Engine

通过使用标准COM组件实例化WUA API(IUpdateSession, IUpdateSearcher, IUpdateCollection),你可以在Win32应用程序内对更新生命周期获得完全控制。

以下是解决方案中实现的关键架构细节:

  1. Thread Isolation: The entire update process is wrapped inside a dedicated worker thread using CoInitializeEx(NULL, COINIT_APARTMENTTHREADED). This ensures that blocking synchronous network/IO calls (pDownloader->Download() and pInstaller->Install()) do not freeze the main UI thread.
  2. Explicit "Up to Date" Handling: If the search returns 0 available updates for a given hardware topology signature, the code explicitly drops out early, returns a clean status string, and avoids downstream errors or falling through to false failures.
  3. Graceful COM Lifetime Management: Every single allocated BSTR and COM interface instance is meticulously cleaned up via SysFreeString and Release() to prevent memory leaks in continuous background scans.

Complete driver_updater.cpp Implementation

#include "driver_updater.h"
#include "resource.h"
#include <windows.h>
#include <wuapi.h>
#include <comdef.h>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>

// Helper to convert lowercase or mixed strings for reliable searching
std::wstring ToLower(const std::wstring& str) {
    std::wstring result = str;
    for (size_t i = 0; i < result.length(); ++i) {
        if (result[i] >= L'A' && result[i] <= L'Z') {
            result[i] = result[i] + 32;
        }
    }
    return result;
}

// Checks if a specific target hardware ID exists inside an update object's metadata or categories
bool IsUpdateCompatible(IUpdate* pUpdate, const std::wstring& targetHwId) {
    if (!pUpdate) return false;

    std::wstring lowerTarget = ToLower(targetHwId);

    // 1. Primary Strategy: Check the Categories Collection for Hardware ID matches
    ICategoryCollection* pCategories = nullptr;
    HRESULT hr = pUpdate->get_Categories(&pCategories);
    if (SUCCEEDED(hr) && pCategories) {
        LONG count = 0;
        pCategories->get_Count(&count);

        for (LONG i = 0; i < count; ++i) {
            ICategory* pCategory = nullptr;
            if (SUCCEEDED(pCategories->get_Item(i, &pCategory)) && pCategory) {
                BSTR bstrCatName = nullptr;
                if (SUCCEEDED(pCategory->get_Name(&bstrCatName)) && bstrCatName) {
                    std::wstring catName = ToLower(bstrCatName);
                    SysFreeString(bstrCatName);

                    // If the hardware ID matches a category signature, we have a match
                    if (!catName.empty() && lowerTarget.find(catName) != std::wstring::npos) {
                        pCategory->Release();
                        pCategories->Release();
                        return true;
                    }
                }
                pCategory->Release();
            }
        }
        pCategories->Release();
    }

    // 2. Fallback Strategy: Check the human-readable update title string
    BSTR bstrTitle = nullptr;
    hr = pUpdate->get_Title(&bstrTitle);
    if (SUCCEEDED(hr) && bstrTitle) {
        std::wstring updateTitle = ToLower(bstrTitle);
        SysFreeString(bstrTitle);

        if (!updateTitle.empty() && lowerTarget.find(updateTitle) != std::wstring::npos) {
            return true;
        }
    }

    return false;
}

// Orchestrates the native Windows Update Agent lifecycle interface
std::wstring ProcessDeviceUpdateNative(const std::wstring& hardwareId) {
    HRESULT hr;

    // 1. Initialize the Update Session
    IUpdateSession* pSession = nullptr;
    hr = CoCreateInstance(__uuidof(UpdateSession), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSession));
    if (FAILED(hr)) {
        return L"Failed (Session Init Err: 0x" + std::to_wstring(hr) + L")";
    }

    // 2. Create Update Searcher
    IUpdateSearcher* pSearcher = nullptr;
    hr = pSession->CreateUpdateSearcher(&pSearcher);
    if (FAILED(hr)) {
        pSession->Release();
        return L"Failed (Searcher Init Err)";
    }

    // Query flags looking for uninstalled driver updates matching the current environment topology
    BSTR queryStr = SysAllocString(L"IsInstalled=0 and Type='Driver'");
    ISearchResult* pSearchResult = nullptr;

    hr = pSearcher->Search(queryStr, &pSearchResult);
    SysFreeString(queryStr);

    if (FAILED(hr)) {
        pSearcher->Release();
        pSession->Release();
        return L"Failed (Catalog Search Drop)";
    }

    // 3. Extract the updates group
    IUpdateCollection* pAllUpdates = nullptr;
    pSearchResult->get_Updates(&pAllUpdates);

    LONG totalUpdates = 0;
    pAllUpdates->get_Count(&totalUpdates);

    // Instantiate a new native COM compilation container to accumulate hardware matches
    IUpdateCollection* pMatchedUpdates = nullptr;
    CoCreateInstance(__uuidof(UpdateCollection), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pMatchedUpdates));

    for (LONG i = 0; i < totalUpdates; ++i) {
        IUpdate* pUpdate = nullptr;
        if (SUCCEEDED(pAllUpdates->get_Item(i, &pUpdate)) && pUpdate) {
            if (IsUpdateCompatible(pUpdate, hardwareId)) {
                LONG indexAdded = 0; // Satisfies the dual argument requirement of IUpdateCollection::Add
                pMatchedUpdates->Add(pUpdate, &indexAdded); 
            }
            pUpdate->Release();
        }
    }

    LONG matchedCount = 0;
    pMatchedUpdates->get_Count(&matchedCount);

    if (matchedCount == 0) {
        pMatchedUpdates->Release();
        pAllUpdates->Release();
        pSearchResult->Release();
        pSearcher->Release();
        pSession->Release();
        return L"Up to Date (No MS Driver)";
    }

    // 4. Download matched updates packages
    IUpdateDownloader* pDownloader = nullptr;
    hr = pSession->CreateUpdateDownloader(&pDownloader);
    if (SUCCEEDED(hr)) {
        pDownloader->put_Updates(pMatchedUpdates);
        IDownloadResult* pDownloadResult = nullptr;
        hr = pDownloader->Download(&pDownloadResult);

        if (SUCCEEDED(hr)) {
            pDownloadResult->Release();
        }
        pDownloader->Release();
    }

    if (FAILED(hr)) {
        pMatchedUpdates->Release();
        pAllUpdates->Release();
        pSearchResult->Release();
        pSearcher->Release();
        pSession->Release();
        return L"Failed (Download Timeout)";
    }

    // 5. Install the staging drivers payload
    std::wstring finalStatus = L"Updated Successfully";
    IUpdateInstaller* pInstaller = nullptr;
    hr = pSession->CreateUpdateInstaller(&pInstaller);
    if (SUCCEEDED(hr)) {
        pInstaller->put_Updates(pMatchedUpdates);
        IInstallationResult* pInstallResult = nullptr;
        hr = pInstaller->Install(&pInstallResult);

        if (SUCCEEDED(hr)) {
            OperationResultCode resCode; 
            pInstallResult->get_ResultCode(&resCode);

            if (resCode != orcSucceeded && resCode != orcSucceededWithErrors) {
                std::wstringstream wss;
                wss << L"Install Failed (Code: " << resCode << L")";
                finalStatus = wss.str(); 
            }
            pInstallResult->Release();
        } else {
            finalStatus = L"Install Exception (0x" + std::to_wstring(hr) + L")";
        }
        pInstaller->Release();
    }

    // Comprehensive Interface Destruction Chain
    pMatchedUpdates->Release();
    pAllUpdates->Release();
    pSearchResult->Release();
    pSearcher->Release();
    pSession->Release();

    return finalStatus;
}

// Thread Entry Point orchestration engine
DWORD WINAPI UpdateDriversThread(LPVOID lpParam) {
    UpdateThreadParams* params = (UpdateThreadParams*)lpParam;
    HWND hWnd = params->hWnd;

    HRESULT comHr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    PostMessage(hWnd, WM_INSTALL_START, 0, 0);

    int processed = 0;

    for (DeviceInfo* device : params->deviceList) {
        if (device->action == L"Check for Update" || device->action == L"Fix / Update") {
            if (device->hardwareId.empty()) {
                device->status = L"No Hardware ID";
                device->action = L"Cannot Update";
                PostMessage(hWnd, WM_INSTALL_UPDATE, (WPARAM)processed, (LPARAM)device);
                processed++;
                continue; 
            }

            device->status = L"Searching MS Catalog...";
            device->action = L"Processing...";
            PostMessage(hWnd, WM_INSTALL_UPDATE, (WPARAM)processed, (LPARAM)device);

            std::wstring executionResult = ProcessDeviceUpdateNative(device->hardwareId);

            device->status = executionResult;
            if (executionResult == L"Updated Successfully") {
                device->action = L"Done";
            } else if (executionResult == L"Up to Date (No MS Driver)") {
                device->action = L"Up to Date";
            } else {
                device->action = L"Error";
            }

            PostMessage(hWnd, WM_INSTALL_UPDATE, (WPARAM)processed, (LPARAM)device);
        }
        processed++;
    }

    if (SUCCEEDED(comHr)) {
        CoUninitialize();
    }

    for (DeviceInfo* device : params->deviceList) delete device;
    delete params;

    PostMessage(hWnd, WM_INSTALL_COMPLETE, 0, 0);
    return 0;
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章