From 0d4df529a7e34f033112a1c6f16f749880334e7d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 16 Mar 2021 10:52:55 -0700 Subject: Use validation now implemented in Core.Native Fixes wixtoolset/issues#5946 --- .../Bind/BindDatabaseCommand.cs | 50 ++- .../Bind/ValidateDatabaseCommand.cs | 177 ++++++++++ src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 4 +- src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 4 +- src/WixToolset.Core.WindowsInstaller/Validator.cs | 365 --------------------- src/WixToolset.Core/CommandLine/BuildCommand.cs | 28 +- 6 files changed, 227 insertions(+), 401 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Validator.cs diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index df1c824a..6503ba65 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -21,11 +21,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator) : this(context, backendExtension, null, validator) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) { } - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, Validator validator) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) { this.ServiceProvider = context.ServiceProvider; @@ -50,7 +50,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.SuppressLayout = context.SuppressLayout; this.SubStorages = subStorages; - this.Validator = validator; + + this.SuppressValidation = context.SuppressValidation; + this.Ices = context.Ices; + this.SuppressedIces = context.SuppressIces; + this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; this.BackendExtensions = backendExtension; } @@ -97,7 +101,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind private string IntermediateFolder { get; } - private Validator Validator { get; } + private bool SuppressValidation { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private IEnumerable CubeFiles { get; } public IBindResult Execute() { @@ -522,34 +532,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (this.Messaging.EncounteredError) { return null; - } + } -#if TODO_FINISH_VALIDATION - // Validate the output if there is an MSI validator. - if (null != this.Validator) + // Validate the output if there are CUBe files and we're not explicitly suppressing validation. + if (this.CubeFiles != null && !this.SuppressValidation) { - Stopwatch stopwatch = Stopwatch.StartNew(); - - // set the output file for source line information - this.Validator.Output = this.Output; - - Messaging.Instance.Write(WixVerboses.ValidatingDatabase()); - - this.Validator.Validate(this.OutputPath); - - stopwatch.Stop(); - Messaging.Instance.Write(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + var command = new ValidateDatabaseCommand(this.Messaging, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); + command.Execute(); + } - // Stop processing if an error occurred. - if (Messaging.Instance.EncounteredError) - { - return; - } + if (this.Messaging.EncounteredError) + { + return null; } -#endif // Process uncompressed files. - if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) + if (!this.SuppressLayout && uncompressedFiles.Any()) { var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); command.Compressed = compressed; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs new file mode 100644 index 00000000..35b6018c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs @@ -0,0 +1,177 @@ +// 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.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Threading; + using WixToolset.Core.Native; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback + { + // Set of ICEs that have equivalent-or-better checks in WiX. + private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; + + public ValidateDatabaseCommand(IMessaging messaging, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) + { + this.Messaging = messaging; + this.Data = data; + this.OutputPath = outputPath; + this.CubeFiles = cubeFiles; + this.Ices = ices; + this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); + + this.IntermediateFolder = intermediateFolder; + this.OutputSourceLineNumber = new SourceLineNumber(outputPath); + } + + /// + /// Encountered error implementation for . + /// + public bool EncounteredError => this.Messaging.EncounteredError; + + private IMessaging Messaging { get; } + + private WindowsInstallerData Data { get; } + + private string OutputPath { get; } + + private IEnumerable CubeFiles { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private string IntermediateFolder { get; } + + /// + /// Fallback when an exact source line number cannot be calculated for a validation error. + /// + private SourceLineNumber OutputSourceLineNumber { get; set; } + + private Dictionary SourceLineNumbersByTablePrimaryKey { get; set; } + + public void Execute() + { + var stopwatch = Stopwatch.StartNew(); + + this.Messaging.Write(VerboseMessages.ValidatingDatabase()); + + // Ensure the temporary files can be created the working folder. + var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); + Directory.CreateDirectory(workingFolder); + + // Copy the database to a temporary location so it can be manipulated. + // Ensure it is not read-only. + var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); + FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); + + var attributes = File.GetAttributes(workingDatabasePath); + File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); + + var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); + validator.Execute(); + + stopwatch.Stop(); + this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + } + + private void LogValidationMessage(ValidationMessage message) + { + var messageSourceLineNumbers = this.OutputSourceLineNumber; + if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) + { + messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); + } + + switch (message.Type) + { + case ValidationMessageType.InternalFailure: + case ValidationMessageType.Error: + this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Warning: + this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Info: + this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); + break; + default: + throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); + } + } + + /// + /// Validation blocked by other installation operation for . + /// + public void ValidationBlocked() + { + this.Messaging.Write(VerboseMessages.ValidationSerialized()); + } + + /// + /// Validation message implementation for . + /// + public bool ValidationMessage(ValidationMessage message) + { + this.LogValidationMessage(message); + return true; + } + + /// + /// Gets the source line information (if available) for a row by its table name and primary key. + /// + /// The table name of the row. + /// The primary keys of the row. + /// The source line number information if found; null otherwise. + private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable primaryKeys) + { + // Source line information only exists if an output file was supplied + if (this.Data == null) + { + // Use the file name as the source line information. + return this.OutputSourceLineNumber; + } + + // Index the source line information if it hasn't been indexed already. + if (this.SourceLineNumbersByTablePrimaryKey == null) + { + this.SourceLineNumbersByTablePrimaryKey = new Dictionary(); + + // Index each real table + foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) + { + // Index each row that contain source line information + foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) + { + // Index the row using its table name and primary key + var primaryKey = row.GetPrimaryKey(';'); + + if (!String.IsNullOrEmpty(primaryKey)) + { + try + { + var key = String.Concat(table.Name, ":", primaryKey); + this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); + } + catch (ArgumentException) + { + this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); + } + } + } + } + } + + return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs index a6cf4f60..3bd58c25 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -24,13 +24,11 @@ namespace WixToolset.Core.WindowsInstaller extension.PreBackendBind(context); } - var validator = Validator.CreateFromContext(context, "darice.cub"); - IBindResult result = null; var dispose = true; try { - var command = new BindDatabaseCommand(context, backendExtensions, validator); + var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); result = command.Execute(); foreach (var extension in backendExtensions) diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs index 96197d44..4927ee8c 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -23,12 +23,10 @@ namespace WixToolset.Core.WindowsInstaller extension.PreBackendBind(context); } - var validator = Validator.CreateFromContext(context, "mergemod.cub"); - IBindResult result = null; try { - var command = new BindDatabaseCommand(context, backendExtensions, validator); + var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); result = command.Execute(); foreach (var extension in backendExtensions) diff --git a/src/WixToolset.Core.WindowsInstaller/Validator.cs b/src/WixToolset.Core.WindowsInstaller/Validator.cs deleted file mode 100644 index a6a41bd7..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Validator.cs +++ /dev/null @@ -1,365 +0,0 @@ -// 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.Core.WindowsInstaller -{ - using System; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.ComponentModel; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Threading; - using WixToolset.Core.WindowsInstaller.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Runs internal consistency evaluators (ICEs) from cub files against a database. - /// - internal sealed class Validator - { - private string actionName; - private readonly StringCollection cubeFiles; - private ValidatorExtension extension; - private WindowsInstallerData output; - private readonly InstallUIHandler validationUIHandler; - private bool validationSessionComplete; - private readonly IMessaging messaging; - - /// - /// Instantiate a new Validator. - /// - public Validator(IMessaging messaging) - { - this.cubeFiles = new StringCollection(); - this.extension = new ValidatorExtension(messaging); - this.validationUIHandler = new InstallUIHandler(this.ValidationUIHandler); - this.messaging = messaging; - } - - /// - /// Gets or sets a that directs messages from the validator. - /// - /// A that directs messages from the validator. - public ValidatorExtension Extension - { - get { return this.extension; } - set { this.extension = value; } - } - - /// - /// Gets or sets the list of ICEs to run. - /// - /// The list of ICEs. - public ISet ICEs { get; set; } - - /// - /// Gets or sets the output used for finding source line information. - /// - /// The output used for finding source line information. - public WindowsInstallerData Output - { - // cache Output object until validation for changes in extension - get { return this.output; } - set { this.output = value; } - } - - /// - /// Gets or sets the suppressed ICEs. - /// - /// The suppressed ICEs. - public ISet SuppressedICEs { get; set; } - - /// - /// Sets the temporary path for the Binder. - /// - public string IntermediateFolder { private get; set; } - - /// - /// Add a cube file to the validation run. - /// - /// A cube file. - public void AddCubeFile(string cubeFile) - { - this.cubeFiles.Add(cubeFile); - } - - /// - /// Validate a database. - /// - /// The database to validate. - /// true if validation succeeded; false otherwise. - public void Validate(string databaseFile) - { - int previousUILevel = (int)InstallUILevels.Basic; - IntPtr previousHwnd = IntPtr.Zero; - InstallUIHandler previousUIHandler = null; - - // initialize the validator extension - this.extension.DatabaseFile = databaseFile ?? throw new ArgumentNullException(nameof(databaseFile)); - this.extension.Output = this.output; - this.extension.InitializeValidator(); - - // Ensure the temporary files can be created. - Directory.CreateDirectory(this.IntermediateFolder); - - // copy the database to a temporary location so it can be manipulated - string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(databaseFile)); - File.Copy(databaseFile, tempDatabaseFile); - - // remove the read-only property from the temporary database - FileAttributes attributes = File.GetAttributes(tempDatabaseFile); - File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); - - Mutex mutex = new Mutex(false, "WixValidator"); - try - { - if (!mutex.WaitOne(0, false)) - { - this.messaging.Write(VerboseMessages.ValidationSerialized()); - mutex.WaitOne(); - } - - using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) - { - bool propertyTableExists = database.TableExists("Property"); - string productCode = null; - - // remove the product code from the database before opening a session to prevent opening an installed product - if (propertyTableExists) - { - using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) - { - using (Record record = view.Fetch()) - { - if (null != record) - { - productCode = record.GetString(1); - - using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) - { - } - } - } - } - } - - // merge in the cube databases - foreach (string cubeFile in this.cubeFiles) - { - try - { - using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) - { - try - { - database.Merge(cubeDatabase, "MergeConflicts"); - } - catch - { - // ignore merge errors since they are expected in the _Validation table - } - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(ErrorMessages.CubeFileNotFound(cubeFile)); - } - - throw; - } - } - - // commit the database before proceeding to ensure the streams don't get confused - database.Commit(); - - // the property table may have been added to the database - // from a cub database without the proper validation rows - if (!propertyTableExists) - { - using (View view = database.OpenExecuteView("DROP table `Property`")) - { - } - } - - // get all the action names for ICEs which have not been suppressed - List actions = new List(); - using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) - { - foreach (Record record in view.Records) - { - string action = record.GetString(1); - - if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action))) - { - actions.Add(action); - } - } - } - - // disable the internal UI handler and set an external UI handler - previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); - previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); - - // create a session for running the ICEs - this.validationSessionComplete = false; - using (Session session = new Session(database)) - { - // add the product code back into the database - if (null != productCode) - { - // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up - using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) - { - } - - using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) - { - } - } - - foreach (string action in actions) - { - this.actionName = action; - try - { - session.DoAction(action); - } - catch (Win32Exception e) - { - if (!this.messaging.EncounteredError) - { - throw e; - } - // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird. - //else - //{ - // this.encounteredError = false; - //} - } - this.actionName = null; - } - - // Mark the validation session complete so we ignore any messages that MSI may fire - // during session clean-up. - this.validationSessionComplete = true; - } - } - } - catch (Win32Exception e) - { - // avoid displaying errors twice since one may have already occurred in the UI handler - if (!this.messaging.EncounteredError) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - // databaseFile is not passed since during light - // this would be the temporary copy and there would be - // no final output since the error occured; during smoke - // they should know the path passed into smoke - this.messaging.Write(ErrorMessages.ValidationFailedToOpenDatabase()); - } - else if (0x64D == e.NativeErrorCode) - { - this.messaging.Write(ErrorMessages.ValidationFailedDueToLowMsiEngine()); - } - else if (0x654 == e.NativeErrorCode) - { - this.messaging.Write(ErrorMessages.ValidationFailedDueToInvalidPackage()); - } - else if (0x658 == e.NativeErrorCode) - { - this.messaging.Write(ErrorMessages.ValidationFailedDueToMultilanguageMergeModule()); - } - else if (0x659 == e.NativeErrorCode) - { - this.messaging.Write(WarningMessages.ValidationFailedDueToSystemPolicy()); - } - else - { - string msgTemp = e.Message; - - if (null != this.actionName) - { - msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message); - } - - this.messaging.Write(ErrorMessages.Win32Exception(e.NativeErrorCode, msgTemp)); - } - } - } - finally - { - Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); - Installer.SetInternalUI(previousUILevel, ref previousHwnd); - - this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag. - - mutex.ReleaseMutex(); - this.cubeFiles.Clear(); - this.extension.FinalizeValidator(); - } - } - - public static Validator CreateFromContext(IBindContext context, string cubeFilename) - { - Validator validator = null; - var messaging = context.ServiceProvider.GetService(); - - // Tell the binder about the validator if validation isn't suppressed - if (!context.SuppressValidation) - { - validator = new Validator(messaging); - validator.IntermediateFolder = Path.Combine(context.IntermediateFolder, "validate"); - - // set the default cube file - string thisPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - validator.AddCubeFile(Path.Combine(thisPath, cubeFilename)); - - // Set the ICEs - validator.ICEs = new SortedSet(context.Ices); - - // Set the suppressed ICEs and disable ICEs that have equivalent-or-better checks in WiX. - validator.SuppressedICEs = new SortedSet(context.SuppressIces.Union(new[] { "ICE08", "ICE33", "ICE47", "ICE66" })); - } - - return validator; - } - - /// - /// The validation external UI handler. - /// - /// Pointer to an application context. - /// This parameter can be used for error checking. - /// Specifies a combination of one message box style, - /// one message box icon type, one default button, and one installation message type. - /// Specifies the message text. - /// -1 for an error, 0 if no action was taken, 1 if OK, 3 to abort. - private int ValidationUIHandler(IntPtr context, uint messageType, string message) - { - try - { - // If we're getting messges during the validation session, send them to - // the extension. Otherwise, ignore the messages. - if (!this.validationSessionComplete) - { - this.extension.Log(message, this.actionName); - } - } - catch (WixException ex) - { - this.messaging.Write(ex.Error); - } - - return 1; - } - } -} diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index baca91ba..eda93f31 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -344,14 +344,14 @@ namespace WixToolset.Core.CommandLine context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; context.Extensions = this.ExtensionManager.GetServices(); context.FileSystemExtensions = this.ExtensionManager.GetServices(); - context.Ices = Array.Empty(); // TODO: set this correctly + context.Ices = this.commandLine.Ices; context.IntermediateFolder = intermediateFolder; context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; context.OutputPath = this.OutputFile; context.PdbType = this.PdbType; context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); - context.SuppressIces = Array.Empty(); // TODO: set this correctly - context.SuppressValidation = true; // TODO: set this correctly + context.SuppressIces = this.commandLine.SuppressIces; + context.SuppressValidation = this.commandLine.SuppressValidation; context.CancellationToken = cancellationToken; var binder = this.ServiceProvider.GetService(); @@ -541,6 +541,12 @@ namespace WixToolset.Core.CommandLine public string BuiltOutputsFile { get; private set; } + public List Ices { get; } = new List(); + + public List SuppressIces { get; } = new List(); + + public bool SuppressValidation { get; set; } + public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) { this.ServiceProvider = serviceProvider; @@ -634,6 +640,13 @@ namespace WixToolset.Core.CommandLine parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); return true; + case "ice": + { + var value = parser.GetNextArgumentOrError(arg); + this.Ices.Add(value); + return true; + } + case "intermediatefolder": this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); return true; @@ -670,6 +683,13 @@ namespace WixToolset.Core.CommandLine return false; } + case "sice": + { + var value = parser.GetNextArgumentOrError(arg); + this.SuppressIces.Add(value); + return true; + } + case "nologo": this.ShowLogo = false; return true; @@ -680,7 +700,7 @@ namespace WixToolset.Core.CommandLine return true; case "sval": - // todo: implement + this.SuppressValidation = true; return true; } -- cgit v1.2.3-55-g6feb