none
How to create a "pipe" for receiving progress information from background installs? RRS feed

  • Question

  • Hello,

    The .NET 4.0 Web installer has in its command-line arsenal a switch named "/pipe" which takes, as argument, the name of a "pipe" to which progress information should be reported. Through trial and error (and a few monitoring tools), I have discovered that this "pipe" is actually a shared memory section. I started out with a 64K section and observed that the installer does a MapViewOfFile call with a length of 1060 bytes, so I reduced the section to that length. When I poll it, sure enough the installer writes various binary values to obscure offsets.

    Does anyone know of documentation of the data structure and communication involved for this switch?

    Thanks,

    Jonathan Gilbert

    Wednesday, April 21, 2010 9:33 PM

All replies

  • I have done some investigation into this and perhaps uncovered enough to use. :-)

    Firstly, one of the things I had noticed was an attempt to open a named event called "" (empty string). With some trial and error, I discovered that the setup engine expects to see a Unicode string at offset 0x21A into the mapped view. This string turns out to be the name of an event that it signals every time the status information changes.

    Armed with that, I wrote a program to write out a log file of every change posted to the progress information. Every time the event is signalled, it wrote out the 1060 bytes of shared memory along with a timestamp. I also had it time out after 2 seconds of waiting and write a log entry in that situation as well. I ran through a complete .NET 4.0 installation in a VM and this produced a ~2 MB log file with around 1800 events in it. Looking through the events, I have noticed the following:

    - There appear to be two status values at offsets 0x218 and 0x219. The first one runs up from byte value 0 to 255, and shortly before it reaches 255, the second one starts changing, apparently independently. Based on the existence of the command-line option "/serialdownload", I infer that these are "download" and "install" progresses.

    - As soon as the download progress becomes 255, the first byte (offset 0) gets set to 1, and then much later on, once the install progress becomes 255, the second byte (offset 1) gets set to 1. Thus I infer that offsets 0 and 1 are "download finished" and "install finished", respectively.

    - Also at the time the download progress becomes 255, a byte 0x20 is written at offset 0x10. The number of bytes between offset 0x10 and the download progress at offset 0x218 is exactly 520. Given that the event name is Unicode, if we assume that the 0x20 -- which could be a space character -- is part of a Unicode text field, if it used up all 520 bytes, it would be 260 characters long -- exactly MAX_PATH. Since the installer writes nothing to these bytes for the duration of the installation, I infer that this is a text field of length MAX_PATH. It only ever contains two strings: "" (empty) and " " (a single space character), so I don't know what its purpose is.

    - When the installation completes, four bytes at offset 8 are written. If these are interpreted as a 32-bit integer, they are 0x80070BC2, which looks suspiciously like Windows Update error "ErrorControlRebootRequired". After the installation is complete, in the system event log, I find an entry saying "The Windows Installer initiated a system restart to complete or continue the configuration of 'Microsoft .NET Framework 4 Client Profile'". Well, there was no actual reboot, but that's probably because I passed /norestart on the command-line. :-) The four bytes at offset 0xC -- in between this code and the start of the aforementioned text field -- are zero for the entire time, but since there are apparently two "finished" flags and two "progress" fields, it seems plausible that there are two "status code" fields as well. However, the status that is being set would end up being the "Download" status by that interpretation, so I don't think that can be right. That leaves the DWORD at 0xC undecided.

    - After the final event is received, after two seconds when the wait timed out, subsequent reads of the mapped view showed the value 1 in offsets 2 and 3. I'm not sure why no event is raised to indicate this, but since it is the very last thing the setup engine does, they could be flags indicating that the setup engine is no longer connected and no further events will arrive -- "all done here". I'm not sure why there are two of them -- related to the fact that there are two "finished" flags, two "status code" fields and two "progress" fields perhaps?

    - There are 4 bytes after the completion flags at the beginning that never get written to. Not sure what these are for.

    - If we assume that the field containing the named event is MAX_PATH characters long (the documentation for the CreateEvent API function states that the name argument has a maximum length of MAX_PATH characters), that leaves just two bytes left over. Nothing is ever assigned to them. Either the developers added an extra byte to the name length "just in case" (or possibly to fit a null terminator? but why not for the other text field?), or these are fields that simply didn't get used in my test. I'm going to go with the former interpretation, but it could be wrong.

    Putting all this together, the following structure seems to be a reasonably accurate interpretation of the content of the mapped file view that gets populated when you pass its name into the .NET 4.0 web installer using the /pipe command-line argument:

    #pragma pack(push)
    #pragma pack(1)
    
    struct InstallerPipeData
    {
     bool   DownloadFinished;
     bool   InstallFinished;
     bool   DownloadEngineDisconnected;
     bool   InstallEngineDisconnected;
     int   Unknown1;
     HRESULT  InstallationStatus;
     int   Unknown2;
     wchar_t  UnknownTextField[MAX_PATH];
     unsigned char DownloadProgress;
     unsigned char InstallProgress;
     wchar_t  NamedEventName[MAX_PATH + 1];
    };
    
    #pragma pack(pop)

    And here's an equivalent structure definition for C# marshalling:

     

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct InstallerPipeData
    {
     [MarshalAs(UnmanagedType.U1)]
     public bool DownloadFinished;
     [MarshalAs(UnmanagedType.U1)]
     public bool InstallFinished;
     [MarshalAs(UnmanagedType.U1)]
     public bool DownloadEngineDisconnected;
     [MarshalAs(UnmanagedType.U1)]
     public bool InstallEngineDisconnected;
     public int Unknown1;
     public int InstallationStatus;
     public int Unknown2;
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260 /* MAX_PATH */)]
     public string UnknownTextField;
     public byte DownloadProgress;
     public byte InstallProgress;
     [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261 /* MAX_PATH + 1 */)]
     public string NamedEventName;
    }
    

     

    If anyone reading this knows that any part of this is glaringly wrong, I'd be much obliged to hear about it. :-)

    Thanks and enjoy,

    Jonathan Gilbert

    Thursday, April 22, 2010 7:36 PM
  • Old thread but here's the official information from MSDN:

    http://msdn.microsoft.com/en-us/library/ff859983.aspx 

     

    No C# example so your code is very useful Jonathan

     

    Cheers,

    Aaron

    Monday, August 15, 2011 11:07 PM