From 3f583916719eeef598d10a5d4e14ef14f008243b Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 11 May 2021 07:36:37 -0700 Subject: Merge Dtf --- .../WixToolset.Dtf.WindowsInstaller/Exceptions.cs | 573 +++++++++++++++++++++ 1 file changed, 573 insertions(+) create mode 100644 src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs (limited to 'src/dtf/WixToolset.Dtf.WindowsInstaller/Exceptions.cs') 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 @@ +// 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. + +namespace WixToolset.Dtf.WindowsInstaller +{ + using System; + using System.IO; + using System.Text; + using System.Collections.Generic; + using System.Globalization; + using System.Runtime.Serialization; + using System.Diagnostics.CodeAnalysis; + + /// + /// Base class for Windows Installer exceptions. + /// + [Serializable] + public class InstallerException : SystemException + { + private int errorCode; + private object[] errorData; + + /// + /// Creates a new InstallerException with a specified error message and a reference to the + /// inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. If the + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + /// is raised in a catch block that handles the inner exception. + public InstallerException(string msg, Exception innerException) + : this(0, msg, innerException) + { + } + + /// + /// Creates a new InstallerException with a specified error message. + /// + /// The message that describes the error. + public InstallerException(string msg) + : this(0, msg) + { + } + + /// + /// Creates a new InstallerException. + /// + public InstallerException() + : this(0, null) + { + } + + internal InstallerException(int errorCode, string msg, Exception innerException) + : base(msg, innerException) + { + this.errorCode = errorCode; + this.SaveErrorRecord(); + } + + internal InstallerException(int errorCode, string msg) + : this(errorCode, msg, null) + { + } + + /// + /// Initializes a new instance of the InstallerException class with serialized data. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected InstallerException(SerializationInfo info, StreamingContext context) : base(info, context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + this.errorCode = info.GetInt32("msiErrorCode"); + } + + /// + /// Gets the system error code that resulted in this exception, or 0 if not applicable. + /// + public int ErrorCode + { + get + { + return this.errorCode; + } + } + + /// + /// Gets a message that describes the exception. This message may contain detailed + /// formatted error data if it was available. + /// + public override String Message + { + get + { + string msg = base.Message; + using (Record errorRec = this.GetErrorRecord()) + { + if (errorRec != null) + { + string errorMsg = Installer.GetErrorMessage(errorRec, CultureInfo.InvariantCulture); + msg = Combine(msg, errorMsg); + } + } + return msg; + } + } + + /// + /// Sets the SerializationInfo with information about the exception. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + info.AddValue("msiErrorCode", this.errorCode); + base.GetObjectData(info, context); + } + + /// + /// Gets extended information about the error, or null if no further information + /// is available. + /// + /// A Record object. Field 1 of the Record contains the installer + /// message code. Other fields contain data specific to the particular error. + ///

+ /// If the record is passed to , it is formatted + /// by looking up the string in the current database. If there is no installation + /// session, the formatted error message may be obtained by a query on the Error table using + /// the error code, followed by a call to . + /// Alternatively, the standard MSI message can by retrieved by calling the + /// method. + ///

+ /// The following methods and properties may report extended error data: + /// + /// (constructor) + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// (constructor) + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// . + /// + ///

+ /// The Record object should be d after use. + /// It is best that the handle be closed manually as soon as it is no longer + /// needed, as leaving lots of unused handles open can degrade performance. + ///

+ /// Win32 MSI API: + /// MsiGetLastErrorRecord + ///

+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public Record GetErrorRecord() + { + return this.errorData != null ? new Record(this.errorData) : null; + } + + internal static Exception ExceptionFromReturnCode(uint errorCode) + { + return ExceptionFromReturnCode(errorCode, null); + } + + internal static Exception ExceptionFromReturnCode(uint errorCode, string msg) + { + msg = Combine(GetSystemMessage(errorCode), msg); + switch (errorCode) + { + case (uint) NativeMethods.Error.FILE_NOT_FOUND: + case (uint) NativeMethods.Error.PATH_NOT_FOUND: return new FileNotFoundException(msg); + + case (uint) NativeMethods.Error.INVALID_PARAMETER: + case (uint) NativeMethods.Error.DIRECTORY: + case (uint) NativeMethods.Error.UNKNOWN_PROPERTY: + case (uint) NativeMethods.Error.UNKNOWN_PRODUCT: + case (uint) NativeMethods.Error.UNKNOWN_FEATURE: + case (uint) NativeMethods.Error.UNKNOWN_COMPONENT: return new ArgumentException(msg); + + case (uint) NativeMethods.Error.BAD_QUERY_SYNTAX: return new BadQuerySyntaxException(msg); + + case (uint) NativeMethods.Error.INVALID_HANDLE_STATE: + case (uint) NativeMethods.Error.INVALID_HANDLE: + InvalidHandleException ihex = new InvalidHandleException(msg); + ihex.errorCode = (int) errorCode; + return ihex; + + case (uint) NativeMethods.Error.INSTALL_USEREXIT: return new InstallCanceledException(msg); + + case (uint) NativeMethods.Error.CALL_NOT_IMPLEMENTED: return new NotImplementedException(msg); + + default: return new InstallerException((int) errorCode, msg); + } + } + + internal static string GetSystemMessage(uint errorCode) + { + const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + StringBuilder buf = new StringBuilder(1024); + uint formatCount = NativeMethods.FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + IntPtr.Zero, + (uint) errorCode, + 0, + buf, + (uint) buf.Capacity, + IntPtr.Zero); + + if (formatCount != 0) + { + return buf.ToString().Trim(); + } + else + { + return null; + } + } + + internal void SaveErrorRecord() + { + // TODO: pass an affinity handle here? + int recordHandle = RemotableNativeMethods.MsiGetLastErrorRecord(0); + if (recordHandle != 0) + { + using (Record errorRec = new Record((IntPtr) recordHandle, true, null)) + { + this.errorData = new object[errorRec.FieldCount]; + for (int i = 0; i < this.errorData.Length; i++) + { + this.errorData[i] = errorRec[i + 1]; + } + } + } + else + { + this.errorData = null; + } + } + + private static string Combine(string msg1, string msg2) + { + if (msg1 == null) return msg2; + if (msg2 == null) return msg1; + return msg1 + " " + msg2; + } + } + + /// + /// User Canceled the installation. + /// + [Serializable] + public class InstallCanceledException : InstallerException + { + /// + /// Creates a new InstallCanceledException with a specified error message and a reference to the + /// inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. If the + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + /// is raised in a catch block that handles the inner exception. + public InstallCanceledException(string msg, Exception innerException) + : base((int) NativeMethods.Error.INSTALL_USEREXIT, msg, innerException) + { + } + + /// + /// Creates a new InstallCanceledException with a specified error message. + /// + /// The message that describes the error. + public InstallCanceledException(string msg) + : this(msg, null) + { + } + + /// + /// Creates a new InstallCanceledException. + /// + public InstallCanceledException() + : this(null, null) + { + } + + /// + /// Initializes a new instance of the InstallCanceledException class with serialized data. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected InstallCanceledException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// A bad SQL query string was passed to or . + /// + [Serializable] + public class BadQuerySyntaxException : InstallerException + { + /// + /// Creates a new BadQuerySyntaxException with a specified error message and a reference to the + /// inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. If the + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + /// is raised in a catch block that handles the inner exception. + public BadQuerySyntaxException(string msg, Exception innerException) + : base((int) NativeMethods.Error.BAD_QUERY_SYNTAX, msg, innerException) + { + } + + /// + /// Creates a new BadQuerySyntaxException with a specified error message. + /// + /// The message that describes the error. + public BadQuerySyntaxException(string msg) + : this(msg, null) + { + } + + /// + /// Creates a new BadQuerySyntaxException. + /// + public BadQuerySyntaxException() + : this(null, null) + { + } + + /// + /// Initializes a new instance of the BadQuerySyntaxException class with serialized data. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected BadQuerySyntaxException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// A method was called on an invalid installer handle. The handle may have been already closed. + /// + [Serializable] + public class InvalidHandleException : InstallerException + { + /// + /// Creates a new InvalidHandleException with a specified error message and a reference to the + /// inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. If the + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + /// is raised in a catch block that handles the inner exception. + public InvalidHandleException(string msg, Exception innerException) + : base((int) NativeMethods.Error.INVALID_HANDLE, msg, innerException) + { + } + + /// + /// Creates a new InvalidHandleException with a specified error message. + /// + /// The message that describes the error. + public InvalidHandleException(string msg) + : this(msg, null) + { + } + + /// + /// Creates a new InvalidHandleException. + /// + public InvalidHandleException() + : this(null, null) + { + } + + /// + /// Initializes a new instance of the InvalidHandleException class with serialized data. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected InvalidHandleException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } + + /// + /// A failure occurred when executing . The exception may contain + /// details about the merge conflict. + /// + [Serializable] + public class MergeException : InstallerException + { + private IList conflictTables; + private IList conflictCounts; + + /// + /// Creates a new MergeException with a specified error message and a reference to the + /// inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. If the + /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception + /// is raised in a catch block that handles the inner exception. + public MergeException(string msg, Exception innerException) + : base(msg, innerException) + { + } + + /// + /// Creates a new MergeException with a specified error message. + /// + /// The message that describes the error. + public MergeException(string msg) + : base(msg) + { + } + + /// + /// Creates a new MergeException. + /// + public MergeException() + : base() + { + } + + internal MergeException(Database db, string conflictsTableName) + : base("Merge failed.") + { + if (conflictsTableName != null) + { + IList conflictTableList = new List(); + IList conflictCountList = new List(); + + using (View view = db.OpenView("SELECT `Table`, `NumRowMergeConflicts` FROM `" + conflictsTableName + "`")) + { + view.Execute(); + + foreach (Record rec in view) using (rec) + { + conflictTableList.Add(rec.GetString(1)); + conflictCountList.Add((int) rec.GetInteger(2)); + } + } + + this.conflictTables = conflictTableList; + this.conflictCounts = conflictCountList; + } + } + + /// + /// Initializes a new instance of the MergeException class with serialized data. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + protected MergeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + this.conflictTables = (string[]) info.GetValue("mergeConflictTables", typeof(string[])); + this.conflictCounts = (int[]) info.GetValue("mergeConflictCounts", typeof(int[])); + } + + /// + /// Gets the number of merge conflicts in each table, corresponding to the tables returned by + /// . + /// + public IList ConflictCounts + { + get + { + return new List(this.conflictCounts); + } + } + + /// + /// Gets the list of tables containing merge conflicts. + /// + public IList ConflictTables + { + get + { + return new List(this.conflictTables); + } + } + + /// + /// Gets a message that describes the merge conflits. + /// + public override String Message + { + get + { + StringBuilder msg = new StringBuilder(base.Message); + if (this.conflictTables != null) + { + for (int i = 0; i < this.conflictTables.Count; i++) + { + msg.Append(i == 0 ? " Conflicts: " : ", "); + msg.Append(this.conflictTables[i]); + msg.Append('('); + msg.Append(this.conflictCounts[i]); + msg.Append(')'); + } + } + return msg.ToString(); + } + } + + /// + /// Sets the SerializationInfo with information about the exception. + /// + /// The SerializationInfo that holds the serialized object data about the exception being thrown. + /// The StreamingContext that contains contextual information about the source or destination. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + info.AddValue("mergeConflictTables", this.conflictTables); + info.AddValue("mergeConflictCounts", this.conflictCounts); + base.GetObjectData(info, context); + } + } +} -- cgit v1.2.3-55-g6feb