방법: .NET Framework 4 설치 관리자에서 진행률 가져오기

업데이트: 2010년 7월

The .NET Framework 버전 4는 재배포 가능 런타임입니다. .NET Framework 4 설치 프로세스를 응용 프로그램 설치에 필수 구성 요소로 포함(연결)할 수 있습니다. 사용자 지정되거나 통합된 설치 환경을 제공하려는 경우 설치 진행률을 자체적으로 표시하면서 .NET Framework 4 설치 프로세스를 자동으로 시작하고 추적할 수 있습니다. 자동 추적을 활성화하려면 설치 프로세스(감시자 또는 체이너)가 읽을 수 있는 MMIO(메모리 매핑된 I/O) 세그먼트에 .NET Framework 4 설치 프로세스(감시 대상인 체이니)가 진행률 메시지를 기록할 수 있습니다. 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에서 255로 증가하는 숫자로 MMIO 섹션에 다시 보내져 기록됩니다. 255가 기록되어 체이니가 종료되면 설치가 완료됩니다.

  2. 종료 코드. 명령에서 .NET Framework 4 재배포 가능 프로그램을 호출하는 다음 종료 코드(이전 예제 참조)는 설치 프로그램의 성공 여부를 나타냅니다.

    • 0 - 설치가 성공적으로 완료되었습니다.

    • 3010 – 설치가 완료되었습니다. 다시 부팅해야 합니다.

    • 1642 – 설치가 취소되었습니다.

    • 기타 모든 코드 - 설치하는 동안 오류가 발생했습니다. 자세한 내용은 %temp%에 만들어진 로그 파일을 참조하십시오.

  3. 설치 취소. MMIO 섹션에서 m_downloadAbort 및 m_ installAbort 플래그를 설정하는 Abort 메서드를 사용하여 언제든지 설치를 취소할 수 있습니다. 그러면 .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.
        };
    
  • 다음 데이터 구조는 체이너를 구현하기 위한 클래스 구조입니다. .NET Framework 4 재배포 가능 패키지를 연결하려면 MmioChainer 클래스에서 서버 클래스를 파생시켜야 합니다. 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)의 경로를 변경하거나 파악 가능하도록 코드를 변경합니다. 코드는 관리자 권한으로 실행해야 합니다.

참고 항목

개념

.NET Framework 및 응용 프로그램 배포

기타 리소스

개발자를 위한 .NET Framework 배포 가이드

변경 기록

날짜

변경 내용

이유

2010년 7월

항목이 추가되었습니다.

향상된 기능 관련 정보