Compartir a través de


Cómo: Obtener el progreso del instalador de .NET Framework 4

.NET Framework versión 4 es un runtime redistribuible. Puede incluir (encadenar) el proceso de instalación de .NET Framework 4 como un componente necesario previo de la instalación de la aplicación. Para presentar al usuario una experiencia de instalación personalizada o unificada, tal vez desee iniciar silenciosamente el proceso de instalación de .NET Framework 4 y realizar su seguimiento mientras se muestra su propia vista del progreso de la instalación. Para habilitar el seguimiento silencioso, el proceso de instalación de .NET Framework 4 (el proceso encadenado que se va a observar) puede escribir el mensaje de progreso en un segmento de E/S asignado a la memoria (MMIO) que el proceso de instalación (el proceso observador o encadenador) pueda leer. Puede cancelar el proceso de instalación de .NET Framework 4 escribiendo un mensaje Abort en el segmento MMIO.

  1. Invocación. Para llamar al proceso de instalación de .NET Framework 4 y recibir información sobre el progreso de la sección MMIO, el programa de instalación debe hacer lo siguiente:

    1. Llamar al programa redistribuible de .NET Framework 4; por ejemplo:

      dotnetfx40x86.exe /q /pipe <section name>
      

      donde section name es cualquier nombre que desee utilizar para identificar la aplicación. El proceso encadenado realiza operaciones asincrónicas de lectura y escritura en la sección MMIO, de modo que tal vez encuentre conveniente usar eventos y mensajes durante ese periodo. En el ejemplo, el proceso encadenado lo crea un constructor que asigna la sección MMIO (YourSection) y define un evento (YourEvent). Sustituya esos nombres por nombres que sean únicos en su programa de instalación.

    2. Leer la sección MMIO. En .NET Framework 4, las operaciones de descarga e instalación son simultáneas: un componente de .NET Framework 4 puede estar instalándose mientras otro se descarga. Por tanto, el progreso se devuelve (es decir, se escribe) en la sección MMIO como un número que va subiendo desde 1 hasta 255. Cuando se escribe 255 y el proceso encadenado sale, la instalación se completa.

  2. Códigos de salida. Los códigos de salida siguientes que proceden del comando que llama al programa redistribuible de .NET Framework 4 (vea el ejemplo anterior) indican si la instalación se ha realizado correctamente o no:

    • 0: la instalación se completó correctamente.

    • 3010: la instalación se completó correctamente. Es necesario reiniciar.

    • 1642: se ha cancelado la instalación.

    • Todos los demás códigos: se han producido errores en la instalación. Consulte los archivos de registro creados en %temp% para ver los detalles.

  3. Cancelar la instalación. Puede cancelar la instalación en cualquier momento utilizando el método Abort para establecer las marcas m_downloadAbort y m_ installAbort en la sección MMIO. Esto detendrá el proceso de instalación de .NET Framework 4.

Ejemplo de encadenador

En el ejemplo siguiente se inicia el proceso de instalación de .NET Framework 4 silenciosamente y se realiza el seguimiento mientras se muestra el progreso.

Nota de precauciónPrecaución

Debe ejecutar el ejemplo como administrador.

Puede descargar la solución de Visual completa para Chain Installer for .NET Framework 4 en el sitio web de Microsoft Code Gallery.

En las siguientes secciones se describen los principales archivos de este ejemplo.

MmIoChainer.h

  • El archivo MmIoChainer.h contiene la definición de la estructura de datos y la clase base de la que debería derivarse la clase del encadenador. La estructura de datos MMIO se compone del código siguiente.

    // 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.
        };
    
  • Tras la estructura de datos, se encuentra la estructura de clases de la implementación de un encadenador. La clase de servidor se deriva de la clase MmioChainer que va a encadenar el programa redistribuible de .NET Framework 4. La clase MmioChainerBase la utilizan tanto el encadenador como el proceso encadenado. En el código siguiente, se han modificado los métodos y miembros para abreviar el ejemplo.

    // 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
            { 
    ...
            }
    
    ...
        };
    
  • El encadenador se implementa como sigue.

    // 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);
            }
        };
    
  • El proceso encadenado se deriva de la misma clase base.

    // 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

  • Puede derivar de la clase MmioChainer e invalidar los métodos adecuados para mostrar la información de progreso. Observe que MmioChainer ya proporciona un método Run() de bloqueo al que llamará la clase derivada. La clase Server del código siguiente inicia el programa de instalación especificado, supervisa su progreso y devuelve un código de salida.

    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);
        }
    ...
    };
    

    Los datos de progreso serán de tipo char sin signo y estarán comprendidos entre 0 (0%) y 255 (100%). La salida del método Finished es un valor HRESULT.

    Nota importanteImportante

    El programa redistribuible de .NET Framework 4 normalmente escribe muchos mensajes de progreso y un solo mensaje que indica la finalización del proceso (en el lado del encadenador).También realiza lecturas asincrónicas en busca de registros Abort.Si recibe un registro Abort, cancela la instalación y (finalmente) escribe un registro de finalización usando E_ABORT como datos.

    Un servidor normal crea un nombre de archivo MMIO aleatorio, crea el archivo (tal y como se muestra en el ejemplo de código anterior, en Server::CreateSection) e inicia el programa redistribuible usando CreateProcess, que pasa el nombre de la canalización con el modificador "-pipe someFileSectionName". Los métodos OnProgress y Finished del servidor contienen código específico del servidor.

IprogressObserver.h

  • El observador del progreso está al tanto del progreso (0-255) y del momento en que finaliza la instalación.

    #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
    
  • El valor HRESULT se pasa al método Finished. En el código siguiente se muestra el punto de entrada principal del programa.

    // 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);
    }
    
  • Cambie la ruta de acceso del ejecutable (Setup.exe en el ejemplo) para señalar a la ubicación correcta o cambie el código para que se deduzca. Debe ejecutar el código como administrador.

Vea también

Conceptos

Implementar .NET Framework y aplicaciones

Otros recursos

Guía de implementación de .NET Framework para desarrolladores

Historial de cambios

Fecha

Historial

Motivo

Julio de 2010

Se ha agregado un tema.

Mejora de la información.