aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Validator.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Validator.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Validator.cs365
1 files changed, 0 insertions, 365 deletions
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}