@joebigelow / wix-1 / commits / 94b941ee

Provide Record enumerator on View that disposes fetched Records

Rob Mensching committed Jan 13, 2020 at 09:10 UTC 94b941ee95a294228516097c269e27dfa41593ab
9 files changed +389 -390
src/WixToolset.Core.TestPackage/WixRunnerResult.cs
-1
@@ -26,7 +26,6 @@ namespace WixToolset.Core.TestPackage
26 var filename = message.SourceLineNumbers?.FileName ?? "TEST";
27 var line = message.SourceLineNumbers?.LineNumber ?? -1;
28 var type = message.Level.ToString().ToLowerInvariant();
29 - var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error;
29
30 if (line > 0)
31 {
src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+29 -37
@@ -89,46 +89,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
89 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
90 {
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 {
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))
107 {
96 - if (null == record)
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;
108 + this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.File.Id.Id));
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;
124 }
125 }
126 }
src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+6 -16
@@ -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]);
221
222 using (View view = db.OpenExecuteView(query))
223 + using (Record record = view.Fetch())
224 {
224 - using (Record record = view.Fetch())
225 + if (null != record)
226 {
226 - if (null != record)
227 - {
228 - this.Messaging.Write(WarningMessages.SuppressMergedAction((string)row[1], row[0].ToString()));
229 - view.Modify(ModifyView.Delete, record);
230 - }
227 + this.Messaging.Write(WarningMessages.SuppressMergedAction((string)row[1], row[0].ToString()));
228 + view.Modify(ModifyView.Delete, record);
229 }
230 }
231 }
@@ -244,17 +242,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
242
243 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
244 {
247 - while (true)
245 + foreach (Record resultRecord in view.Records)
246 {
249 - using (Record resultRecord = view.Fetch())
250 - {
251 - if (null == resultRecord)
252 - {
253 - break;
254 - }
255 -
256 - this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
257 - }
247 + this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
248 }
249 }
250
src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+6 -14
@@ -57,25 +57,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
57
58 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
59
60 - using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
60 + using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
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 {
64 - while (true)
64 + foreach (var directoryRecord in directoryView.Records)
65 {
66 - using (Record directoryRecord = directoryView.Fetch())
67 - {
68 - if (null == directoryRecord)
69 - {
70 - break;
71 - }
66 + var sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
67
73 - string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
68 + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName);
69
75 - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName);
76 -
77 - directories.Add(directoryRecord.GetString(1), resolvedDirectory);
78 - }
70 + directories.Add(directoryRecord.GetString(1), resolvedDirectory);
71 }
72 }
73
src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs
+109 -133
@@ -60,47 +60,39 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
60 {
61 using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'"))
62 {
63 - while (true)
63 + foreach (Record digitalSignatureRecord in digitalSignatureView.Records)
64 {
65 - using (Record digitalSignatureRecord = digitalSignatureView.Fetch())
66 - {
67 - if (null == digitalSignatureRecord)
68 - {
69 - break;
70 - }
65 + Row digitalSignatureRow = null;
66 + digitalSignatureRow = digitalSignatureTable.CreateRow(null);
67
72 - Row digitalSignatureRow = null;
73 - digitalSignatureRow = digitalSignatureTable.CreateRow(null);
68 + string table = digitalSignatureRecord.GetString(0);
69 + string signObject = digitalSignatureRecord.GetString(1);
70
75 - string table = digitalSignatureRecord.GetString(0);
76 - string signObject = digitalSignatureRecord.GetString(1);
71 + digitalSignatureRow[0] = table;
72 + digitalSignatureRow[1] = signObject;
73 + digitalSignatureRow[2] = digitalSignatureRecord.GetString(2);
74
78 - digitalSignatureRow[0] = table;
79 - digitalSignatureRow[1] = signObject;
80 - digitalSignatureRow[2] = digitalSignatureRecord.GetString(2);
75 + if (false == digitalSignatureRecord.IsNull(3))
76 + {
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");
80
82 - if (false == digitalSignatureRecord.IsNull(3))
83 - {
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");
81 + Directory.CreateDirectory(hashPath);
82 + hashPath = Path.Combine(hashPath, hashFileName);
83
88 - Directory.CreateDirectory(hashPath);
89 - hashPath = Path.Combine(hashPath, hashFileName);
84 + using (FileStream fs = File.Create(hashPath))
85 + {
86 + int bytesRead;
87 + byte[] buffer = new byte[1024 * 4];
88
91 - using (FileStream fs = File.Create(hashPath))
89 + while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length)))
90 {
93 - int 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 - }
91 + fs.Write(buffer, 0, bytesRead);
92 }
101 -
102 - digitalSignatureRow[3] = hashFileName;
93 }
94 +
95 + digitalSignatureRow[3] = hashFileName;
96 }
97 }
98 }
@@ -111,145 +103,129 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
103 {
104 using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`"))
105 {
114 - while (true)
106 + foreach (Record digitalCertificateRecord in digitalCertificateView.Records)
107 {
116 - using (Record digitalCertificateRecord = digitalCertificateView.Fetch())
117 - {
118 - if (null == digitalCertificateRecord)
119 - {
120 - break;
121 - }
108 + string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate
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"));
114
125 - // Export to a file, because the MSI API's require us to provide a file path on disk
126 - string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
127 - Directory.CreateDirectory(certPath);
128 - certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer"));
115 + using (FileStream fs = File.Create(certPath))
116 + {
117 + int bytesRead;
118 + byte[] buffer = new byte[1024 * 4];
119
130 - using (FileStream fs = File.Create(certPath))
120 + while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length)))
121 {
132 - int 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 - }
122 + fs.Write(buffer, 0, bytesRead);
123 }
124 + }
125
141 - // Add it to our "add to MsiDigitalCertificate" table dictionary
142 - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
143 - digitalCertificateRow[0] = certificateId;
126 + // Add it to our "add to MsiDigitalCertificate" table dictionary
127 + Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
128 + digitalCertificateRow[0] = certificateId;
129
145 - // 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");
130 + // Now set the file path on disk where this binary stream will be picked up at import time
131 + digitalCertificateRow[1] = string.Concat(certificateId, ".cer");
132
148 - // Load the cert to get it's thumbprint
149 - X509Certificate cert = X509Certificate.CreateFromCertFile(certPath);
150 - X509Certificate2 cert2 = new X509Certificate2(cert);
133 + // Load the cert to get it's thumbprint
134 + X509Certificate cert = X509Certificate.CreateFromCertFile(certPath);
135 + X509Certificate2 cert2 = new X509Certificate2(cert);
136
152 - certificates.Add(cert2.Thumbprint, certificateId);
153 - }
137 + certificates.Add(cert2.Thumbprint, certificateId);
138 }
139 }
140 }
141
142 using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`"))
143 {
160 - while (true)
144 + foreach (Record mediaRecord in mediaView.Records)
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))
152 {
164 - if (null == mediaRecord)
165 - {
166 - break;
167 - }
153 + continue;
154 + }
155
169 - X509Certificate2 cert2 = null;
170 - Row digitalSignatureRow = null;
156 + string cabId = mediaRecord.GetString(1); // get the ID of the cab
157 + string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName);
158
172 - string cabName = mediaRecord.GetString(4); // get the name of the cab
173 - // If there is no cabinet or it's an internal cab, skip it.
174 - if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal))
175 - {
176 - continue;
177 - }
159 + // If the cabs aren't there, throw an error but continue to catch the other errors
160 + if (!File.Exists(cabPath))
161 + {
162 + this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath));
163 + continue;
164 + }
165
179 - string cabId = mediaRecord.GetString(1); // get the ID of the cab
180 - string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName);
166 + try
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));
175
182 - // If the cabs aren't there, throw an error but continue to catch the other errors
183 - if (!File.Exists(cabPath))
176 + // If the file has no cert, continue, but flag that we found at least one so we can later give a warning
177 + if (0x80092009 == HResult) // CRYPT_E_NO_MATCH
178 {
185 - this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath));
179 + foundUnsignedExternals = true;
180 continue;
181 }
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
187 {
191 - // Get the certificate from the cab
192 - X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
193 - cert2 = new X509Certificate2(signedFileCert);
188 + this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
189 }
195 - catch (System.Security.Cryptography.CryptographicException e)
190 + else // otherwise, generic error
191 {
197 - uint HResult = unchecked((uint)Marshal.GetHRForException(e));
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 - }
192 + this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
193 }
194 + }
195
219 - // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added
220 - if (!certificates.ContainsKey(cert2.Thumbprint))
221 - {
222 - // generate a stable identifier
223 - 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);
196 + // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added
197 + if (!certificates.ContainsKey(cert2.Thumbprint))
198 + {
199 + // generate a stable identifier
200 + string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint);
201
235 - using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create)))
236 - {
237 - writer.Write(cert2.RawData);
238 - writer.Close();
239 - }
202 + // Add it to our "add to MsiDigitalCertificate" table dictionary
203 + Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
204 + digitalCertificateRow[0] = certificateGeneratedId;
205
241 - // Now set the file path on disk where this binary stream will be picked up at import time
242 - digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer");
206 + // Export to a file, because the MSI API's require us to provide a file path on disk
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);
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();
216 }
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");
220
249 - digitalSignatureRow[0] = "Media";
250 - digitalSignatureRow[1] = cabId;
251 - digitalSignatureRow[2] = certificates[cert2.Thumbprint];
221 + certificates.Add(cert2.Thumbprint, certificateGeneratedId);
222 }
223 +
224 + digitalSignatureRow = digitalSignatureTable.CreateRow(null);
225 +
226 + digitalSignatureRow[0] = "Media";
227 + digitalSignatureRow[1] = cabId;
228 + digitalSignatureRow[2] = certificates[cert2.Thumbprint];
229 }
230 }
231
@@ -275,7 +251,7 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe
251
252 certificates = null;
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
255 if (foundUnsignedExternals)
256 {
257 this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath));
src/WixToolset.Core.WindowsInstaller/Msi/View.cs
+79 -4
@@ -3,6 +3,8 @@
3 namespace WixToolset.Core.WindowsInstaller.Msi
4 {
5 using System;
6 + using System.Collections;
7 + using System.Collections.Generic;
8 using System.Globalization;
9
10 /// <summary>
@@ -109,6 +111,11 @@ namespace WixToolset.Core.WindowsInstaller.Msi
111 this.Handle = handle;
112 }
113
114 + /// <summary>
115 + /// Enumerator that automatically disposes of the retrieved Records.
116 + /// </summary>
117 + public IEnumerable<Record> Records => new ViewEnumerable(this);
118 +
119 /// <summary>
120 /// Executes a view with no customizable parameters.
121 /// </summary>
@@ -124,7 +131,7 @@ namespace WixToolset.Core.WindowsInstaller.Msi
131 /// <param name="record">Record containing parameters to be substituded into the view.</param>
132 public void Execute(Record record)
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);
135 if (0 != error)
136 {
137 throw new MsiException(error);
@@ -137,9 +144,7 @@ namespace WixToolset.Core.WindowsInstaller.Msi
144 /// <returns>Returns the fetched record; otherwise null.</returns>
145 public Record Fetch()
146 {
140 - uint recordHandle;
141 -
142 - int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle);
147 + var error = MsiInterop.MsiViewFetch(this.Handle, out var recordHandle);
148 if (259 == error)
149 {
150 return null;
@@ -183,5 +188,75 @@ namespace WixToolset.Core.WindowsInstaller.Msi
188
189 return new Record(recordHandle);
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 + }
261 }
262 }
src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+154 -171
@@ -121,142 +121,125 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
121 // get the normal tables
122 using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables"))
123 {
124 - while (true)
124 + foreach (var tableRecord in tablesView.Records)
125 {
126 - using (var tableRecord = tablesView.Fetch())
127 - {
128 - if (null == tableRecord)
129 - {
130 - break;
131 - }
126 + var tableName = tableRecord.GetString(1);
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);
132
135 - using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName)))
133 + foreach (var rowRecord in tableView.Records)
134 {
137 - var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView);
138 - var table = new Table(tableDefinition);
135 + var recordCount = rowRecord.GetFieldCount();
136 + var row = table.CreateRow(output.SourceLineNumbers);
137
140 - while (true)
138 + for (var i = 0; recordCount > i && row.Fields.Length > i; i++)
139 {
142 - using (var rowRecord = tableView.Fetch())
140 + if (rowRecord.IsNull(i + 1))
141 {
144 - if (null == rowRecord)
142 + if (!row.Fields[i].Column.Nullable)
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)
148 }
148 -
149 - var recordCount = rowRecord.GetFieldCount();
150 - var row = table.CreateRow(output.SourceLineNumbers);
151 -
152 - for (var i = 0; recordCount > i && row.Fields.Length > i; i++)
149 + }
150 + else
151 + {
152 + switch (row.Fields[i].Column.Type)
153 {
154 - if (rowRecord.IsNull(i + 1))
155 - {
156 - if (!row.Fields[i].Column.Nullable)
154 + case ColumnType.Number:
155 + var success = false;
156 + var intValue = rowRecord.GetInteger(i + 1);
157 + if (row.Fields[i].Column.IsLocalizable)
158 {
158 - // TODO: display an error for a null value in a non-nullable field OR
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)
159 + success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture));
160 }
163 - }
164 - else
165 - {
166 - switch (row.Fields[i].Column.Type)
161 + else
162 {
168 - case ColumnType.Number:
169 - var success = false;
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 - }
163 + success = row.BestEffortSetField(i, intValue);
164 + }
165
180 - if (!success)
181 - {
182 - this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name));
183 - }
184 - break;
185 - case ColumnType.Object:
186 - var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES";
166 + if (!success)
167 + {
168 + this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name));
169 + }
170 + break;
171 + case ColumnType.Object:
172 + var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES";
173
188 - if (null != this.ExportBasePath)
189 - {
190 - var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.'));
191 - sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile);
174 + if (null != this.ExportBasePath)
175 + {
176 + var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.'));
177 + sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile);
178
193 - // ensure the parent directory exists
194 - System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
179 + // ensure the parent directory exists
180 + System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
181
196 - using (var fs = System.IO.File.Create(sourceFile))
197 - {
198 - int bytesRead;
199 - var buffer = new byte[512];
182 + using (var fs = System.IO.File.Create(sourceFile))
183 + {
184 + int bytesRead;
185 + var buffer = new byte[512];
186
201 - while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length)))
202 - {
203 - fs.Write(buffer, 0, bytesRead);
204 - }
187 + while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length)))
188 + {
189 + fs.Write(buffer, 0, bytesRead);
190 }
206 -
207 - this.exportedFiles.Add(sourceFile);
191 }
192
210 - row[i] = sourceFile;
211 - break;
212 - default:
213 - var value = rowRecord.GetString(i + 1);
193 + this.exportedFiles.Add(sourceFile);
194 + }
195
215 - switch (row.Fields[i].Column.Category)
216 - {
196 + row[i] = sourceFile;
197 + break;
198 + default:
199 + var value = rowRecord.GetString(i + 1);
200 +
201 + switch (row.Fields[i].Column.Category)
202 + {
203 case ColumnCategory.Guid:
204 value = value.ToUpper(CultureInfo.InvariantCulture);
205 break;
220 - }
206 + }
207
222 - // de-modularize
223 - if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType)
224 - {
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}");
208 + // de-modularize
209 + if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType)
210 + {
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}");
212
227 - if (null == modularizationGuid)
213 + if (null == modularizationGuid)
214 + {
215 + var match = modularization.Match(value);
216 + if (match.Success)
217 {
229 - var match = modularization.Match(value);
230 - if (match.Success)
231 - {
232 - modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
233 - }
218 + modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
219 }
235 -
236 - value = modularization.Replace(value, String.Empty);
220 }
221
239 - // escape "$(" for the preprocessor
240 - value = value.Replace("$(", "$$(");
222 + value = modularization.Replace(value, String.Empty);
223 + }
224
242 - // escape things that look like wix variables
243 - var matches = Common.WixVariableRegex.Matches(value);
244 - for (var j = matches.Count - 1; 0 <= j; j--)
245 - {
246 - value = value.Insert(matches[j].Index, "!");
247 - }
225 + // escape "$(" for the preprocessor
226 + value = value.Replace("$(", "$$(");
227
249 - row[i] = value;
250 - break;
228 + // escape things that look like wix variables
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, "!");
233 }
252 - }
234 +
235 + row[i] = value;
236 + break;
237 }
238 }
239 }
256 -
257 - output.Tables.Add(table);
240 }
241
242 + output.Tables.Add(table);
243 }
244 }
245 }
@@ -634,82 +617,82 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
617 {
618 switch (table.Name)
619 {
637 - case "WixFile":
638 - case "MsiFileHash":
639 - ConnectTableToSection(table, fileSectionIdIndex, 0);
640 - break;
641 - case "MsiAssembly":
642 - case "MsiAssemblyName":
643 - ConnectTableToSection(table, componentSectionIdIndex, 0);
644 - break;
645 - case "MsiPackageCertificate":
646 - case "MsiPatchCertificate":
647 - ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
648 - break;
649 - case "CreateFolder":
650 - case "FeatureComponents":
651 - case "MoveFile":
652 - case "ReserveCost":
653 - case "ODBCTranslator":
654 - ConnectTableToSection(table, componentSectionIdIndex, 1);
655 - break;
656 - case "TypeLib":
657 - ConnectTableToSection(table, componentSectionIdIndex, 2);
658 - break;
659 - case "Shortcut":
660 - case "Environment":
661 - ConnectTableToSection(table, componentSectionIdIndex, 3);
662 - break;
663 - case "RemoveRegistry":
664 - ConnectTableToSection(table, componentSectionIdIndex, 4);
665 - break;
666 - case "ServiceControl":
667 - ConnectTableToSection(table, componentSectionIdIndex, 5);
668 - break;
669 - case "IniFile":
670 - case "RemoveIniFile":
671 - ConnectTableToSection(table, componentSectionIdIndex, 7);
672 - break;
673 - case "AppId":
674 - ConnectTableToSection(table, appIdSectionIdIndex, 0);
675 - break;
676 - case "Condition":
677 - ConnectTableToSection(table, featureSectionIdIndex, 0);
678 - break;
679 - case "ODBCSourceAttribute":
680 - ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
681 - break;
682 - case "ODBCAttribute":
683 - ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
684 - break;
685 - case "AdminExecuteSequence":
686 - case "AdminUISequence":
687 - case "AdvtExecuteSequence":
688 - case "AdvtUISequence":
689 - case "InstallExecuteSequence":
690 - case "InstallUISequence":
691 - ConnectTableToSection(table, customActionSectionIdIndex, 0);
692 - break;
693 - case "LockPermissions":
694 - case "MsiLockPermissions":
695 - foreach (var row in table.Rows)
696 - {
697 - var lockObject = (string)row[0];
698 - var tableName = (string)row[1];
699 - switch (tableName)
620 + case "WixFile":
621 + case "MsiFileHash":
622 + ConnectTableToSection(table, fileSectionIdIndex, 0);
623 + break;
624 + case "MsiAssembly":
625 + case "MsiAssemblyName":
626 + ConnectTableToSection(table, componentSectionIdIndex, 0);
627 + break;
628 + case "MsiPackageCertificate":
629 + case "MsiPatchCertificate":
630 + ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
631 + break;
632 + case "CreateFolder":
633 + case "FeatureComponents":
634 + case "MoveFile":
635 + case "ReserveCost":
636 + case "ODBCTranslator":
637 + ConnectTableToSection(table, componentSectionIdIndex, 1);
638 + break;
639 + case "TypeLib":
640 + ConnectTableToSection(table, componentSectionIdIndex, 2);
641 + break;
642 + case "Shortcut":
643 + case "Environment":
644 + ConnectTableToSection(table, componentSectionIdIndex, 3);
645 + break;
646 + case "RemoveRegistry":
647 + ConnectTableToSection(table, componentSectionIdIndex, 4);
648 + break;
649 + case "ServiceControl":
650 + ConnectTableToSection(table, componentSectionIdIndex, 5);
651 + break;
652 + case "IniFile":
653 + case "RemoveIniFile":
654 + ConnectTableToSection(table, componentSectionIdIndex, 7);
655 + break;
656 + case "AppId":
657 + ConnectTableToSection(table, appIdSectionIdIndex, 0);
658 + break;
659 + case "Condition":
660 + ConnectTableToSection(table, featureSectionIdIndex, 0);
661 + break;
662 + case "ODBCSourceAttribute":
663 + ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
664 + break;
665 + case "ODBCAttribute":
666 + ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
667 + break;
668 + case "AdminExecuteSequence":
669 + case "AdminUISequence":
670 + case "AdvtExecuteSequence":
671 + case "AdvtUISequence":
672 + case "InstallExecuteSequence":
673 + case "InstallUISequence":
674 + ConnectTableToSection(table, customActionSectionIdIndex, 0);
675 + break;
676 + case "LockPermissions":
677 + case "MsiLockPermissions":
678 + foreach (var row in table.Rows)
679 {
701 - case "File":
702 - row.SectionId = (string)fileSectionIdIndex[lockObject];
703 - break;
704 - case "Registry":
705 - row.SectionId = (string)registrySectionIdIndex[lockObject];
706 - break;
707 - case "ServiceInstall":
708 - row.SectionId = (string)serviceInstallSectionIdIndex[lockObject];
709 - break;
680 + var lockObject = (string)row[0];
681 + var tableName = (string)row[1];
682 + switch (tableName)
683 + {
684 + case "File":
685 + row.SectionId = (string)fileSectionIdIndex[lockObject];
686 + break;
687 + case "Registry":
688 + row.SectionId = (string)registrySectionIdIndex[lockObject];
689 + break;
690 + case "ServiceInstall":
691 + row.SectionId = (string)serviceInstallSectionIdIndex[lockObject];
692 + break;
693 + }
694 }
711 - }
712 - break;
695 + break;
696 }
697 }
698
src/WixToolset.Core.WindowsInstaller/Validator.cs
+5 -13
@@ -201,21 +201,13 @@ namespace WixToolset.Core.WindowsInstaller
201 List<string> actions = new List<string>();
202 using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`"))
203 {
204 - while (true)
204 + foreach (Record record in view.Records)
205 {
206 - using (Record record = view.Fetch())
207 - {
208 - if (null == record)
209 - {
210 - break;
211 - }
206 + string action = record.GetString(1);
207
213 - string action = record.GetString(1);
214 -
215 - if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action)))
216 - {
217 - actions.Add(action);
218 - }
208 + if ((this.SuppressedICEs == null || !this.SuppressedICEs.Contains(action)) && (this.ICEs == null || this.ICEs.Contains(action)))
209 + {
210 + actions.Add(action);
211 }
212 }
213 }
src/WixToolset.Core/CommandLine/BuildCommand.cs
+1 -1
@@ -591,7 +591,7 @@ namespace WixToolset.Core.CommandLine
591 this.OutputType = Path.GetExtension(this.OutputFile);
592 }
593
594 - switch (this.OutputType.ToLowerInvariant())
594 + switch (this.OutputType?.ToLowerInvariant())
595 {
596 case "bundle":
597 case ".exe":