main
cs 779 lines 32.3 KB
Raw
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.Test.BA
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq;
10 using System.Threading;
11 using System.Windows.Forms;
12 using Microsoft.Win32;
13 using WixToolset.BootstrapperApplicationApi;
14
15 /// <summary>
16 /// A minimal UX used for testing.
17 /// </summary>
18 public class TestBA : BootstrapperApplication
19 {
20 private const string BurnBundleVersionVariable = "WixBundleVersion";
21
22 private Form dummyWindow;
23 private IntPtr windowHandle;
24 private LaunchAction action;
25 private readonly ManualResetEvent wait;
26 private int result;
27
28 private string updateBundlePath;
29
30 private bool allowAcquireAfterValidationFailure;
31 private bool forceKeepRegistration;
32 private bool immediatelyQuit;
33 private bool quitAfterDetect;
34 private bool explicitlyElevateAndPlanFromOnElevateBegin;
35 private int redetectRemaining;
36 private int sleepDuringCache;
37 private int cancelCacheAtProgress;
38 private int sleepDuringExecute;
39 private int cancelExecuteAtProgress;
40 private string cancelExecuteActionName;
41 private int cancelOnProgressAtProgress;
42 private int retryExecuteFilesInUse;
43 private bool rollingBack;
44
45 private IBootstrapperCommand Command { get; set; }
46
47 private IEngine Engine => this.engine;
48
49 /// <summary>
50 /// Initializes test user experience.
51 /// </summary>
52 public TestBA()
53 {
54 this.wait = new ManualResetEvent(false);
55 }
56
57 /// <summary>
58 /// Get the version of the install.
59 /// </summary>
60 public string Version { get; private set; }
61
62 /// <summary>
63 /// Indicates if DetectUpdate found a newer version to update.
64 /// </summary>
65 private bool UpdateAvailable { get; set; }
66
67 protected override void OnCreate(CreateEventArgs args)
68 {
69 base.OnCreate(args);
70 this.Command = args.Command;
71 }
72
73 /// <summary>
74 /// UI Thread entry point for TestUX.
75 /// </summary>
76 protected override void OnStartup(StartupEventArgs args)
77 {
78 string immediatelyQuit = this.ReadPackageAction(null, "ImmediatelyQuit");
79 if (!String.IsNullOrEmpty(immediatelyQuit) && Boolean.TryParse(immediatelyQuit, out this.immediatelyQuit) && this.immediatelyQuit)
80 {
81 this.Engine.Quit(0);
82 return;
83 }
84
85 this.action = this.Command.Action;
86 this.TestVariables();
87
88 this.Version = this.engine.GetVariableVersion(BurnBundleVersionVariable);
89 this.Log("Version: {0}", this.Version);
90
91 List<string> verifyArguments = this.ReadVerifyArguments();
92
93 IBootstrapperApplicationData baManifest = new BootstrapperApplicationData();
94 IMbaCommand mbaCommand = this.Command.ParseCommandLine();
95 mbaCommand.SetOverridableVariables(baManifest.Bundle.OverridableVariables, this.engine);
96
97 foreach (string arg in mbaCommand.UnknownCommandLineArgs)
98 {
99 // If we're not in the update already, process the updatebundle.
100 if (this.Command.Relation != RelationType.Update && arg.StartsWith("-updatebundle:", StringComparison.OrdinalIgnoreCase))
101 {
102 this.updateBundlePath = arg.Substring(14);
103 FileInfo info = new FileInfo(this.updateBundlePath);
104 this.Engine.SetUpdate(this.updateBundlePath, null, info.Length, UpdateHashType.None, null, null);
105 this.UpdateAvailable = true;
106 this.action = LaunchAction.UpdateReplaceEmbedded;
107 }
108 else if (this.Command.Relation != RelationType.Update && arg.StartsWith("-checkupdate", StringComparison.OrdinalIgnoreCase))
109 {
110 this.action = LaunchAction.UpdateReplace;
111 }
112
113 verifyArguments.Remove(arg);
114 }
115 this.Log("Action: {0}", this.action);
116
117 // If there are any verification arguments left, error out.
118 if (0 < verifyArguments.Count)
119 {
120 foreach (string expectedArg in verifyArguments)
121 {
122 this.Log("Failure. Expected command-line to have argument: {0}", expectedArg);
123 }
124
125 this.Engine.Quit(-1);
126 return;
127 }
128
129 base.OnStartup(args);
130
131 string redetect = this.ReadPackageAction(null, "RedetectCount");
132 if (String.IsNullOrEmpty(redetect) || !Int32.TryParse(redetect, out var redetectCount))
133 {
134 redetectCount = 0;
135 }
136
137 string allowAcquireAfterValidationFailure = this.ReadPackageAction(null, "AllowAcquireAfterValidationFailure");
138 if (String.IsNullOrEmpty(allowAcquireAfterValidationFailure) || !Boolean.TryParse(allowAcquireAfterValidationFailure, out this.allowAcquireAfterValidationFailure))
139 {
140 this.allowAcquireAfterValidationFailure = false;
141 }
142
143 string explicitlyElevateAndPlanFromOnElevateBegin = this.ReadPackageAction(null, "ExplicitlyElevateAndPlanFromOnElevateBegin");
144 if (String.IsNullOrEmpty(explicitlyElevateAndPlanFromOnElevateBegin) || !Boolean.TryParse(explicitlyElevateAndPlanFromOnElevateBegin, out this.explicitlyElevateAndPlanFromOnElevateBegin))
145 {
146 this.explicitlyElevateAndPlanFromOnElevateBegin = false;
147 }
148
149 string forceKeepRegistration = this.ReadPackageAction(null, "ForceKeepRegistration");
150 if (String.IsNullOrEmpty(forceKeepRegistration) || !Boolean.TryParse(forceKeepRegistration, out this.forceKeepRegistration))
151 {
152 this.forceKeepRegistration = false;
153 }
154
155 string quitAfterDetect = this.ReadPackageAction(null, "QuitAfterDetect");
156 if (String.IsNullOrEmpty(quitAfterDetect) || !Boolean.TryParse(quitAfterDetect, out this.quitAfterDetect))
157 {
158 this.quitAfterDetect = false;
159 }
160
161 this.ImportContainerSources();
162 this.ImportPayloadSources();
163
164 this.wait.WaitOne();
165
166 if (this.action == LaunchAction.Help)
167 {
168 this.Log("This is a BA for automated testing");
169 this.ShutdownUiThread(0);
170 return;
171 }
172
173 this.redetectRemaining = redetectCount;
174 for (int i = -1; i < redetectCount; i++)
175 {
176 this.Engine.Detect(this.windowHandle);
177 }
178 }
179
180 protected override void Run()
181 {
182 using (this.dummyWindow = new Form())
183 {
184 this.windowHandle = this.dummyWindow.Handle;
185
186 this.Log("Running TestBA application");
187 this.wait.Set();
188
189 Application.Run();
190 this.dummyWindow = null;
191 }
192
193 var exitCode = this.result;
194 if ((exitCode & 0xFFFF0000) == unchecked(0x80070000))
195 {
196 exitCode &= 0xFFFF; // return plain old Win32 error, not HRESULT.
197 }
198
199 this.Engine.Quit(exitCode);
200 }
201
202 private void ShutdownUiThread(int? exitCode = null)
203 {
204 try
205 {
206 if (exitCode.HasValue)
207 {
208 this.result = exitCode.Value;
209 }
210
211 this.dummyWindow?.Invoke(new Action(Application.ExitThread));
212 }
213 catch (Exception e)
214 {
215 this.Log("Failed to shutdown TestBA window, exception: {0}", e.Message);
216 }
217 }
218
219 protected override void OnDetectUpdateBegin(DetectUpdateBeginEventArgs args)
220 {
221 this.Log("OnDetectUpdateBegin");
222 if (LaunchAction.UpdateReplaceEmbedded == this.action || LaunchAction.UpdateReplace == this.action)
223 {
224 args.Skip = false;
225 }
226 }
227
228 protected override void OnDetectUpdate(DetectUpdateEventArgs e)
229 {
230 // The list of updates is sorted in descending version, so the first callback should be the largest update available.
231 // This update should be either larger than ours (so we are out of date), the same as ours (so we are current)
232 // or smaller than ours (we have a private build).
233 // Enumerate all of the updates anyway in case something's broken.
234 this.Log(String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, this.Version));
235 if (!this.UpdateAvailable && this.Engine.CompareVersions(e.Version, this.Version) > 0)
236 {
237 this.Log(String.Format("Selected update v{0}", e.Version));
238 this.Engine.SetUpdate(null, e.UpdateLocation, e.Size, e.HashAlgorithm, e.Hash, null);
239 this.UpdateAvailable = true;
240 }
241 }
242
243 protected override void OnDetectUpdateComplete(DetectUpdateCompleteEventArgs e)
244 {
245 this.Log("OnDetectUpdateComplete");
246
247 // Failed to process an update, allow the existing bundle to still install.
248 if (!Hresult.Succeeded(e.Status))
249 {
250 this.Log(String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status));
251 e.IgnoreError = true; // But continue on...
252 }
253 }
254
255 protected override void OnDetectComplete(DetectCompleteEventArgs args)
256 {
257 this.result = args.Status;
258
259 if (Hresult.Succeeded(this.result) &&
260 (this.UpdateAvailable || LaunchAction.UpdateReplaceEmbedded != this.action && LaunchAction.UpdateReplace != this.action))
261 {
262 if (this.redetectRemaining > 0)
263 {
264 this.Log("Completed detection phase: {0} re-runs remaining", this.redetectRemaining--);
265 }
266 else if (this.quitAfterDetect)
267 {
268 this.ShutdownUiThread();
269 }
270 else if (this.explicitlyElevateAndPlanFromOnElevateBegin)
271 {
272 this.Engine.Elevate(this.windowHandle);
273 }
274 else
275 {
276 this.Engine.Plan(this.action);
277 }
278 }
279 else
280 {
281 this.ShutdownUiThread();
282 }
283 }
284
285 protected override void OnDetectRelatedBundle(DetectRelatedBundleEventArgs args)
286 {
287 this.Log("OnDetectRelatedBundle() - id: {0}, missing from cache: {1}", args.ProductCode, args.MissingFromCache);
288 }
289
290 protected override void OnElevateBegin(ElevateBeginEventArgs args)
291 {
292 if (this.explicitlyElevateAndPlanFromOnElevateBegin)
293 {
294 this.Engine.Plan(this.action);
295
296 // Simulate showing some UI since these tests won't actually show the UAC prompt.
297 MessagePump.ProcessMessages(10);
298 }
299 }
300
301 protected override void OnPlanPackageBegin(PlanPackageBeginEventArgs args)
302 {
303 RequestState state;
304 string action = this.ReadPackageAction(args.PackageId, "Requested");
305 if (TryParseEnum<RequestState>(action, out state))
306 {
307 args.State = state;
308 }
309
310 BOOTSTRAPPER_CACHE_TYPE cacheType;
311 string cacheAction = this.ReadPackageAction(args.PackageId, "CacheRequested");
312 if (TryParseEnum<BOOTSTRAPPER_CACHE_TYPE>(cacheAction, out cacheType))
313 {
314 args.CacheType = cacheType;
315 }
316
317 this.Log("OnPlanPackageBegin() - id: {0}, currentState: {1}, defaultState: {2}, requestedState: {3}, defaultCache: {4}, requestedCache: {5}", args.PackageId, args.CurrentState, args.RecommendedState, args.State, args.RecommendedCacheType, args.CacheType);
318 }
319
320 protected override void OnPlanPatchTarget(PlanPatchTargetEventArgs args)
321 {
322 RequestState state;
323 string action = this.ReadPackageAction(args.PackageId, "Requested");
324 if (TryParseEnum<RequestState>(action, out state))
325 {
326 args.State = state;
327 }
328 }
329
330 protected override void OnPlanMsiFeature(PlanMsiFeatureEventArgs args)
331 {
332 FeatureState state;
333 string action = this.ReadFeatureAction(args.PackageId, args.FeatureId, "Requested");
334 if (TryParseEnum<FeatureState>(action, out state))
335 {
336 args.State = state;
337 }
338
339 this.Log("OnPlanMsiFeature() - id: {0}, defaultState: {1}, requestedState: {2}", args.PackageId, args.RecommendedState, args.State);
340 }
341
342 protected override void OnPlanComplete(PlanCompleteEventArgs args)
343 {
344 this.result = args.Status;
345 if (Hresult.Succeeded(this.result))
346 {
347 this.Engine.Apply(this.windowHandle);
348 }
349 else
350 {
351 this.ShutdownUiThread();
352 }
353 }
354
355 protected override void OnCachePackageBegin(CachePackageBeginEventArgs args)
356 {
357 this.Log("OnCachePackageBegin() - package: {0}, payloads to cache: {1}", args.PackageId, args.CachePayloads);
358
359 string slowProgress = this.ReadPackageAction(args.PackageId, "SlowCache");
360 if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringCache))
361 {
362 this.sleepDuringCache = 0;
363 }
364 else
365 {
366 this.Log(" SlowCache: {0}", this.sleepDuringCache);
367 }
368
369 string cancelCache = this.ReadPackageAction(args.PackageId, "CancelCacheAtProgress");
370 if (String.IsNullOrEmpty(cancelCache) || !Int32.TryParse(cancelCache, out this.cancelCacheAtProgress))
371 {
372 this.cancelCacheAtProgress = -1;
373 }
374 else
375 {
376 this.Log(" CancelCacheAtProgress: {0}", this.cancelCacheAtProgress);
377 }
378 }
379
380 protected override void OnCachePackageNonVitalValidationFailure(CachePackageNonVitalValidationFailureEventArgs args)
381 {
382 if (this.allowAcquireAfterValidationFailure)
383 {
384 args.Action = BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION.Acquire;
385 }
386
387 this.Log("OnCachePackageNonVitalValidationFailure() - id: {0}, default: {1}, requested: {2}", args.PackageId, args.Recommendation, args.Action);
388 }
389
390 protected override void OnCacheAcquireProgress(CacheAcquireProgressEventArgs args)
391 {
392 this.Log("OnCacheAcquireProgress() - container/package: {0}, payload: {1}, progress: {2}, total: {3}, overall progress: {4}%", args.PackageOrContainerId, args.PayloadId, args.Progress, args.Total, args.OverallPercentage);
393
394 if (this.cancelCacheAtProgress >= 0 && this.cancelCacheAtProgress <= args.Progress)
395 {
396 args.Cancel = true;
397 this.Log("OnCacheAcquireProgress(cancel)");
398 }
399 else if (this.sleepDuringCache > 0)
400 {
401 this.Log("OnCacheAcquireProgress(sleep {0})", this.sleepDuringCache);
402 Thread.Sleep(this.sleepDuringCache);
403 }
404 }
405
406 protected override void OnCacheContainerOrPayloadVerifyProgress(CacheContainerOrPayloadVerifyProgressEventArgs args)
407 {
408 this.Log("OnCacheContainerOrPayloadVerifyProgress() - container/package: {0}, payload: {1}, progress: {2}, total: {3}, overall progress: {4}%", args.PackageOrContainerId, args.PayloadId, args.Progress, args.Total, args.OverallPercentage);
409 }
410
411 protected override void OnCachePayloadExtractProgress(CachePayloadExtractProgressEventArgs args)
412 {
413 this.Log("OnCachePayloadExtractProgress() - container/package: {0}, payload: {1}, progress: {2}, total: {3}, overall progress: {4}%", args.PackageOrContainerId, args.PayloadId, args.Progress, args.Total, args.OverallPercentage);
414 }
415
416 protected override void OnCacheVerifyProgress(CacheVerifyProgressEventArgs args)
417 {
418 this.Log("OnCacheVerifyProgress() - container/package: {0}, payload: {1}, progress: {2}, total: {3}, overall progress: {4}%, step: {5}", args.PackageOrContainerId, args.PayloadId, args.Progress, args.Total, args.OverallPercentage, args.Step);
419 }
420
421 protected override void OnExecutePackageBegin(ExecutePackageBeginEventArgs args)
422 {
423 this.Log("OnExecutePackageBegin() - package: {0}, rollback: {1}", args.PackageId, !args.ShouldExecute);
424
425 this.rollingBack = !args.ShouldExecute;
426
427 string slowProgress = this.ReadPackageAction(args.PackageId, "SlowExecute");
428 if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringExecute))
429 {
430 this.sleepDuringExecute = 0;
431 }
432 else
433 {
434 this.Log(" SlowExecute: {0}", this.sleepDuringExecute);
435 }
436
437 string cancelExecute = this.ReadPackageAction(args.PackageId, "CancelExecuteAtProgress");
438 if (String.IsNullOrEmpty(cancelExecute) || !Int32.TryParse(cancelExecute, out this.cancelExecuteAtProgress))
439 {
440 this.cancelExecuteAtProgress = -1;
441 }
442 else
443 {
444 this.Log(" CancelExecuteAtProgress: {0}", this.cancelExecuteAtProgress);
445 }
446
447 this.cancelExecuteActionName = this.ReadPackageAction(args.PackageId, "CancelExecuteAtActionStart");
448 if (!String.IsNullOrEmpty(this.cancelExecuteActionName))
449 {
450 this.Log(" CancelExecuteAtActionState: {0}", this.cancelExecuteActionName);
451 }
452
453 string cancelOnProgressAtProgress = this.ReadPackageAction(args.PackageId, "CancelOnProgressAtProgress");
454 if (String.IsNullOrEmpty(cancelOnProgressAtProgress) || !Int32.TryParse(cancelOnProgressAtProgress, out this.cancelOnProgressAtProgress))
455 {
456 this.cancelOnProgressAtProgress = -1;
457 }
458 else
459 {
460 this.Log(" CancelOnProgressAtProgress: {0}", this.cancelOnProgressAtProgress);
461 }
462
463 string retryBeforeCancel = this.ReadPackageAction(args.PackageId, "RetryExecuteFilesInUse");
464 if (String.IsNullOrEmpty(retryBeforeCancel) || !Int32.TryParse(retryBeforeCancel, out this.retryExecuteFilesInUse))
465 {
466 this.retryExecuteFilesInUse = 0;
467 }
468 else
469 {
470 this.Log(" RetryExecuteFilesInUse: {0}", this.retryExecuteFilesInUse);
471 }
472 }
473
474 protected override void OnExecutePackageComplete(ExecutePackageCompleteEventArgs args)
475 {
476 bool logTestRegistryValue;
477 string recordTestRegistryValue = this.ReadPackageAction(args.PackageId, "RecordTestRegistryValue");
478 if (!String.IsNullOrEmpty(recordTestRegistryValue) && Boolean.TryParse(recordTestRegistryValue, out logTestRegistryValue) && logTestRegistryValue)
479 {
480 var value = this.ReadTestRegistryValue(args.PackageId);
481 this.Log("TestRegistryValue: {0}, {1}, Version, '{2}'", this.rollingBack ? "Rollback" : "Execute", args.PackageId, value);
482 }
483 }
484
485 protected override void OnExecuteProcessCancel(ExecuteProcessCancelEventArgs args)
486 {
487 BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action;
488 string actionValue = this.ReadPackageAction(args.PackageId, "ProcessCancelAction");
489 if (actionValue != null && TryParseEnum<BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION>(actionValue, out action))
490 {
491 args.Action = action;
492 }
493
494 if (args.Action == BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION.Abandon)
495 {
496 // Kill process to make sure it doesn't affect other tests.
497 try
498 {
499 using (Process process = Process.GetProcessById(args.ProcessId))
500 {
501 if (process != null)
502 {
503 process.Kill();
504 }
505 }
506 }
507 catch (Exception e)
508 {
509 this.Log("Failed to kill process {0}: {1}", args.ProcessId, e);
510 Thread.Sleep(5000);
511 }
512 }
513
514 this.Log("OnExecuteProcessCancel({0})", args.Action);
515 }
516
517 protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args)
518 {
519 this.Log("OnExecuteFilesInUse() - package: {0}, source: {1}, retries remaining: {2}, data: {3}", args.PackageId, args.Source, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray()));
520
521 if (this.retryExecuteFilesInUse > 0)
522 {
523 --this.retryExecuteFilesInUse;
524 args.Result = Result.Retry;
525 }
526 else
527 {
528 args.Result = Result.Cancel;
529 }
530 }
531
532 protected override void OnExecuteMsiMessage(ExecuteMsiMessageEventArgs args)
533 {
534 this.Log("OnExecuteMsiMessage() - MessageType: {0}, Message: {1}, Data: '{2}'", args.MessageType, args.Message, String.Join("','", args.Data.ToArray()));
535
536 if (!String.IsNullOrEmpty(this.cancelExecuteActionName) && args.MessageType == InstallMessage.ActionStart &&
537 args.Data.Count > 0 && args.Data[0] == this.cancelExecuteActionName)
538 {
539 this.Log("OnExecuteMsiMessage(cancelNextProgress)");
540 this.cancelExecuteAtProgress = 0;
541 }
542 }
543
544 protected override void OnExecuteProgress(ExecuteProgressEventArgs args)
545 {
546 this.Log("OnExecuteProgress() - package: {0}, progress: {1}%, overall progress: {2}%", args.PackageId, args.ProgressPercentage, args.OverallPercentage);
547
548 if (this.cancelExecuteAtProgress >= 0 && this.cancelExecuteAtProgress <= args.ProgressPercentage)
549 {
550 args.Cancel = true;
551 this.Log("OnExecuteProgress(cancel)");
552 }
553 else if (this.sleepDuringExecute > 0)
554 {
555 this.Log("OnExecuteProgress(sleep {0})", this.sleepDuringExecute);
556 Thread.Sleep(this.sleepDuringExecute);
557 }
558 }
559
560 protected override void OnExecutePatchTarget(ExecutePatchTargetEventArgs args)
561 {
562 this.Log("OnExecutePatchTarget - Patch Package: {0}, Target Product Code: {1}", args.PackageId, args.TargetProductCode);
563 }
564
565 protected override void OnProgress(ProgressEventArgs args)
566 {
567 this.Log("OnProgress() - progress: {0}%, overall progress: {1}%", args.ProgressPercentage, args.OverallPercentage);
568 if (this.Command.Display == Display.Embedded)
569 {
570 this.Engine.SendEmbeddedProgress(args.ProgressPercentage, args.OverallPercentage);
571 }
572
573 if (this.cancelOnProgressAtProgress >= 0 && this.cancelOnProgressAtProgress <= args.OverallPercentage)
574 {
575 args.Cancel = true;
576 this.Log("OnProgress(cancel)");
577 }
578 }
579
580 protected override void OnApplyBegin(ApplyBeginEventArgs args)
581 {
582 this.cancelOnProgressAtProgress = -1;
583 this.cancelExecuteAtProgress = -1;
584 this.cancelCacheAtProgress = -1;
585 this.rollingBack = false;
586 }
587
588 protected override void OnApplyComplete(ApplyCompleteEventArgs args)
589 {
590 // Output what the privileges are now.
591 this.Log("After elevation: WixBundleElevated = {0}", this.Engine.GetVariableNumeric("WixBundleElevated"));
592
593 this.ShutdownUiThread(args.Status);
594 }
595
596 protected override void OnUnregisterBegin(UnregisterBeginEventArgs args)
597 {
598 if (this.forceKeepRegistration && args.RegistrationType == RegistrationType.None)
599 {
600 args.RegistrationType = RegistrationType.InProgress;
601 }
602
603 this.Log("OnUnregisterBegin, default: {0}, requested: {1}", args.RecommendedRegistrationType, args.RegistrationType);
604 }
605
606 private void TestVariables()
607 {
608 // First make sure we can check and get standard variables of each type.
609 if (this.Engine.ContainsVariable("WindowsFolder"))
610 {
611 string value = this.Engine.GetVariableString("WindowsFolder");
612 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a string variable: WindowsFolder '{0}'", value));
613 }
614 else
615 {
616 throw new Exception("Engine did not define a standard variable: WindowsFolder");
617 }
618
619 if (this.Engine.ContainsVariable("NTProductType"))
620 {
621 long value = this.Engine.GetVariableNumeric("NTProductType");
622 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a numeric variable: NTProductType '{0}'", value));
623 }
624 else
625 {
626 throw new Exception("Engine did not define a standard variable: NTProductType");
627 }
628
629 if (this.Engine.ContainsVariable("VersionMsi"))
630 {
631 string value = this.Engine.GetVariableVersion("VersionMsi");
632 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a version variable: VersionMsi '{0}'", value));
633 }
634 else
635 {
636 throw new Exception("Engine did not define a standard variable: VersionMsi");
637 }
638
639 // Now validate that Contians returns false for non-existant variables of each type.
640 if (this.Engine.ContainsVariable("TestStringVariableShouldNotExist"))
641 {
642 throw new Exception("Engine defined a variable that should not exist: TestStringVariableShouldNotExist");
643 }
644 else
645 {
646 this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent string variable: TestStringVariableShouldNotExist");
647 }
648
649 if (this.Engine.ContainsVariable("TestNumericVariableShouldNotExist"))
650 {
651 throw new Exception("Engine defined a variable that should not exist: TestNumericVariableShouldNotExist");
652 }
653 else
654 {
655 this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent numeric variable: TestNumericVariableShouldNotExist");
656 }
657
658 if (this.Engine.ContainsVariable("TestVersionVariableShouldNotExist"))
659 {
660 throw new Exception("Engine defined a variable that should not exist: TestVersionVariableShouldNotExist");
661 }
662 else
663 {
664 this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent version variable: TestVersionVariableShouldNotExist");
665 }
666
667 // Output what the initially run privileges were.
668 this.Engine.Log(LogLevel.Verbose, String.Format("TEST: WixBundleElevated = {0}", this.Engine.GetVariableNumeric("WixBundleElevated")));
669 }
670
671 private void Log(string format, params object[] args)
672 {
673 string relation = this.Command.Relation != RelationType.None ? String.Concat(" (", this.Command.Relation.ToString().ToLowerInvariant(), ")") : String.Empty;
674 string message = String.Format(format, args);
675
676 this.Engine.Log(LogLevel.Standard, String.Concat("TESTBA", relation, ": ", message));
677 }
678
679 private void ImportContainerSources()
680 {
681 string testName = this.Engine.GetVariableString("TestGroupName");
682 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\container", testName)))
683 {
684 if (testKey == null)
685 {
686 return;
687 }
688
689 foreach (var containerId in testKey.GetSubKeyNames())
690 {
691 using (RegistryKey subkey = testKey.OpenSubKey(containerId))
692 {
693 string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string;
694 if (initialSource != null)
695 {
696 this.Engine.SetLocalSource(containerId, null, initialSource);
697 }
698 }
699 }
700 }
701 }
702
703 private void ImportPayloadSources()
704 {
705 string testName = this.Engine.GetVariableString("TestGroupName");
706 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\payload", testName)))
707 {
708 if (testKey == null)
709 {
710 return;
711 }
712
713 foreach (var payloadId in testKey.GetSubKeyNames())
714 {
715 using (RegistryKey subkey = testKey.OpenSubKey(payloadId))
716 {
717 string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string;
718 if (initialSource != null)
719 {
720 this.Engine.SetLocalSource(null, payloadId, initialSource);
721 }
722 }
723 }
724 }
725 }
726
727 private List<string> ReadVerifyArguments()
728 {
729 string testName = this.Engine.GetVariableString("TestGroupName");
730 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}", testName)))
731 {
732 string verifyArguments = testKey == null ? null : testKey.GetValue("VerifyArguments") as string;
733 return verifyArguments == null ? new List<string>() : new List<string>(verifyArguments.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
734 }
735 }
736
737 private string ReadPackageAction(string packageId, string state)
738 {
739 string testName = this.Engine.GetVariableString("TestGroupName");
740 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\{1}", testName, String.IsNullOrEmpty(packageId) ? String.Empty : packageId)))
741 {
742 return testKey == null ? null : testKey.GetValue(state) as string;
743 }
744 }
745
746 private string ReadFeatureAction(string packageId, string featureId, string state)
747 {
748 string testName = this.Engine.GetVariableString("TestGroupName");
749 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\{1}", testName, packageId)))
750 {
751 string registryName = String.Concat(featureId, state);
752 return testKey == null ? null : testKey.GetValue(registryName) as string;
753 }
754 }
755
756 private string ReadTestRegistryValue(string name)
757 {
758 string testName = this.Engine.GetVariableString("TestGroupName");
759 using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\{0}\{1}", testName, name)))
760 {
761 return testKey == null ? null : testKey.GetValue("Version") as string;
762 }
763 }
764
765 private static bool TryParseEnum<T>(string value, out T t)
766 {
767 try
768 {
769 t = (T)Enum.Parse(typeof(T), value, true);
770 return true;
771 }
772 catch (ArgumentException) { }
773 catch (OverflowException) { }
774
775 t = default(T);
776 return false;
777 }
778 }
779 }