aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2020-01-13 09:10:13 -0800
committerRob Mensching <rob@firegiant.com>2020-01-13 14:19:45 -0800
commit94b941ee95a294228516097c269e27dfa41593ab (patch)
tree208cb36a5a6a3e17f5d458cfaa679d6ca1a76e15 /src
parenta2b1235d9c0dfba48b1badac428d89d1137da698 (diff)
downloadwix-94b941ee95a294228516097c269e27dfa41593ab.tar.gz
wix-94b941ee95a294228516097c269e27dfa41593ab.tar.bz2
wix-94b941ee95a294228516097c269e27dfa41593ab.zip
Provide Record enumerator on View that disposes fetched Records
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Core.TestPackage/WixRunnerResult.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs66
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs22
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs20
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs242
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Msi/View.cs83
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs325
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Validator.cs18
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs2
9 files changed, 389 insertions, 390 deletions
diff --git a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs
index f4870ae3..88f20158 100644
--- a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs
+++ b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs
@@ -26,7 +26,6 @@ namespace WixToolset.Core.TestPackage
26 var filename = message.SourceLineNumbers?.FileName ?? "TEST"; 26 var filename = message.SourceLineNumbers?.FileName ?? "TEST";
27 var line = message.SourceLineNumbers?.LineNumber ?? -1; 27 var line = message.SourceLineNumbers?.LineNumber ?? -1;
28 var type = message.Level.ToString().ToLowerInvariant(); 28 var type = message.Level.ToString().ToLowerInvariant();
29 var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error;
30 29
31 if (line > 0) 30 if (line > 0)
32 { 31 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
index 4105cb8f..5412c6f9 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
@@ -89,46 +89,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
89 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) 89 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
90 { 90 {
91 // add each file row from the merge module into the file row collection (check for errors along the way) 91 // add each file row from the merge module into the file row collection (check for errors along the way)
92 while (true) 92 foreach (Record record in view.Records)
93 { 93 {
94 using (Record record = view.Fetch()) 94 // NOTE: this is very tricky - the merge module file rows are not added to the
95 // file table because they should not be created via idt import. Instead, these
96 // rows are created by merging in the actual modules.
97 var fileTuple = new FileTuple(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Private, record[1]));
98 fileTuple.Attributes = wixMergeRow.FileAttributes;
99 fileTuple.DirectoryRef = record[2];
100 fileTuple.DiskId = wixMergeRow.DiskId;
101 fileTuple.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) };
102
103 var mergeModuleFileFacade = new FileFacade(true, fileTuple);
104
105 // If case-sensitive collision with another merge module or a user-authored file identifier.
106 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.Id.Id, out var collidingFacade))
95 { 107 {
96 if (null == record) 108 this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.File.Id.Id));
97 {
98 break;
99 }
100
101 // NOTE: this is very tricky - the merge module file rows are not added to the
102 // file table because they should not be created via idt import. Instead, these
103 // rows are created by merging in the actual modules.
104 var fileTuple = new FileTuple(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Private, record[1]));
105 fileTuple.Attributes = wixMergeRow.FileAttributes;
106 fileTuple.DirectoryRef = record[2];
107 fileTuple.DiskId = wixMergeRow.DiskId;
108 fileTuple.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) };
109
110 var mergeModuleFileFacade = new FileFacade(true, fileTuple);
111
112 // If case-sensitive collision with another merge module or a user-authored file identifier.
113 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.Id.Id, out var collidingFacade))
114 {
115 this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.File.Id.Id));
116 }
117 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.Id.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
118 {
119 this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.File.Id.Id, collidingFacade.File.Id.Id));
120 }
121 else // no collision
122 {
123 mergeModulesFileFacades.Add(mergeModuleFileFacade);
124
125 // Keep updating the indexes as new rows are added.
126 indexedFileFacades.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade);
127 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade);
128 }
129
130 containsFiles = true;
131 } 109 }
110 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.Id.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
111 {
112 this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.File.Id.Id, collidingFacade.File.Id.Id));
113 }
114 else // no collision
115 {
116 mergeModulesFileFacades.Add(mergeModuleFileFacade);
117
118 // Keep updating the indexes as new rows are added.
119 indexedFileFacades.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade);
120 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade);
121 }
122
123 containsFiles = true;
132 } 124 }
133 } 125 }
134 } 126 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
index 7ee33997..8c11555e 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -220,14 +220,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
220 string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); 220 string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]);
221 221
222 using (View view = db.OpenExecuteView(query)) 222 using (View view = db.OpenExecuteView(query))
223 using (Record record = view.Fetch())
223 { 224 {
224 using (Record record = view.Fetch()) 225 if (null != record)
225 { 226 {
226 if (null != record) 227 this.Messaging.Write(WarningMessages.SuppressMergedAction((string)row[1], row[0].ToString()));
227 { 228 view.Modify(ModifyView.Delete, record);
228 this.Messaging.Write(WarningMessages.SuppressMergedAction((string)row[1], row[0].ToString()));
229 view.Modify(ModifyView.Delete, record);
230 }
231 } 229 }
232 } 230 }
233 } 231 }
@@ -244,17 +242,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
244 242
245 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) 243 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
246 { 244 {
247 while (true) 245 foreach (Record resultRecord in view.Records)
248 { 246 {
249 using (Record resultRecord = view.Fetch()) 247 this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
250 {
251 if (null == resultRecord)
252 {
253 break;
254 }
255
256 this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
257 }
258 } 248 }
259 } 249 }
260 250
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
index 64fb3e4d..373ada38 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
@@ -57,25 +57,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
57 57
58 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId); 58 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
59 59
60 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) 60 using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
61 { 61 {
62 using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) 62 using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
63 { 63 {
64 while (true) 64 foreach (var directoryRecord in directoryView.Records)
65 { 65 {
66 using (Record directoryRecord = directoryView.Fetch()) 66 var sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
67 {
68 if (null == directoryRecord)
69 {
70 break;
71 }
72 67
73 string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); 68 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName);
74 69
75 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); 70 directories.Add(directoryRecord.GetString(1), resolvedDirectory);
76
77 directories.Add(directoryRecord.GetString(1), resolvedDirectory);
78 }
79 } 71 }
80 } 72 }
81 73
diff --git a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
index b91eeeef..ff7472ff 100644
--- a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
@@ -60,47 +60,39 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
60 { 60 {
61 using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) 61 using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'"))
62 { 62 {
63 while (true) 63 foreach (Record digitalSignatureRecord in digitalSignatureView.Records)
64 { 64 {
65 using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) 65 Row digitalSignatureRow = null;
66 { 66 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
67 if (null == digitalSignatureRecord)
68 {
69 break;
70 }
71 67
72 Row digitalSignatureRow = null; 68 string table = digitalSignatureRecord.GetString(0);
73 digitalSignatureRow = digitalSignatureTable.CreateRow(null); 69 string signObject = digitalSignatureRecord.GetString(1);
74 70
75 string table = digitalSignatureRecord.GetString(0); 71 digitalSignatureRow[0] = table;
76 string signObject = digitalSignatureRecord.GetString(1); 72 digitalSignatureRow[1] = signObject;
73 digitalSignatureRow[2] = digitalSignatureRecord.GetString(2);
77 74
78 digitalSignatureRow[0] = table; 75 if (false == digitalSignatureRecord.IsNull(3))
79 digitalSignatureRow[1] = signObject; 76 {
80 digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); 77 // Export to a file, because the MSI API's require us to provide a file path on disk
78 string hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature");
79 string hashFileName = string.Concat(table, ".", signObject, ".bin");
81 80
82 if (false == digitalSignatureRecord.IsNull(3)) 81 Directory.CreateDirectory(hashPath);
83 { 82 hashPath = Path.Combine(hashPath, hashFileName);
84 // Export to a file, because the MSI API's require us to provide a file path on disk
85 string hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature");
86 string hashFileName = string.Concat(table, ".", signObject, ".bin");
87 83
88 Directory.CreateDirectory(hashPath); 84 using (FileStream fs = File.Create(hashPath))
89 hashPath = Path.Combine(hashPath, hashFileName); 85 {
86 int bytesRead;
87 byte[] buffer = new byte[1024 * 4];
90 88
91 using (FileStream fs = File.Create(hashPath)) 89 while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length)))
92 { 90 {
93 int bytesRead; 91 fs.Write(buffer, 0, bytesRead);
94 byte[] buffer = new byte[1024 * 4];
95
96 while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length)))
97 {
98 fs.Write(buffer, 0, bytesRead);
99 }
100 } 92 }
101
102 digitalSignatureRow[3] = hashFileName;
103 } 93 }
94
95 digitalSignatureRow[3] = hashFileName;
104 } 96 }
105 } 97 }
106 } 98 }
@@ -111,145 +103,129 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
111 { 103 {
112 using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) 104 using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`"))
113 { 105 {
114 while (true) 106 foreach (Record digitalCertificateRecord in digitalCertificateView.Records)
115 { 107 {
116 using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) 108 string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate
117 {
118 if (null == digitalCertificateRecord)
119 {
120 break;
121 }
122 109
123 string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate 110 // Export to a file, because the MSI API's require us to provide a file path on disk
111 string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
112 Directory.CreateDirectory(certPath);
113 certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer"));
124 114
125 // Export to a file, because the MSI API's require us to provide a file path on disk 115 using (FileStream fs = File.Create(certPath))
126 string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); 116 {
127 Directory.CreateDirectory(certPath); 117 int bytesRead;
128 certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); 118 byte[] buffer = new byte[1024 * 4];
129 119
130 using (FileStream fs = File.Create(certPath)) 120 while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length)))
131 { 121 {
132 int bytesRead; 122 fs.Write(buffer, 0, bytesRead);
133 byte[] buffer = new byte[1024 * 4];
134
135 while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length)))
136 {
137 fs.Write(buffer, 0, bytesRead);
138 }
139 } 123 }
124 }
140 125
141 // Add it to our "add to MsiDigitalCertificate" table dictionary 126 // Add it to our "add to MsiDigitalCertificate" table dictionary
142 Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); 127 Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
143 digitalCertificateRow[0] = certificateId; 128 digitalCertificateRow[0] = certificateId;
144 129
145 // Now set the file path on disk where this binary stream will be picked up at import time 130 // Now set the file path on disk where this binary stream will be picked up at import time
146 digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); 131 digitalCertificateRow[1] = string.Concat(certificateId, ".cer");
147 132
148 // Load the cert to get it's thumbprint 133 // Load the cert to get it's thumbprint
149 X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); 134 X509Certificate cert = X509Certificate.CreateFromCertFile(certPath);
150 X509Certificate2 cert2 = new X509Certificate2(cert); 135 X509Certificate2 cert2 = new X509Certificate2(cert);
151 136
152 certificates.Add(cert2.Thumbprint, certificateId); 137 certificates.Add(cert2.Thumbprint, certificateId);
153 }
154 } 138 }
155 } 139 }
156 } 140 }
157 141
158 using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) 142 using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`"))
159 { 143 {
160 while (true) 144 foreach (Record mediaRecord in mediaView.Records)
161 { 145 {
162 using (Record mediaRecord = mediaView.Fetch()) 146 X509Certificate2 cert2 = null;
147 Row digitalSignatureRow = null;
148
149 string cabName = mediaRecord.GetString(4); // get the name of the cab
150 // If there is no cabinet or it's an internal cab, skip it.
151 if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal))
163 { 152 {
164 if (null == mediaRecord) 153 continue;
165 { 154 }
166 break;
167 }
168 155
169 X509Certificate2 cert2 = null; 156 string cabId = mediaRecord.GetString(1); // get the ID of the cab
170 Row digitalSignatureRow = null; 157 string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName);
171 158
172 string cabName = mediaRecord.GetString(4); // get the name of the cab 159 // If the cabs aren't there, throw an error but continue to catch the other errors
173 // If there is no cabinet or it's an internal cab, skip it. 160 if (!File.Exists(cabPath))
174 if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) 161 {
175 { 162 this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath));
176 continue; 163 continue;
177 } 164 }
178 165
179 string cabId = mediaRecord.GetString(1); // get the ID of the cab 166 try
180 string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); 167 {
168 // Get the certificate from the cab
169 X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
170 cert2 = new X509Certificate2(signedFileCert);
171 }
172 catch (System.Security.Cryptography.CryptographicException e)
173 {
174 uint HResult = unchecked((uint)Marshal.GetHRForException(e));
181 175
182 // If the cabs aren't there, throw an error but continue to catch the other errors 176 // If the file has no cert, continue, but flag that we found at least one so we can later give a warning
183 if (!File.Exists(cabPath)) 177 if (0x80092009 == HResult) // CRYPT_E_NO_MATCH
184 { 178 {
185 this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath)); 179 foundUnsignedExternals = true;
186 continue; 180 continue;
187 } 181 }
188 182
189 try 183 // todo: exactly which HRESULT corresponds to this issue?
184 // If it's one of these exact platforms, warn the user that it may be due to their OS.
185 if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3
186 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP
190 { 187 {
191 // Get the certificate from the cab 188 this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
192 X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
193 cert2 = new X509Certificate2(signedFileCert);
194 } 189 }
195 catch (System.Security.Cryptography.CryptographicException e) 190 else // otherwise, generic error
196 { 191 {
197 uint HResult = unchecked((uint)Marshal.GetHRForException(e)); 192 this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
198
199 // If the file has no cert, continue, but flag that we found at least one so we can later give a warning
200 if (0x80092009 == HResult) // CRYPT_E_NO_MATCH
201 {
202 foundUnsignedExternals = true;
203 continue;
204 }
205
206 // todo: exactly which HRESULT corresponds to this issue?
207 // If it's one of these exact platforms, warn the user that it may be due to their OS.
208 if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3
209 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP
210 {
211 this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
212 }
213 else // otherwise, generic error
214 {
215 this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
216 }
217 } 193 }
194 }
218 195
219 // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added 196 // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added
220 if (!certificates.ContainsKey(cert2.Thumbprint)) 197 if (!certificates.ContainsKey(cert2.Thumbprint))
221 { 198 {
222 // generate a stable identifier 199 // generate a stable identifier
223 string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); 200 string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint);
224
225 // Add it to our "add to MsiDigitalCertificate" table dictionary
226 Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
227 digitalCertificateRow[0] = certificateGeneratedId;
228
229 // Export to a file, because the MSI API's require us to provide a file path on disk
230 string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
231 Directory.CreateDirectory(certPath);
232 certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer"));
233 File.Delete(certPath);
234 201
235 using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) 202 // Add it to our "add to MsiDigitalCertificate" table dictionary
236 { 203 Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
237 writer.Write(cert2.RawData); 204 digitalCertificateRow[0] = certificateGeneratedId;
238 writer.Close();
239 }
240 205
241 // Now set the file path on disk where this binary stream will be picked up at import time 206 // Export to a file, because the MSI API's require us to provide a file path on disk
242 digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); 207 string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
208 Directory.CreateDirectory(certPath);
209 certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer"));
210 File.Delete(certPath);
243 211
244 certificates.Add(cert2.Thumbprint, certificateGeneratedId); 212 using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create)))
213 {
214 writer.Write(cert2.RawData);
215 writer.Close();
245 } 216 }
246 217
247 digitalSignatureRow = digitalSignatureTable.CreateRow(null); 218 // Now set the file path on disk where this binary stream will be picked up at import time
219 digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer");
248 220
249 digitalSignatureRow[0] = "Media"; 221 certificates.Add(cert2.Thumbprint, certificateGeneratedId);
250 digitalSignatureRow[1] = cabId;
251 digitalSignatureRow[2] = certificates[cert2.Thumbprint];
252 } 222 }
223
224 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
225
226 digitalSignatureRow[0] = "Media";
227 digitalSignatureRow[1] = cabId;
228 digitalSignatureRow[2] = certificates[cert2.Thumbprint];
253 } 229 }
254 } 230 }
255 231
@@ -275,7 +251,7 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
275 251
276 certificates = null; 252 certificates = null;
277 253
278 // If we did find external cabs but none of them were signed, give a warning 254 // If we did find external cabs but not all of them were signed, give a warning
279 if (foundUnsignedExternals) 255 if (foundUnsignedExternals)
280 { 256 {
281 this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath)); 257 this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath));
diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/View.cs b/src/WixToolset.Core.WindowsInstaller/Msi/View.cs
index 1beb72da..0fb7fc62 100644
--- a/src/WixToolset.Core.WindowsInstaller/Msi/View.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Msi/View.cs
@@ -3,6 +3,8 @@
3namespace WixToolset.Core.WindowsInstaller.Msi 3namespace WixToolset.Core.WindowsInstaller.Msi
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
6 using System.Globalization; 8 using System.Globalization;
7 9
8 /// <summary> 10 /// <summary>
@@ -110,6 +112,11 @@ namespace WixToolset.Core.WindowsInstaller.Msi
110 } 112 }
111 113
112 /// <summary> 114 /// <summary>
115 /// Enumerator that automatically disposes of the retrieved Records.
116 /// </summary>
117 public IEnumerable<Record> Records => new ViewEnumerable(this);
118
119 /// <summary>
113 /// Executes a view with no customizable parameters. 120 /// Executes a view with no customizable parameters.
114 /// </summary> 121 /// </summary>
115 public void Execute() 122 public void Execute()
@@ -124,7 +131,7 @@ namespace WixToolset.Core.WindowsInstaller.Msi
124 /// <param name="record">Record containing parameters to be substituded into the view.</param> 131 /// <param name="record">Record containing parameters to be substituded into the view.</param>
125 public void Execute(Record record) 132 public void Execute(Record record)
126 { 133 {
127 int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle); 134 var error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle);
128 if (0 != error) 135 if (0 != error)
129 { 136 {
130 throw new MsiException(error); 137 throw new MsiException(error);
@@ -137,9 +144,7 @@ namespace WixToolset.Core.WindowsInstaller.Msi
137 /// <returns>Returns the fetched record; otherwise null.</returns> 144 /// <returns>Returns the fetched record; otherwise null.</returns>
138 public Record Fetch() 145 public Record Fetch()
139 { 146 {
140 uint recordHandle; 147 var error = MsiInterop.MsiViewFetch(this.Handle, out var recordHandle);
141
142 int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle);
143 if (259 == error) 148 if (259 == error)
144 { 149 {
145 return null; 150 return null;
@@ -183,5 +188,75 @@ namespace WixToolset.Core.WindowsInstaller.Msi
183 188
184 return new Record(recordHandle); 189 return new Record(recordHandle);
185 } 190 }
191
192 private class ViewEnumerable : IEnumerable<Record>
193 {
194 private readonly View view;
195
196 public ViewEnumerable(View view) => this.view = view;
197
198 public IEnumerator<Record> GetEnumerator() => new ViewEnumerator(this.view);
199
200 IEnumerator IEnumerable.GetEnumerator() => new ViewEnumerator(this.view);
201 }
202
203 private class ViewEnumerator : IEnumerator<Record>
204 {
205 private readonly View view;
206 private readonly List<Record> records = new List<Record>();
207 private int position = -1;
208 private bool disposed;
209
210 public ViewEnumerator(View view) => this.view = view;
211
212 public Record Current => this.records[this.position];
213
214 object IEnumerator.Current => this.records[this.position];
215
216 public bool MoveNext()
217 {
218 if (this.position + 1 >= this.records.Count)
219 {
220 var record = this.view.Fetch();
221
222 if (record == null)
223 {
224 return false;
225 }
226
227 this.records.Add(record);
228 this.position = this.records.Count - 1;
229 }
230 else
231 {
232 ++this.position;
233 }
234
235 return true;
236 }
237
238 public void Reset() => this.position = -1;
239
240 public void Dispose()
241 {
242 this.Dispose(true);
243 }
244
245 protected virtual void Dispose(bool disposing)
246 {
247 if (!this.disposed)
248 {
249 if (disposing)
250 {
251 foreach (var record in this.records)
252 {
253 record.Dispose();
254 }
255 }
256
257 this.disposed = true;
258 }
259 }
260 }
186 } 261 }
187} 262}
diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
index 557500e8..fb4b4ee3 100644
--- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
@@ -121,142 +121,125 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
121 // get the normal tables 121 // get the normal tables
122 using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) 122 using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables"))
123 { 123 {
124 while (true) 124 foreach (var tableRecord in tablesView.Records)
125 { 125 {
126 using (var tableRecord = tablesView.Fetch()) 126 var tableName = tableRecord.GetString(1);
127 {
128 if (null == tableRecord)
129 {
130 break;
131 }
132 127
133 var tableName = tableRecord.GetString(1); 128 using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName)))
129 {
130 var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView);
131 var table = new Table(tableDefinition);
134 132
135 using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) 133 foreach (var rowRecord in tableView.Records)
136 { 134 {
137 var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); 135 var recordCount = rowRecord.GetFieldCount();
138 var table = new Table(tableDefinition); 136 var row = table.CreateRow(output.SourceLineNumbers);
139 137
140 while (true) 138 for (var i = 0; recordCount > i && row.Fields.Length > i; i++)
141 { 139 {
142 using (var rowRecord = tableView.Fetch()) 140 if (rowRecord.IsNull(i + 1))
143 { 141 {
144 if (null == rowRecord) 142 if (!row.Fields[i].Column.Nullable)
145 { 143 {
146 break; 144 // TODO: display an error for a null value in a non-nullable field OR
145 // display a warning and put an empty string in the value to let the compiler handle it
146 // (the second option is risky because the later code may make certain assumptions about
147 // the contents of a row value)
147 } 148 }
148 149 }
149 var recordCount = rowRecord.GetFieldCount(); 150 else
150 var row = table.CreateRow(output.SourceLineNumbers); 151 {
151 152 switch (row.Fields[i].Column.Type)
152 for (var i = 0; recordCount > i && row.Fields.Length > i; i++)
153 { 153 {
154 if (rowRecord.IsNull(i + 1)) 154 case ColumnType.Number:
155 { 155 var success = false;
156 if (!row.Fields[i].Column.Nullable) 156 var intValue = rowRecord.GetInteger(i + 1);
157 if (row.Fields[i].Column.IsLocalizable)
157 { 158 {
158 // TODO: display an error for a null value in a non-nullable field OR 159 success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture));
159 // display a warning and put an empty string in the value to let the compiler handle it
160 // (the second option is risky because the later code may make certain assumptions about
161 // the contents of a row value)
162 } 160 }
163 } 161 else
164 else
165 {
166 switch (row.Fields[i].Column.Type)
167 { 162 {
168 case ColumnType.Number: 163 success = row.BestEffortSetField(i, intValue);
169 var success = false; 164 }
170 var intValue = rowRecord.GetInteger(i + 1);
171 if (row.Fields[i].Column.IsLocalizable)
172 {
173 success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture));
174 }
175 else
176 {
177 success = row.BestEffortSetField(i, intValue);
178 }
179 165
180 if (!success) 166 if (!success)
181 { 167 {
182 this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); 168 this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name));
183 } 169 }
184 break; 170 break;
185 case ColumnType.Object: 171 case ColumnType.Object:
186 var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; 172 var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES";
187 173
188 if (null != this.ExportBasePath) 174 if (null != this.ExportBasePath)
189 { 175 {
190 var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); 176 var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.'));
191 sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); 177 sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile);
192 178
193 // ensure the parent directory exists 179 // ensure the parent directory exists
194 System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); 180 System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
195 181
196 using (var fs = System.IO.File.Create(sourceFile)) 182 using (var fs = System.IO.File.Create(sourceFile))
197 { 183 {
198 int bytesRead; 184 int bytesRead;
199 var buffer = new byte[512]; 185 var buffer = new byte[512];
200 186
201 while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) 187 while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length)))
202 { 188 {
203 fs.Write(buffer, 0, bytesRead); 189 fs.Write(buffer, 0, bytesRead);
204 }
205 } 190 }
206
207 this.exportedFiles.Add(sourceFile);
208 } 191 }
209 192
210 row[i] = sourceFile; 193 this.exportedFiles.Add(sourceFile);
211 break; 194 }
212 default:
213 var value = rowRecord.GetString(i + 1);
214 195
215 switch (row.Fields[i].Column.Category) 196 row[i] = sourceFile;
216 { 197 break;
198 default:
199 var value = rowRecord.GetString(i + 1);
200
201 switch (row.Fields[i].Column.Category)
202 {
217 case ColumnCategory.Guid: 203 case ColumnCategory.Guid:
218 value = value.ToUpper(CultureInfo.InvariantCulture); 204 value = value.ToUpper(CultureInfo.InvariantCulture);
219 break; 205 break;
220 } 206 }
221 207
222 // de-modularize 208 // de-modularize
223 if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) 209 if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType)
224 { 210 {
225 var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); 211 var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}");
226 212
227 if (null == modularizationGuid) 213 if (null == modularizationGuid)
214 {
215 var match = modularization.Match(value);
216 if (match.Success)
228 { 217 {
229 var match = modularization.Match(value); 218 modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
230 if (match.Success)
231 {
232 modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
233 }
234 } 219 }
235
236 value = modularization.Replace(value, String.Empty);
237 } 220 }
238 221
239 // escape "$(" for the preprocessor 222 value = modularization.Replace(value, String.Empty);
240 value = value.Replace("$(", "$$("); 223 }
241 224
242 // escape things that look like wix variables 225 // escape "$(" for the preprocessor
243 var matches = Common.WixVariableRegex.Matches(value); 226 value = value.Replace("$(", "$$(");
244 for (var j = matches.Count - 1; 0 <= j; j--)
245 {
246 value = value.Insert(matches[j].Index, "!");
247 }
248 227
249 row[i] = value; 228 // escape things that look like wix variables
250 break; 229 var matches = Common.WixVariableRegex.Matches(value);
230 for (var j = matches.Count - 1; 0 <= j; j--)
231 {
232 value = value.Insert(matches[j].Index, "!");
251 } 233 }
252 } 234
235 row[i] = value;
236 break;
253 } 237 }
254 } 238 }
255 } 239 }
256
257 output.Tables.Add(table);
258 } 240 }
259 241
242 output.Tables.Add(table);
260 } 243 }
261 } 244 }
262 } 245 }
@@ -634,82 +617,82 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
634 { 617 {
635 switch (table.Name) 618 switch (table.Name)
636 { 619 {
637 case "WixFile": 620 case "WixFile":
638 case "MsiFileHash": 621 case "MsiFileHash":
639 ConnectTableToSection(table, fileSectionIdIndex, 0); 622 ConnectTableToSection(table, fileSectionIdIndex, 0);
640 break; 623 break;
641 case "MsiAssembly": 624 case "MsiAssembly":
642 case "MsiAssemblyName": 625 case "MsiAssemblyName":
643 ConnectTableToSection(table, componentSectionIdIndex, 0); 626 ConnectTableToSection(table, componentSectionIdIndex, 0);
644 break; 627 break;
645 case "MsiPackageCertificate": 628 case "MsiPackageCertificate":
646 case "MsiPatchCertificate": 629 case "MsiPatchCertificate":
647 ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); 630 ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
648 break; 631 break;
649 case "CreateFolder": 632 case "CreateFolder":
650 case "FeatureComponents": 633 case "FeatureComponents":
651 case "MoveFile": 634 case "MoveFile":
652 case "ReserveCost": 635 case "ReserveCost":
653 case "ODBCTranslator": 636 case "ODBCTranslator":
654 ConnectTableToSection(table, componentSectionIdIndex, 1); 637 ConnectTableToSection(table, componentSectionIdIndex, 1);
655 break; 638 break;
656 case "TypeLib": 639 case "TypeLib":
657 ConnectTableToSection(table, componentSectionIdIndex, 2); 640 ConnectTableToSection(table, componentSectionIdIndex, 2);
658 break; 641 break;
659 case "Shortcut": 642 case "Shortcut":
660 case "Environment": 643 case "Environment":
661 ConnectTableToSection(table, componentSectionIdIndex, 3); 644 ConnectTableToSection(table, componentSectionIdIndex, 3);
662 break; 645 break;
663 case "RemoveRegistry": 646 case "RemoveRegistry":
664 ConnectTableToSection(table, componentSectionIdIndex, 4); 647 ConnectTableToSection(table, componentSectionIdIndex, 4);
665 break; 648 break;
666 case "ServiceControl": 649 case "ServiceControl":
667 ConnectTableToSection(table, componentSectionIdIndex, 5); 650 ConnectTableToSection(table, componentSectionIdIndex, 5);
668 break; 651 break;
669 case "IniFile": 652 case "IniFile":
670 case "RemoveIniFile": 653 case "RemoveIniFile":
671 ConnectTableToSection(table, componentSectionIdIndex, 7); 654 ConnectTableToSection(table, componentSectionIdIndex, 7);
672 break; 655 break;
673 case "AppId": 656 case "AppId":
674 ConnectTableToSection(table, appIdSectionIdIndex, 0); 657 ConnectTableToSection(table, appIdSectionIdIndex, 0);
675 break; 658 break;
676 case "Condition": 659 case "Condition":
677 ConnectTableToSection(table, featureSectionIdIndex, 0); 660 ConnectTableToSection(table, featureSectionIdIndex, 0);
678 break; 661 break;
679 case "ODBCSourceAttribute": 662 case "ODBCSourceAttribute":
680 ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); 663 ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
681 break; 664 break;
682 case "ODBCAttribute": 665 case "ODBCAttribute":
683 ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); 666 ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
684 break; 667 break;
685 case "AdminExecuteSequence": 668 case "AdminExecuteSequence":
686 case "AdminUISequence": 669 case "AdminUISequence":
687 case "AdvtExecuteSequence": 670 case "AdvtExecuteSequence":
688 case "AdvtUISequence": 671 case "AdvtUISequence":
689 case "InstallExecuteSequence": 672 case "InstallExecuteSequence":
690 case "InstallUISequence": 673 case "InstallUISequence":
691 ConnectTableToSection(table, customActionSectionIdIndex, 0); 674 ConnectTableToSection(table, customActionSectionIdIndex, 0);
692 break; 675 break;
693 case "LockPermissions": 676 case "LockPermissions":
694 case "MsiLockPermissions": 677 case "MsiLockPermissions":
695 foreach (var row in table.Rows) 678 foreach (var row in table.Rows)
696 {
697 var lockObject = (string)row[0];
698 var tableName = (string)row[1];
699 switch (tableName)
700 { 679 {
701 case "File": 680 var lockObject = (string)row[0];
702 row.SectionId = (string)fileSectionIdIndex[lockObject]; 681 var tableName = (string)row[1];
703 break; 682 switch (tableName)
704 case "Registry": 683 {
705 row.SectionId = (string)registrySectionIdIndex[lockObject]; 684 case "File":
706 break; 685 row.SectionId = (string)fileSectionIdIndex[lockObject];
707 case "ServiceInstall": 686 break;
708 row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; 687 case "Registry":
709 break; 688 row.SectionId = (string)registrySectionIdIndex[lockObject];
689 break;
690 case "ServiceInstall":
691 row.SectionId = (string)serviceInstallSectionIdIndex[lockObject];
692 break;
693 }
710 } 694 }
711 } 695 break;
712 break;
713 } 696 }
714 } 697 }
715 698
diff --git a/src/WixToolset.Core.WindowsInstaller/Validator.cs b/src/WixToolset.Core.WindowsInstaller/Validator.cs
index 1c9cdc2f..72b09ebc 100644
--- a/src/WixToolset.Core.WindowsInstaller/Validator.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Validator.cs
@@ -201,21 +201,13 @@ namespace WixToolset.Core.WindowsInstaller
201 List<string> actions = new List<string>(); 201 List<string> actions = new List<string>();
202 using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) 202 using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`"))
203 { 203 {
204 while (true) 204 foreach (Record record in view.Records)
205 { 205 {
206 using (Record record = view.Fetch()) 206 string action = record.GetString(1);
207 {
208 if (null == record)
209 {
210 break;
211 }
212 207
213 string action = record.GetString(1); 208 if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action)))
214 209 {
215 if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action))) 210 actions.Add(action);
216 {
217 actions.Add(action);
218 }
219 } 211 }
220 } 212 }
221 } 213 }
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
index 5ee60984..023a3c1e 100644
--- a/src/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -591,7 +591,7 @@ namespace WixToolset.Core.CommandLine
591 this.OutputType = Path.GetExtension(this.OutputFile); 591 this.OutputType = Path.GetExtension(this.OutputFile);
592 } 592 }
593 593
594 switch (this.OutputType.ToLowerInvariant()) 594 switch (this.OutputType?.ToLowerInvariant())
595 { 595 {
596 case "bundle": 596 case "bundle":
597 case ".exe": 597 case ".exe":