HOW TO:取得 .NET Framework 4 安裝程式的進度
更新:2010 年 7 月
.NET Framework 4 版 為可轉散發執行階段。 您可以包括 (鏈結) .NET Framework 4 安裝程序當做應用程式安裝的必要元件。 若要呈現自訂或統一的安裝經驗,您可能會想要以無訊息模式啟動並追蹤 .NET Framework 4 安裝程序,同時顯示您自己安裝進度的檢視。 為了啟用無訊息模式追蹤,.NET Framework 4 安裝程序 (可被監看的 Chainee) 可以將進度訊息寫入您的安裝程序 (監看者或 Chainer) 可以讀取的記憶體對應 I/O (MMIO) 區段。 您可以將 Abort 訊息寫入 MMIO 區段來取消 .NET Framework 4 安裝程序。
引動過程。 若要呼叫 .NET Framework 4 安裝程序並從 MMIO 區段接收進度資訊,您的安裝程式必須執行以下動作:
呼叫 .NET Framework 4 可轉散發程式,例如:
dotnetfx40x86.exe /q /pipe <section name>
其中 section name 是您想要用來識別應用程式的任何名稱。 Chainee 會以非同步方式讀取和寫入 MMIO 區段,所以在這段期間使用事件和訊息可能會很方便。 在此範例中,Chainee 是由配置 MMIO 區段 (YourSection) 和定義事件 (YourEvent) 的建構函式所建立。 請使用對您的安裝程式獨一無二的名稱來取代這些名稱。
從 MMIO 區段讀取。 在 .NET Framework 4 中,下載和安裝作業是同時的:其中一個 .NET Framework 4 元件可能在安裝中,而另一個元件則在下載中。 因此,進度會以 1 到 255 遞增數字的形式傳回 (也就是寫入) MMIO 區段。 當寫入 255 而且 Chainee 結束時,表示安裝已完成。
結束代碼。 以下來自呼叫 .NET Framework 4 可轉散發程式 (請參閱上一個範例) 的命令所送出的結束代碼會指出安裝成功或失敗:
0 - 已成功完成安裝。
3010 - 已成功完成安裝,但是需要重新開機。
1642 - 已取消安裝。
所有其他代碼 - 安裝遇到錯誤,請檢查 %temp% 中所建立的記錄檔以找出詳細資訊。
取消安裝。 您可以隨時取消安裝,其方式是使用 Abort 方法在 MMIO 區段中設定 m_downloadAbort 和 m_ installAbort 旗標。 這樣會停止 .NET Framework 4 安裝程序。
Chainer 範例
下列範例會以無訊息模式啟動及追蹤 .NET Framework 4 安裝程序,同時也會顯示進度。
警告 |
---|
您必須以系統管理員身分執行此範例。 |
您可以從 Microsoft Code Gallery 下載 .NET Framework 4 鏈結安裝程式 (英文) 所適用的完整 Visual Studio 方案。
下列章節描述此範例中的重要檔案。
MmIoChainer.h
MmIoChainer.h 檔案包含 Chainer 類別應該衍生自的資料結構定義和基底類別。 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. };
在此資料結構後面為實作 Chainer 的類別結構。 您會從 MmioChainer 類別衍生您的伺服器類別,以便鏈結 .NET Framework 4 可轉散發程式。 MmioChainerBase 類別會由 Chainer 和 Chainee 兩者使用。 在下列程式碼中,方法和成員已經過編輯,好讓這個範例維持很短的長度。
// 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 { ... } ... };
Chainer 會以下列方式實作。
// 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); } };
Chainee 衍生自相同的基底類別。
// 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 可轉散發程式通常會寫入許多進度訊息以及指示完成的單一訊息 (在 Chainer 端)。它也會以非同步方式讀取,以尋找 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 月 |
加入主題。 |
資訊加強。 |