Gewusst wie: Abrufen des Status vom Installationsprogramm für .NET Framework 4

Bei .NET Framework, Version 4 handelt es sich um eine verteilbare Laufzeit. Sie können den .NET Framework 4-Installationsvorgang als erforderliche Komponente in das Setup der Anwendung einschließen (verketten). Für eine angepasste oder einheitliche Setupumgebung können Sie festlegen, dass der .NET Framework 4-Setupvorgang automatisch gestartet und nachverfolgt wird, während Ihre eigene Ansicht des Setupstatus angezeigt wird. Um die automatische Nachverfolgung zu aktivieren, kann der .NET Framework 4-Installationsvorgang (der Chainee, der beobachtet werden kann) eine Statusmeldung in ein MMIO-Segment (Memory-mapped I/O, E/A mit Speicherabbildern) schreiben, das vom Installationsvorgang (dem Monitor oder Chainer) gelesen werden kann. Sie können den .NET Framework 4-Installationsvorgang abbrechen, indem Sie eine Abort-Meldung in das MMIO-Segment schreiben.

  1. Aufruf Um den .NET Framework 4-Installationsvorgang aufzurufen und Statusinformationen im MMIO-Abschnitt zu empfangen, muss vom Setupprogramm wie folgt vorgegangen werden:

    1. Aufruf des verteilbaren Programms von .NET Framework 4; Beispiel:

      dotnetfx40x86.exe /q /pipe <section name>
      

      Dabei ist section name ein beliebiger Name, mit dem Sie die Anwendung identifizieren. Der Chainee liest und schreibt asynchron in den MMIO-Abschnitt, deshalb kann es praktisch für Sie sein, Ereignisse und Meldungen während dieser Zeit zu verwenden. Im Beispiel wird der Chainee von einem Konstruktor erstellt, der sowohl den MMIO-Abschnitt (YourSection) zuordnet als auch ein Ereignis (YourEvent) definiert. Ersetzen Sie diese Namen durch Namen, die für das Setupprogramm eindeutig sind.

    2. Lesen aus dem MMIO-Abschnitt. In .NET Framework 4 laufen die Download- und Installationsvorgänge gleichzeitig ab: Eine Komponente von .NET Framework 4 wird möglicherweise installiert, während eine andere heruntergeladen wird. Als Ergebnis wird der Status als von 1 bis 255 zunehmende Zahl an den MMIO-Abschnitt zurückgesendet (das heißt, in den Abschnitt geschrieben). Wenn 255 geschrieben und der Chainee beendet wird, ist die Installation abgeschlossen.

  2. Exitcodes. Mit den folgenden Exitcodes im Befehl zum Aufrufen des verteilbaren Programms von .NET Framework 4 (siehe vorheriges Beispiel) wird angegeben, ob Setup erfolgreich war oder Fehler aufwies:

    • 0 - erfolgreich abgeschlossenes Setup.

    • 3010 – Setup wurde erfolgreich abgeschlossen; Neustart ist erforderlich.

    • 1642 – Setup wurde abgebrochen.

    • Alle anderen Codes - in Setup sind Fehler aufgetreten; Details dazu finden Sie in den unter "%temp%" erstellten Protokolldateien.

  3. Abbrechen von Setup. Sie können Setup jederzeit abbrechen, indem Sie mit der Abort-Methode das m_downloadAbort-Flag und das m_ installAbort-Flag im MMIO-Abschnitt festlegen. Dadurch wird der .NET Framework 4-Setupvorgang beendet.

Chainer-Beispiel

Das folgende Beispiel wird automatisch gestartet. Dabei wird der .NET Framework 4-Setupvorgang nachverfolgt und der Status angezeigt.

WarnhinweisVorsicht

Sie müssen das Beispiel als Administrator ausführen.

Sie können die vollständige Visual Studio-Lösung für den Chain Installer für .NET Framework 4 von der Microsoft Code Gallery herunterladen.

In den folgenden Abschnitten werden die entscheidenden Dateien in diesem Beispiel beschrieben.

MmIoChainer.h

  • Die Datei "MmIoChainer.h" enthält die Datenstrukturdefinition und die Basisklasse, von der die chainer-Klasse abgeleitet werden soll. Die MMIO-Datenstruktur besteht aus dem folgenden Code.

    // 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.
        };
    
  • Der Datenstruktur folgt die Klassenstruktur zum Implementieren eines Chainers. Sie leiten die Serverklasse von der MmioChainer-Klasse ab, um den .NET Framework 4 verteilbar zu verketten. Die MmioChainerBase-Klasse wird sowohl vom Chainer als auch dem Chainee verwendet. Im folgenden Code wurden Methoden und Members bearbeitet, um dieses Beispiel kurz zu halten.

    // 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
            { 
    ...
            }
    
    ...
        };
    
  • Der Chainer wird wie folgt implementiert.

    // 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);
            }
        };
    
  • Der Chainee leitet sich von der gleichen Basisklasse ab.

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

  • Sie können die Ableitung von der MmioChainer-Klasse ausführen und die entsprechenden Methoden außer Kraft setzen, um Statusinformationen anzuzeigen. Beachten Sie, dass MmioChainer bereits eine Run()-Sperrmethode bereitstellt, die von der abgeleiteten Klasse aufgerufen wird. Die Server-Klasse im folgenden Code startet das angegebene Setupprogramm, überwacht seinen Status, und gibt einen Exitcode zurück.

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

    Statusdaten sind ein char ohne Vorzeichen zwischen 0 (0%) und 255 (100%). Die Ausgabe aus der Finished-Methode ist ein HRESULT.

    Wichtiger HinweisWichtig

    Das verteilbare Programm von .NET Framework 4 schreibt in der Regel viele Statusmeldungen und eine einzelne Meldung, die den Abschluss angibt (auf Chainer-Seite).Es liest außerdem asynchron und sucht nach Abort-Datensätzen.Beim Empfang eines Abort-Datensatz wird die Installation abgebrochen und ein fertiger Datensatz wird schließlich mit E_ABORT als Daten geschrieben.

    Ein typischer Server erstellt einen zufälligen MMIO-Dateinamen, erstellt die Datei (wie im vorherigen Codebeispiel gezeigt, in Server::CreateSection), und startet das verteilbare Programm mit CreateProcess. Dabei wird der Pipename mit dem Schalter "-pipe someFileSectionName" übergeben. Die Methoden OnProgress und Finished des Servers enthalten serverspezifischen Code.

IprogressObserver.h

  • Der Statusbeobachter wird zum Status (0-255) und nach Abschluss der Installation benachrichtigt.

    #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
    
  • Das HRESULT wird an die Finished-Methode übergeben. Im folgenden Code wird der Haupteinstiegspunkt zum Programm veranschaulicht.

    // 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);
    }
    
  • Ändern Sie den Pfad der ausführbaren Datei (Setup.exe im Beispiel), sodass er auf seinen richtigen Speicherort zeigt, oder ändern Sie den Code, um ihn zu ermitteln. Sie müssen den Code als Administrator ausführen.

Siehe auch

Konzepte

Bereitstellen von .NET Framework und Anwendungen

Weitere Ressourcen

Handbuch für die Bereitstellung von .NET Framework für Entwickler

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

Juli 2010

Thema hinzugefügt.

Informationsergänzung.