aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind
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/WixToolset.Core.WindowsInstaller/Bind
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/WixToolset.Core.WindowsInstaller/Bind')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs50
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs177
2 files changed, 201 insertions, 26 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}