如何:获取 .NET Framework 4 安装程序的进度
更新:2010 年 7 月
.NET Framework 4 版是可再发行运行时。 您可将 .NET Framework 4 安装过程作为一个系统必备组件包含(链接)到您的应用程序的安装程序中。 若要提供自定义或统一的安装体验,您可能需要在显示自己的安装进度视图的同时,在不提示的情况下启动并跟踪 .NET Framework 4 安装过程。 若要启用无提示跟踪,.NET Framework 4 安装过程(可观察的链接项)可将进度消息写入安装过程(观察程序或链接器)可以读取的内存映射 I/O (MMIO) 段。 您可通过将 Abort 消息写入 MMIO 段来取消 .NET Framework 4 安装过程。
调用。 若要调用 .NET Framework 4 安装过程并接收来自 MMIO 节的进度信息,安装程序必须执行以下操作:
调用 .NET Framework 4 可再发行组件程序;例如:
dotnetfx40x86.exe /q /pipe <section name>
其中 section name 是您要用来标识应用程序的任意名称。 链接项以异步方式在 MMIO 节进行读写,因此您可能会发现在此期间使用事件和消息很方便。 在此示例中,链接项由分配 MMIO 节 (YourSection) 和定义事件 (YourEvent) 的构造函数创建。 请使用对于安装程序唯一的名称替换这些名称。
从 MMIO 节读取。 在 .NET Framework 4 中,下载和安装操作同时进行:正在下载 .NET Framework 4 的一个组件的同时,另一个组件可能正在安装。 因此,进度会以一个从 1 到 225 递增的数字发送回(即写入)MMIO 节。 如果写入 255 且链接项存在,则表示安装完成。
退出代码。 下面用于调用 .NET Framework 4 可再发行组件程序(请参见上一个示例)的命令中的退出代码指示安装程序是成功还是失败:
0 - 安装程序已成功完成。
3010 – 安装程序已成功完成;需要重新启动。
1642 – 安装程序已取消。
所有其他的代码 - 安装程序遇到了错误;请检查 %temp% 中创建的日志文件了解详细信息。
取消安装程序。 您可随时通过使用 Abort 方法在 MMIO 节中设置 m_downloadAbort 和 m_ installAbort 标志来取消安装程序。 这样将停止 .NET Framework 4 安装过程。
链接器示例
以下示例在显示进度的同时,在不提示的情况下启动和跟踪 .NET Framework 4 安装过程。
警告 |
---|
您必须以管理员身份运行此示例。 |
您可从 Microsoft 代码库为 Chain Installer for .NET Framework 4(.NET Framework 4 的链接安装程序)下载完整的 Visual Studio 解决方案。
下面各节介绍了此示例中的重要文件。
MmIoChainer.h
MmIoChainer.h 文件包含数据结构定义和链接器类应派生自的基类。 MMIO 数据结构包括以下代码。
// MMIO data structure for inter-process communication. struct MmioDataStructure { bool m_downloadFinished; // Is download done yet? bool m_installFinished; // Is installer operation done yet? bool m_downloadAbort; // Set to cause downloader to abort. bool m_installAbort; // Set to cause installer operation to abort. HRESULT m_hrDownloadFinished; // HRESULT for download. HRESULT m_hrInstallFinished; // HRESULT for installer operation. HRESULT m_hrInternalError; // Internal error from MSI if applicable. WCHAR m_szCurrentItemStep[MAX_PATH]; // This identifies the windows installer step being executed if an error occurs while processing an MSI, for example, "Rollback". unsigned char m_downloadProgressSoFar; // Download progress 0 - 255 (0 to 100% done). unsigned char m_installProgressSoFar; // Install progress 0 - 255 (0 to 100% done). WCHAR m_szEventName[MAX_PATH]; // Event that chainer creates and chainee opens to sync communications. };
数据结构后面是用于实现链接器的类结构。 您应从 MmioChainer 类派生服务器类以链接 .NET Framework 4 可再发行组件。 MmioChainerBase 类由链接器和链接项共同使用。 在以下代码中,删除了方法和成员以保持此示例的长度简短。
// MmioChainerBase class manages the communication and synchronization data // structures. It also implements common get accessors (for chainer) and set accessors(for chainee). class MmioChainerBase { ... // This is called by the chainer to force the chained setup to be canceled. void Abort() { //Don't do anything if it is invalid. if (NULL == m_pData) { return; } ... // Chainer told us to cancel. m_pData->m_downloadAbort= true; m_pData->m_installAbort = true; } // Called when chainer wants to know if chained setup has finished both download and installation. bool IsDone() const { ... } // Called by the chainer to get the overall progress, i.e., the combination of the download and installation. unsigned char GetProgress() const { ... } // Get download progress. unsigned char GetDownloadProgress() const { ... } // Get installation progress. unsigned char GetInstallProgress() const { ... } // Get the combined setup result, installation taking priority over download if both failed. HRESULT GetResult() const { ... } ... };
链接器的实现方式如下。
// This is the class that the consumer (chainer) should derive from. class MmioChainer : protected MmioChainerBase { public: // Constructor MmioChainer (LPCWSTR sectionName, LPCWSTR eventName) : MmioChainerBase(CreateSection(sectionName), CreateEvent(eventName)) { Init(eventName); } // Destructor virtual ~MmioChainer () { ::CloseHandle(GetEventHandle()); ::CloseHandle(GetMmioHandle()); } public: // The public methods: Abort and Run using MmioChainerBase::Abort; using MmioChainerBase::GetInstallResult; using MmioChainerBase::GetInstallProgress; using MmioChainerBase::GetDownloadResult; using MmioChainerBase::GetDownloadProgress; using MmioChainerBase::GetCurrentItemStep; HRESULT GetInternalErrorCode() { return GetInternalResult(); } // Called by the chainer to start the chained setup. This blocks until setup is complete. void Run(HANDLE process, IProgressObserver& observer) { HANDLE handles[2] = { process, GetEventHandle() }; while(!IsDone()) { DWORD ret = ::WaitForMultipleObjects(2, handles, FALSE, 100); // INFINITE ?? switch(ret) { case WAIT_OBJECT_0: { // Process handle closed. Maybe it blew up, maybe it's just really fast. Let's find out. if (IsDone() == false) // Not a good sign { HRESULT hr = GetResult(); if (hr == E_PENDING) // Untouched observer.Finished(E_FAIL); else observer.Finished(hr); return; } break; } case WAIT_OBJECT_0 + 1: observer.OnProgress(GetProgress()); break; default: break; } } observer.Finished(GetResult()); } private: static HANDLE CreateSection(LPCWSTR sectionName) { return ::CreateFileMapping (INVALID_HANDLE_VALUE, NULL, // Security attributes PAGE_READWRITE, 0, // high-order DWORD of maximum size sizeof(MmioDataStructure), // Low-order DWORD of maximum size. sectionName); } static HANDLE CreateEvent(LPCWSTR eventName) { return ::CreateEvent(NULL, FALSE, FALSE, eventName); } };
链接项派生自同一基类。
// This class is used by the chainee. class MmioChainee : protected MmioChainerBase { public: MmioChainee(LPCWSTR sectionName) : MmioChainerBase(OpenSection(sectionName), OpenEvent(GetEventName(sectionName))) { } virtual ~MmioChainee() { } private: static HANDLE OpenSection(LPCWSTR sectionName) { return ::OpenFileMapping(FILE_MAP_WRITE, // Read/write access. FALSE, // Do not inherit the name. sectionName); } static HANDLE OpenEvent(LPCWSTR eventName) { return ::OpenEvent (EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, eventName); } static CString GetEventName(LPCWSTR sectionName) { CString cs = L""; HANDLE handle = OpenSection(sectionName); if (NULL == handle) { DWORD dw; dw = GetLastError(); printf("OpenFileMapping fails with last error: %08x",dw); } else { const MmioDataStructure* pData = MapView(handle); if (pData) { cs = pData->m_szEventName; ::UnmapViewOfFile(pData); } ::CloseHandle(handle); } return cs; } };
ChainingdotNet4.cpp
您可从 MmioChainer 类派生并重写相应方法来显示进度信息。 请注意,MmioChainer 已提供一个派生类将调用的阻塞 Run() 方法。 以下代码中的 Server 类会启动指定的安装程序,监视其进度,并返回退出代码。
class Server : public ChainerSample::MmioChainer, public ChainerSample::IProgressObserver { public: // Mmiochainer will create section with given name. Create this section and the event name. // Event is also created by the Mmiochainer, and name is saved in the mapped data structure. Server():ChainerSample::MmioChainer(L"TheSectionName", L"TheEventName") // Customize for your event names. {} bool Launch(const CString& args) { CString cmdline = L"Setup.exe -pipe TheSectionName " + args; // Customize with name and location of setup .exe file that you want to run. STARTUPINFO si = {0}; si.cb = sizeof(si); PROCESS_INFORMATION pi = {0}; // Launch the Setup.exe that installs the .NET Framework 4. BOOL bLaunchedSetup = ::CreateProcess(NULL, cmdline.GetBuffer(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); // If successful if (bLaunchedSetup != 0) { IProgressObserver& observer = dynamic_cast<IProgressObserver&>(*this); Run(pi.hProcess, observer); // To get the return code of the chainee, check exit code // of the chainee process after it exits. DWORD exitCode = 0; // Get the true return code. ::GetExitCodeProcess(pi.hProcess, &exitCode); printf("Exit code: %08X\n ", exitCode); // Get internal result. // If the failure is in an MSI/MSP payload, the internal result refers to the error messages listed at // https://msdn.microsoft.com/en-us/library/aa372835(VS.85).aspx HRESULT hrInternalResult = GetInternalResult(); printf("Internal result: %08X\n",hrInternalResult); ::CloseHandle(pi.hThread); ::CloseHandle(pi.hProcess); } else { printf("CreateProcess failed"); ReportLastError(); } return (bLaunchedSetup != 0); } private: // IProgressObserver virtual void OnProgress(unsigned char ubProgressSoFar) { printf("Progress: %i\n ", ubProgressSoFar); // Testing: BEGIN - To test Abort behavior, uncomment the following code: //if (ubProgressSoFar > 127) //{ // printf("\rDeliberately Aborting with progress at %i ", ubProgressSoFar); // Abort(); //} // Testing END } virtual void Finished(HRESULT hr) { // This HRESULT is communicated over MMIO and may be different from process // exit code of the chainee Setup.exe. printf("\r\nFinished HRESULT: 0x%08X\r\n", hr); } ... };
进度数据将是一个介于 0 (0%) 与 255 (100%) 之间的无符号 char。 Finished 方法的输出为 HRESULT。
重要事项 .NET Framework 4 可再发行组件通常会写入多条进度消息和一条指示完成的消息(在链接器端)。它还会异步读取来查找 Abort 记录。如果它接收到一条 Abort 记录,则会取消安装,并最终写入一条已完成记录,其中包含 E_ABORT 作为其数据。
典型的服务器会创建随机 MMIO 文件名,创建文件(如上一代码示例的 Server::CreateSection 中所示),并通过使用 CreateProcess 然后使用“-pipe someFileSectionName”开关传递管道名称来启动可再发行组件。 服务器的 OnProgress 和 Finished 方法包含特定于服务器的代码。
IprogressObserver.h
进度观察器会获得进度 (0-255) 通知以及安装已完成通知。
#ifndef IPROGRESSOBSERVER_H #define IPROGRESSOBSERVER_H #include <oaidl.h> namespace ChainerSample { class IProgressObserver { public: virtual void OnProgress(unsigned char) = 0; // 0 - 255: 255 == 100% virtual void Finished(HRESULT) = 0; // Called when operation is complete. }; } #endif
HRESULT 会传递给 Finished 方法。以下代码显示程序的主入口点。
// Main entry point for program. int __cdecl main(int argc, _In_count_(argc) char **_argv) { CString args; if (argc > 1) { args = CString(_argv[1]); } return Server().Launch(args); }
更改可执行文件(在此示例中为 Setup.exe)的路径以指向其正确位置,或者更改代码以确定位置。 您必须以管理员身份运行该代码。
请参见
概念
其他资源
修订记录
日期 |
修订记录 |
原因 |
---|---|---|
2010 年 7 月 |
新增主题。 |
信息补充。 |