From af10c45d7b3a44af0b461a557847fe03263dcc10 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 17:06:54 -0700 Subject: Move burn into burn --- .../ManagedBundleRunner/BundleErrorEventArgs.cs | 33 ++++ .../ManagedBundleRunner/BundleProgressEventArgs.cs | 23 +++ .../burn/ManagedBundleRunner/BundleResult.cs | 24 +++ .../burn/ManagedBundleRunner/BundleRunner.cs | 212 +++++++++++++++++++++ src/samples/burn/runbundle/AssemblyInfo.cs | 12 ++ src/samples/burn/runbundle/Program.cs | 47 +++++ 6 files changed, 351 insertions(+) create mode 100644 src/samples/burn/ManagedBundleRunner/BundleErrorEventArgs.cs create mode 100644 src/samples/burn/ManagedBundleRunner/BundleProgressEventArgs.cs create mode 100644 src/samples/burn/ManagedBundleRunner/BundleResult.cs create mode 100644 src/samples/burn/ManagedBundleRunner/BundleRunner.cs create mode 100644 src/samples/burn/runbundle/AssemblyInfo.cs create mode 100644 src/samples/burn/runbundle/Program.cs (limited to 'src/samples') diff --git a/src/samples/burn/ManagedBundleRunner/BundleErrorEventArgs.cs b/src/samples/burn/ManagedBundleRunner/BundleErrorEventArgs.cs new file mode 100644 index 00000000..2c377326 --- /dev/null +++ b/src/samples/burn/ManagedBundleRunner/BundleErrorEventArgs.cs @@ -0,0 +1,33 @@ +// 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 Wix.Samples +{ + using System; + + /// + /// Arguments provided when bundle encounters an error. + /// + [Serializable] + public class BundleErrorEventArgs : EventArgs + { + /// + /// Gets the error code. + /// + public int Code { get; set; } + + /// + /// Gets the error message. + /// + public string Message { get; set; } + + /// + /// Gets the recommended display flags for an error dialog. + /// + public int UIHint { get; set; } + + /// + /// Gets or sets the of the operation. This is passed back to the bundle. + /// + public BundleResult Result { get; set; } + } +} diff --git a/src/samples/burn/ManagedBundleRunner/BundleProgressEventArgs.cs b/src/samples/burn/ManagedBundleRunner/BundleProgressEventArgs.cs new file mode 100644 index 00000000..ed42b5b1 --- /dev/null +++ b/src/samples/burn/ManagedBundleRunner/BundleProgressEventArgs.cs @@ -0,0 +1,23 @@ +// 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 Wix.Samples +{ + using System; + + /// + /// Arguments provided when bundle progress is updated. + /// + [Serializable] + public class BundleProgressEventArgs : EventArgs + { + /// + /// Gets the percentage from 0 to 100 completed for a bundle. + /// + public int Progress { get; set; } + + /// + /// Gets or sets the of the operation. This is passed back to the bundle. + /// + public BundleResult Result { get; set; } + } +} diff --git a/src/samples/burn/ManagedBundleRunner/BundleResult.cs b/src/samples/burn/ManagedBundleRunner/BundleResult.cs new file mode 100644 index 00000000..c32644f4 --- /dev/null +++ b/src/samples/burn/ManagedBundleRunner/BundleResult.cs @@ -0,0 +1,24 @@ +// 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 Wix.Samples +{ + /// + /// Result codes. + /// + public enum BundleResult + { + Error = -1, + None, + Ok, + Cancel, + Abort, + Retry, + Ignore, + Yes, + No, + Close, + Help, + TryAgain, + Continue, + } +} diff --git a/src/samples/burn/ManagedBundleRunner/BundleRunner.cs b/src/samples/burn/ManagedBundleRunner/BundleRunner.cs new file mode 100644 index 00000000..e2089787 --- /dev/null +++ b/src/samples/burn/ManagedBundleRunner/BundleRunner.cs @@ -0,0 +1,212 @@ +// 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 Wix.Samples +{ + 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); + } + } +} diff --git a/src/samples/burn/runbundle/AssemblyInfo.cs b/src/samples/burn/runbundle/AssemblyInfo.cs new file mode 100644 index 00000000..3a66d5e3 --- /dev/null +++ b/src/samples/burn/runbundle/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// 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. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Executable to demonstrate Bundle Runner Sample")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] diff --git a/src/samples/burn/runbundle/Program.cs b/src/samples/burn/runbundle/Program.cs new file mode 100644 index 00000000..8edca5dc --- /dev/null +++ b/src/samples/burn/runbundle/Program.cs @@ -0,0 +1,47 @@ +// 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 Wix.Samples +{ + using System; + using System.Linq; + using Wix.Samples; + + /// + /// Example executable that installs then immediately uninstalls a bundle showing progress. + /// + class Program + { + static int Main(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Must provide the path to the bundle to install then uninstall."); + return -1; + } + + BundleRunner runner = new BundleRunner(args[0]); + runner.Error += Program.OnError; + runner.Progress += Program.OnProgress; + + Console.WriteLine("Installing: {0}", runner.Path); + int exitCode = runner.Run(String.Join(" ", args.Skip(1).ToArray())); + if (0 == exitCode) + { + Console.WriteLine("\r\nUninstalling: {0}", runner.Path); + exitCode = runner.Run("-uninstall"); + } + + return exitCode; + } + + static void OnError(object sender, BundleErrorEventArgs e) + { + Console.WriteLine("error: {0}, uiHint: {1}, message: {2}", e.Code, e.UIHint, e.Message); + } + + static void OnProgress(object sender, BundleProgressEventArgs e) + { + Console.WriteLine("progresss: {0}%", e.Progress); + } + } +} -- cgit v1.2.3-55-g6feb