aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs332
1 files changed, 332 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
new file mode 100644
index 00000000..26d254f2
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -0,0 +1,332 @@
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.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13 using WixToolset.Msi;
14 using WixToolset.Core.Native;
15
16 internal class GenerateDatabaseCommand
17 {
18 public int Codepage { private get; set; }
19
20 public IEnumerable<IBinderExtension> Extensions { private get; set; }
21
22 /// <summary>
23 /// Whether to keep columns added in a transform.
24 /// </summary>
25 public bool KeepAddedColumns { private get; set; }
26
27 public Output Output { private get; set; }
28
29 public string OutputPath { private get; set; }
30
31 public TableDefinitionCollection TableDefinitions { private get; set; }
32
33 public string TempFilesLocation { private get; set; }
34
35 /// <summary>
36 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.
37 /// </summary>
38 public bool SuppressAddingValidationRows { private get; set; }
39
40 public bool UseSubDirectory { private get; set; }
41
42 public void Execute()
43 {
44 // Add the _Validation rows.
45 if (!this.SuppressAddingValidationRows)
46 {
47 Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]);
48
49 foreach (Table table in this.Output.Tables)
50 {
51 if (!table.Definition.Unreal)
52 {
53 // Add the validation rows for this table.
54 table.Definition.AddValidationRows(validationTable);
55 }
56 }
57 }
58
59 // Set the base directory.
60 string baseDirectory = this.TempFilesLocation;
61
62 if (this.UseSubDirectory)
63 {
64 string filename = Path.GetFileNameWithoutExtension(this.OutputPath);
65 baseDirectory = Path.Combine(baseDirectory, filename);
66
67 // make sure the directory exists
68 Directory.CreateDirectory(baseDirectory);
69 }
70
71 try
72 {
73 OpenDatabase type = OpenDatabase.CreateDirect;
74
75 // set special flag for patch files
76 if (OutputType.Patch == this.Output.Type)
77 {
78 type |= OpenDatabase.OpenPatchFile;
79 }
80
81#if DEBUG
82 Console.WriteLine("Opening database at: {0}", this.OutputPath);
83#endif
84
85 using (Database db = new Database(this.OutputPath, type))
86 {
87 // Localize the codepage if a value was specified directly.
88 if (-1 != this.Codepage)
89 {
90 this.Output.Codepage = this.Codepage;
91 }
92
93 // if we're not using the default codepage, import a new one into our
94 // database before we add any tables (or the tables would be added
95 // with the wrong codepage).
96 if (0 != this.Output.Codepage)
97 {
98 this.SetDatabaseCodepage(db, this.Output.Codepage);
99 }
100
101 foreach (Table table in this.Output.Tables)
102 {
103 Table importTable = table;
104 bool hasBinaryColumn = false;
105
106 // Skip all unreal tables other than _Streams.
107 if (table.Definition.Unreal && "_Streams" != table.Name)
108 {
109 continue;
110 }
111
112 // Do not put the _Validation table in patches, it is not needed.
113 if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name)
114 {
115 continue;
116 }
117
118 // The only way to import binary data is to copy it to a local subdirectory first.
119 // To avoid this extra copying and perf hit, import an empty table with the same
120 // definition and later import the binary data from source using records.
121 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
122 {
123 if (ColumnType.Object == columnDefinition.Type)
124 {
125 importTable = new Table(table.Section, table.Definition);
126 hasBinaryColumn = true;
127 break;
128 }
129 }
130
131 // Create the table via IDT import.
132 if ("_Streams" != importTable.Name)
133 {
134 try
135 {
136 db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns);
137 }
138 catch (WixInvalidIdtException)
139 {
140 // If ValidateRows finds anything it doesn't like, it throws
141 importTable.ValidateRows();
142
143 // Otherwise we rethrow the InvalidIdt
144 throw;
145 }
146 }
147
148 // insert the rows via SQL query if this table contains object fields
149 if (hasBinaryColumn)
150 {
151 StringBuilder query = new StringBuilder("SELECT ");
152
153 // Build the query for the view.
154 bool firstColumn = true;
155 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
156 {
157 if (!firstColumn)
158 {
159 query.Append(",");
160 }
161
162 query.AppendFormat(" `{0}`", columnDefinition.Name);
163 firstColumn = false;
164 }
165 query.AppendFormat(" FROM `{0}`", table.Name);
166
167 using (View tableView = db.OpenExecuteView(query.ToString()))
168 {
169 // Import each row containing a stream
170 foreach (Row row in table.Rows)
171 {
172 using (Record record = new Record(table.Definition.Columns.Count))
173 {
174 StringBuilder streamName = new StringBuilder();
175 bool needStream = false;
176
177 // the _Streams table doesn't prepend the table name (or a period)
178 if ("_Streams" != table.Name)
179 {
180 streamName.Append(table.Name);
181 }
182
183 for (int i = 0; i < table.Definition.Columns.Count; i++)
184 {
185 ColumnDefinition columnDefinition = table.Definition.Columns[i];
186
187 switch (columnDefinition.Type)
188 {
189 case ColumnType.Localized:
190 case ColumnType.Preserved:
191 case ColumnType.String:
192 if (columnDefinition.PrimaryKey)
193 {
194 if (0 < streamName.Length)
195 {
196 streamName.Append(".");
197 }
198 streamName.Append((string)row[i]);
199 }
200
201 record.SetString(i + 1, (string)row[i]);
202 break;
203 case ColumnType.Number:
204 record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture));
205 break;
206 case ColumnType.Object:
207 if (null != row[i])
208 {
209 needStream = true;
210 try
211 {
212 record.SetStream(i + 1, (string)row[i]);
213 }
214 catch (Win32Exception e)
215 {
216 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
217 {
218 throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i]));
219 }
220 else
221 {
222 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
223 }
224 }
225 }
226 break;
227 }
228 }
229
230 // stream names are created by concatenating the name of the table with the values
231 // of the primary key (delimited by periods)
232 // check for a stream name that is more than 62 characters long (the maximum allowed length)
233 if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length)
234 {
235 Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
236 }
237 else // add the row to the database
238 {
239 tableView.Modify(ModifyView.Assign, record);
240 }
241 }
242 }
243 }
244
245 // Remove rows from the _Streams table for wixpdbs.
246 if ("_Streams" == table.Name)
247 {
248 table.Rows.Clear();
249 }
250 }
251 }
252
253 // Insert substorages (usually transforms inside a patch or instance transforms in a package).
254 if (0 < this.Output.SubStorages.Count)
255 {
256 using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
257 {
258 foreach (SubStorage subStorage in this.Output.SubStorages)
259 {
260 string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst"));
261
262 // Bind the transform.
263 this.BindTransform(subStorage.Data, transformFile);
264
265 if (Messaging.Instance.EncounteredError)
266 {
267 continue;
268 }
269
270 // add the storage
271 using (Record record = new Record(2))
272 {
273 record.SetString(1, subStorage.Name);
274 record.SetStream(2, transformFile);
275 storagesView.Modify(ModifyView.Assign, record);
276 }
277 }
278 }
279 }
280
281 // We're good, commit the changes to the new database.
282 db.Commit();
283 }
284 }
285 catch (IOException)
286 {
287 // TODO: this error message doesn't seem specific enough
288 throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath);
289 }
290 }
291
292 private void BindTransform(Output transform, string outputPath)
293 {
294 BindTransformCommand command = new BindTransformCommand();
295 command.Extensions = this.Extensions;
296 command.TempFilesLocation = this.TempFilesLocation;
297 command.Transform = transform;
298 command.OutputPath = outputPath;
299 command.TableDefinitions = this.TableDefinitions;
300 command.Execute();
301 }
302
303 /// <summary>
304 /// Sets the codepage of a database.
305 /// </summary>
306 /// <param name="db">Database to set codepage into.</param>
307 /// <param name="output">Output with the codepage for the database.</param>
308 private void SetDatabaseCodepage(Database db, int codepage)
309 {
310 // write out the _ForceCodepage IDT file
311 string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt");
312 using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
313 {
314 idtFile.WriteLine(); // dummy column name record
315 idtFile.WriteLine(); // dummy column definition record
316 idtFile.Write(codepage);
317 idtFile.WriteLine("\t_ForceCodepage");
318 }
319
320 // try to import the table into the MSI
321 try
322 {
323 db.Import(idtPath);
324 }
325 catch (WixInvalidIdtException)
326 {
327 // the IDT should be valid, so an invalid code page was given
328 throw new WixException(WixErrors.IllegalCodepage(codepage));
329 }
330 }
331 }
332}