aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs573
1 files changed, 573 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs b/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs
new file mode 100644
index 00000000..4954cda0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs
@@ -0,0 +1,573 @@
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.Dtf.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using System.Text;
8 using System.Collections.Generic;
9 using System.Globalization;
10 using System.Runtime.Serialization;
11 using System.Diagnostics.CodeAnalysis;
12
13 /// <summary>
14 /// Base class for Windows Installer exceptions.
15 /// </summary>
16 [Serializable]
17 public class InstallerException : SystemException
18 {
19 private int errorCode;
20 private object[] errorData;
21
22 /// <summary>
23 /// Creates a new InstallerException with a specified error message and a reference to the
24 /// inner exception that is the cause of this exception.
25 /// </summary>
26 /// <param name="msg">The message that describes the error.</param>
27 /// <param name="innerException">The exception that is the cause of the current exception. If the
28 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
29 /// is raised in a catch block that handles the inner exception.</param>
30 public InstallerException(string msg, Exception innerException)
31 : this(0, msg, innerException)
32 {
33 }
34
35 /// <summary>
36 /// Creates a new InstallerException with a specified error message.
37 /// </summary>
38 /// <param name="msg">The message that describes the error.</param>
39 public InstallerException(string msg)
40 : this(0, msg)
41 {
42 }
43
44 /// <summary>
45 /// Creates a new InstallerException.
46 /// </summary>
47 public InstallerException()
48 : this(0, null)
49 {
50 }
51
52 internal InstallerException(int errorCode, string msg, Exception innerException)
53 : base(msg, innerException)
54 {
55 this.errorCode = errorCode;
56 this.SaveErrorRecord();
57 }
58
59 internal InstallerException(int errorCode, string msg)
60 : this(errorCode, msg, null)
61 {
62 }
63
64 /// <summary>
65 /// Initializes a new instance of the InstallerException class with serialized data.
66 /// </summary>
67 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
68 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
69 protected InstallerException(SerializationInfo info, StreamingContext context) : base(info, context)
70 {
71 if (info == null)
72 {
73 throw new ArgumentNullException("info");
74 }
75
76 this.errorCode = info.GetInt32("msiErrorCode");
77 }
78
79 /// <summary>
80 /// Gets the system error code that resulted in this exception, or 0 if not applicable.
81 /// </summary>
82 public int ErrorCode
83 {
84 get
85 {
86 return this.errorCode;
87 }
88 }
89
90 /// <summary>
91 /// Gets a message that describes the exception. This message may contain detailed
92 /// formatted error data if it was available.
93 /// </summary>
94 public override String Message
95 {
96 get
97 {
98 string msg = base.Message;
99 using (Record errorRec = this.GetErrorRecord())
100 {
101 if (errorRec != null)
102 {
103 string errorMsg = Installer.GetErrorMessage(errorRec, CultureInfo.InvariantCulture);
104 msg = Combine(msg, errorMsg);
105 }
106 }
107 return msg;
108 }
109 }
110
111 /// <summary>
112 /// Sets the SerializationInfo with information about the exception.
113 /// </summary>
114 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
115 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
116 public override void GetObjectData(SerializationInfo info, StreamingContext context)
117 {
118 if (info == null)
119 {
120 throw new ArgumentNullException("info");
121 }
122
123 info.AddValue("msiErrorCode", this.errorCode);
124 base.GetObjectData(info, context);
125 }
126
127 /// <summary>
128 /// Gets extended information about the error, or null if no further information
129 /// is available.
130 /// </summary>
131 /// <returns>A Record object. Field 1 of the Record contains the installer
132 /// message code. Other fields contain data specific to the particular error.</returns>
133 /// <remarks><p>
134 /// If the record is passed to <see cref="Session.Message"/>, it is formatted
135 /// by looking up the string in the current database. If there is no installation
136 /// session, the formatted error message may be obtained by a query on the Error table using
137 /// the error code, followed by a call to <see cref="Record.ToString()"/>.
138 /// Alternatively, the standard MSI message can by retrieved by calling the
139 /// <see cref="Installer.GetErrorMessage(Record,CultureInfo)"/> method.
140 /// </p><p>
141 /// The following methods and properties may report extended error data:
142 /// <list type="bullet">
143 /// <item><see cref="Database"/> (constructor)</item>
144 /// <item><see cref="Database"/>.<see cref="Database.ApplyTransform(string,TransformErrors)"/></item>
145 /// <item><see cref="Database"/>.<see cref="Database.Commit"/></item>
146 /// <item><see cref="Database"/>.<see cref="Database.Execute(string,object[])"/></item>
147 /// <item><see cref="Database"/>.<see cref="Database.ExecuteQuery(string,object[])"/></item>
148 /// <item><see cref="Database"/>.<see cref="Database.ExecuteIntegerQuery(string,object[])"/></item>
149 /// <item><see cref="Database"/>.<see cref="Database.ExecuteStringQuery(string,object[])"/></item>
150 /// <item><see cref="Database"/>.<see cref="Database.Export"/></item>
151 /// <item><see cref="Database"/>.<see cref="Database.ExportAll"/></item>
152 /// <item><see cref="Database"/>.<see cref="Database.GenerateTransform"/></item>
153 /// <item><see cref="Database"/>.<see cref="Database.Import"/></item>
154 /// <item><see cref="Database"/>.<see cref="Database.ImportAll"/></item>
155 /// <item><see cref="Database"/>.<see cref="Database.Merge(Database,string)"/></item>
156 /// <item><see cref="Database"/>.<see cref="Database.OpenView"/></item>
157 /// <item><see cref="Database"/>.<see cref="Database.SummaryInfo"/></item>
158 /// <item><see cref="Database"/>.<see cref="Database.ViewTransform"/></item>
159 /// <item><see cref="View"/>.<see cref="View.Assign"/></item>
160 /// <item><see cref="View"/>.<see cref="View.Delete"/></item>
161 /// <item><see cref="View"/>.<see cref="View.Execute(Record)"/></item>
162 /// <item><see cref="View"/>.<see cref="View.Insert"/></item>
163 /// <item><see cref="View"/>.<see cref="View.InsertTemporary"/></item>
164 /// <item><see cref="View"/>.<see cref="View.Merge"/></item>
165 /// <item><see cref="View"/>.<see cref="View.Modify"/></item>
166 /// <item><see cref="View"/>.<see cref="View.Refresh"/></item>
167 /// <item><see cref="View"/>.<see cref="View.Replace"/></item>
168 /// <item><see cref="View"/>.<see cref="View.Seek"/></item>
169 /// <item><see cref="View"/>.<see cref="View.Update"/></item>
170 /// <item><see cref="View"/>.<see cref="View.Validate"/></item>
171 /// <item><see cref="View"/>.<see cref="View.ValidateFields"/></item>
172 /// <item><see cref="View"/>.<see cref="View.ValidateDelete"/></item>
173 /// <item><see cref="View"/>.<see cref="View.ValidateNew"/></item>
174 /// <item><see cref="SummaryInfo"/> (constructor)</item>
175 /// <item><see cref="Record"/>.<see cref="Record.SetStream(int,string)"/></item>
176 /// <item><see cref="Session"/>.<see cref="Session.SetInstallLevel"/></item>
177 /// <item><see cref="Session"/>.<see cref="Session.GetSourcePath"/></item>
178 /// <item><see cref="Session"/>.<see cref="Session.GetTargetPath"/></item>
179 /// <item><see cref="Session"/>.<see cref="Session.SetTargetPath"/></item>
180 /// <item><see cref="ComponentInfo"/>.<see cref="ComponentInfo.CurrentState"/></item>
181 /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.CurrentState"/></item>
182 /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.ValidStates"/></item>
183 /// <item><see cref="FeatureInfo"/>.<see cref="FeatureInfo.GetCost"/></item>
184 /// </list>
185 /// </p><p>
186 /// The Record object should be <see cref="InstallerHandle.Close"/>d after use.
187 /// It is best that the handle be closed manually as soon as it is no longer
188 /// needed, as leaving lots of unused handles open can degrade performance.
189 /// </p><p>
190 /// Win32 MSI API:
191 /// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetlasterrorrecord.asp">MsiGetLastErrorRecord</a>
192 /// </p></remarks>
193 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
194 public Record GetErrorRecord()
195 {
196 return this.errorData != null ? new Record(this.errorData) : null;
197 }
198
199 internal static Exception ExceptionFromReturnCode(uint errorCode)
200 {
201 return ExceptionFromReturnCode(errorCode, null);
202 }
203
204 internal static Exception ExceptionFromReturnCode(uint errorCode, string msg)
205 {
206 msg = Combine(GetSystemMessage(errorCode), msg);
207 switch (errorCode)
208 {
209 case (uint) NativeMethods.Error.FILE_NOT_FOUND:
210 case (uint) NativeMethods.Error.PATH_NOT_FOUND: return new FileNotFoundException(msg);
211
212 case (uint) NativeMethods.Error.INVALID_PARAMETER:
213 case (uint) NativeMethods.Error.DIRECTORY:
214 case (uint) NativeMethods.Error.UNKNOWN_PROPERTY:
215 case (uint) NativeMethods.Error.UNKNOWN_PRODUCT:
216 case (uint) NativeMethods.Error.UNKNOWN_FEATURE:
217 case (uint) NativeMethods.Error.UNKNOWN_COMPONENT: return new ArgumentException(msg);
218
219 case (uint) NativeMethods.Error.BAD_QUERY_SYNTAX: return new BadQuerySyntaxException(msg);
220
221 case (uint) NativeMethods.Error.INVALID_HANDLE_STATE:
222 case (uint) NativeMethods.Error.INVALID_HANDLE:
223 InvalidHandleException ihex = new InvalidHandleException(msg);
224 ihex.errorCode = (int) errorCode;
225 return ihex;
226
227 case (uint) NativeMethods.Error.INSTALL_USEREXIT: return new InstallCanceledException(msg);
228
229 case (uint) NativeMethods.Error.CALL_NOT_IMPLEMENTED: return new NotImplementedException(msg);
230
231 default: return new InstallerException((int) errorCode, msg);
232 }
233 }
234
235 internal static string GetSystemMessage(uint errorCode)
236 {
237 const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
238 const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
239
240 StringBuilder buf = new StringBuilder(1024);
241 uint formatCount = NativeMethods.FormatMessage(
242 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
243 IntPtr.Zero,
244 (uint) errorCode,
245 0,
246 buf,
247 (uint) buf.Capacity,
248 IntPtr.Zero);
249
250 if (formatCount != 0)
251 {
252 return buf.ToString().Trim();
253 }
254 else
255 {
256 return null;
257 }
258 }
259
260 internal void SaveErrorRecord()
261 {
262 // TODO: pass an affinity handle here?
263 int recordHandle = RemotableNativeMethods.MsiGetLastErrorRecord(0);
264 if (recordHandle != 0)
265 {
266 using (Record errorRec = new Record((IntPtr) recordHandle, true, null))
267 {
268 this.errorData = new object[errorRec.FieldCount];
269 for (int i = 0; i < this.errorData.Length; i++)
270 {
271 this.errorData[i] = errorRec[i + 1];
272 }
273 }
274 }
275 else
276 {
277 this.errorData = null;
278 }
279 }
280
281 private static string Combine(string msg1, string msg2)
282 {
283 if (msg1 == null) return msg2;
284 if (msg2 == null) return msg1;
285 return msg1 + " " + msg2;
286 }
287 }
288
289 /// <summary>
290 /// User Canceled the installation.
291 /// </summary>
292 [Serializable]
293 public class InstallCanceledException : InstallerException
294 {
295 /// <summary>
296 /// Creates a new InstallCanceledException with a specified error message and a reference to the
297 /// inner exception that is the cause of this exception.
298 /// </summary>
299 /// <param name="msg">The message that describes the error.</param>
300 /// <param name="innerException">The exception that is the cause of the current exception. If the
301 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
302 /// is raised in a catch block that handles the inner exception.</param>
303 public InstallCanceledException(string msg, Exception innerException)
304 : base((int) NativeMethods.Error.INSTALL_USEREXIT, msg, innerException)
305 {
306 }
307
308 /// <summary>
309 /// Creates a new InstallCanceledException with a specified error message.
310 /// </summary>
311 /// <param name="msg">The message that describes the error.</param>
312 public InstallCanceledException(string msg)
313 : this(msg, null)
314 {
315 }
316
317 /// <summary>
318 /// Creates a new InstallCanceledException.
319 /// </summary>
320 public InstallCanceledException()
321 : this(null, null)
322 {
323 }
324
325 /// <summary>
326 /// Initializes a new instance of the InstallCanceledException class with serialized data.
327 /// </summary>
328 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
329 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
330 protected InstallCanceledException(SerializationInfo info, StreamingContext context)
331 : base(info, context)
332 {
333 }
334 }
335
336 /// <summary>
337 /// A bad SQL query string was passed to <see cref="Database.OpenView"/> or <see cref="Database.Execute(string,object[])"/>.
338 /// </summary>
339 [Serializable]
340 public class BadQuerySyntaxException : InstallerException
341 {
342 /// <summary>
343 /// Creates a new BadQuerySyntaxException with a specified error message and a reference to the
344 /// inner exception that is the cause of this exception.
345 /// </summary>
346 /// <param name="msg">The message that describes the error.</param>
347 /// <param name="innerException">The exception that is the cause of the current exception. If the
348 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
349 /// is raised in a catch block that handles the inner exception.</param>
350 public BadQuerySyntaxException(string msg, Exception innerException)
351 : base((int) NativeMethods.Error.BAD_QUERY_SYNTAX, msg, innerException)
352 {
353 }
354
355 /// <summary>
356 /// Creates a new BadQuerySyntaxException with a specified error message.
357 /// </summary>
358 /// <param name="msg">The message that describes the error.</param>
359 public BadQuerySyntaxException(string msg)
360 : this(msg, null)
361 {
362 }
363
364 /// <summary>
365 /// Creates a new BadQuerySyntaxException.
366 /// </summary>
367 public BadQuerySyntaxException()
368 : this(null, null)
369 {
370 }
371
372 /// <summary>
373 /// Initializes a new instance of the BadQuerySyntaxException class with serialized data.
374 /// </summary>
375 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
376 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
377 protected BadQuerySyntaxException(SerializationInfo info, StreamingContext context)
378 : base(info, context)
379 {
380 }
381 }
382
383 /// <summary>
384 /// A method was called on an invalid installer handle. The handle may have been already closed.
385 /// </summary>
386 [Serializable]
387 public class InvalidHandleException : InstallerException
388 {
389 /// <summary>
390 /// Creates a new InvalidHandleException with a specified error message and a reference to the
391 /// inner exception that is the cause of this exception.
392 /// </summary>
393 /// <param name="msg">The message that describes the error.</param>
394 /// <param name="innerException">The exception that is the cause of the current exception. If the
395 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
396 /// is raised in a catch block that handles the inner exception.</param>
397 public InvalidHandleException(string msg, Exception innerException)
398 : base((int) NativeMethods.Error.INVALID_HANDLE, msg, innerException)
399 {
400 }
401
402 /// <summary>
403 /// Creates a new InvalidHandleException with a specified error message.
404 /// </summary>
405 /// <param name="msg">The message that describes the error.</param>
406 public InvalidHandleException(string msg)
407 : this(msg, null)
408 {
409 }
410
411 /// <summary>
412 /// Creates a new InvalidHandleException.
413 /// </summary>
414 public InvalidHandleException()
415 : this(null, null)
416 {
417 }
418
419 /// <summary>
420 /// Initializes a new instance of the InvalidHandleException class with serialized data.
421 /// </summary>
422 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
423 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
424 protected InvalidHandleException(SerializationInfo info, StreamingContext context)
425 : base(info, context)
426 {
427 }
428 }
429
430 /// <summary>
431 /// A failure occurred when executing <see cref="Database.Merge(Database,string)"/>. The exception may contain
432 /// details about the merge conflict.
433 /// </summary>
434 [Serializable]
435 public class MergeException : InstallerException
436 {
437 private IList<string> conflictTables;
438 private IList<int> conflictCounts;
439
440 /// <summary>
441 /// Creates a new MergeException with a specified error message and a reference to the
442 /// inner exception that is the cause of this exception.
443 /// </summary>
444 /// <param name="msg">The message that describes the error.</param>
445 /// <param name="innerException">The exception that is the cause of the current exception. If the
446 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
447 /// is raised in a catch block that handles the inner exception.</param>
448 public MergeException(string msg, Exception innerException)
449 : base(msg, innerException)
450 {
451 }
452
453 /// <summary>
454 /// Creates a new MergeException with a specified error message.
455 /// </summary>
456 /// <param name="msg">The message that describes the error.</param>
457 public MergeException(string msg)
458 : base(msg)
459 {
460 }
461
462 /// <summary>
463 /// Creates a new MergeException.
464 /// </summary>
465 public MergeException()
466 : base()
467 {
468 }
469
470 internal MergeException(Database db, string conflictsTableName)
471 : base("Merge failed.")
472 {
473 if (conflictsTableName != null)
474 {
475 IList<string> conflictTableList = new List<string>();
476 IList<int> conflictCountList = new List<int>();
477
478 using (View view = db.OpenView("SELECT `Table`, `NumRowMergeConflicts` FROM `" + conflictsTableName + "`"))
479 {
480 view.Execute();
481
482 foreach (Record rec in view) using (rec)
483 {
484 conflictTableList.Add(rec.GetString(1));
485 conflictCountList.Add((int) rec.GetInteger(2));
486 }
487 }
488
489 this.conflictTables = conflictTableList;
490 this.conflictCounts = conflictCountList;
491 }
492 }
493
494 /// <summary>
495 /// Initializes a new instance of the MergeException class with serialized data.
496 /// </summary>
497 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
498 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
499 protected MergeException(SerializationInfo info, StreamingContext context) : base(info, context)
500 {
501 if (info == null)
502 {
503 throw new ArgumentNullException("info");
504 }
505
506 this.conflictTables = (string[]) info.GetValue("mergeConflictTables", typeof(string[]));
507 this.conflictCounts = (int[]) info.GetValue("mergeConflictCounts", typeof(int[]));
508 }
509
510 /// <summary>
511 /// Gets the number of merge conflicts in each table, corresponding to the tables returned by
512 /// <see cref="ConflictTables"/>.
513 /// </summary>
514 public IList<int> ConflictCounts
515 {
516 get
517 {
518 return new List<int>(this.conflictCounts);
519 }
520 }
521
522 /// <summary>
523 /// Gets the list of tables containing merge conflicts.
524 /// </summary>
525 public IList<string> ConflictTables
526 {
527 get
528 {
529 return new List<string>(this.conflictTables);
530 }
531 }
532
533 /// <summary>
534 /// Gets a message that describes the merge conflits.
535 /// </summary>
536 public override String Message
537 {
538 get
539 {
540 StringBuilder msg = new StringBuilder(base.Message);
541 if (this.conflictTables != null)
542 {
543 for (int i = 0; i < this.conflictTables.Count; i++)
544 {
545 msg.Append(i == 0 ? " Conflicts: " : ", ");
546 msg.Append(this.conflictTables[i]);
547 msg.Append('(');
548 msg.Append(this.conflictCounts[i]);
549 msg.Append(')');
550 }
551 }
552 return msg.ToString();
553 }
554 }
555
556 /// <summary>
557 /// Sets the SerializationInfo with information about the exception.
558 /// </summary>
559 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
560 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
561 public override void GetObjectData(SerializationInfo info, StreamingContext context)
562 {
563 if (info == null)
564 {
565 throw new ArgumentNullException("info");
566 }
567
568 info.AddValue("mergeConflictTables", this.conflictTables);
569 info.AddValue("mergeConflictCounts", this.conflictCounts);
570 base.GetObjectData(info, context);
571 }
572 }
573}