// 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.Test.BA { using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Windows.Forms; using Microsoft.Win32; using WixToolset.BootstrapperApplicationApi; /// <summary> /// A minimal UX used for testing. /// </summary> public class TestBA : BootstrapperApplication { private const string BurnBundleVersionVariable = "WixBundleVersion"; private Form dummyWindow; private IntPtr windowHandle; private LaunchAction action; private readonly ManualResetEvent wait; private int result; private string updateBundlePath; private bool allowAcquireAfterValidationFailure; private bool forceKeepRegistration; private bool immediatelyQuit; private bool quitAfterDetect; private bool explicitlyElevateAndPlanFromOnElevateBegin; private int redetectRemaining; private int sleepDuringCache; private int cancelCacheAtProgress; private int sleepDuringExecute; private int cancelExecuteAtProgress; private string cancelExecuteActionName; private int cancelOnProgressAtProgress; private int retryExecuteFilesInUse; private bool rollingBack; private IBootstrapperCommand Command { get; set; } private IEngine Engine => this.engine; /// <summary> /// Initializes test user experience. /// </summary> public TestBA() { this.wait = new ManualResetEvent(false); } /// <summary> /// Get the version of the install. /// </summary> public string Version { get; private set; } /// <summary> /// Indicates if DetectUpdate found a newer version to update. /// </summary> private bool UpdateAvailable { get; set; } protected override void OnCreate(CreateEventArgs args) { base.OnCreate(args); this.Command = args.Command; } /// <summary> /// UI Thread entry point for TestUX. /// </summary> protected override void OnStartup(StartupEventArgs args) { string immediatelyQuit = this.ReadPackageAction(null, "ImmediatelyQuit"); if (!String.IsNullOrEmpty(immediatelyQuit) && Boolean.TryParse(immediatelyQuit, out this.immediatelyQuit) && this.immediatelyQuit) { this.Engine.Quit(0); return; } this.action = this.Command.Action; this.TestVariables(); this.Version = this.engine.GetVariableVersion(BurnBundleVersionVariable); this.Log("Version: {0}", this.Version); List<string> verifyArguments = this.ReadVerifyArguments(); IBootstrapperApplicationData baManifest = new BootstrapperApplicationData(); IMbaCommand mbaCommand = this.Command.ParseCommandLine(); mbaCommand.SetOverridableVariables(baManifest.Bundle.OverridableVariables, this.engine); foreach (string arg in mbaCommand.UnknownCommandLineArgs) { // If we're not in the update already, process the updatebundle. if (this.Command.Relation != RelationType.Update && arg.StartsWith("-updatebundle:", StringComparison.OrdinalIgnoreCase)) { this.updateBundlePath = arg.Substring(14); FileInfo info = new FileInfo(this.updateBundlePath); this.Engine.SetUpdate(this.updateBundlePath, null, info.Length, UpdateHashType.None, null, null); this.UpdateAvailable = true; this.action = LaunchAction.UpdateReplaceEmbedded; } else if (this.Command.Relation != RelationType.Update && arg.StartsWith("-checkupdate", StringComparison.OrdinalIgnoreCase)) { this.action = LaunchAction.UpdateReplace; } verifyArguments.Remove(arg); } this.Log("Action: {0}", this.action); // If there are any verification arguments left, error out. if (0 < verifyArguments.Count) { foreach (string expectedArg in verifyArguments) { this.Log("Failure. Expected command-line to have argument: {0}", expectedArg); } this.Engine.Quit(-1); return; } base.OnStartup(args); string redetect = this.ReadPackageAction(null, "RedetectCount"); if (String.IsNullOrEmpty(redetect) || !Int32.TryParse(redetect, out var redetectCount)) { redetectCount = 0; } string allowAcquireAfterValidationFailure = this.ReadPackageAction(null, "AllowAcquireAfterValidationFailure"); if (String.IsNullOrEmpty(allowAcquireAfterValidationFailure) || !Boolean.TryParse(allowAcquireAfterValidationFailure, out this.allowAcquireAfterValidationFailure)) { this.allowAcquireAfterValidationFailure = false; } string explicitlyElevateAndPlanFromOnElevateBegin = this.ReadPackageAction(null, "ExplicitlyElevateAndPlanFromOnElevateBegin"); if (String.IsNullOrEmpty(explicitlyElevateAndPlanFromOnElevateBegin) || !Boolean.TryParse(explicitlyElevateAndPlanFromOnElevateBegin, out this.explicitlyElevateAndPlanFromOnElevateBegin)) { this.explicitlyElevateAndPlanFromOnElevateBegin = false; } string forceKeepRegistration = this.ReadPackageAction(null, "ForceKeepRegistration"); if (String.IsNullOrEmpty(forceKeepRegistration) || !Boolean.TryParse(forceKeepRegistration, out this.forceKeepRegistration)) { this.forceKeepRegistration = false; } string quitAfterDetect = this.ReadPackageAction(null, "QuitAfterDetect"); if (String.IsNullOrEmpty(quitAfterDetect) || !Boolean.TryParse(quitAfterDetect, out this.quitAfterDetect)) { this.quitAfterDetect = false; } this.ImportContainerSources(); this.ImportPayloadSources(); this.wait.WaitOne(); if (this.action == LaunchAction.Help) { this.Log("This is a BA for automated testing"); this.ShutdownUiThread(0); return; } this.redetectRemaining = redetectCount; for (int i = -1; i < redetectCount; i++) { this.Engine.Detect(this.windowHandle); } } protected override void Run() { using (this.dummyWindow = new Form()) { this.windowHandle = this.dummyWindow.Handle; this.Log("Running TestBA application"); this.wait.Set(); Application.Run(); this.dummyWindow = null; } var exitCode = this.result; if ((exitCode & 0xFFFF0000) == unchecked(0x80070000)) { exitCode &= 0xFFFF; // return plain old Win32 error, not HRESULT. } this.Engine.Quit(exitCode); } private void ShutdownUiThread(int? exitCode = null) { try { if (exitCode.HasValue) { this.result = exitCode.Value; } this.dummyWindow?.Invoke(new Action(Application.ExitThread)); } catch (Exception e) { this.Log("Failed to shutdown TestBA window, exception: {0}", e.Message); } } protected override void OnDetectUpdateBegin(DetectUpdateBeginEventArgs args) { this.Log("OnDetectUpdateBegin"); if (LaunchAction.UpdateReplaceEmbedded == this.action || LaunchAction.UpdateReplace == this.action) { args.Skip = false; } } protected override void OnDetectUpdate(DetectUpdateEventArgs e) { // The list of updates is sorted in descending version, so the first callback should be the largest update available. // This update should be either larger than ours (so we are out of date), the same as ours (so we are current) // or smaller than ours (we have a private build). // Enumerate all of the updates anyway in case something's broken. this.Log(String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, this.Version)); if (!this.UpdateAvailable && this.Engine.CompareVersions(e.Version, this.Version) > 0) { this.Log(String.Format("Selected update v{0}", e.Version)); this.Engine.SetUpdate(null, e.UpdateLocation, e.Size, e.HashAlgorithm, e.Hash, null); this.UpdateAvailable = true; } } protected override void OnDetectUpdateComplete(DetectUpdateCompleteEventArgs e) { this.Log("OnDetectUpdateComplete"); // Failed to process an update, allow the existing bundle to still install. if (!Hresult.Succeeded(e.Status)) { this.Log(String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status)); e.IgnoreError = true; // But continue on... } } protected override void OnDetectComplete(DetectCompleteEventArgs args) { this.result = args.Status; if (Hresult.Succeeded(this.result) && (this.UpdateAvailable || LaunchAction.UpdateReplaceEmbedded != this.action && LaunchAction.UpdateReplace != this.action)) { if (this.redetectRemaining > 0) { this.Log("Completed detection phase: {0} re-runs remaining", this.redetectRemaining--); } else if (this.quitAfterDetect) { this.ShutdownUiThread(); } else if (this.explicitlyElevateAndPlanFromOnElevateBegin) { this.Engine.Elevate(this.windowHandle); } else { this.Engine.Plan(this.action); } } else { this.ShutdownUiThread(); } } protected override void OnDetectRelatedBundle(DetectRelatedBundleEventArgs args) { this.Log("OnDetectRelatedBundle() - id: {0}, missing from cache: {1}", args.ProductCode, args.MissingFromCache); } protected override void OnElevateBegin(ElevateBeginEventArgs args) { if (this.explicitlyElevateAndPlanFromOnElevateBegin) { this.Engine.Plan(this.action); // Simulate showing some UI since these tests won't actually show the UAC prompt. MessagePump.ProcessMessages(10); } } protected override void OnPlanPackageBegin(PlanPackageBeginEventArgs args) { RequestState state; string action = this.ReadPackageAction(args.PackageId, "Requested"); if (TryParseEnum<RequestState>(action, out state)) { args.State = state; } BOOTSTRAPPER_CACHE_TYPE cacheType; string cacheAction = this.ReadPackageAction(args.PackageId, "CacheRequested"); if (TryParseEnum<BOOTSTRAPPER_CACHE_TYPE>(cacheAction, out cacheType)) { args.CacheType = cacheType; } 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); } protected override void OnPlanPatchTarget(PlanPatchTargetEventArgs args) { RequestState state; string action = this.ReadPackageAction(args.PackageId, "Requested"); if (TryParseEnum<RequestState>(action, out state)) { args.State = state; } } protected override void OnPlanMsiFeature(PlanMsiFeatureEventArgs args) { FeatureState state; string action = this.ReadFeatureAction(args.PackageId, args.FeatureId, "Requested"); if (TryParseEnum<FeatureState>(action, out state)) { args.State = state; } this.Log("OnPlanMsiFeature() - id: {0}, defaultState: {1}, requestedState: {2}", args.PackageId, args.RecommendedState, args.State); } protected override void OnPlanComplete(PlanCompleteEventArgs args) { this.result = args.Status; if (Hresult.Succeeded(this.result)) { this.Engine.Apply(this.windowHandle); } else { this.ShutdownUiThread(); } } protected override void OnCachePackageBegin(CachePackageBeginEventArgs args) { this.Log("OnCachePackageBegin() - package: {0}, payloads to cache: {1}", args.PackageId, args.CachePayloads); string slowProgress = this.ReadPackageAction(args.PackageId, "SlowCache"); if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringCache)) { this.sleepDuringCache = 0; } else { this.Log(" SlowCache: {0}", this.sleepDuringCache); } string cancelCache = this.ReadPackageAction(args.PackageId, "CancelCacheAtProgress"); if (String.IsNullOrEmpty(cancelCache) || !Int32.TryParse(cancelCache, out this.cancelCacheAtProgress)) { this.cancelCacheAtProgress = -1; } else { this.Log(" CancelCacheAtProgress: {0}", this.cancelCacheAtProgress); } } protected override void OnCachePackageNonVitalValidationFailure(CachePackageNonVitalValidationFailureEventArgs args) { if (this.allowAcquireAfterValidationFailure) { args.Action = BOOTSTRAPPER_CACHEPACKAGENONVITALVALIDATIONFAILURE_ACTION.Acquire; } this.Log("OnCachePackageNonVitalValidationFailure() - id: {0}, default: {1}, requested: {2}", args.PackageId, args.Recommendation, args.Action); } protected override void OnCacheAcquireProgress(CacheAcquireProgressEventArgs args) { 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); if (this.cancelCacheAtProgress >= 0 && this.cancelCacheAtProgress <= args.Progress) { args.Cancel = true; this.Log("OnCacheAcquireProgress(cancel)"); } else if (this.sleepDuringCache > 0) { this.Log("OnCacheAcquireProgress(sleep {0})", this.sleepDuringCache); Thread.Sleep(this.sleepDuringCache); } } protected override void OnCacheContainerOrPayloadVerifyProgress(CacheContainerOrPayloadVerifyProgressEventArgs args) { 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); } protected override void OnCachePayloadExtractProgress(CachePayloadExtractProgressEventArgs args) { 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); } protected override void OnCacheVerifyProgress(CacheVerifyProgressEventArgs args) { 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); } protected override void OnExecutePackageBegin(ExecutePackageBeginEventArgs args) { this.Log("OnExecutePackageBegin() - package: {0}, rollback: {1}", args.PackageId, !args.ShouldExecute); this.rollingBack = !args.ShouldExecute; string slowProgress = this.ReadPackageAction(args.PackageId, "SlowExecute"); if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringExecute)) { this.sleepDuringExecute = 0; } else { this.Log(" SlowExecute: {0}", this.sleepDuringExecute); } string cancelExecute = this.ReadPackageAction(args.PackageId, "CancelExecuteAtProgress"); if (String.IsNullOrEmpty(cancelExecute) || !Int32.TryParse(cancelExecute, out this.cancelExecuteAtProgress)) { this.cancelExecuteAtProgress = -1; } else { this.Log(" CancelExecuteAtProgress: {0}", this.cancelExecuteAtProgress); } this.cancelExecuteActionName = this.ReadPackageAction(args.PackageId, "CancelExecuteAtActionStart"); if (!String.IsNullOrEmpty(this.cancelExecuteActionName)) { this.Log(" CancelExecuteAtActionState: {0}", this.cancelExecuteActionName); } string cancelOnProgressAtProgress = this.ReadPackageAction(args.PackageId, "CancelOnProgressAtProgress"); if (String.IsNullOrEmpty(cancelOnProgressAtProgress) || !Int32.TryParse(cancelOnProgressAtProgress, out this.cancelOnProgressAtProgress)) { this.cancelOnProgressAtProgress = -1; } else { this.Log(" CancelOnProgressAtProgress: {0}", this.cancelOnProgressAtProgress); } string retryBeforeCancel = this.ReadPackageAction(args.PackageId, "RetryExecuteFilesInUse"); if (String.IsNullOrEmpty(retryBeforeCancel) || !Int32.TryParse(retryBeforeCancel, out this.retryExecuteFilesInUse)) { this.retryExecuteFilesInUse = 0; } else { this.Log(" RetryExecuteFilesInUse: {0}", this.retryExecuteFilesInUse); } } protected override void OnExecutePackageComplete(ExecutePackageCompleteEventArgs args) { bool logTestRegistryValue; string recordTestRegistryValue = this.ReadPackageAction(args.PackageId, "RecordTestRegistryValue"); if (!String.IsNullOrEmpty(recordTestRegistryValue) && Boolean.TryParse(recordTestRegistryValue, out logTestRegistryValue) && logTestRegistryValue) { var value = this.ReadTestRegistryValue(args.PackageId); this.Log("TestRegistryValue: {0}, {1}, Version, '{2}'", this.rollingBack ? "Rollback" : "Execute", args.PackageId, value); } } protected override void OnExecuteProcessCancel(ExecuteProcessCancelEventArgs args) { BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION action; string actionValue = this.ReadPackageAction(args.PackageId, "ProcessCancelAction"); if (actionValue != null && TryParseEnum<BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION>(actionValue, out action)) { args.Action = action; } if (args.Action == BOOTSTRAPPER_EXECUTEPROCESSCANCEL_ACTION.Abandon) { // Kill process to make sure it doesn't affect other tests. try { using (Process process = Process.GetProcessById(args.ProcessId)) { if (process != null) { process.Kill(); } } } catch (Exception e) { this.Log("Failed to kill process {0}: {1}", args.ProcessId, e); Thread.Sleep(5000); } } this.Log("OnExecuteProcessCancel({0})", args.Action); } protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args) { this.Log("OnExecuteFilesInUse() - package: {0}, source: {1}, retries remaining: {2}, data: {3}", args.PackageId, args.Source, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray())); if (this.retryExecuteFilesInUse > 0) { --this.retryExecuteFilesInUse; args.Result = Result.Retry; } else { args.Result = Result.Cancel; } } protected override void OnExecuteMsiMessage(ExecuteMsiMessageEventArgs args) { this.Log("OnExecuteMsiMessage() - MessageType: {0}, Message: {1}, Data: '{2}'", args.MessageType, args.Message, String.Join("','", args.Data.ToArray())); if (!String.IsNullOrEmpty(this.cancelExecuteActionName) && args.MessageType == InstallMessage.ActionStart && args.Data.Count > 0 && args.Data[0] == this.cancelExecuteActionName) { this.Log("OnExecuteMsiMessage(cancelNextProgress)"); this.cancelExecuteAtProgress = 0; } } protected override void OnExecuteProgress(ExecuteProgressEventArgs args) { this.Log("OnExecuteProgress() - package: {0}, progress: {1}%, overall progress: {2}%", args.PackageId, args.ProgressPercentage, args.OverallPercentage); if (this.cancelExecuteAtProgress >= 0 && this.cancelExecuteAtProgress <= args.ProgressPercentage) { args.Cancel = true; this.Log("OnExecuteProgress(cancel)"); } else if (this.sleepDuringExecute > 0) { this.Log("OnExecuteProgress(sleep {0})", this.sleepDuringExecute); Thread.Sleep(this.sleepDuringExecute); } } protected override void OnExecutePatchTarget(ExecutePatchTargetEventArgs args) { this.Log("OnExecutePatchTarget - Patch Package: {0}, Target Product Code: {1}", args.PackageId, args.TargetProductCode); } protected override void OnProgress(ProgressEventArgs args) { this.Log("OnProgress() - progress: {0}%, overall progress: {1}%", args.ProgressPercentage, args.OverallPercentage); if (this.Command.Display == Display.Embedded) { this.Engine.SendEmbeddedProgress(args.ProgressPercentage, args.OverallPercentage); } if (this.cancelOnProgressAtProgress >= 0 && this.cancelOnProgressAtProgress <= args.OverallPercentage) { args.Cancel = true; this.Log("OnProgress(cancel)"); } } protected override void OnApplyBegin(ApplyBeginEventArgs args) { this.cancelOnProgressAtProgress = -1; this.cancelExecuteAtProgress = -1; this.cancelCacheAtProgress = -1; this.rollingBack = false; } protected override void OnApplyComplete(ApplyCompleteEventArgs args) { // Output what the privileges are now. this.Log("After elevation: WixBundleElevated = {0}", this.Engine.GetVariableNumeric("WixBundleElevated")); this.ShutdownUiThread(args.Status); } protected override void OnUnregisterBegin(UnregisterBeginEventArgs args) { if (this.forceKeepRegistration && args.RegistrationType == RegistrationType.None) { args.RegistrationType = RegistrationType.InProgress; } this.Log("OnUnregisterBegin, default: {0}, requested: {1}", args.RecommendedRegistrationType, args.RegistrationType); } private void TestVariables() { // First make sure we can check and get standard variables of each type. if (this.Engine.ContainsVariable("WindowsFolder")) { string value = this.Engine.GetVariableString("WindowsFolder"); this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a string variable: WindowsFolder '{0}'", value)); } else { throw new Exception("Engine did not define a standard variable: WindowsFolder"); } if (this.Engine.ContainsVariable("NTProductType")) { long value = this.Engine.GetVariableNumeric("NTProductType"); this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a numeric variable: NTProductType '{0}'", value)); } else { throw new Exception("Engine did not define a standard variable: NTProductType"); } if (this.Engine.ContainsVariable("VersionMsi")) { string value = this.Engine.GetVariableVersion("VersionMsi"); this.Engine.Log(LogLevel.Verbose, String.Format("TEST: Successfully retrieved a version variable: VersionMsi '{0}'", value)); } else { throw new Exception("Engine did not define a standard variable: VersionMsi"); } // Now validate that Contians returns false for non-existant variables of each type. if (this.Engine.ContainsVariable("TestStringVariableShouldNotExist")) { throw new Exception("Engine defined a variable that should not exist: TestStringVariableShouldNotExist"); } else { this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent string variable: TestStringVariableShouldNotExist"); } if (this.Engine.ContainsVariable("TestNumericVariableShouldNotExist")) { throw new Exception("Engine defined a variable that should not exist: TestNumericVariableShouldNotExist"); } else { this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent numeric variable: TestNumericVariableShouldNotExist"); } if (this.Engine.ContainsVariable("TestVersionVariableShouldNotExist")) { throw new Exception("Engine defined a variable that should not exist: TestVersionVariableShouldNotExist"); } else { this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent version variable: TestVersionVariableShouldNotExist"); } // Output what the initially run privileges were. this.Engine.Log(LogLevel.Verbose, String.Format("TEST: WixBundleElevated = {0}", this.Engine.GetVariableNumeric("WixBundleElevated"))); } private void Log(string format, params object[] args) { string relation = this.Command.Relation != RelationType.None ? String.Concat(" (", this.Command.Relation.ToString().ToLowerInvariant(), ")") : String.Empty; string message = String.Format(format, args); this.Engine.Log(LogLevel.Standard, String.Concat("TESTBA", relation, ": ", message)); } private void ImportContainerSources() { string testName = this.Engine.GetVariableString("TestGroupName"); using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\container", testName))) { if (testKey == null) { return; } foreach (var containerId in testKey.GetSubKeyNames()) { using (RegistryKey subkey = testKey.OpenSubKey(containerId)) { string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string; if (initialSource != null) { this.Engine.SetLocalSource(containerId, null, initialSource); } } } } } private void ImportPayloadSources() { string testName = this.Engine.GetVariableString("TestGroupName"); using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\payload", testName))) { if (testKey == null) { return; } foreach (var payloadId in testKey.GetSubKeyNames()) { using (RegistryKey subkey = testKey.OpenSubKey(payloadId)) { string initialSource = subkey == null ? null : subkey.GetValue("InitialLocalSource") as string; if (initialSource != null) { this.Engine.SetLocalSource(null, payloadId, initialSource); } } } } } private List<string> ReadVerifyArguments() { string testName = this.Engine.GetVariableString("TestGroupName"); using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}", testName))) { string verifyArguments = testKey == null ? null : testKey.GetValue("VerifyArguments") as string; return verifyArguments == null ? new List<string>() : new List<string>(verifyArguments.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); } } private string ReadPackageAction(string packageId, string state) { string testName = this.Engine.GetVariableString("TestGroupName"); using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\{1}", testName, String.IsNullOrEmpty(packageId) ? String.Empty : packageId))) { return testKey == null ? null : testKey.GetValue(state) as string; } } private string ReadFeatureAction(string packageId, string featureId, string state) { string testName = this.Engine.GetVariableString("TestGroupName"); using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\{1}", testName, packageId))) { string registryName = String.Concat(featureId, state); return testKey == null ? null : testKey.GetValue(registryName) as string; } } private string ReadTestRegistryValue(string name) { string testName = this.Engine.GetVariableString("TestGroupName"); using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\{0}\{1}", testName, name))) { return testKey == null ? null : testKey.GetValue("Version") as string; } } private static bool TryParseEnum<T>(string value, out T t) { try { t = (T)Enum.Parse(typeof(T), value, true); return true; } catch (ArgumentException) { } catch (OverflowException) { } t = default(T); return false; } } }