方法: .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 セットアップ プロセスを取り消すことができます。

  1. 呼び出し。 .NET Framework 4 セットアップ プロセスを呼び出して、MMIO セクションから進行状況情報を受け取るには、セットアップ プログラムで以下の処理を行う必要があります。

    1. .NET Framework 4 再頒布可能プログラムを呼び出します。次はその例です。

      dotnetfx40x86.exe /q /pipe <section name>
      

      section name は、アプリケーションの識別に使用する名前です。 チェーンされたプロセスは、MMIO セクションに対する読み書きを非同期に行うので、その間、イベントおよびメッセージの使用が役立つ場合があります。 例では、チェーン対象プロセスを作成するコンストラクターで、MMIO セクションの割り当て (YourSection) とイベントの定義 (YourEvent) の両方を行っています。 これらの名前は、実際のセットアップ プログラムの固有な名前に置き換えてください。

    2. MMIO セクションから読み取ります。 .NET Framework 4 では、ダウンロード操作とインストール操作は同時に行われます。.NET Framework 4 の 1 つのコンポーネントがインストールを行っている間に、別のコンポーネントがダウンロードを行います。 その結果、進行状況は、1 から 255 まで増加する値として MMIO セクションに送り返されます (書き込まれます)。 255 が書き込まれて、チェーン対象プロセスが終了すると、インストールは完了します。

  2. 終了コード。 .NET Framework 4 再配布可能プログラムを呼び出すためのコマンド (前の例を参照) からの以下の終了コードは、セットアップが成功または失敗したことを示します。

    • 0: セットアップは、正常に完了しました。

    • 3010: セットアップは正常に完了しました。再起動が必要です。

    • 1642: セットアップは取り消されました。

    • 他のすべてのコード: セットアップでエラーが発生しました。詳細については、%temp% に作成されるログ ファイルを調べてください。

  3. セットアップの取り消し。 Abort メソッドを使用して MMIO セクションの m_downloadAbort フラグと m_ installAbort フラグを設定することで、いつでもセットアップを取り消すことができます。 これにより、.NET Framework 4 のセットアップ プロセスが停止します。

チェーン元のサンプル

次に示す例では、.NET Framework 4 セットアップ プロセスをサイレントで起動および追跡し、進行状況を表示しています。

Caution メモ注意

例の実行は、管理者として行う必要があります。

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) のパスを変更するか、パスを特定できるようにコードを変更してください。 コードの実行は、管理者として行う必要があります。

参照

概念

.NET Framework およびアプリケーションの配置

その他の技術情報

.NET Framework 配置ガイド (開発者向け)

履歴の変更

日付

履歴

理由

2010 年 7 月

トピックを追加

情報の拡充