方法: .NET Framework 4 インストーラーの進行状況を表示する
更新 : 2010 年 7 月
.NET Framework Version 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 つのコンポーネントがインストールを行っている間に、別のコンポーネントがダウンロードを行います。 その結果、進行状況は、1 から 255 まで増加する値として MMIO セクションに送り返されます (書き込まれます)。 255 が書き込まれて、チェーン対象プロセスが終了すると、インストールは完了します。
終了コード。 .NET Framework 4 再配布可能プログラムを呼び出すためのコマンド (前の例を参照) からの以下の終了コードは、セットアップが成功または失敗したことを示します。
0: セットアップは、正常に完了しました。
3010: セットアップは正常に完了しました。再起動が必要です。
1642: セットアップは取り消されました。
他のすべてのコード: セットアップでエラーが発生しました。詳細については、%temp% に作成されるログ ファイルを調べてください。
セットアップの取り消し。 Abort メソッドを使用して MMIO セクションの m_downloadAbort フラグと m_ installAbort フラグを設定することで、いつでもセットアップを取り消すことができます。 これにより、.NET Framework 4 のセットアップ プロセスが停止します。
チェーン元のサンプル
次に示す例では、.NET Framework 4 セットアップ プロセスをサイレントで起動および追跡し、進行状況を表示しています。
注意 |
---|
例の実行は、管理者として行う必要があります。 |
Microsoft Code Gallery から、Chain Installer for .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 の再頒布可能プログラムは、通常、多数の進行状況メッセージと、(チェーン元側で) 完了を示す 1 つのメッセージを書き込みます。また、非同期に読み取りを行って、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
Finished メソッドには HRESULT が渡されます。次のコードでは、プログラムへのメイン エントリ ポイントを示します。
// 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 月 |
トピックを追加 |
情報の拡充 |