aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-03-16 10:52:55 -0700
committerRob Mensching <rob@firegiant.com>2021-03-16 11:07:44 -0700
commit0d4df529a7e34f033112a1c6f16f749880334e7d (patch)
tree4c472f0f49ef732951f211221aa0524706523146 /src
parent60f75abcd1fe49052c118a2597ac59a82c372b64 (diff)
downloadwix-0d4df529a7e34f033112a1c6f16f749880334e7d.tar.gz
wix-0d4df529a7e34f033112a1c6f16f749880334e7d.tar.bz2
wix-0d4df529a7e34f033112a1c6f16f749880334e7d.zip
Use validation now implemented in Core.Native
Fixes wixtoolset/issues#5946
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs50
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs177
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MsiBackend.cs4
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MsmBackend.cs4
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Validator.cs365
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs28
6 files changed, 227 insertions, 401 deletions
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
21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. 21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); 22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
23 23
24 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, Validator validator) : this(context, backendExtension, null, validator) 24 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile)
25 { 25 {
26 } 26 }
27 27
28 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> subStorages, Validator validator) 28 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> subStorages, string cubeFile)
29 { 29 {
30 this.ServiceProvider = context.ServiceProvider; 30 this.ServiceProvider = context.ServiceProvider;
31 31
@@ -50,7 +50,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50 this.SuppressLayout = context.SuppressLayout; 50 this.SuppressLayout = context.SuppressLayout;
51 51
52 this.SubStorages = subStorages; 52 this.SubStorages = subStorages;
53 this.Validator = validator; 53
54 this.SuppressValidation = context.SuppressValidation;
55 this.Ices = context.Ices;
56 this.SuppressedIces = context.SuppressIces;
57 this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile };
54 58
55 this.BackendExtensions = backendExtension; 59 this.BackendExtensions = backendExtension;
56 } 60 }
@@ -97,7 +101,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
97 101
98 private string IntermediateFolder { get; } 102 private string IntermediateFolder { get; }
99 103
100 private Validator Validator { get; } 104 private bool SuppressValidation { get; }
105
106 private IEnumerable<string> Ices { get; }
107
108 private IEnumerable<string> SuppressedIces { get; }
109
110 private IEnumerable<string> CubeFiles { get; }
101 111
102 public IBindResult Execute() 112 public IBindResult Execute()
103 { 113 {
@@ -522,34 +532,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind
522 if (this.Messaging.EncounteredError) 532 if (this.Messaging.EncounteredError)
523 { 533 {
524 return null; 534 return null;
525 } 535 }
526 536
527#if TODO_FINISH_VALIDATION 537 // Validate the output if there are CUBe files and we're not explicitly suppressing validation.
528 // Validate the output if there is an MSI validator. 538 if (this.CubeFiles != null && !this.SuppressValidation)
529 if (null != this.Validator)
530 { 539 {
531 Stopwatch stopwatch = Stopwatch.StartNew(); 540 var command = new ValidateDatabaseCommand(this.Messaging, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces);
532 541 command.Execute();
533 // set the output file for source line information 542 }
534 this.Validator.Output = this.Output;
535
536 Messaging.Instance.Write(WixVerboses.ValidatingDatabase());
537
538 this.Validator.Validate(this.OutputPath);
539
540 stopwatch.Stop();
541 Messaging.Instance.Write(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
542 543
543 // Stop processing if an error occurred. 544 if (this.Messaging.EncounteredError)
544 if (Messaging.Instance.EncounteredError) 545 {
545 { 546 return null;
546 return;
547 }
548 } 547 }
549#endif
550 548
551 // Process uncompressed files. 549 // Process uncompressed files.
552 if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) 550 if (!this.SuppressLayout && uncompressedFiles.Any())
553 { 551 {
554 var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); 552 var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver);
555 command.Compressed = compressed; 553 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 @@
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.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Diagnostics;
9 using System.IO;
10 using System.Linq;
11 using System.Threading;
12 using WixToolset.Core.Native;
13 using WixToolset.Core.Native.Msi;
14 using WixToolset.Data;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility.Services;
17
18 internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback
19 {
20 // Set of ICEs that have equivalent-or-better checks in WiX.
21 private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" };
22
23 public ValidateDatabaseCommand(IMessaging messaging, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable<string> cubeFiles, IEnumerable<string> ices, IEnumerable<string> suppressedIces)
24 {
25 this.Messaging = messaging;
26 this.Data = data;
27 this.OutputPath = outputPath;
28 this.CubeFiles = cubeFiles;
29 this.Ices = ices;
30 this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces);
31
32 this.IntermediateFolder = intermediateFolder;
33 this.OutputSourceLineNumber = new SourceLineNumber(outputPath);
34 }
35
36 /// <summary>
37 /// Encountered error implementation for <see cref="IWindowsInstallerValidatorCallback"/>.
38 /// </summary>
39 public bool EncounteredError => this.Messaging.EncounteredError;
40
41 private IMessaging Messaging { get; }
42
43 private WindowsInstallerData Data { get; }
44
45 private string OutputPath { get; }
46
47 private IEnumerable<string> CubeFiles { get; }
48
49 private IEnumerable<string> Ices { get; }
50
51 private IEnumerable<string> SuppressedIces { get; }
52
53 private string IntermediateFolder { get; }
54
55 /// <summary>
56 /// Fallback when an exact source line number cannot be calculated for a validation error.
57 /// </summary>
58 private SourceLineNumber OutputSourceLineNumber { get; set; }
59
60 private Dictionary<string, SourceLineNumber> SourceLineNumbersByTablePrimaryKey { get; set; }
61
62 public void Execute()
63 {
64 var stopwatch = Stopwatch.StartNew();
65
66 this.Messaging.Write(VerboseMessages.ValidatingDatabase());
67
68 // Ensure the temporary files can be created the working folder.
69 var workingFolder = Path.Combine(this.IntermediateFolder, "_validate");
70 Directory.CreateDirectory(workingFolder);
71
72 // Copy the database to a temporary location so it can be manipulated.
73 // Ensure it is not read-only.
74 var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath));
75 FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false);
76
77 var attributes = File.GetAttributes(workingDatabasePath);
78 File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly);
79
80 var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces);
81 validator.Execute();
82
83 stopwatch.Stop();
84 this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
85 }
86
87 private void LogValidationMessage(ValidationMessage message)
88 {
89 var messageSourceLineNumbers = this.OutputSourceLineNumber;
90 if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null)
91 {
92 messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys);
93 }
94
95 switch (message.Type)
96 {
97 case ValidationMessageType.InternalFailure:
98 case ValidationMessageType.Error:
99 this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description));
100 break;
101 case ValidationMessageType.Warning:
102 this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description));
103 break;
104 case ValidationMessageType.Info:
105 this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description));
106 break;
107 default:
108 throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString()));
109 }
110 }
111
112 /// <summary>
113 /// Validation blocked by other installation operation for <see cref="IWindowsInstallerValidatorCallback"/>.
114 /// </summary>
115 public void ValidationBlocked()
116 {
117 this.Messaging.Write(VerboseMessages.ValidationSerialized());
118 }
119
120 /// <summary>
121 /// Validation message implementation for <see cref="IWindowsInstallerValidatorCallback"/>.
122 /// </summary>
123 public bool ValidationMessage(ValidationMessage message)
124 {
125 this.LogValidationMessage(message);
126 return true;
127 }
128
129 /// <summary>
130 /// Gets the source line information (if available) for a row by its table name and primary key.
131 /// </summary>
132 /// <param name="tableName">The table name of the row.</param>
133 /// <param name="primaryKeys">The primary keys of the row.</param>
134 /// <returns>The source line number information if found; null otherwise.</returns>
135 private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable<string> primaryKeys)
136 {
137 // Source line information only exists if an output file was supplied
138 if (this.Data == null)
139 {
140 // Use the file name as the source line information.
141 return this.OutputSourceLineNumber;
142 }
143
144 // Index the source line information if it hasn't been indexed already.
145 if (this.SourceLineNumbersByTablePrimaryKey == null)
146 {
147 this.SourceLineNumbersByTablePrimaryKey = new Dictionary<string, SourceLineNumber>();
148
149 // Index each real table
150 foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal))
151 {
152 // Index each row that contain source line information
153 foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null))
154 {
155 // Index the row using its table name and primary key
156 var primaryKey = row.GetPrimaryKey(';');
157
158 if (!String.IsNullOrEmpty(primaryKey))
159 {
160 try
161 {
162 var key = String.Concat(table.Name, ":", primaryKey);
163 this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers);
164 }
165 catch (ArgumentException)
166 {
167 this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name));
168 }
169 }
170 }
171 }
172 }
173
174 return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null;
175 }
176 }
177}
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
24 extension.PreBackendBind(context); 24 extension.PreBackendBind(context);
25 } 25 }
26 26
27 var validator = Validator.CreateFromContext(context, "darice.cub");
28
29 IBindResult result = null; 27 IBindResult result = null;
30 var dispose = true; 28 var dispose = true;
31 try 29 try
32 { 30 {
33 var command = new BindDatabaseCommand(context, backendExtensions, validator); 31 var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub");
34 result = command.Execute(); 32 result = command.Execute();
35 33
36 foreach (var extension in backendExtensions) 34 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
23 extension.PreBackendBind(context); 23 extension.PreBackendBind(context);
24 } 24 }
25 25
26 var validator = Validator.CreateFromContext(context, "mergemod.cub");
27
28 IBindResult result = null; 26 IBindResult result = null;
29 try 27 try
30 { 28 {
31 var command = new BindDatabaseCommand(context, backendExtensions, validator); 29 var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub");
32 result = command.Execute(); 30 result = command.Execute();
33 31
34 foreach (var extension in backendExtensions) 32 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 @@
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.Core.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Globalization;
11 using System.IO;
12 using System.Linq;
13 using System.Reflection;
14 using System.Threading;
15 using WixToolset.Core.WindowsInstaller.Msi;
16 using WixToolset.Data;
17 using WixToolset.Data.WindowsInstaller;
18 using WixToolset.Extensibility;
19 using WixToolset.Extensibility.Data;
20 using WixToolset.Extensibility.Services;
21
22 /// <summary>
23 /// Runs internal consistency evaluators (ICEs) from cub files against a database.
24 /// </summary>
25 internal sealed class Validator
26 {
27 private string actionName;
28 private readonly StringCollection cubeFiles;
29 private ValidatorExtension extension;
30 private WindowsInstallerData output;
31 private readonly InstallUIHandler validationUIHandler;
32 private bool validationSessionComplete;
33 private readonly IMessaging messaging;
34
35 /// <summary>
36 /// Instantiate a new Validator.
37 /// </summary>
38 public Validator(IMessaging messaging)
39 {
40 this.cubeFiles = new StringCollection();
41 this.extension = new ValidatorExtension(messaging);
42 this.validationUIHandler = new InstallUIHandler(this.ValidationUIHandler);
43 this.messaging = messaging;
44 }
45
46 /// <summary>
47 /// Gets or sets a <see cref="ValidatorExtension"/> that directs messages from the validator.
48 /// </summary>
49 /// <value>A <see cref="ValidatorExtension"/> that directs messages from the validator.</value>
50 public ValidatorExtension Extension
51 {
52 get { return this.extension; }
53 set { this.extension = value; }
54 }
55
56 /// <summary>
57 /// Gets or sets the list of ICEs to run.
58 /// </summary>
59 /// <value>The list of ICEs.</value>
60 public ISet<string> ICEs { get; set; }
61
62 /// <summary>
63 /// Gets or sets the output used for finding source line information.
64 /// </summary>
65 /// <value>The output used for finding source line information.</value>
66 public WindowsInstallerData Output
67 {
68 // cache Output object until validation for changes in extension
69 get { return this.output; }
70 set { this.output = value; }
71 }
72
73 /// <summary>
74 /// Gets or sets the suppressed ICEs.
75 /// </summary>
76 /// <value>The suppressed ICEs.</value>
77 public ISet<string> SuppressedICEs { get; set; }
78
79 /// <summary>
80 /// Sets the temporary path for the Binder.
81 /// </summary>
82 public string IntermediateFolder { private get; set; }
83
84 /// <summary>
85 /// Add a cube file to the validation run.
86 /// </summary>
87 /// <param name="cubeFile">A cube file.</param>
88 public void AddCubeFile(string cubeFile)
89 {
90 this.cubeFiles.Add(cubeFile);
91 }
92
93 /// <summary>
94 /// Validate a database.
95 /// </summary>
96 /// <param name="databaseFile">The database to validate.</param>
97 /// <returns>true if validation succeeded; false otherwise.</returns>
98 public void Validate(string databaseFile)
99 {
100 int previousUILevel = (int)InstallUILevels.Basic;
101 IntPtr previousHwnd = IntPtr.Zero;
102 InstallUIHandler previousUIHandler = null;
103
104 // initialize the validator extension
105 this.extension.DatabaseFile = databaseFile ?? throw new ArgumentNullException(nameof(databaseFile));
106 this.extension.Output = this.output;
107 this.extension.InitializeValidator();
108
109 // Ensure the temporary files can be created.
110 Directory.CreateDirectory(this.IntermediateFolder);
111
112 // copy the database to a temporary location so it can be manipulated
113 string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(databaseFile));
114 File.Copy(databaseFile, tempDatabaseFile);
115
116 // remove the read-only property from the temporary database
117 FileAttributes attributes = File.GetAttributes(tempDatabaseFile);
118 File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly);
119
120 Mutex mutex = new Mutex(false, "WixValidator");
121 try
122 {
123 if (!mutex.WaitOne(0, false))
124 {
125 this.messaging.Write(VerboseMessages.ValidationSerialized());
126 mutex.WaitOne();
127 }
128
129 using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct))
130 {
131 bool propertyTableExists = database.TableExists("Property");
132 string productCode = null;
133
134 // remove the product code from the database before opening a session to prevent opening an installed product
135 if (propertyTableExists)
136 {
137 using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'"))
138 {
139 using (Record record = view.Fetch())
140 {
141 if (null != record)
142 {
143 productCode = record.GetString(1);
144
145 using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'"))
146 {
147 }
148 }
149 }
150 }
151 }
152
153 // merge in the cube databases
154 foreach (string cubeFile in this.cubeFiles)
155 {
156 try
157 {
158 using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly))
159 {
160 try
161 {
162 database.Merge(cubeDatabase, "MergeConflicts");
163 }
164 catch
165 {
166 // ignore merge errors since they are expected in the _Validation table
167 }
168 }
169 }
170 catch (Win32Exception e)
171 {
172 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
173 {
174 throw new WixException(ErrorMessages.CubeFileNotFound(cubeFile));
175 }
176
177 throw;
178 }
179 }
180
181 // commit the database before proceeding to ensure the streams don't get confused
182 database.Commit();
183
184 // the property table may have been added to the database
185 // from a cub database without the proper validation rows
186 if (!propertyTableExists)
187 {
188 using (View view = database.OpenExecuteView("DROP table `Property`"))
189 {
190 }
191 }
192
193 // get all the action names for ICEs which have not been suppressed
194 List<string> actions = new List<string>();
195 using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`"))
196 {
197 foreach (Record record in view.Records)
198 {
199 string action = record.GetString(1);
200
201 if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action)))
202 {
203 actions.Add(action);
204 }
205 }
206 }
207
208 // disable the internal UI handler and set an external UI handler
209 previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd);
210 previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero);
211
212 // create a session for running the ICEs
213 this.validationSessionComplete = false;
214 using (Session session = new Session(database))
215 {
216 // add the product code back into the database
217 if (null != productCode)
218 {
219 // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up
220 using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'"))
221 {
222 }
223
224 using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode)))
225 {
226 }
227 }
228
229 foreach (string action in actions)
230 {
231 this.actionName = action;
232 try
233 {
234 session.DoAction(action);
235 }
236 catch (Win32Exception e)
237 {
238 if (!this.messaging.EncounteredError)
239 {
240 throw e;
241 }
242 // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird.
243 //else
244 //{
245 // this.encounteredError = false;
246 //}
247 }
248 this.actionName = null;
249 }
250
251 // Mark the validation session complete so we ignore any messages that MSI may fire
252 // during session clean-up.
253 this.validationSessionComplete = true;
254 }
255 }
256 }
257 catch (Win32Exception e)
258 {
259 // avoid displaying errors twice since one may have already occurred in the UI handler
260 if (!this.messaging.EncounteredError)
261 {
262 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
263 {
264 // databaseFile is not passed since during light
265 // this would be the temporary copy and there would be
266 // no final output since the error occured; during smoke
267 // they should know the path passed into smoke
268 this.messaging.Write(ErrorMessages.ValidationFailedToOpenDatabase());
269 }
270 else if (0x64D == e.NativeErrorCode)
271 {
272 this.messaging.Write(ErrorMessages.ValidationFailedDueToLowMsiEngine());
273 }
274 else if (0x654 == e.NativeErrorCode)
275 {
276 this.messaging.Write(ErrorMessages.ValidationFailedDueToInvalidPackage());
277 }
278 else if (0x658 == e.NativeErrorCode)
279 {
280 this.messaging.Write(ErrorMessages.ValidationFailedDueToMultilanguageMergeModule());
281 }
282 else if (0x659 == e.NativeErrorCode)
283 {
284 this.messaging.Write(WarningMessages.ValidationFailedDueToSystemPolicy());
285 }
286 else
287 {
288 string msgTemp = e.Message;
289
290 if (null != this.actionName)
291 {
292 msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message);
293 }
294
295 this.messaging.Write(ErrorMessages.Win32Exception(e.NativeErrorCode, msgTemp));
296 }
297 }
298 }
299 finally
300 {
301 Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero);
302 Installer.SetInternalUI(previousUILevel, ref previousHwnd);
303
304 this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag.
305
306 mutex.ReleaseMutex();
307 this.cubeFiles.Clear();
308 this.extension.FinalizeValidator();
309 }
310 }
311
312 public static Validator CreateFromContext(IBindContext context, string cubeFilename)
313 {
314 Validator validator = null;
315 var messaging = context.ServiceProvider.GetService<IMessaging>();
316
317 // Tell the binder about the validator if validation isn't suppressed
318 if (!context.SuppressValidation)
319 {
320 validator = new Validator(messaging);
321 validator.IntermediateFolder = Path.Combine(context.IntermediateFolder, "validate");
322
323 // set the default cube file
324 string thisPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
325 validator.AddCubeFile(Path.Combine(thisPath, cubeFilename));
326
327 // Set the ICEs
328 validator.ICEs = new SortedSet<string>(context.Ices);
329
330 // Set the suppressed ICEs and disable ICEs that have equivalent-or-better checks in WiX.
331 validator.SuppressedICEs = new SortedSet<string>(context.SuppressIces.Union(new[] { "ICE08", "ICE33", "ICE47", "ICE66" }));
332 }
333
334 return validator;
335 }
336
337 /// <summary>
338 /// The validation external UI handler.
339 /// </summary>
340 /// <param name="context">Pointer to an application context.
341 /// This parameter can be used for error checking.</param>
342 /// <param name="messageType">Specifies a combination of one message box style,
343 /// one message box icon type, one default button, and one installation message type.</param>
344 /// <param name="message">Specifies the message text.</param>
345 /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns>
346 private int ValidationUIHandler(IntPtr context, uint messageType, string message)
347 {
348 try
349 {
350 // If we're getting messges during the validation session, send them to
351 // the extension. Otherwise, ignore the messages.
352 if (!this.validationSessionComplete)
353 {
354 this.extension.Log(message, this.actionName);
355 }
356 }
357 catch (WixException ex)
358 {
359 this.messaging.Write(ex.Error);
360 }
361
362 return 1;
363 }
364 }
365}
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
344 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; 344 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
345 context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>(); 345 context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>();
346 context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>(); 346 context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
347 context.Ices = Array.Empty<string>(); // TODO: set this correctly 347 context.Ices = this.commandLine.Ices;
348 context.IntermediateFolder = intermediateFolder; 348 context.IntermediateFolder = intermediateFolder;
349 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; 349 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
350 context.OutputPath = this.OutputFile; 350 context.OutputPath = this.OutputFile;
351 context.PdbType = this.PdbType; 351 context.PdbType = this.PdbType;
352 context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); 352 context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb");
353 context.SuppressIces = Array.Empty<string>(); // TODO: set this correctly 353 context.SuppressIces = this.commandLine.SuppressIces;
354 context.SuppressValidation = true; // TODO: set this correctly 354 context.SuppressValidation = this.commandLine.SuppressValidation;
355 context.CancellationToken = cancellationToken; 355 context.CancellationToken = cancellationToken;
356 356
357 var binder = this.ServiceProvider.GetService<IBinder>(); 357 var binder = this.ServiceProvider.GetService<IBinder>();
@@ -541,6 +541,12 @@ namespace WixToolset.Core.CommandLine
541 541
542 public string BuiltOutputsFile { get; private set; } 542 public string BuiltOutputsFile { get; private set; }
543 543
544 public List<string> Ices { get; } = new List<string>();
545
546 public List<string> SuppressIces { get; } = new List<string>();
547
548 public bool SuppressValidation { get; set; }
549
544 public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) 550 public CommandLine(IServiceProvider serviceProvider, IMessaging messaging)
545 { 551 {
546 this.ServiceProvider = serviceProvider; 552 this.ServiceProvider = serviceProvider;
@@ -634,6 +640,13 @@ namespace WixToolset.Core.CommandLine
634 parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); 640 parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths);
635 return true; 641 return true;
636 642
643 case "ice":
644 {
645 var value = parser.GetNextArgumentOrError(arg);
646 this.Ices.Add(value);
647 return true;
648 }
649
637 case "intermediatefolder": 650 case "intermediatefolder":
638 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); 651 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
639 return true; 652 return true;
@@ -670,6 +683,13 @@ namespace WixToolset.Core.CommandLine
670 return false; 683 return false;
671 } 684 }
672 685
686 case "sice":
687 {
688 var value = parser.GetNextArgumentOrError(arg);
689 this.SuppressIces.Add(value);
690 return true;
691 }
692
673 case "nologo": 693 case "nologo":
674 this.ShowLogo = false; 694 this.ShowLogo = false;
675 return true; 695 return true;
@@ -680,7 +700,7 @@ namespace WixToolset.Core.CommandLine
680 return true; 700 return true;
681 701
682 case "sval": 702 case "sval":
683 // todo: implement 703 this.SuppressValidation = true;
684 return true; 704 return true;
685 } 705 }
686 706