aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
committerRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
commitdbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch)
tree0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.WindowsInstaller/Msi/Database.cs
parentfbf986eb97f68396797a89fc7d40dec07b775440 (diff)
downloadwix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Msi/Database.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Msi/Database.cs303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs b/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs
new file mode 100644
index 00000000..801ebdde
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Msi/Database.cs
@@ -0,0 +1,303 @@
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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10 using System.Threading;
11 using WixToolset.Data;
12 using WixToolset.Core.Native;
13
14 /// <summary>
15 /// Wrapper class for managing MSI API database handles.
16 /// </summary>
17 internal sealed class Database : MsiHandle
18 {
19 private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021);
20
21 /// <summary>
22 /// Constructor that opens an MSI database.
23 /// </summary>
24 /// <param name="path">Path to the database to be opened.</param>
25 /// <param name="type">Persist mode to use when opening the database.</param>
26 public Database(string path, OpenDatabase type)
27 {
28 uint handle = 0;
29 int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out handle);
30 if (0 != error)
31 {
32 throw new MsiException(error);
33 }
34 this.Handle = handle;
35 }
36
37 public void ApplyTransform(string transformFile)
38 {
39 // get the curret validation bits
40 TransformErrorConditions conditions = TransformErrorConditions.None;
41 using (SummaryInformation summaryInfo = new SummaryInformation(transformFile))
42 {
43 string value = summaryInfo.GetProperty((int)SummaryInformation.Transform.ValidationFlags);
44 try
45 {
46 int validationFlags = Int32.Parse(value, CultureInfo.InvariantCulture);
47 conditions = (TransformErrorConditions)(validationFlags & 0xffff);
48 }
49 catch (FormatException)
50 {
51 // fallback to default of None
52 }
53 }
54
55 this.ApplyTransform(transformFile, conditions);
56 }
57
58 /// <summary>
59 /// Applies a transform to this database.
60 /// </summary>
61 /// <param name="transformFile">Path to the transform file being applied.</param>
62 /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param>
63 public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions)
64 {
65 int error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions);
66 if (0 != error)
67 {
68 throw new MsiException(error);
69 }
70 }
71
72 /// <summary>
73 /// Commits changes made to the database.
74 /// </summary>
75 public void Commit()
76 {
77 // Retry this call 3 times to deal with an MSI internal locking problem.
78 const int retryWait = 300;
79 const int retryLimit = 3;
80 int error = 0;
81
82 for (int i = 1; i <= retryLimit; ++i)
83 {
84 error = MsiInterop.MsiDatabaseCommit(this.Handle);
85
86 if (0 == error)
87 {
88 return;
89 }
90 else
91 {
92 MsiException exception = new MsiException(error);
93
94 // We need to see if the error code is contained in any of the strings in ErrorInfo.
95 // Join the array together and search for the error code to cover the string array.
96 if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString()))
97 {
98 break;
99 }
100
101 Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit));
102 Thread.Sleep(retryWait);
103 }
104 }
105
106 throw new MsiException(error);
107 }
108
109 /// <summary>
110 /// Creates and populates the summary information stream of an existing transform file.
111 /// </summary>
112 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
113 /// <param name="transformFile">The name of the generated transform file.</param>
114 /// <param name="errorConditions">Required error conditions that should be suppressed when the transform is applied.</param>
115 /// <param name="validations">Required when the transform is applied to a database;
116 /// shows which properties should be validated to verify that this transform can be applied to the database.</param>
117 public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations)
118 {
119 int error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations);
120 if (0 != error)
121 {
122 throw new MsiException(error);
123 }
124 }
125
126 /// <summary>
127 /// Imports an installer text archive table (idt file) into an open database.
128 /// </summary>
129 /// <param name="idtPath">Specifies the path to the file to import.</param>
130 /// <exception cref="WixInvalidIdtException">Attempted to import an IDT file with an invalid format or unsupported data.</exception>
131 /// <exception cref="MsiException">Another error occured while importing the IDT file.</exception>
132 public void Import(string idtPath)
133 {
134 string folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath));
135 string fileName = Path.GetFileName(idtPath);
136
137 int error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName);
138 if (1627 == error) // ERROR_FUNCTION_FAILED
139 {
140 throw new WixInvalidIdtException(idtPath);
141 }
142 else if (0 != error)
143 {
144 throw new MsiException(error);
145 }
146 }
147
148 /// <summary>
149 /// Exports an installer table from an open database to a text archive file (idt file).
150 /// </summary>
151 /// <param name="tableName">Specifies the name of the table to export.</param>
152 /// <param name="folderPath">Specifies the name of the folder that contains archive files. If null or empty string, uses current directory.</param>
153 /// <param name="fileName">Specifies the name of the exported table archive file.</param>
154 public void Export(string tableName, string folderPath, string fileName)
155 {
156 if (null == folderPath || 0 == folderPath.Length)
157 {
158 folderPath = System.Environment.CurrentDirectory;
159 }
160
161 int error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName);
162 if (0 != error)
163 {
164 throw new MsiException(error);
165 }
166 }
167
168 /// <summary>
169 /// Creates a transform that, when applied to the reference database, results in this database.
170 /// </summary>
171 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
172 /// <param name="transformFile">The name of the generated transform file. This is optional.</param>
173 /// <returns>true if a transform is generated; false if a transform is not generated because
174 /// there are no differences between the two databases.</returns>
175 public bool GenerateTransform(Database referenceDatabase, string transformFile)
176 {
177 int error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0);
178 if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found
179 {
180 throw new MsiException(error);
181 }
182
183 return (0xE8 != error);
184 }
185
186 /// <summary>
187 /// Merges two databases together.
188 /// </summary>
189 /// <param name="mergeDatabase">The database to merge into the base database.</param>
190 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
191 public void Merge(Database mergeDatabase, string tableName)
192 {
193 int error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName);
194 if (0 != error)
195 {
196 throw new MsiException(error);
197 }
198 }
199
200 /// <summary>
201 /// Prepares a database query and creates a <see cref="View">View</see> object.
202 /// </summary>
203 /// <param name="query">Specifies a SQL query string for querying the database.</param>
204 /// <returns>A view object is returned if the query was successful.</returns>
205 public View OpenView(string query)
206 {
207 return new View(this, query);
208 }
209
210 /// <summary>
211 /// Prepares and executes a database query and creates a <see cref="View">View</see> object.
212 /// </summary>
213 /// <param name="query">Specifies a SQL query string for querying the database.</param>
214 /// <returns>A view object is returned if the query was successful.</returns>
215 public View OpenExecuteView(string query)
216 {
217 View view = new View(this, query);
218
219 view.Execute();
220 return view;
221 }
222
223 /// <summary>
224 /// Verifies the existence or absence of a table.
225 /// </summary>
226 /// <param name="tableName">Table name to to verify the existence of.</param>
227 /// <returns>Returns true if the table exists, false if it does not.</returns>
228 public bool TableExists(string tableName)
229 {
230 int result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName);
231 return MsiInterop.MSICONDITIONTRUE == result;
232 }
233
234 /// <summary>
235 /// Returns a <see cref="Record">Record</see> containing the names of all the primary
236 /// key columns for a specified table.
237 /// </summary>
238 /// <param name="tableName">Specifies the name of the table from which to obtain
239 /// primary key names.</param>
240 /// <returns>Returns a <see cref="Record">Record</see> containing the names of all the
241 /// primary key columns for a specified table.</returns>
242 public Record PrimaryKeys(string tableName)
243 {
244 uint recordHandle;
245 int error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out recordHandle);
246 if (0 != error)
247 {
248 throw new MsiException(error);
249 }
250
251 return new Record(recordHandle);
252 }
253
254 /// <summary>
255 /// Imports a table into the database.
256 /// </summary>
257 /// <param name="codepage">Codepage of the database to import table to.</param>
258 /// <param name="table">Table to import into database.</param>
259 /// <param name="baseDirectory">The base directory where intermediate files are created.</param>
260 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
261 public void ImportTable(int codepage, Table table, string baseDirectory, bool keepAddedColumns)
262 {
263 // write out the table to an IDT file
264 string idtPath = Path.Combine(baseDirectory, String.Concat(table.Name, ".idt"));
265 Encoding encoding;
266
267 // If UTF8 encoding, use the UTF8-specific constructor to avoid writing
268 // the byte order mark at the beginning of the file
269 if (Encoding.UTF8.CodePage == codepage)
270 {
271 encoding = new UTF8Encoding(false, true);
272 }
273 else
274 {
275 if (0 == codepage)
276 {
277 codepage = Encoding.ASCII.CodePage;
278 }
279
280 encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback());
281 }
282
283 using (StreamWriter idtWriter = new StreamWriter(idtPath, false, encoding))
284 {
285 table.ToIdtDefinition(idtWriter, keepAddedColumns);
286 }
287
288 // try to import the table into the MSI
289 try
290 {
291 this.Import(idtPath);
292 }
293 catch (WixInvalidIdtException)
294 {
295 table.ValidateRows();
296
297 // If ValidateRows finds anything it doesn't like, it throws. Otherwise, we'll
298 // throw WixInvalidIdtException here which is caught in light and turns off tidy.
299 throw new WixInvalidIdtException(idtPath, table.Name);
300 }
301 }
302 }
303}