diff options
Diffstat (limited to 'src/tools/burn/ManagedBundleRunner/BundleRunner.cs')
-rw-r--r-- | src/tools/burn/ManagedBundleRunner/BundleRunner.cs | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/tools/burn/ManagedBundleRunner/BundleRunner.cs b/src/tools/burn/ManagedBundleRunner/BundleRunner.cs new file mode 100644 index 00000000..98d92c66 --- /dev/null +++ b/src/tools/burn/ManagedBundleRunner/BundleRunner.cs | |||
@@ -0,0 +1,212 @@ | |||
1 | // 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. | ||
2 | |||
3 | namespace WixToolset.Tools | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics; | ||
7 | using System.IO.Pipes; | ||
8 | using System.Text; | ||
9 | using System.Threading; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Runs a bundle with provided command-line. | ||
13 | /// </summary> | ||
14 | public class BundleRunner | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// Creates a runner for the provided bundle. | ||
18 | /// </summary> | ||
19 | /// <param name="bundle">Path to the bundle to run.</param> | ||
20 | public BundleRunner(string bundle) | ||
21 | { | ||
22 | this.Path = bundle; | ||
23 | } | ||
24 | |||
25 | /// <summary> | ||
26 | /// Fired when the bundle encounters an error. | ||
27 | /// </summary> | ||
28 | public event EventHandler<BundleErrorEventArgs> Error; | ||
29 | |||
30 | /// <summary> | ||
31 | /// Fired when the bundle progress is udpated. | ||
32 | /// </summary> | ||
33 | public event EventHandler<BundleProgressEventArgs> Progress; | ||
34 | |||
35 | /// <summary> | ||
36 | /// Gets the path to the bundle to run. | ||
37 | /// </summary> | ||
38 | public string Path { get; private set; } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Runs the bundle with the provided command-line. | ||
42 | /// </summary> | ||
43 | /// <param name="commandLine">Optional command-line to pass to the bundle.</param> | ||
44 | /// <returns>Exit code from the bundle.</returns> | ||
45 | public int Run(string commandLine = null) | ||
46 | { | ||
47 | WaitHandle[] waits = new WaitHandle[] { new ManualResetEvent(false), new ManualResetEvent(false) }; | ||
48 | int returnCode = 0; | ||
49 | int pid = Process.GetCurrentProcess().Id; | ||
50 | string pipeName = String.Concat("bpe_", pid); | ||
51 | string pipeSecret = Guid.NewGuid().ToString("N"); | ||
52 | |||
53 | using (NamedPipeServerStream pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1)) | ||
54 | { | ||
55 | using (Process bundleProcess = new Process()) | ||
56 | { | ||
57 | bundleProcess.StartInfo.FileName = this.Path; | ||
58 | bundleProcess.StartInfo.Arguments = String.Format("{0} -burn.embedded {1} {2} {3}", commandLine ?? String.Empty, pipeName, pipeSecret, pid); | ||
59 | bundleProcess.StartInfo.UseShellExecute = false; | ||
60 | bundleProcess.StartInfo.CreateNoWindow = true; | ||
61 | bundleProcess.Start(); | ||
62 | |||
63 | Connect(pipe, pipeSecret, pid, bundleProcess.Id); | ||
64 | |||
65 | PumpMessages(pipe); | ||
66 | |||
67 | bundleProcess.WaitForExit(); | ||
68 | returnCode = bundleProcess.ExitCode; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | return returnCode; | ||
73 | } | ||
74 | |||
75 | /// <summary> | ||
76 | /// Called when bundle encounters an error. | ||
77 | /// </summary> | ||
78 | /// <param name="e">Additional arguments for this event.</param> | ||
79 | protected virtual void OnError(BundleErrorEventArgs e) | ||
80 | { | ||
81 | EventHandler<BundleErrorEventArgs> handler = this.Error; | ||
82 | if (handler != null) | ||
83 | { | ||
84 | handler(this, e); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// Called when bundle progress is updated. | ||
90 | /// </summary> | ||
91 | /// <param name="e">Additional arguments for this event.</param> | ||
92 | protected virtual void OnProgress(BundleProgressEventArgs e) | ||
93 | { | ||
94 | EventHandler<BundleProgressEventArgs> handler = this.Progress; | ||
95 | if (handler != null) | ||
96 | { | ||
97 | handler(this, e); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | private void Connect(NamedPipeServerStream pipe, string pipeSecret, int pid, int childPid) | ||
102 | { | ||
103 | pipe.WaitForConnection(); | ||
104 | |||
105 | WriteSecretToPipe(pipe, pipeSecret); | ||
106 | |||
107 | WriteNumberToPipe(pipe, (uint)pid); | ||
108 | |||
109 | uint ack = ReadNumberFromPipe(pipe); | ||
110 | // This is not true when bundle is run under a debugger | ||
111 | //if (ack != childPid) | ||
112 | //{ | ||
113 | // throw new ApplicationException("Incorrect child process."); | ||
114 | //} | ||
115 | } | ||
116 | |||
117 | private void PumpMessages(NamedPipeServerStream pipe) | ||
118 | { | ||
119 | uint messageId; | ||
120 | while (TryReadNumberFromPipe(pipe, out messageId)) | ||
121 | { | ||
122 | uint messageSize = ReadNumberFromPipe(pipe); | ||
123 | |||
124 | BundleResult result = BundleResult.None; | ||
125 | switch (messageId) | ||
126 | { | ||
127 | case 1: //error | ||
128 | result = ProcessErrorMessage(pipe); | ||
129 | break; | ||
130 | |||
131 | case 2: // progress | ||
132 | result = ProcessProgressMessage(pipe); | ||
133 | break; | ||
134 | |||
135 | default: // unknown message, do nothing. | ||
136 | break; | ||
137 | } | ||
138 | |||
139 | CompleteMessage(pipe, result); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | private BundleResult ProcessErrorMessage(NamedPipeServerStream pipe) | ||
144 | { | ||
145 | BundleErrorEventArgs e = new BundleErrorEventArgs(); | ||
146 | e.Code = (int)ReadNumberFromPipe(pipe); | ||
147 | e.Message = ReadStringFromPipe(pipe); | ||
148 | e.UIHint = (int)ReadNumberFromPipe(pipe); | ||
149 | |||
150 | this.OnError(e); | ||
151 | |||
152 | return e.Result; | ||
153 | } | ||
154 | |||
155 | private BundleResult ProcessProgressMessage(NamedPipeServerStream pipe) | ||
156 | { | ||
157 | ReadNumberFromPipe(pipe); // eat the first progress number because it is always zero. | ||
158 | |||
159 | BundleProgressEventArgs e = new BundleProgressEventArgs(); | ||
160 | e.Progress = (int)ReadNumberFromPipe(pipe); | ||
161 | |||
162 | this.OnProgress(e); | ||
163 | |||
164 | return e.Result; | ||
165 | } | ||
166 | |||
167 | private void CompleteMessage(NamedPipeServerStream pipe, BundleResult result) | ||
168 | { | ||
169 | uint complete = 0xF0000002; | ||
170 | WriteNumberToPipe(pipe, complete); | ||
171 | WriteNumberToPipe(pipe, 4); // size of message data | ||
172 | WriteNumberToPipe(pipe, (uint)result); | ||
173 | } | ||
174 | |||
175 | private uint ReadNumberFromPipe(NamedPipeServerStream pipe) | ||
176 | { | ||
177 | byte[] buffer = new byte[4]; | ||
178 | pipe.Read(buffer, 0, buffer.Length); | ||
179 | return BitConverter.ToUInt32(buffer, 0); | ||
180 | } | ||
181 | |||
182 | private string ReadStringFromPipe(NamedPipeServerStream pipe) | ||
183 | { | ||
184 | uint length = ReadNumberFromPipe(pipe); | ||
185 | |||
186 | byte[] buffer = new byte[length * 2]; | ||
187 | pipe.Read(buffer, 0, buffer.Length); | ||
188 | |||
189 | return Encoding.Unicode.GetString(buffer); | ||
190 | } | ||
191 | |||
192 | private bool TryReadNumberFromPipe(NamedPipeServerStream pipe, out uint value) | ||
193 | { | ||
194 | value = ReadNumberFromPipe(pipe); // reading will not block and return zero if pipe is not connected. | ||
195 | return pipe.IsConnected; | ||
196 | } | ||
197 | |||
198 | private void WriteNumberToPipe(NamedPipeServerStream pipe, uint value) | ||
199 | { | ||
200 | byte[] buffer = BitConverter.GetBytes(value); | ||
201 | pipe.Write(buffer, 0, buffer.Length); | ||
202 | } | ||
203 | |||
204 | private void WriteSecretToPipe(NamedPipeServerStream pipe, string secret) | ||
205 | { | ||
206 | byte[] buffer = Encoding.Unicode.GetBytes(secret); | ||
207 | |||
208 | WriteNumberToPipe(pipe, (uint)buffer.Length); | ||
209 | pipe.Write(buffer, 0, buffer.Length); | ||
210 | } | ||
211 | } | ||
212 | } | ||