// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Tools { using System; using System.Diagnostics; using System.IO.Pipes; using System.Text; using System.Threading; /// /// Runs a bundle with provided command-line. /// public class BundleRunner { /// /// Creates a runner for the provided bundle. /// /// Path to the bundle to run. public BundleRunner(string bundle) { this.Path = bundle; } /// /// Fired when the bundle encounters an error. /// public event EventHandler Error; /// /// Fired when the bundle progress is udpated. /// public event EventHandler Progress; /// /// Gets the path to the bundle to run. /// public string Path { get; private set; } /// /// Runs the bundle with the provided command-line. /// /// Optional command-line to pass to the bundle. /// Exit code from the bundle. public int Run(string commandLine = null) { WaitHandle[] waits = new WaitHandle[] { new ManualResetEvent(false), new ManualResetEvent(false) }; int returnCode = 0; int pid = Process.GetCurrentProcess().Id; string pipeName = String.Concat("bpe_", pid); string pipeSecret = Guid.NewGuid().ToString("N"); using (NamedPipeServerStream pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1)) { using (Process bundleProcess = new Process()) { bundleProcess.StartInfo.FileName = this.Path; bundleProcess.StartInfo.Arguments = String.Format("{0} -burn.embedded {1} {2} {3}", commandLine ?? String.Empty, pipeName, pipeSecret, pid); bundleProcess.StartInfo.UseShellExecute = false; bundleProcess.StartInfo.CreateNoWindow = true; bundleProcess.Start(); Connect(pipe, pipeSecret, pid, bundleProcess.Id); PumpMessages(pipe); bundleProcess.WaitForExit(); returnCode = bundleProcess.ExitCode; } } return returnCode; } /// /// Called when bundle encounters an error. /// /// Additional arguments for this event. protected virtual void OnError(BundleErrorEventArgs e) { EventHandler handler = this.Error; if (handler != null) { handler(this, e); } } /// /// Called when bundle progress is updated. /// /// Additional arguments for this event. protected virtual void OnProgress(BundleProgressEventArgs e) { EventHandler handler = this.Progress; if (handler != null) { handler(this, e); } } private void Connect(NamedPipeServerStream pipe, string pipeSecret, int pid, int childPid) { pipe.WaitForConnection(); WriteSecretToPipe(pipe, pipeSecret); WriteNumberToPipe(pipe, (uint)pid); uint ack = ReadNumberFromPipe(pipe); // This is not true when bundle is run under a debugger //if (ack != childPid) //{ // throw new ApplicationException("Incorrect child process."); //} } private void PumpMessages(NamedPipeServerStream pipe) { uint messageId; while (TryReadNumberFromPipe(pipe, out messageId)) { uint messageSize = ReadNumberFromPipe(pipe); BundleResult result = BundleResult.None; switch (messageId) { case 1: //error result = ProcessErrorMessage(pipe); break; case 2: // progress result = ProcessProgressMessage(pipe); break; default: // unknown message, do nothing. break; } CompleteMessage(pipe, result); } } private BundleResult ProcessErrorMessage(NamedPipeServerStream pipe) { BundleErrorEventArgs e = new BundleErrorEventArgs(); e.Code = (int)ReadNumberFromPipe(pipe); e.Message = ReadStringFromPipe(pipe); e.UIHint = (int)ReadNumberFromPipe(pipe); this.OnError(e); return e.Result; } private BundleResult ProcessProgressMessage(NamedPipeServerStream pipe) { ReadNumberFromPipe(pipe); // eat the first progress number because it is always zero. BundleProgressEventArgs e = new BundleProgressEventArgs(); e.Progress = (int)ReadNumberFromPipe(pipe); this.OnProgress(e); return e.Result; } private void CompleteMessage(NamedPipeServerStream pipe, BundleResult result) { uint complete = 0xF0000002; WriteNumberToPipe(pipe, complete); WriteNumberToPipe(pipe, 4); // size of message data WriteNumberToPipe(pipe, (uint)result); } private uint ReadNumberFromPipe(NamedPipeServerStream pipe) { byte[] buffer = new byte[4]; pipe.Read(buffer, 0, buffer.Length); return BitConverter.ToUInt32(buffer, 0); } private string ReadStringFromPipe(NamedPipeServerStream pipe) { uint length = ReadNumberFromPipe(pipe); byte[] buffer = new byte[length * 2]; pipe.Read(buffer, 0, buffer.Length); return Encoding.Unicode.GetString(buffer); } private bool TryReadNumberFromPipe(NamedPipeServerStream pipe, out uint value) { value = ReadNumberFromPipe(pipe); // reading will not block and return zero if pipe is not connected. return pipe.IsConnected; } private void WriteNumberToPipe(NamedPipeServerStream pipe, uint value) { byte[] buffer = BitConverter.GetBytes(value); pipe.Write(buffer, 0, buffer.Length); } private void WriteSecretToPipe(NamedPipeServerStream pipe, string secret) { byte[] buffer = Encoding.Unicode.GetBytes(secret); WriteNumberToPipe(pipe, (uint)buffer.Length); pipe.Write(buffer, 0, buffer.Length); } } }