aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Mba.Core/Engine.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Mba.Core/Engine.cs')
-rw-r--r--src/WixToolset.Mba.Core/Engine.cs516
1 files changed, 516 insertions, 0 deletions
diff --git a/src/WixToolset.Mba.Core/Engine.cs b/src/WixToolset.Mba.Core/Engine.cs
new file mode 100644
index 00000000..ad62134f
--- /dev/null
+++ b/src/WixToolset.Mba.Core/Engine.cs
@@ -0,0 +1,516 @@
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
3namespace WixToolset.BootstrapperCore
4{
5 using System;
6 using System.ComponentModel;
7 using System.Runtime.InteropServices;
8 using System.Security;
9 using System.Text;
10
11 /// <summary>
12 /// Container class for the <see cref="IBootstrapperEngine"/> interface.
13 /// </summary>
14 public sealed class Engine : IEngine
15 {
16 // Burn errs on empty strings, so declare initial buffer size.
17 private const int InitialBufferSize = 80;
18 private static readonly string normalizeVersionFormatString = "{0} must be less than or equal to " + UInt16.MaxValue;
19
20 private IBootstrapperEngine engine;
21 private Variables<long> numericVariables;
22 private Variables<SecureString> secureStringVariables;
23 private Variables<string> stringVariables;
24 private Variables<Version> versionVariables;
25
26 /// <summary>
27 /// Creates a new instance of the <see cref="Engine"/> container class.
28 /// </summary>
29 /// <param name="engine">The <see cref="IBootstrapperEngine"/> to contain.</param>
30 internal Engine(IBootstrapperEngine engine)
31 {
32 this.engine = engine;
33
34 // Wrap the calls to get and set numeric variables.
35 this.numericVariables = new Variables<long>(
36 delegate(string name)
37 {
38 long value;
39 int ret = this.engine.GetVariableNumeric(name, out value);
40 if (NativeMethods.S_OK != ret)
41 {
42 throw new Win32Exception(ret);
43 }
44
45 return value;
46 },
47 delegate(string name, long value)
48 {
49 this.engine.SetVariableNumeric(name, value);
50 },
51 delegate(string name)
52 {
53 long value;
54 int ret = this.engine.GetVariableNumeric(name, out value);
55
56 return NativeMethods.E_NOTFOUND != ret;
57 }
58 );
59
60 // Wrap the calls to get and set string variables using SecureStrings.
61 this.secureStringVariables = new Variables<SecureString>(
62 delegate(string name)
63 {
64 var pUniString = this.getStringVariable(name, out var length);
65 try
66 {
67 return this.convertToSecureString(pUniString, length);
68 }
69 finally
70 {
71 if (IntPtr.Zero != pUniString)
72 {
73 Marshal.FreeCoTaskMem(pUniString);
74 }
75 }
76 },
77 delegate(string name, SecureString value)
78 {
79 IntPtr pValue = Marshal.SecureStringToCoTaskMemUnicode(value);
80 try
81 {
82 this.engine.SetVariableString(name, pValue);
83 }
84 finally
85 {
86 Marshal.FreeCoTaskMem(pValue);
87 }
88 },
89 delegate(string name)
90 {
91 return this.containsVariable(name);
92 }
93 );
94
95 // Wrap the calls to get and set string variables.
96 this.stringVariables = new Variables<string>(
97 delegate(string name)
98 {
99 int length;
100 IntPtr pUniString = this.getStringVariable(name, out length);
101 try
102 {
103 return Marshal.PtrToStringUni(pUniString, length);
104 }
105 finally
106 {
107 if (IntPtr.Zero != pUniString)
108 {
109 Marshal.FreeCoTaskMem(pUniString);
110 }
111 }
112 },
113 delegate(string name, string value)
114 {
115 IntPtr pValue = Marshal.StringToCoTaskMemUni(value);
116 try
117 {
118 this.engine.SetVariableString(name, pValue);
119 }
120 finally
121 {
122 Marshal.FreeCoTaskMem(pValue);
123 }
124 },
125 delegate(string name)
126 {
127 return this.containsVariable(name);
128 }
129 );
130
131 // Wrap the calls to get and set version variables.
132 this.versionVariables = new Variables<Version>(
133 delegate(string name)
134 {
135 long value;
136 int ret = this.engine.GetVariableVersion(name, out value);
137 if (NativeMethods.S_OK != ret)
138 {
139 throw new Win32Exception(ret);
140 }
141
142 return LongToVersion(value);
143 },
144 delegate(string name, Version value)
145 {
146 long version = VersionToLong(value);
147 this.engine.SetVariableVersion(name, version);
148 },
149 delegate(string name)
150 {
151 long value;
152 int ret = this.engine.GetVariableVersion(name, out value);
153
154 return NativeMethods.E_NOTFOUND != ret;
155 }
156 );
157 }
158
159 public IVariables<long> NumericVariables
160 {
161 get { return this.numericVariables; }
162 }
163
164 public int PackageCount
165 {
166 get
167 {
168 int count;
169 this.engine.GetPackageCount(out count);
170
171 return count;
172 }
173 }
174
175 public IVariables<SecureString> SecureStringVariables
176 {
177 get { return this.secureStringVariables; }
178 }
179
180 public IVariables<string> StringVariables
181 {
182 get { return this.stringVariables; }
183 }
184
185 public IVariables<Version> VersionVariables
186 {
187 get { return this.versionVariables; }
188 }
189
190 public void Apply(IntPtr hwndParent)
191 {
192 this.engine.Apply(hwndParent);
193 }
194
195 public void CloseSplashScreen()
196 {
197 this.engine.CloseSplashScreen();
198 }
199
200 public void Detect()
201 {
202 this.Detect(IntPtr.Zero);
203 }
204
205 public void Detect(IntPtr hwndParent)
206 {
207 this.engine.Detect(hwndParent);
208 }
209
210 public bool Elevate(IntPtr hwndParent)
211 {
212 int ret = this.engine.Elevate(hwndParent);
213
214 if (NativeMethods.S_OK == ret || NativeMethods.E_ALREADYINITIALIZED == ret)
215 {
216 return true;
217 }
218 else if (NativeMethods.E_CANCELLED == ret)
219 {
220 return false;
221 }
222 else
223 {
224 throw new Win32Exception(ret);
225 }
226 }
227
228 public string EscapeString(string input)
229 {
230 int capacity = InitialBufferSize;
231 StringBuilder sb = new StringBuilder(capacity);
232
233 // Get the size of the buffer.
234 int ret = this.engine.EscapeString(input, sb, ref capacity);
235 if (NativeMethods.E_INSUFFICIENT_BUFFER == ret || NativeMethods.E_MOREDATA == ret)
236 {
237 sb.Capacity = ++capacity; // Add one for the null terminator.
238 ret = this.engine.EscapeString(input, sb, ref capacity);
239 }
240
241 if (NativeMethods.S_OK != ret)
242 {
243 throw new Win32Exception(ret);
244 }
245
246 return sb.ToString();
247 }
248
249 public bool EvaluateCondition(string condition)
250 {
251 bool value;
252 this.engine.EvaluateCondition(condition, out value);
253
254 return value;
255 }
256
257 public string FormatString(string format)
258 {
259 int capacity = InitialBufferSize;
260 StringBuilder sb = new StringBuilder(capacity);
261
262 // Get the size of the buffer.
263 int ret = this.engine.FormatString(format, sb, ref capacity);
264 if (NativeMethods.E_INSUFFICIENT_BUFFER == ret || NativeMethods.E_MOREDATA == ret)
265 {
266 sb.Capacity = ++capacity; // Add one for the null terminator.
267 ret = this.engine.FormatString(format, sb, ref capacity);
268 }
269
270 if (NativeMethods.S_OK != ret)
271 {
272 throw new Win32Exception(ret);
273 }
274
275 return sb.ToString();
276 }
277
278 public void LaunchApprovedExe(IntPtr hwndParent, string approvedExeForElevationId, string arguments)
279 {
280 this.LaunchApprovedExe(hwndParent, approvedExeForElevationId, arguments, 0);
281 }
282
283 public void LaunchApprovedExe(IntPtr hwndParent, string approvedExeForElevationId, string arguments, int waitForInputIdleTimeout)
284 {
285 this.engine.LaunchApprovedExe(hwndParent, approvedExeForElevationId, arguments, waitForInputIdleTimeout);
286 }
287
288 public void Log(LogLevel level, string message)
289 {
290 this.engine.Log(level, message);
291 }
292
293 public void Plan(LaunchAction action)
294 {
295 this.engine.Plan(action);
296 }
297
298 public void SetUpdate(string localSource, string downloadSource, long size, UpdateHashType hashType, byte[] hash)
299 {
300 this.engine.SetUpdate(localSource, downloadSource, size, hashType, hash, null == hash ? 0 : hash.Length);
301 }
302
303 public void SetLocalSource(string packageOrContainerId, string payloadId, string path)
304 {
305 this.engine.SetLocalSource(packageOrContainerId, payloadId, path);
306 }
307
308 public void SetDownloadSource(string packageOrContainerId, string payloadId, string url, string user, string password)
309 {
310 this.engine.SetDownloadSource(packageOrContainerId, payloadId, url, user, password);
311 }
312
313 public int SendEmbeddedError(int errorCode, string message, int uiHint)
314 {
315 int result = 0;
316 this.engine.SendEmbeddedError(errorCode, message, uiHint, out result);
317 return result;
318 }
319
320 public int SendEmbeddedProgress(int progressPercentage, int overallPercentage)
321 {
322 int result = 0;
323 this.engine.SendEmbeddedProgress(progressPercentage, overallPercentage, out result);
324 return result;
325 }
326
327 public void Quit(int exitCode)
328 {
329 this.engine.Quit(exitCode);
330 }
331
332 internal sealed class Variables<T> : IVariables<T>
333 {
334 // .NET 2.0 does not support Func<T, TResult> or Action<T1, T2>.
335 internal delegate T Getter<T>(string name);
336 internal delegate void Setter<T>(string name, T value);
337
338 private Getter<T> getter;
339 private Setter<T> setter;
340 private Predicate<string> contains;
341
342 internal Variables(Getter<T> getter, Setter<T> setter, Predicate<string> contains)
343 {
344 this.getter = getter;
345 this.setter = setter;
346 this.contains = contains;
347 }
348
349 public T this[string name]
350 {
351 get { return this.getter(name); }
352 set { this.setter(name, value); }
353 }
354
355 public bool Contains(string name)
356 {
357 return this.contains(name);
358 }
359 }
360
361 /// <summary>
362 /// Gets whether the variable given by <paramref name="name"/> exists.
363 /// </summary>
364 /// <param name="name">The name of the variable to check.</param>
365 /// <returns>True if the variable given by <paramref name="name"/> exists; otherwise, false.</returns>
366 internal bool containsVariable(string name)
367 {
368 int capacity = 0;
369 IntPtr pValue = IntPtr.Zero;
370 int ret = this.engine.GetVariableString(name, pValue, ref capacity);
371
372 return NativeMethods.E_NOTFOUND != ret;
373 }
374
375 /// <summary>
376 /// Gets the variable given by <paramref name="name"/> as a string.
377 /// </summary>
378 /// <param name="name">The name of the variable to get.</param>
379 /// <param name="length">The length of the Unicode string.</param>
380 /// <returns>The value by a pointer to a Unicode string. Must be freed by Marshal.FreeCoTaskMem.</returns>
381 /// <exception cref="Exception">An error occurred getting the variable.</exception>
382 internal IntPtr getStringVariable(string name, out int length)
383 {
384 int capacity = InitialBufferSize;
385 bool success = false;
386 IntPtr pValue = Marshal.AllocCoTaskMem(capacity * UnicodeEncoding.CharSize);
387 try
388 {
389 // Get the size of the buffer.
390 int ret = this.engine.GetVariableString(name, pValue, ref capacity);
391 if (NativeMethods.E_INSUFFICIENT_BUFFER == ret || NativeMethods.E_MOREDATA == ret)
392 {
393 // Don't need to add 1 for the null terminator, the engine already includes that.
394 pValue = Marshal.ReAllocCoTaskMem(pValue, capacity * UnicodeEncoding.CharSize);
395 ret = this.engine.GetVariableString(name, pValue, ref capacity);
396 }
397
398 if (NativeMethods.S_OK != ret)
399 {
400 throw Marshal.GetExceptionForHR(ret);
401 }
402
403 // The engine only returns the exact length of the string if the buffer was too small, so calculate it ourselves.
404 for (length = 0; length < capacity; ++length)
405 {
406 if(0 == Marshal.ReadInt16(pValue, length * UnicodeEncoding.CharSize))
407 {
408 break;
409 }
410 }
411
412 success = true;
413 return pValue;
414 }
415 finally
416 {
417 if (!success && IntPtr.Zero != pValue)
418 {
419 Marshal.FreeCoTaskMem(pValue);
420 }
421 }
422 }
423
424 /// <summary>
425 /// Initialize a SecureString with the given Unicode string.
426 /// </summary>
427 /// <param name="pUniString">Pointer to Unicode string.</param>
428 /// <param name="length">The string's length.</param>
429 internal SecureString convertToSecureString(IntPtr pUniString, int length)
430 {
431 if (IntPtr.Zero == pUniString)
432 {
433 return null;
434 }
435
436 SecureString value = new SecureString();
437 short s;
438 char c;
439 for (int charIndex = 0; charIndex < length; charIndex++)
440 {
441 s = Marshal.ReadInt16(pUniString, charIndex * UnicodeEncoding.CharSize);
442 c = (char)s;
443 value.AppendChar(c);
444 s = 0;
445 c = (char)0;
446 }
447 return value;
448 }
449
450 public static long VersionToLong(Version version)
451 {
452 // In Windows, each version component has a max value of 65535,
453 // so we truncate the version before shifting it, which will overflow if invalid.
454 long major = (long)(ushort)version.Major << 48;
455 long minor = (long)(ushort)version.Minor << 32;
456 long build = (long)(ushort)version.Build << 16;
457 long revision = (long)(ushort)version.Revision;
458
459 return major | minor | build | revision;
460 }
461
462 public static Version LongToVersion(long version)
463 {
464 int major = (int)((version & ((long)0xffff << 48)) >> 48);
465 int minor = (int)((version & ((long)0xffff << 32)) >> 32);
466 int build = (int)((version & ((long)0xffff << 16)) >> 16);
467 int revision = (int)(version & 0xffff);
468
469 return new Version(major, minor, build, revision);
470 }
471
472 /// <summary>
473 /// Verifies that VersionVariables can pass on the given Version to the engine.
474 /// If the Build or Revision fields are undefined, they are set to zero.
475 /// </summary>
476 public static Version NormalizeVersion(Version version)
477 {
478 if (version == null)
479 {
480 throw new ArgumentNullException("version");
481 }
482
483 int major = version.Major;
484 int minor = version.Minor;
485 int build = version.Build;
486 int revision = version.Revision;
487
488 if (major > UInt16.MaxValue)
489 {
490 throw new ArgumentOutOfRangeException("version", String.Format(normalizeVersionFormatString, "Major"));
491 }
492 if (minor > UInt16.MaxValue)
493 {
494 throw new ArgumentOutOfRangeException("version", String.Format(normalizeVersionFormatString, "Minor"));
495 }
496 if (build > UInt16.MaxValue)
497 {
498 throw new ArgumentOutOfRangeException("version", String.Format(normalizeVersionFormatString, "Build"));
499 }
500 if (build == -1)
501 {
502 build = 0;
503 }
504 if (revision > UInt16.MaxValue)
505 {
506 throw new ArgumentOutOfRangeException("version", String.Format(normalizeVersionFormatString, "Revision"));
507 }
508 if (revision == -1)
509 {
510 revision = 0;
511 }
512
513 return new Version(major, minor, build, revision);
514 }
515 }
516}