aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.Native/Msi
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-03-16 10:34:28 -0700
committerRob Mensching <rob@firegiant.com>2021-03-16 11:01:46 -0700
commit089a08fd6b9398b0e1040f96b8e24ba81acfe05b (patch)
tree1f521b0b91f2d6373a16f3632c5cb4d835303920 /src/WixToolset.Core.Native/Msi
parent533fb3c24290f5c9684a661e2576d857fbee9fb6 (diff)
downloadwix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.tar.gz
wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.tar.bz2
wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.zip
Migrate PInvoke to Core.Native out of Core
Diffstat (limited to 'src/WixToolset.Core.Native/Msi')
-rw-r--r--src/WixToolset.Core.Native/Msi/Database.cs257
-rw-r--r--src/WixToolset.Core.Native/Msi/InstallLogModes.cs111
-rw-r--r--src/WixToolset.Core.Native/Msi/InstallMessage.cs88
-rw-r--r--src/WixToolset.Core.Native/Msi/InstallUILevels.cs72
-rw-r--r--src/WixToolset.Core.Native/Msi/Installer.cs113
-rw-r--r--src/WixToolset.Core.Native/Msi/ModifyView.cs75
-rw-r--r--src/WixToolset.Core.Native/Msi/MsiException.cs77
-rw-r--r--src/WixToolset.Core.Native/Msi/MsiHandle.cs117
-rw-r--r--src/WixToolset.Core.Native/Msi/MsiInterop.cs397
-rw-r--r--src/WixToolset.Core.Native/Msi/OpenDatabase.cs40
-rw-r--r--src/WixToolset.Core.Native/Msi/Record.cs181
-rw-r--r--src/WixToolset.Core.Native/Msi/Session.cs42
-rw-r--r--src/WixToolset.Core.Native/Msi/SummaryInformation.cs243
-rw-r--r--src/WixToolset.Core.Native/Msi/TransformErrorConditions.cs58
-rw-r--r--src/WixToolset.Core.Native/Msi/TransformValidations.cs73
-rw-r--r--src/WixToolset.Core.Native/Msi/View.cs206
-rw-r--r--src/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs49
17 files changed, 2199 insertions, 0 deletions
diff --git a/src/WixToolset.Core.Native/Msi/Database.cs b/src/WixToolset.Core.Native/Msi/Database.cs
new file mode 100644
index 00000000..a44e8cf9
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/Database.cs
@@ -0,0 +1,257 @@
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.Native.Msi
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Threading;
9
10 /// <summary>
11 /// Wrapper class for managing MSI API database handles.
12 /// </summary>
13 public sealed class Database : MsiHandle
14 {
15 private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021);
16
17 /// <summary>
18 /// Constructor that opens an MSI database.
19 /// </summary>
20 /// <param name="path">Path to the database to be opened.</param>
21 /// <param name="type">Persist mode to use when opening the database.</param>
22 public Database(string path, OpenDatabase type)
23 {
24 var error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out var handle);
25 if (0 != error)
26 {
27 throw new MsiException(error);
28 }
29 this.Handle = handle;
30 }
31
32 /// <summary>
33 /// Maximum length of stream in an MSI database.
34 /// </summary>
35 public static int MsiMaxStreamNameLength => MsiInterop.MsiMaxStreamNameLength;
36
37 /// <summary>
38 /// Apply a transform to the MSI.
39 /// </summary>
40 /// <param name="transformFile">Path to transform to apply.</param>
41 public void ApplyTransform(string transformFile)
42 {
43 // get the curret validation bits
44 var conditions = TransformErrorConditions.None;
45 using (var summaryInfo = new SummaryInformation(transformFile))
46 {
47 var value = summaryInfo.GetProperty((int)SummaryInformation.Transform.ValidationFlags);
48 try
49 {
50 var validationFlags = Int32.Parse(value, CultureInfo.InvariantCulture);
51 conditions = (TransformErrorConditions)(validationFlags & 0xffff);
52 }
53 catch (FormatException)
54 {
55 // fallback to default of None
56 }
57 }
58
59 this.ApplyTransform(transformFile, conditions);
60 }
61
62 /// <summary>
63 /// Applies a transform to this database.
64 /// </summary>
65 /// <param name="transformFile">Path to the transform file being applied.</param>
66 /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param>
67 public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions)
68 {
69 var error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions);
70 if (0 != error)
71 {
72 throw new MsiException(error);
73 }
74 }
75
76 /// <summary>
77 /// Commits changes made to the database.
78 /// </summary>
79 public void Commit()
80 {
81 // Retry this call 3 times to deal with an MSI internal locking problem.
82 const int retryWait = 300;
83 const int retryLimit = 3;
84 var error = 0;
85
86 for (var i = 1; i <= retryLimit; ++i)
87 {
88 error = MsiInterop.MsiDatabaseCommit(this.Handle);
89
90 if (0 == error)
91 {
92 return;
93 }
94 else
95 {
96 var exception = new MsiException(error);
97
98 // We need to see if the error code is contained in any of the strings in ErrorInfo.
99 // Join the array together and search for the error code to cover the string array.
100 if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString()))
101 {
102 break;
103 }
104
105 Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit));
106 Thread.Sleep(retryWait);
107 }
108 }
109
110 throw new MsiException(error);
111 }
112
113 /// <summary>
114 /// Creates and populates the summary information stream of an existing transform file.
115 /// </summary>
116 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
117 /// <param name="transformFile">The name of the generated transform file.</param>
118 /// <param name="errorConditions">Required error conditions that should be suppressed when the transform is applied.</param>
119 /// <param name="validations">Required when the transform is applied to a database;
120 /// shows which properties should be validated to verify that this transform can be applied to the database.</param>
121 public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations)
122 {
123 var error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations);
124 if (0 != error)
125 {
126 throw new MsiException(error);
127 }
128 }
129
130 /// <summary>
131 /// Imports an installer text archive table (idt file) into an open database.
132 /// </summary>
133 /// <param name="idtPath">Specifies the path to the file to import.</param>
134 /// <exception cref="WixInvalidIdtException">Attempted to import an IDT file with an invalid format or unsupported data.</exception>
135 /// <exception cref="MsiException">Another error occured while importing the IDT file.</exception>
136 public void Import(string idtPath)
137 {
138 var folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath));
139 var fileName = Path.GetFileName(idtPath);
140
141 var error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName);
142 if (1627 == error) // ERROR_FUNCTION_FAILED
143 {
144 throw new WixInvalidIdtException(idtPath);
145 }
146 else if (0 != error)
147 {
148 throw new MsiException(error);
149 }
150 }
151
152 /// <summary>
153 /// Exports an installer table from an open database to a text archive file (idt file).
154 /// </summary>
155 /// <param name="tableName">Specifies the name of the table to export.</param>
156 /// <param name="folderPath">Specifies the name of the folder that contains archive files. If null or empty string, uses current directory.</param>
157 /// <param name="fileName">Specifies the name of the exported table archive file.</param>
158 public void Export(string tableName, string folderPath, string fileName)
159 {
160 if (String.IsNullOrEmpty(folderPath))
161 {
162 folderPath = Environment.CurrentDirectory;
163 }
164
165 var error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName);
166 if (0 != error)
167 {
168 throw new MsiException(error);
169 }
170 }
171
172 /// <summary>
173 /// Creates a transform that, when applied to the reference database, results in this database.
174 /// </summary>
175 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
176 /// <param name="transformFile">The name of the generated transform file. This is optional.</param>
177 /// <returns>true if a transform is generated; false if a transform is not generated because
178 /// there are no differences between the two databases.</returns>
179 public bool GenerateTransform(Database referenceDatabase, string transformFile)
180 {
181 var error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0);
182 if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found
183 {
184 throw new MsiException(error);
185 }
186
187 return (0xE8 != error);
188 }
189
190 /// <summary>
191 /// Merges two databases together.
192 /// </summary>
193 /// <param name="mergeDatabase">The database to merge into the base database.</param>
194 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
195 public void Merge(Database mergeDatabase, string tableName)
196 {
197 var error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName);
198 if (0 != error)
199 {
200 throw new MsiException(error);
201 }
202 }
203
204 /// <summary>
205 /// Prepares a database query and creates a <see cref="View">View</see> object.
206 /// </summary>
207 /// <param name="query">Specifies a SQL query string for querying the database.</param>
208 /// <returns>A view object is returned if the query was successful.</returns>
209 public View OpenView(string query)
210 {
211 return new View(this, query);
212 }
213
214 /// <summary>
215 /// Prepares and executes a database query and creates a <see cref="View">View</see> object.
216 /// </summary>
217 /// <param name="query">Specifies a SQL query string for querying the database.</param>
218 /// <returns>A view object is returned if the query was successful.</returns>
219 public View OpenExecuteView(string query)
220 {
221 var view = new View(this, query);
222
223 view.Execute();
224 return view;
225 }
226
227 /// <summary>
228 /// Verifies the existence or absence of a table.
229 /// </summary>
230 /// <param name="tableName">Table name to to verify the existence of.</param>
231 /// <returns>Returns true if the table exists, false if it does not.</returns>
232 public bool TableExists(string tableName)
233 {
234 var result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName);
235 return MsiInterop.MSICONDITIONTRUE == result;
236 }
237
238 /// <summary>
239 /// Returns a <see cref="Record">Record</see> containing the names of all the primary
240 /// key columns for a specified table.
241 /// </summary>
242 /// <param name="tableName">Specifies the name of the table from which to obtain
243 /// primary key names.</param>
244 /// <returns>Returns a <see cref="Record">Record</see> containing the names of all the
245 /// primary key columns for a specified table.</returns>
246 public Record PrimaryKeys(string tableName)
247 {
248 var error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out var recordHandle);
249 if (error != 0)
250 {
251 throw new MsiException(error);
252 }
253
254 return new Record(recordHandle);
255 }
256 }
257}
diff --git a/src/WixToolset.Core.Native/Msi/InstallLogModes.cs b/src/WixToolset.Core.Native/Msi/InstallLogModes.cs
new file mode 100644
index 00000000..f7012b35
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/InstallLogModes.cs
@@ -0,0 +1,111 @@
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.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// Windows Installer log modes.
9 /// </summary>
10 [Flags]
11 public enum InstallLogModes
12 {
13 /// <summary>
14 /// Premature termination of installation.
15 /// </summary>
16 FatalExit = (1 << ((int)InstallMessage.FatalExit >> 24)),
17
18 /// <summary>
19 /// The error messages are logged.
20 /// </summary>
21 Error = (1 << ((int)InstallMessage.Error >> 24)),
22
23 /// <summary>
24 /// The warning messages are logged.
25 /// </summary>
26 Warning = (1 << ((int)InstallMessage.Warning >> 24)),
27
28 /// <summary>
29 /// The user requests are logged.
30 /// </summary>
31 User = (1 << ((int)InstallMessage.User >> 24)),
32
33 /// <summary>
34 /// The status messages that are not displayed are logged.
35 /// </summary>
36 Info = (1 << ((int)InstallMessage.Info >> 24)),
37
38 /// <summary>
39 /// Request to determine a valid source location.
40 /// </summary>
41 ResolveSource = (1 << ((int)InstallMessage.ResolveSource >> 24)),
42
43 /// <summary>
44 /// The was insufficient disk space.
45 /// </summary>
46 OutOfDiskSpace = (1 << ((int)InstallMessage.OutOfDiskSpace >> 24)),
47
48 /// <summary>
49 /// The start of new installation actions are logged.
50 /// </summary>
51 ActionStart = (1 << ((int)InstallMessage.ActionStart >> 24)),
52
53 /// <summary>
54 /// The data record with the installation action is logged.
55 /// </summary>
56 ActionData = (1 << ((int)InstallMessage.ActionData >> 24)),
57
58 /// <summary>
59 /// The parameters for user-interface initialization are logged.
60 /// </summary>
61 CommonData = (1 << ((int)InstallMessage.CommonData >> 24)),
62
63 /// <summary>
64 /// Logs the property values at termination.
65 /// </summary>
66 PropertyDump = (1 << ((int)InstallMessage.Progress >> 24)),
67
68 /// <summary>
69 /// Sends large amounts of information to a log file not generally useful to users.
70 /// May be used for technical support.
71 /// </summary>
72 Verbose = (1 << ((int)InstallMessage.Initilize >> 24)),
73
74 /// <summary>
75 /// Sends extra debugging information, such as handle creation information, to the log file.
76 /// </summary>
77 ExtraDebug = (1 << ((int)InstallMessage.Terminate >> 24)),
78
79 /// <summary>
80 /// Progress bar information. This message includes information on units so far and total number of units.
81 /// See MsiProcessMessage for an explanation of the message format.
82 /// This message is only sent to an external user interface and is not logged.
83 /// </summary>
84 Progress = (1 << ((int)InstallMessage.Progress >> 24)),
85
86 /// <summary>
87 /// If this is not a quiet installation, then the basic UI has been initialized.
88 /// If this is a full UI installation, the full UI is not yet initialized.
89 /// This message is only sent to an external user interface and is not logged.
90 /// </summary>
91 Initialize = (1 << ((int)InstallMessage.Initilize >> 24)),
92
93 /// <summary>
94 /// If a full UI is being used, the full UI has ended.
95 /// If this is not a quiet installation, the basic UI has not yet ended.
96 /// This message is only sent to an external user interface and is not logged.
97 /// </summary>
98 Terminate = (1 << ((int)InstallMessage.Terminate >> 24)),
99
100 /// <summary>
101 /// Sent prior to display of the full UI dialog.
102 /// This message is only sent to an external user interface and is not logged.
103 /// </summary>
104 ShowDialog = (1 << ((int)InstallMessage.ShowDialog >> 24)),
105
106 /// <summary>
107 /// Files in use information. When this message is received, a FilesInUse Dialog should be displayed.
108 /// </summary>
109 FilesInUse = (1 << ((int)InstallMessage.FilesInUse >> 24))
110 }
111}
diff --git a/src/WixToolset.Core.Native/Msi/InstallMessage.cs b/src/WixToolset.Core.Native/Msi/InstallMessage.cs
new file mode 100644
index 00000000..35773e13
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/InstallMessage.cs
@@ -0,0 +1,88 @@
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.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// Windows Installer message types.
9 /// </summary>
10 [Flags]
11 public enum InstallMessage
12 {
13 /// <summary>
14 /// Premature termination, possibly fatal out of memory.
15 /// </summary>
16 FatalExit = 0x00000000,
17
18 /// <summary>
19 /// Formatted error message, [1] is message number in Error table.
20 /// </summary>
21 Error = 0x01000000,
22
23 /// <summary>
24 /// Formatted warning message, [1] is message number in Error table.
25 /// </summary>
26 Warning = 0x02000000,
27
28 /// <summary>
29 /// User request message, [1] is message number in Error table.
30 /// </summary>
31 User = 0x03000000,
32
33 /// <summary>
34 /// Informative message for log, not to be displayed.
35 /// </summary>
36 Info = 0x04000000,
37
38 /// <summary>
39 /// List of files in use that need to be replaced.
40 /// </summary>
41 FilesInUse = 0x05000000,
42
43 /// <summary>
44 /// Request to determine a valid source location.
45 /// </summary>
46 ResolveSource = 0x06000000,
47
48 /// <summary>
49 /// Insufficient disk space message.
50 /// </summary>
51 OutOfDiskSpace = 0x07000000,
52
53 /// <summary>
54 /// Progress: start of action, [1] action name, [2] description, [3] template for ACTIONDATA messages.
55 /// </summary>
56 ActionStart = 0x08000000,
57
58 /// <summary>
59 /// Action data. Record fields correspond to the template of ACTIONSTART message.
60 /// </summary>
61 ActionData = 0x09000000,
62
63 /// <summary>
64 /// Progress bar information. See the description of record fields below.
65 /// </summary>
66 Progress = 0x0A000000,
67
68 /// <summary>
69 /// To enable the Cancel button set [1] to 2 and [2] to 1. To disable the Cancel button set [1] to 2 and [2] to 0.
70 /// </summary>
71 CommonData = 0x0B000000,
72
73 /// <summary>
74 /// Sent prior to UI initialization, no string data.
75 /// </summary>
76 Initilize = 0x0C000000,
77
78 /// <summary>
79 /// Sent after UI termination, no string data.
80 /// </summary>
81 Terminate = 0x0D000000,
82
83 /// <summary>
84 /// Sent prior to display or authored dialog or wizard.
85 /// </summary>
86 ShowDialog = 0x0E000000
87 }
88}
diff --git a/src/WixToolset.Core.Native/Msi/InstallUILevels.cs b/src/WixToolset.Core.Native/Msi/InstallUILevels.cs
new file mode 100644
index 00000000..e84b5215
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/InstallUILevels.cs
@@ -0,0 +1,72 @@
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.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// Windows Installer UI levels.
9 /// </summary>
10 [Flags]
11 public enum InstallUILevels
12 {
13 /// <summary>
14 /// No change in the UI level. However, if phWnd is not Null, the parent window can change.
15 /// </summary>
16 NoChange = 0,
17
18 /// <summary>
19 /// The installer chooses an appropriate user interface level.
20 /// </summary>
21 Default = 1,
22
23 /// <summary>
24 /// Completely silent installation.
25 /// </summary>
26 None = 2,
27
28 /// <summary>
29 /// Simple progress and error handling.
30 /// </summary>
31 Basic = 3,
32
33 /// <summary>
34 /// Authored user interface with wizard dialog boxes suppressed.
35 /// </summary>
36 Reduced = 4,
37
38 /// <summary>
39 /// Authored user interface with wizards, progress, and errors.
40 /// </summary>
41 Full = 5,
42
43 /// <summary>
44 /// If combined with the Basic value, the installer shows simple progress dialog boxes but
45 /// does not display a Cancel button on the dialog. This prevents users from canceling the install.
46 /// Available with Windows Installer version 2.0.
47 /// </summary>
48 HideCancel = 0x20,
49
50 /// <summary>
51 /// If combined with the Basic value, the installer shows simple progress
52 /// dialog boxes but does not display any modal dialog boxes or error dialog boxes.
53 /// </summary>
54 ProgressOnly = 0x40,
55
56 /// <summary>
57 /// If combined with any above value, the installer displays a modal dialog
58 /// box at the end of a successful installation or if there has been an error.
59 /// No dialog box is displayed if the user cancels.
60 /// </summary>
61 EndDialog = 0x80,
62
63 /// <summary>
64 /// If this value is combined with the None value, the installer displays only the dialog
65 /// boxes used for source resolution. No other dialog boxes are shown. This value has no
66 /// effect if the UI level is not INSTALLUILEVEL_NONE. It is used with an external user
67 /// interface designed to handle all of the UI except for source resolution. In this case,
68 /// the installer handles source resolution. This value is only available with Windows Installer 2.0 and later.
69 /// </summary>
70 SourceResOnly = 0x100
71 }
72}
diff --git a/src/WixToolset.Core.Native/Msi/Installer.cs b/src/WixToolset.Core.Native/Msi/Installer.cs
new file mode 100644
index 00000000..2bb41078
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/Installer.cs
@@ -0,0 +1,113 @@
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.Native.Msi
4{
5 using System;
6 using System.Diagnostics;
7 using System.Text;
8
9 /// <summary>
10 /// A callback function that the installer calls for progress notification and error messages.
11 /// </summary>
12 /// <param name="context">Pointer to an application context.
13 /// This parameter can be used for error checking.</param>
14 /// <param name="messageType">Specifies a combination of one message box style,
15 /// one message box icon type, one default button, and one installation message type.</param>
16 /// <param name="message">Specifies the message text.</param>
17 /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns>
18 public delegate int InstallUIHandler(IntPtr context, uint messageType, string message);
19
20 /// <summary>
21 /// Represents the Windows Installer, provides wrappers to
22 /// create the top-level objects and access their methods.
23 /// </summary>
24 public static class Installer
25 {
26 /// <summary>
27 /// Takes the path to a file and returns a 128-bit hash of that file.
28 /// </summary>
29 /// <param name="filePath">Path to file that is to be hashed.</param>
30 /// <param name="options">The value in this column must be 0. This parameter is reserved for future use.</param>
31 /// <param name="hash">Int array that receives the returned file hash information.</param>
32 public static void GetFileHash(string filePath, int options, out int[] hash)
33 {
34 var hashInterop = new MSIFILEHASHINFO();
35 hashInterop.FileHashInfoSize = 20;
36
37 var error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop);
38 if (0 != error)
39 {
40 throw new MsiException(error);
41 }
42
43 Debug.Assert(20 == hashInterop.FileHashInfoSize);
44
45 hash = new int[4];
46 hash[0] = hashInterop.Data0;
47 hash[1] = hashInterop.Data1;
48 hash[2] = hashInterop.Data2;
49 hash[3] = hashInterop.Data3;
50 }
51
52 /// <summary>
53 /// Returns the version string and language string in the format that the installer
54 /// expects to find them in the database. If you just want version information, set
55 /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set
56 /// lpVersionBuf and pcchVersionBuf to zero.
57 /// </summary>
58 /// <param name="filePath">Specifies the path to the file.</param>
59 /// <param name="version">Returns the file version. Set to 0 for language information only.</param>
60 /// <param name="language">Returns the file language. Set to 0 for version information only.</param>
61 public static void GetFileVersion(string filePath, out string version, out string language)
62 {
63 var versionLength = 20;
64 var languageLength = 20;
65 var versionBuffer = new StringBuilder(versionLength);
66 var languageBuffer = new StringBuilder(languageLength);
67
68 var error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
69 if (234 == error)
70 {
71 versionBuffer.EnsureCapacity(++versionLength);
72 languageBuffer.EnsureCapacity(++languageLength);
73 error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
74 }
75 else if (1006 == error)
76 {
77 // file has no version or language, so no error
78 error = 0;
79 }
80
81 if (0 != error)
82 {
83 throw new MsiException(error);
84 }
85
86 version = versionBuffer.ToString();
87 language = languageBuffer.ToString();
88 }
89
90 /// <summary>
91 /// Enables an external user-interface handler.
92 /// </summary>
93 /// <param name="installUIHandler">Specifies a callback function.</param>
94 /// <param name="messageFilter">Specifies which messages to handle using the external message handler.</param>
95 /// <param name="context">Pointer to an application context that is passed to the callback function.</param>
96 /// <returns>The return value is the previously set external handler, or null if there was no previously set handler.</returns>
97 public static InstallUIHandler SetExternalUI(InstallUIHandler installUIHandler, int messageFilter, IntPtr context)
98 {
99 return MsiInterop.MsiSetExternalUI(installUIHandler, messageFilter, context);
100 }
101
102 /// <summary>
103 /// Enables the installer's internal user interface.
104 /// </summary>
105 /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param>
106 /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.</param>
107 /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns>
108 public static int SetInternalUI(int uiLevel, ref IntPtr hwnd)
109 {
110 return MsiInterop.MsiSetInternalUI(uiLevel, ref hwnd);
111 }
112 }
113}
diff --git a/src/WixToolset.Core.Native/Msi/ModifyView.cs b/src/WixToolset.Core.Native/Msi/ModifyView.cs
new file mode 100644
index 00000000..989de174
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/ModifyView.cs
@@ -0,0 +1,75 @@
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.Native.Msi
4{
5 /// <summary>
6 /// Enumeration of different modify modes.
7 /// </summary>
8 public enum ModifyView
9 {
10 /// <summary>
11 /// Writes current data in the cursor to a table row. Updates record if the primary
12 /// keys match an existing row and inserts if they do not match. Fails with a read-only
13 /// database. This mode cannot be used with a view containing joins.
14 /// </summary>
15 Assign = 3, // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins.
16
17 /// <summary>
18 /// Remove a row from the table. You must first call the Fetch function with the same
19 /// record. Fails if the row has been deleted. Works only with read-write records. This
20 /// mode cannot be used with a view containing joins.
21 /// </summary>
22 Delete = 6, // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins.
23
24 /// <summary>
25 /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only
26 /// database. This mode cannot be used with a view containing joins.
27 /// </summary>
28 Insert = 1, // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins.
29
30 /// <summary>
31 /// Inserts a temporary record. The information is not persistent. Fails if a row with the
32 /// same primary key exists. Works only with read-write records. This mode cannot be
33 /// used with a view containing joins.
34 /// </summary>
35 InsertTemporary = 7, // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins.
36
37 /// <summary>
38 /// Inserts or validates a record in a table. Inserts if primary keys do not match any row
39 /// and validates if there is a match. Fails if the record does not match the data in
40 /// the table. Fails if there is a record with a duplicate key that is not identical.
41 /// Works only with read-write records. This mode cannot be used with a view containing joins.
42 /// </summary>
43 Merge = 5, // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins.
44
45 /// <summary>
46 /// Refreshes the information in the record. Must first call Fetch with the
47 /// same record. Fails for a deleted row. Works with read-write and read-only records.
48 /// </summary>
49 Refresh = 0, // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records.
50
51 /// <summary>
52 /// Updates or deletes and inserts a record into a table. Must first call Fetch with
53 /// the same record. Updates record if the primary keys are unchanged. Deletes old row and
54 /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot
55 /// be used with a view containing joins.
56 /// </summary>
57 Replace = 4, // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins.
58
59 /// <summary>
60 /// Refreshes the information in the supplied record without changing the position in the
61 /// result set and without affecting subsequent fetch operations. The record may then
62 /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the
63 /// table must be in the query and the record must have at least as many fields as the
64 /// query. Seek cannot be used with multi-table queries. This mode cannot be used with
65 /// a view containing joins. See also the remarks.
66 /// </summary>
67 Seek = -1, // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks.
68
69 /// <summary>
70 /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a
71 /// deleted record. Works only with read-write records.
72 /// </summary>
73 Update = 2, // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records.
74 }
75}
diff --git a/src/WixToolset.Core.Native/Msi/MsiException.cs b/src/WixToolset.Core.Native/Msi/MsiException.cs
new file mode 100644
index 00000000..07c83d81
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/MsiException.cs
@@ -0,0 +1,77 @@
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.Native.Msi
4{
5 using System;
6 using System.ComponentModel;
7
8 /// <summary>
9 /// Exception that wraps MsiGetLastError().
10 /// </summary>
11 [Serializable]
12 public sealed class MsiException : Win32Exception
13 {
14 /// <summary>
15 /// Instantiate a new MsiException with a given error.
16 /// </summary>
17 /// <param name="error">The error code from the MsiXxx() function call.</param>
18 public MsiException(int error) : base(error)
19 {
20 uint handle = MsiInterop.MsiGetLastErrorRecord();
21 if (0 != handle)
22 {
23 using (Record record = new Record(handle))
24 {
25 this.MsiError = record.GetInteger(1);
26
27 int errorInfoCount = record.GetFieldCount() - 1;
28 this.ErrorInfo = new string[errorInfoCount];
29 for (int i = 0; i < errorInfoCount; ++i)
30 {
31 this.ErrorInfo[i] = record.GetString(i + 2);
32 }
33 }
34 }
35 else
36 {
37 this.MsiError = 0;
38 this.ErrorInfo = new string[0];
39 }
40
41 this.Error = error;
42 }
43
44 /// <summary>
45 /// Gets the error number.
46 /// </summary>
47 public int Error { get; private set; }
48
49 /// <summary>
50 /// Gets the internal MSI error number.
51 /// </summary>
52 public int MsiError { get; private set; }
53
54 /// <summary>
55 /// Gets any additional the error information.
56 /// </summary>
57 public string[] ErrorInfo { get; private set; }
58
59 /// <summary>
60 /// Overrides Message property to return useful error message.
61 /// </summary>
62 public override string Message
63 {
64 get
65 {
66 if (0 == this.MsiError)
67 {
68 return base.Message;
69 }
70 else
71 {
72 return String.Format("Internal MSI failure. Win32 error: {0}, MSI error: {1}, detail: {2}", this.Error, this.MsiError, String.Join(", ", this.ErrorInfo));
73 }
74 }
75 }
76 }
77}
diff --git a/src/WixToolset.Core.Native/Msi/MsiHandle.cs b/src/WixToolset.Core.Native/Msi/MsiHandle.cs
new file mode 100644
index 00000000..dc2ce605
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/MsiHandle.cs
@@ -0,0 +1,117 @@
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.Native.Msi
4{
5 using System;
6 using System.ComponentModel;
7#if !DEBUG
8 using System.Diagnostics;
9#endif
10 using System.Threading;
11
12 /// <summary>
13 /// Wrapper class for MSI handle.
14 /// </summary>
15 public abstract class MsiHandle : IDisposable
16 {
17 private bool disposed;
18 private uint handle;
19 private int owningThread;
20#if DEBUG
21 private string creationStack;
22#endif
23
24 /// <summary>
25 /// MSI handle destructor.
26 /// </summary>
27 ~MsiHandle()
28 {
29 this.Dispose(false);
30 }
31
32 /// <summary>
33 /// Gets or sets the MSI handle.
34 /// </summary>
35 /// <value>The MSI handle.</value>
36 internal uint Handle
37 {
38 get
39 {
40 if (this.disposed)
41 {
42 throw new ObjectDisposedException("MsiHandle");
43 }
44
45 return this.handle;
46 }
47
48 set
49 {
50 if (this.disposed)
51 {
52 throw new ObjectDisposedException("MsiHandle");
53 }
54
55 this.handle = value;
56 this.owningThread = Thread.CurrentThread.ManagedThreadId;
57#if DEBUG
58 this.creationStack = Environment.StackTrace;
59#endif
60 }
61 }
62
63 /// <summary>
64 /// Close the MSI handle.
65 /// </summary>
66 public void Close()
67 {
68 this.Dispose();
69 }
70
71 /// <summary>
72 /// Disposes the managed and unmanaged objects in this object.
73 /// </summary>
74 public void Dispose()
75 {
76 this.Dispose(true);
77 GC.SuppressFinalize(this);
78 }
79
80 /// <summary>
81 /// Disposes the managed and unmanaged objects in this object.
82 /// </summary>
83 /// <param name="disposing">true to dispose the managed objects.</param>
84 protected virtual void Dispose(bool disposing)
85 {
86 if (!this.disposed)
87 {
88 if (0 != this.handle)
89 {
90 if (Thread.CurrentThread.ManagedThreadId == this.owningThread)
91 {
92 int error = MsiInterop.MsiCloseHandle(this.handle);
93 if (0 != error)
94 {
95 throw new Win32Exception(error);
96 }
97 this.handle = 0;
98 }
99 else
100 {
101 // Don't try to close the handle on a different thread than it was opened.
102 // This will occasionally cause MSI to AV.
103 string message = String.Format("Leaked msi handle {0} created on thread {1} by type {2}. This handle cannot be closed on thread {3}",
104 this.handle, this.owningThread, this.GetType(), Thread.CurrentThread.ManagedThreadId);
105#if DEBUG
106 throw new InvalidOperationException(String.Format("{0}. Created {1}", message, this.creationStack));
107#else
108 Debug.WriteLine(message);
109#endif
110 }
111 }
112
113 this.disposed = true;
114 }
115 }
116 }
117}
diff --git a/src/WixToolset.Core.Native/Msi/MsiInterop.cs b/src/WixToolset.Core.Native/Msi/MsiInterop.cs
new file mode 100644
index 00000000..0d16fcb2
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/MsiInterop.cs
@@ -0,0 +1,397 @@
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.Native.Msi
4{
5 using System;
6 using System.Text;
7 using System.Runtime.InteropServices;
8
9 /// <summary>
10 /// Class exposing static functions and structs from MSI API.
11 /// </summary>
12 internal static class MsiInterop
13 {
14 // Patching constants
15 internal const int MsiMaxStreamNameLength = 62; // http://msdn2.microsoft.com/library/aa370551.aspx
16
17 internal const int MSICONDITIONFALSE = 0; // The table is temporary.
18 internal const int MSICONDITIONTRUE = 1; // The table is persistent.
19 internal const int MSICONDITIONNONE = 2; // The table is unknown.
20 internal const int MSICONDITIONERROR = 3; // An invalid handle or invalid parameter was passed to the function.
21
22 /*
23 internal const int MSIDBOPENREADONLY = 0;
24 internal const int MSIDBOPENTRANSACT = 1;
25 internal const int MSIDBOPENDIRECT = 2;
26 internal const int MSIDBOPENCREATE = 3;
27 internal const int MSIDBOPENCREATEDIRECT = 4;
28 internal const int MSIDBOPENPATCHFILE = 32;
29
30 internal const int MSIMODIFYSEEK = -1; // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks.
31 internal const int MSIMODIFYREFRESH = 0; // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records.
32 internal const int MSIMODIFYINSERT = 1; // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins.
33 internal const int MSIMODIFYUPDATE = 2; // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records.
34 internal const int MSIMODIFYASSIGN = 3; // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins.
35 internal const int MSIMODIFYREPLACE = 4; // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins.
36 internal const int MSIMODIFYMERGE = 5; // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins.
37 internal const int MSIMODIFYDELETE = 6; // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins.
38 internal const int MSIMODIFYINSERTTEMPORARY = 7; // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins.
39 internal const int MSIMODIFYVALIDATE = 8; // Validates a record. Does not validate across joins. You must first call the MsiViewFetch function with the same record. Obtain validation errors with MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
40 internal const int MSIMODIFYVALIDATENEW = 9; // Validate a new record. Does not validate across joins. Checks for duplicate keys. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
41 internal const int MSIMODIFYVALIDATEFIELD = 10; // Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
42 internal const int MSIMODIFYVALIDATEDELETE = 11; // Validates a record that will be deleted later. You must first call MsiViewFetch. Fails if another row refers to the primary keys of this row. Validation does not check for the existence of the primary keys of this row in properties or strings. Does not check if a column is a foreign key to multiple tables. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
43
44 internal const uint VTI2 = 2;
45 internal const uint VTI4 = 3;
46 internal const uint VTLPWSTR = 30;
47 internal const uint VTFILETIME = 64;
48 */
49
50 internal const int MSICOLINFONAMES = 0; // return column names
51 internal const int MSICOLINFOTYPES = 1; // return column definitions, datatype code followed by width
52
53 /// <summary>
54 /// PInvoke of MsiCloseHandle.
55 /// </summary>
56 /// <param name="database">Handle to a database.</param>
57 /// <returns>Error code.</returns>
58 [DllImport("msi.dll", EntryPoint = "MsiCloseHandle", CharSet = CharSet.Unicode, ExactSpelling = true)]
59 internal static extern int MsiCloseHandle(uint database);
60
61 /// <summary>
62 /// PInvoke of MsiCreateRecord
63 /// </summary>
64 /// <param name="parameters">Count of columns in the record.</param>
65 /// <returns>Handle referencing the record.</returns>
66 [DllImport("msi.dll", EntryPoint = "MsiCreateRecord", CharSet = CharSet.Unicode, ExactSpelling = true)]
67 internal static extern uint MsiCreateRecord(int parameters);
68
69 /// <summary>
70 /// Creates summary information of an existing transform to include validation and error conditions.
71 /// </summary>
72 /// <param name="database">The handle to the database that contains the new database summary information.</param>
73 /// <param name="referenceDatabase">The handle to the database that contains the original summary information.</param>
74 /// <param name="transformFile">The name of the transform to which the summary information is added.</param>
75 /// <param name="errorConditions">The error conditions that should be suppressed when the transform is applied.</param>
76 /// <param name="validations">Specifies the properties to be validated to verify that the transform can be applied to the database.</param>
77 /// <returns>Error code.</returns>
78 [DllImport("msi.dll", EntryPoint = "MsiCreateTransformSummaryInfoW", CharSet = CharSet.Unicode, ExactSpelling = true)]
79 internal static extern int MsiCreateTransformSummaryInfo(uint database, uint referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations);
80
81 /// <summary>
82 /// Applies a transform to a database.
83 /// </summary>
84 /// <param name="database">Handle to the database obtained from MsiOpenDatabase to transform.</param>
85 /// <param name="transformFile">Specifies the name of the transform file to apply.</param>
86 /// <param name="errorConditions">Error conditions that should be suppressed.</param>
87 /// <returns>Error code.</returns>
88 [DllImport("msi.dll", EntryPoint = "MsiDatabaseApplyTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)]
89 internal static extern int MsiDatabaseApplyTransform(uint database, string transformFile, TransformErrorConditions errorConditions);
90
91 /// <summary>
92 /// PInvoke of MsiDatabaseCommit.
93 /// </summary>
94 /// <param name="database">Handle to a databse.</param>
95 /// <returns>Error code.</returns>
96 [DllImport("msi.dll", EntryPoint = "MsiDatabaseCommit", CharSet = CharSet.Unicode, ExactSpelling = true)]
97 internal static extern int MsiDatabaseCommit(uint database);
98
99 /// <summary>
100 /// PInvoke of MsiDatabaseExportW.
101 /// </summary>
102 /// <param name="database">Handle to a database.</param>
103 /// <param name="tableName">Table name.</param>
104 /// <param name="folderPath">Folder path.</param>
105 /// <param name="fileName">File name.</param>
106 /// <returns>Error code.</returns>
107 [DllImport("msi.dll", EntryPoint = "MsiDatabaseExportW", CharSet = CharSet.Unicode, ExactSpelling = true)]
108 internal static extern int MsiDatabaseExport(uint database, string tableName, string folderPath, string fileName);
109
110 /// <summary>
111 /// Generates a transform file of differences between two databases.
112 /// </summary>
113 /// <param name="database">Handle to the database obtained from MsiOpenDatabase that includes the changes.</param>
114 /// <param name="databaseReference">Handle to the database obtained from MsiOpenDatabase that does not include the changes.</param>
115 /// <param name="transformFile">A null-terminated string that specifies the name of the transform file being generated.
116 /// This parameter can be null. If szTransformFile is null, you can use MsiDatabaseGenerateTransform to test whether two
117 /// databases are identical without creating a transform. If the databases are identical, the function returns ERROR_NO_DATA.
118 /// If the databases are different the function returns NOERROR.</param>
119 /// <param name="reserved1">This is a reserved argument and must be set to 0.</param>
120 /// <param name="reserved2">This is a reserved argument and must be set to 0.</param>
121 /// <returns>Error code.</returns>
122 [DllImport("msi.dll", EntryPoint = "MsiDatabaseGenerateTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)]
123 internal static extern int MsiDatabaseGenerateTransform(uint database, uint databaseReference, string transformFile, int reserved1, int reserved2);
124
125 /// <summary>
126 /// PInvoke of MsiDatabaseImportW.
127 /// </summary>
128 /// <param name="database">Handle to a database.</param>
129 /// <param name="folderPath">Folder path.</param>
130 /// <param name="fileName">File name.</param>
131 /// <returns>Error code.</returns>
132 [DllImport("msi.dll", EntryPoint = "MsiDatabaseImportW", CharSet = CharSet.Unicode, ExactSpelling = true)]
133 internal static extern int MsiDatabaseImport(uint database, string folderPath, string fileName);
134
135 /// <summary>
136 /// PInvoke of MsiDatabaseMergeW.
137 /// </summary>
138 /// <param name="database">The handle to the database obtained from MsiOpenDatabase.</param>
139 /// <param name="databaseMerge">The handle to the database obtained from MsiOpenDatabase to merge into the base database.</param>
140 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
141 /// <returns>Error code.</returns>
142 [DllImport("msi.dll", EntryPoint = "MsiDatabaseMergeW", CharSet = CharSet.Unicode, ExactSpelling = true)]
143 internal static extern int MsiDatabaseMerge(uint database, uint databaseMerge, string tableName);
144
145 /// <summary>
146 /// PInvoke of MsiDatabaseOpenViewW.
147 /// </summary>
148 /// <param name="database">Handle to a database.</param>
149 /// <param name="query">SQL query.</param>
150 /// <param name="view">View handle.</param>
151 /// <returns>Error code.</returns>
152 [DllImport("msi.dll", EntryPoint = "MsiDatabaseOpenViewW", CharSet = CharSet.Unicode, ExactSpelling = true)]
153 internal static extern int MsiDatabaseOpenView(uint database, string query, out uint view);
154
155 /// <summary>
156 /// PInvoke of MsiGetFileHashW.
157 /// </summary>
158 /// <param name="filePath">File path.</param>
159 /// <param name="options">Hash options (must be 0).</param>
160 /// <param name="hash">Buffer to recieve hash.</param>
161 /// <returns>Error code.</returns>
162 [DllImport("msi.dll", EntryPoint = "MsiGetFileHashW", CharSet = CharSet.Unicode, ExactSpelling = true)]
163 internal static extern int MsiGetFileHash(string filePath, uint options, MSIFILEHASHINFO hash);
164
165 /// <summary>
166 /// PInvoke of MsiGetFileVersionW.
167 /// </summary>
168 /// <param name="filePath">File path.</param>
169 /// <param name="versionBuf">Buffer to receive version info.</param>
170 /// <param name="versionBufSize">Size of version buffer.</param>
171 /// <param name="langBuf">Buffer to recieve lang info.</param>
172 /// <param name="langBufSize">Size of lang buffer.</param>
173 /// <returns>Error code.</returns>
174 [DllImport("msi.dll", EntryPoint = "MsiGetFileVersionW", CharSet = CharSet.Unicode, ExactSpelling = true)]
175 internal static extern int MsiGetFileVersion(string filePath, StringBuilder versionBuf, ref int versionBufSize, StringBuilder langBuf, ref int langBufSize);
176
177 /// <summary>
178 /// PInvoke of MsiGetLastErrorRecord.
179 /// </summary>
180 /// <returns>Handle to error record if one exists.</returns>
181 [DllImport("msi.dll", EntryPoint = "MsiGetLastErrorRecord", CharSet = CharSet.Unicode, ExactSpelling = true)]
182 internal static extern uint MsiGetLastErrorRecord();
183
184 /// <summary>
185 /// PInvoke of MsiDatabaseGetPrimaryKeysW.
186 /// </summary>
187 /// <param name="database">Handle to a database.</param>
188 /// <param name="tableName">Table name.</param>
189 /// <param name="record">Handle to receive resulting record.</param>
190 /// <returns>Error code.</returns>
191 [DllImport("msi.dll", EntryPoint = "MsiDatabaseGetPrimaryKeysW", CharSet = CharSet.Unicode, ExactSpelling = true)]
192 internal static extern int MsiDatabaseGetPrimaryKeys(uint database, string tableName, out uint record);
193
194 /// <summary>
195 /// PInvoke of MsiDoActionW.
196 /// </summary>
197 /// <param name="product">Handle to the installation provided to a DLL custom action or
198 /// obtained through MsiOpenPackage, MsiOpenPackageEx, or MsiOpenProduct.</param>
199 /// <param name="action">Specifies the action to execute.</param>
200 /// <returns>Error code.</returns>
201 [DllImport("msi.dll", EntryPoint = "MsiDoActionW", CharSet = CharSet.Unicode, ExactSpelling = true)]
202 internal static extern int MsiDoAction(uint product, string action);
203
204 /// <summary>
205 /// PInvoke of MsiGetSummaryInformationW. Can use either database handle or database path as input.
206 /// </summary>
207 /// <param name="database">Handle to a database.</param>
208 /// <param name="databasePath">Path to a database.</param>
209 /// <param name="updateCount">Max number of updated values.</param>
210 /// <param name="summaryInfo">Handle to summary information.</param>
211 /// <returns>Error code.</returns>
212 [DllImport("msi.dll", EntryPoint = "MsiGetSummaryInformationW", CharSet = CharSet.Unicode, ExactSpelling = true)]
213 internal static extern int MsiGetSummaryInformation(uint database, string databasePath, uint updateCount, ref uint summaryInfo);
214
215 /// <summary>
216 /// PInvoke of MsiDatabaseIsTablePersitentW.
217 /// </summary>
218 /// <param name="database">Handle to a database.</param>
219 /// <param name="tableName">Table name.</param>
220 /// <returns>MSICONDITION</returns>
221 [DllImport("msi.dll", EntryPoint = "MsiDatabaseIsTablePersistentW", CharSet = CharSet.Unicode, ExactSpelling = true)]
222 internal static extern int MsiDatabaseIsTablePersistent(uint database, string tableName);
223
224 /// <summary>
225 /// PInvoke of MsiOpenDatabaseW.
226 /// </summary>
227 /// <param name="databasePath">Path to database.</param>
228 /// <param name="persist">Persist mode.</param>
229 /// <param name="database">Handle to database.</param>
230 /// <returns>Error code.</returns>
231 [DllImport("msi.dll", EntryPoint = "MsiOpenDatabaseW", CharSet = CharSet.Unicode, ExactSpelling = true)]
232 internal static extern int MsiOpenDatabase(string databasePath, IntPtr persist, out uint database);
233
234 /// <summary>
235 /// PInvoke of MsiOpenPackageW.
236 /// </summary>
237 /// <param name="packagePath">The path to the package.</param>
238 /// <param name="product">A pointer to a variable that receives the product handle.</param>
239 /// <returns>Error code.</returns>
240 [DllImport("msi.dll", EntryPoint = "MsiOpenPackageW", CharSet = CharSet.Unicode, ExactSpelling = true)]
241 internal static extern int MsiOpenPackage(string packagePath, out uint product);
242
243 /// <summary>
244 /// PInvoke of MsiRecordIsNull.
245 /// </summary>
246 /// <param name="record">MSI Record handle.</param>
247 /// <param name="field">Index of field to check for null value.</param>
248 /// <returns>true if the field is null, false if not, and an error code for any error.</returns>
249 [DllImport("msi.dll", EntryPoint = "MsiRecordIsNull", CharSet = CharSet.Unicode, ExactSpelling = true)]
250 internal static extern int MsiRecordIsNull(uint record, int field);
251
252 /// <summary>
253 /// PInvoke of MsiRecordGetInteger.
254 /// </summary>
255 /// <param name="record">MSI Record handle.</param>
256 /// <param name="field">Index of field to retrieve integer from.</param>
257 /// <returns>Integer value.</returns>
258 [DllImport("msi.dll", EntryPoint = "MsiRecordGetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)]
259 internal static extern int MsiRecordGetInteger(uint record, int field);
260
261 /// <summary>
262 /// PInvoke of MsiRectordSetInteger.
263 /// </summary>
264 /// <param name="record">MSI Record handle.</param>
265 /// <param name="field">Index of field to set integer value in.</param>
266 /// <param name="value">Value to set field to.</param>
267 /// <returns>Error code.</returns>
268 [DllImport("msi.dll", EntryPoint = "MsiRecordSetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)]
269 internal static extern int MsiRecordSetInteger(uint record, int field, int value);
270
271 /// <summary>
272 /// PInvoke of MsiRecordGetStringW.
273 /// </summary>
274 /// <param name="record">MSI Record handle.</param>
275 /// <param name="field">Index of field to get string value from.</param>
276 /// <param name="valueBuf">Buffer to recieve value.</param>
277 /// <param name="valueBufSize">Size of buffer.</param>
278 /// <returns>Error code.</returns>
279 [DllImport("msi.dll", EntryPoint = "MsiRecordGetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)]
280 internal static extern int MsiRecordGetString(uint record, int field, StringBuilder valueBuf, ref int valueBufSize);
281
282 /// <summary>
283 /// PInvoke of MsiRecordSetStringW.
284 /// </summary>
285 /// <param name="record">MSI Record handle.</param>
286 /// <param name="field">Index of field to set string value in.</param>
287 /// <param name="value">String value.</param>
288 /// <returns>Error code.</returns>
289 [DllImport("msi.dll", EntryPoint = "MsiRecordSetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)]
290 internal static extern int MsiRecordSetString(uint record, int field, string value);
291
292 /// <summary>
293 /// PInvoke of MsiRecordSetStreamW.
294 /// </summary>
295 /// <param name="record">MSI Record handle.</param>
296 /// <param name="field">Index of field to set stream value in.</param>
297 /// <param name="filePath">Path to file to set stream value to.</param>
298 /// <returns>Error code.</returns>
299 [DllImport("msi.dll", EntryPoint = "MsiRecordSetStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)]
300 internal static extern int MsiRecordSetStream(uint record, int field, string filePath);
301
302 /// <summary>
303 /// PInvoke of MsiRecordReadStreamW.
304 /// </summary>
305 /// <param name="record">MSI Record handle.</param>
306 /// <param name="field">Index of field to read stream from.</param>
307 /// <param name="dataBuf">Data buffer to recieve stream value.</param>
308 /// <param name="dataBufSize">Size of data buffer.</param>
309 /// <returns>Error code.</returns>
310 [DllImport("msi.dll", EntryPoint = "MsiRecordReadStream", CharSet = CharSet.Unicode, ExactSpelling = true)]
311 internal static extern int MsiRecordReadStream(uint record, int field, byte[] dataBuf, ref int dataBufSize);
312
313 /// <summary>
314 /// PInvoke of MsiRecordGetFieldCount.
315 /// </summary>
316 /// <param name="record">MSI Record handle.</param>
317 /// <returns>Count of fields in the record.</returns>
318 [DllImport("msi.dll", EntryPoint = "MsiRecordGetFieldCount", CharSet = CharSet.Unicode, ExactSpelling = true)]
319 internal static extern int MsiRecordGetFieldCount(uint record);
320
321 /// <summary>
322 /// PInvoke of MsiSetExternalUIW.
323 /// </summary>
324 /// <param name="installUIHandler">Specifies a callback function that conforms to the INSTALLUI_HANDLER specification.</param>
325 /// <param name="installLogMode">Specifies which messages to handle using the external message handler. If the external
326 /// handler returns a non-zero result, then that message will not be sent to the UI, instead the message will be logged
327 /// if logging has been enabled.</param>
328 /// <param name="context">Pointer to an application context that is passed to the callback function.
329 /// This parameter can be used for error checking.</param>
330 /// <returns>The return value is the previously set external handler, or zero (0) if there was no previously set handler.</returns>
331 [DllImport("msi.dll", EntryPoint = "MsiSetExternalUIW", CharSet = CharSet.Unicode, ExactSpelling = true)]
332 internal static extern InstallUIHandler MsiSetExternalUI(InstallUIHandler installUIHandler, int installLogMode, IntPtr context);
333
334 /// <summary>
335 /// PInvoke of MsiSetInternalUI.
336 /// </summary>
337 /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param>
338 /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.
339 /// A pointer to the previous owner of the user interface is returned.
340 /// If this parameter is null, the owner of the user interface does not change.</param>
341 /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns>
342 [DllImport("msi.dll", EntryPoint = "MsiSetInternalUI", CharSet = CharSet.Unicode, ExactSpelling = true)]
343 internal static extern int MsiSetInternalUI(int uiLevel, ref IntPtr hwnd);
344
345 /// <summary>
346 /// PInvoke of MsiSummaryInfoGetPropertyW.
347 /// </summary>
348 /// <param name="summaryInfo">Handle to summary info.</param>
349 /// <param name="property">Property to get value from.</param>
350 /// <param name="dataType">Data type of property.</param>
351 /// <param name="integerValue">Integer to receive integer value.</param>
352 /// <param name="fileTimeValue">File time to receive file time value.</param>
353 /// <param name="stringValueBuf">String buffer to receive string value.</param>
354 /// <param name="stringValueBufSize">Size of string buffer.</param>
355 /// <returns>Error code.</returns>
356 [DllImport("msi.dll", EntryPoint = "MsiSummaryInfoGetPropertyW", CharSet = CharSet.Unicode, ExactSpelling = true)]
357 internal static extern int MsiSummaryInfoGetProperty(uint summaryInfo, int property, out uint dataType, out int integerValue, ref System.Runtime.InteropServices.ComTypes.FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize);
358
359 /// <summary>
360 /// PInvoke of MsiViewGetColumnInfo.
361 /// </summary>
362 /// <param name="view">Handle to view.</param>
363 /// <param name="columnInfo">Column info.</param>
364 /// <param name="record">Handle for returned record.</param>
365 /// <returns>Error code.</returns>
366 [DllImport("msi.dll", EntryPoint = "MsiViewGetColumnInfo", CharSet = CharSet.Unicode, ExactSpelling = true)]
367 internal static extern int MsiViewGetColumnInfo(uint view, int columnInfo, out uint record);
368
369 /// <summary>
370 /// PInvoke of MsiViewExecute.
371 /// </summary>
372 /// <param name="view">Handle of view to execute.</param>
373 /// <param name="record">Handle to a record that supplies the parameters for the view.</param>
374 /// <returns>Error code.</returns>
375 [DllImport("msi.dll", EntryPoint = "MsiViewExecute", CharSet = CharSet.Unicode, ExactSpelling = true)]
376 internal static extern int MsiViewExecute(uint view, uint record);
377
378 /// <summary>
379 /// PInvoke of MsiViewFetch.
380 /// </summary>
381 /// <param name="view">Handle of view to fetch a row from.</param>
382 /// <param name="record">Handle to receive record info.</param>
383 /// <returns>Error code.</returns>
384 [DllImport("msi.dll", EntryPoint = "MsiViewFetch", CharSet = CharSet.Unicode, ExactSpelling = true)]
385 internal static extern int MsiViewFetch(uint view, out uint record);
386
387 /// <summary>
388 /// PInvoke of MsiViewModify.
389 /// </summary>
390 /// <param name="view">Handle of view to modify.</param>
391 /// <param name="modifyMode">Modify mode.</param>
392 /// <param name="record">Handle of record.</param>
393 /// <returns>Error code.</returns>
394 [DllImport("msi.dll", EntryPoint = "MsiViewModify", CharSet = CharSet.Unicode, ExactSpelling = true)]
395 internal static extern int MsiViewModify(uint view, int modifyMode, uint record);
396 }
397}
diff --git a/src/WixToolset.Core.Native/Msi/OpenDatabase.cs b/src/WixToolset.Core.Native/Msi/OpenDatabase.cs
new file mode 100644
index 00000000..18a78f77
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/OpenDatabase.cs
@@ -0,0 +1,40 @@
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.Native.Msi
4{
5 /// <summary>
6 /// Enum of predefined persist modes used when opening a database.
7 /// </summary>
8 public enum OpenDatabase
9 {
10 /// <summary>
11 /// Open a database read-only, no persistent changes.
12 /// </summary>
13 ReadOnly = 0,
14
15 /// <summary>
16 /// Open a database read/write in transaction mode.
17 /// </summary>
18 Transact = 1,
19
20 /// <summary>
21 /// Open a database direct read/write without transaction.
22 /// </summary>
23 Direct = 2,
24
25 /// <summary>
26 /// Create a new database, transact mode read/write.
27 /// </summary>
28 Create = 3,
29
30 /// <summary>
31 /// Create a new database, direct mode read/write.
32 /// </summary>
33 CreateDirect = 4,
34
35 /// <summary>
36 /// Indicates a patch file is being opened.
37 /// </summary>
38 OpenPatchFile = 32
39 }
40}
diff --git a/src/WixToolset.Core.Native/Msi/Record.cs b/src/WixToolset.Core.Native/Msi/Record.cs
new file mode 100644
index 00000000..c25e76e2
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/Record.cs
@@ -0,0 +1,181 @@
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.Native.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Text;
8
9 /// <summary>
10 /// Wrapper class around msi.dll interop for a record.
11 /// </summary>
12 public sealed class Record : MsiHandle
13 {
14 /// <summary>
15 /// Creates a record with the specified number of fields.
16 /// </summary>
17 /// <param name="fieldCount">Number of fields in record.</param>
18 public Record(int fieldCount)
19 {
20 this.Handle = MsiInterop.MsiCreateRecord(fieldCount);
21 if (0 == this.Handle)
22 {
23 throw new OutOfMemoryException();
24 }
25 }
26
27 /// <summary>
28 /// Creates a record from a handle.
29 /// </summary>
30 /// <param name="handle">Handle to create record from.</param>
31 internal Record(uint handle)
32 {
33 this.Handle = handle;
34 }
35
36 /// <summary>
37 /// Gets a string value at specified location.
38 /// </summary>
39 /// <param name="field">Index into record to get string.</param>
40 public string this[int field]
41 {
42 get => this.GetString(field);
43 set => this.SetString(field, value);
44 }
45
46 /// <summary>
47 /// Determines if the value is null at the specified location.
48 /// </summary>
49 /// <param name="field">Index into record of the field to query.</param>
50 /// <returns>true if the value is null, false otherwise.</returns>
51 public bool IsNull(int field)
52 {
53 var error = MsiInterop.MsiRecordIsNull(this.Handle, field);
54
55 switch (error)
56 {
57 case 0:
58 return false;
59 case 1:
60 return true;
61 default:
62 throw new Win32Exception(error);
63 }
64 }
65
66 /// <summary>
67 /// Gets integer value at specified location.
68 /// </summary>
69 /// <param name="field">Index into record to get integer</param>
70 /// <returns>Integer value</returns>
71 public int GetInteger(int field)
72 {
73 return MsiInterop.MsiRecordGetInteger(this.Handle, field);
74 }
75
76 /// <summary>
77 /// Sets integer value at specified location.
78 /// </summary>
79 /// <param name="field">Index into record to set integer.</param>
80 /// <param name="value">Value to set into record.</param>
81 public void SetInteger(int field, int value)
82 {
83 var error = MsiInterop.MsiRecordSetInteger(this.Handle, field, value);
84 if (0 != error)
85 {
86 throw new Win32Exception(error);
87 }
88 }
89
90 /// <summary>
91 /// Gets string value at specified location.
92 /// </summary>
93 /// <param name="field">Index into record to get string.</param>
94 /// <returns>String value</returns>
95 public string GetString(int field)
96 {
97 var bufferSize = 256;
98 var buffer = new StringBuilder(bufferSize);
99 var error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize);
100 if (234 == error)
101 {
102 buffer.EnsureCapacity(++bufferSize);
103 error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize);
104 }
105
106 if (0 != error)
107 {
108 throw new Win32Exception(error);
109 }
110
111 return (0 < buffer.Length ? buffer.ToString() : null);
112 }
113
114 /// <summary>
115 /// Set string value at specified location
116 /// </summary>
117 /// <param name="field">Index into record to set string.</param>
118 /// <param name="value">Value to set into record</param>
119 public void SetString(int field, string value)
120 {
121 var error = MsiInterop.MsiRecordSetString(this.Handle, field, value);
122 if (0 != error)
123 {
124 throw new Win32Exception(error);
125 }
126 }
127
128 /// <summary>
129 /// Get stream at specified location.
130 /// </summary>
131 /// <param name="field">Index into record to get stream.</param>
132 /// <param name="buffer">buffer to receive bytes from stream.</param>
133 /// <param name="requestedBufferSize">Buffer size to read.</param>
134 /// <returns>Stream read into string.</returns>
135 public int GetStream(int field, byte[] buffer, int requestedBufferSize)
136 {
137 var bufferSize = buffer.Length;
138 if (requestedBufferSize > 0)
139 {
140 bufferSize = requestedBufferSize;
141 }
142
143 var error = MsiInterop.MsiRecordReadStream(this.Handle, field, buffer, ref bufferSize);
144 if (0 != error)
145 {
146 throw new Win32Exception(error);
147 }
148
149 return bufferSize;
150 }
151
152 /// <summary>
153 /// Sets a stream at a specified location.
154 /// </summary>
155 /// <param name="field">Index into record to set stream.</param>
156 /// <param name="path">Path to file to read into stream.</param>
157 public void SetStream(int field, string path)
158 {
159 var error = MsiInterop.MsiRecordSetStream(this.Handle, field, path);
160 if (0 != error)
161 {
162 throw new Win32Exception(error);
163 }
164 }
165
166 /// <summary>
167 /// Gets the number of fields in record.
168 /// </summary>
169 /// <returns>Count of fields in record.</returns>
170 public int GetFieldCount()
171 {
172 var size = MsiInterop.MsiRecordGetFieldCount(this.Handle);
173 if (0 > size)
174 {
175 throw new Win32Exception();
176 }
177
178 return size;
179 }
180 }
181}
diff --git a/src/WixToolset.Core.Native/Msi/Session.cs b/src/WixToolset.Core.Native/Msi/Session.cs
new file mode 100644
index 00000000..743fb2be
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/Session.cs
@@ -0,0 +1,42 @@
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.Native.Msi
4{
5 using System.Globalization;
6
7 /// <summary>
8 /// Controls the installation process.
9 /// </summary>
10 public sealed class Session : MsiHandle
11 {
12 /// <summary>
13 /// Instantiate a new Session.
14 /// </summary>
15 /// <param name="database">The database to open.</param>
16 public Session(Database database)
17 {
18 var packagePath = "#" + database.Handle.ToString(CultureInfo.InvariantCulture);
19
20 var error = MsiInterop.MsiOpenPackage(packagePath, out var handle);
21 if (0 != error)
22 {
23 throw new MsiException(error);
24 }
25
26 this.Handle = handle;
27 }
28
29 /// <summary>
30 /// Executes a built-in action, custom action, or user-interface wizard action.
31 /// </summary>
32 /// <param name="action">Specifies the action to execute.</param>
33 public void DoAction(string action)
34 {
35 var error = MsiInterop.MsiDoAction(this.Handle, action);
36 if (0 != error)
37 {
38 throw new MsiException(error);
39 }
40 }
41 }
42}
diff --git a/src/WixToolset.Core.Native/Msi/SummaryInformation.cs b/src/WixToolset.Core.Native/Msi/SummaryInformation.cs
new file mode 100644
index 00000000..a7ba5717
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/SummaryInformation.cs
@@ -0,0 +1,243 @@
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.Native.Msi
4{
5 using System;
6 using System.Globalization;
7 using System.Text;
8 using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
9
10 /// <summary>
11 /// Summary information for the MSI files.
12 /// </summary>
13 public sealed class SummaryInformation : MsiHandle
14 {
15 /// <summary>
16 /// Summary information properties for transforms.
17 /// </summary>
18 public enum Transform
19 {
20 /// <summary>PID_CODEPAGE = code page for the summary information stream</summary>
21 CodePage = 1,
22
23 /// <summary>PID_TITLE = typically just "Transform"</summary>
24 Title = 2,
25
26 /// <summary>PID_SUBJECT = original subject of target</summary>
27 TargetSubject = 3,
28
29 /// <summary>PID_AUTHOR = original manufacturer of target</summary>
30 TargetManufacturer = 4,
31
32 /// <summary>PID_KEYWORDS = keywords for the transform, typically including at least "Installer"</summary>
33 Keywords = 5,
34
35 /// <summary>PID_COMMENTS = describes what this package does</summary>
36 Comments = 6,
37
38 /// <summary>PID_TEMPLATE = target platform;language</summary>
39 TargetPlatformAndLanguage = 7,
40
41 /// <summary>PID_LASTAUTHOR = updated platform;language</summary>
42 UpdatedPlatformAndLanguage = 8,
43
44 /// <summary>PID_REVNUMBER = {productcode}version;{newproductcode}newversion;upgradecode</summary>
45 ProductCodes = 9,
46
47 /// <summary>PID_LASTPRINTED should be null for transforms</summary>
48 Reserved11 = 11,
49
50 ///.<summary>PID_CREATE_DTM = the timestamp when the transform was created</summary>
51 CreationTime = 12,
52
53 /// <summary>PID_PAGECOUNT = minimum installer version</summary>
54 InstallerRequirement = 14,
55
56 /// <summary>PID_CHARCOUNT = validation and error flags</summary>
57 ValidationFlags = 16,
58
59 /// <summary>PID_APPNAME = the application that created the transform</summary>
60 CreatingApplication = 18,
61
62 /// <summary>PID_SECURITY = whether read-only is enforced; should always be 4 for transforms</summary>
63 Security = 19,
64 }
65
66 /// <summary>
67 /// Summary information properties for patches.
68 /// </summary>
69 public enum Patch
70 {
71 /// <summary>PID_CODEPAGE = code page of the summary information stream</summary>
72 CodePage = 1,
73
74 /// <summary>PID_TITLE = a brief description of the package type</summary>
75 Title = 2,
76
77 /// <summary>PID_SUBJECT = package name</summary>
78 PackageName = 3,
79
80 /// <summary>PID_AUTHOR = manufacturer of the patch package</summary>
81 Manufacturer = 4,
82
83 /// <summary>PID_KEYWORDS = alternate sources for the patch package</summary>
84 Sources = 5,
85
86 /// <summary>PID_COMMENTS = general purpose of the patch package</summary>
87 Comments = 6,
88
89 /// <summary>PID_TEMPLATE = semicolon delimited list of ProductCodes</summary>
90 ProductCodes = 7,
91
92 /// <summary>PID_LASTAUTHOR = semicolon delimited list of transform names</summary>
93 TransformNames = 8,
94
95 /// <summary>PID_REVNUMBER = GUID patch code</summary>
96 PatchCode = 9,
97
98 /// <summary>PID_LASTPRINTED should be null for patches</summary>
99 Reserved11 = 11,
100
101 /// <summary>PID_PAGECOUNT should be null for patches</summary>
102 Reserved14 = 14,
103
104 /// <summary>PID_WORDCOUNT = minimum installer version</summary>
105 InstallerRequirement = 15,
106
107 /// <summary>PID_CHARCOUNT should be null for patches</summary>
108 Reserved16 = 16,
109
110 /// <summary>PID_SECURITY = read-only attribute of the patch package</summary>
111 Security = 19,
112 }
113
114 /// <summary>
115 /// Summary information values for the InstallerRequirement property.
116 /// </summary>
117 public enum InstallerRequirement
118 {
119 /// <summary>Any version of the installer will do</summary>
120 Version10 = 1,
121
122 /// <summary>At least 1.2</summary>
123 Version12 = 2,
124
125 /// <summary>At least 2.0</summary>
126 Version20 = 3,
127
128 /// <summary>At least 3.0</summary>
129 Version30 = 4,
130
131 /// <summary>At least 3.1</summary>
132 Version31 = 5,
133 }
134
135 /// <summary>
136 /// Instantiate a new SummaryInformation class from an open database.
137 /// </summary>
138 /// <param name="db">Database to retrieve summary information from.</param>
139 public SummaryInformation(Database db)
140 {
141 if (null == db)
142 {
143 throw new ArgumentNullException("db");
144 }
145
146 uint handle = 0;
147 var error = MsiInterop.MsiGetSummaryInformation(db.Handle, null, 0, ref handle);
148 if (0 != error)
149 {
150 throw new MsiException(error);
151 }
152 this.Handle = handle;
153 }
154
155 /// <summary>
156 /// Instantiate a new SummaryInformation class from a database file.
157 /// </summary>
158 /// <param name="databaseFile">The database file.</param>
159 public SummaryInformation(string databaseFile)
160 {
161 if (null == databaseFile)
162 {
163 throw new ArgumentNullException("databaseFile");
164 }
165
166 uint handle = 0;
167 var error = MsiInterop.MsiGetSummaryInformation(0, databaseFile, 0, ref handle);
168 if (0 != error)
169 {
170 throw new MsiException(error);
171 }
172 this.Handle = handle;
173 }
174
175 /// <summary>
176 /// Gets a summary information property.
177 /// </summary>
178 /// <param name="index">Index of the summary information property.</param>
179 /// <returns>The summary information property.</returns>
180 public string GetProperty(int index)
181 {
182 var bufSize = 64;
183 var stringValue = new StringBuilder(bufSize);
184
185 FILETIME timeValue;
186 timeValue.dwHighDateTime = 0;
187 timeValue.dwLowDateTime = 0;
188
189 var error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out var dataType, out var intValue, ref timeValue, stringValue, ref bufSize);
190 if (234 == error)
191 {
192 stringValue.EnsureCapacity(++bufSize);
193 error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize);
194 }
195
196 if (0 != error)
197 {
198 throw new MsiException(error);
199 }
200
201 switch ((VT)dataType)
202 {
203 case VT.EMPTY:
204 return String.Empty;
205 case VT.LPSTR:
206 return stringValue.ToString();
207 case VT.I2:
208 case VT.I4:
209 return Convert.ToString(intValue, CultureInfo.InvariantCulture);
210 case VT.FILETIME:
211 var longFileTime = (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime);
212 var dateTime = DateTime.FromFileTime(longFileTime);
213 return dateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
214 default:
215 throw new InvalidOperationException();
216 }
217 }
218
219 /// <summary>
220 /// Variant types in the summary information table.
221 /// </summary>
222 private enum VT : uint
223 {
224 /// <summary>Variant has not been assigned.</summary>
225 EMPTY = 0,
226
227 /// <summary>Null variant type.</summary>
228 NULL = 1,
229
230 /// <summary>16-bit integer variant type.</summary>
231 I2 = 2,
232
233 /// <summary>32-bit integer variant type.</summary>
234 I4 = 3,
235
236 /// <summary>String variant type.</summary>
237 LPSTR = 30,
238
239 /// <summary>Date time (FILETIME, converted to Variant time) variant type.</summary>
240 FILETIME = 64,
241 }
242 }
243}
diff --git a/src/WixToolset.Core.Native/Msi/TransformErrorConditions.cs b/src/WixToolset.Core.Native/Msi/TransformErrorConditions.cs
new file mode 100644
index 00000000..313dceeb
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/TransformErrorConditions.cs
@@ -0,0 +1,58 @@
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.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// The errors to suppress when applying a transform.
9 /// </summary>
10 [Flags]
11 public enum TransformErrorConditions
12 {
13 /// <summary>
14 /// None of the following conditions.
15 /// </summary>
16 None = 0x0,
17
18 /// <summary>
19 /// Suppress error when adding a row that exists.
20 /// </summary>
21 AddExistingRow = 0x1,
22
23 /// <summary>
24 /// Suppress error when deleting a row that does not exist.
25 /// </summary>
26 DeleteMissingRow = 0x2,
27
28 /// <summary>
29 /// Suppress error when adding a table that exists.
30 /// </summary>
31 AddExistingTable = 0x4,
32
33 /// <summary>
34 /// Suppress error when deleting a table that does not exist.
35 /// </summary>
36 DeleteMissingTable = 0x8,
37
38 /// <summary>
39 /// Suppress error when updating a row that does not exist.
40 /// </summary>
41 UpdateMissingRow = 0x10,
42
43 /// <summary>
44 /// Suppress error when transform and database code pages do not match, and their code pages are neutral.
45 /// </summary>
46 ChangeCodepage = 0x20,
47
48 /// <summary>
49 /// Create the temporary _TransformView table when applying a transform.
50 /// </summary>
51 ViewTransform = 0x100,
52
53 /// <summary>
54 /// Suppress all errors but the option to create the temporary _TransformView table.
55 /// </summary>
56 All = 0x3F
57 }
58}
diff --git a/src/WixToolset.Core.Native/Msi/TransformValidations.cs b/src/WixToolset.Core.Native/Msi/TransformValidations.cs
new file mode 100644
index 00000000..52bddeaf
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/TransformValidations.cs
@@ -0,0 +1,73 @@
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.Native.Msi
4{
5 using System;
6
7 /// <summary>
8 /// The validation to run while applying a transform.
9 /// </summary>
10 [Flags]
11 public enum TransformValidations
12 {
13 /// <summary>
14 /// Do not validate properties.
15 /// </summary>
16 None = 0x0,
17
18 /// <summary>
19 /// Default language must match base database.
20 /// </summary>
21 Language = 0x1,
22
23 /// <summary>
24 /// Product must match base database.
25 /// </summary>
26 Product = 0x2,
27
28 /// <summary>
29 /// Check major version only.
30 /// </summary>
31 MajorVersion = 0x8,
32
33 /// <summary>
34 /// Check major and minor versions only.
35 /// </summary>
36 MinorVersion = 0x10,
37
38 /// <summary>
39 /// Check major, minor, and update versions.
40 /// </summary>
41 UpdateVersion = 0x20,
42
43 /// <summary>
44 /// Installed version &lt; base version.
45 /// </summary>
46 NewLessBaseVersion = 0x40,
47
48 /// <summary>
49 /// Installed version &lt;= base version.
50 /// </summary>
51 NewLessEqualBaseVersion = 0x80,
52
53 /// <summary>
54 /// Installed version = base version.
55 /// </summary>
56 NewEqualBaseVersion = 0x100,
57
58 /// <summary>
59 /// Installed version &gt;= base version.
60 /// </summary>
61 NewGreaterEqualBaseVersion = 0x200,
62
63 /// <summary>
64 /// Installed version &gt; base version.
65 /// </summary>
66 NewGreaterBaseVersion = 0x400,
67
68 /// <summary>
69 /// UpgradeCode must match base database.
70 /// </summary>
71 UpgradeCode = 0x800
72 }
73}
diff --git a/src/WixToolset.Core.Native/Msi/View.cs b/src/WixToolset.Core.Native/Msi/View.cs
new file mode 100644
index 00000000..6305a9de
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/View.cs
@@ -0,0 +1,206 @@
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.Native.Msi
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9
10 /// <summary>
11 /// Wrapper class for MSI API views.
12 /// </summary>
13 public sealed class View : MsiHandle
14 {
15 /// <summary>
16 /// Constructor that creates a view given a database handle and a query.
17 /// </summary>
18 /// <param name="db">Handle to the database to run the query on.</param>
19 /// <param name="query">Query to be executed.</param>
20 public View(Database db, string query)
21 {
22 if (null == db)
23 {
24 throw new ArgumentNullException(nameof(db));
25 }
26
27 if (null == query)
28 {
29 throw new ArgumentNullException(nameof(query));
30 }
31
32 var error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out var handle);
33 if (0 != error)
34 {
35 throw new MsiException(error);
36 }
37
38 this.Handle = handle;
39 }
40
41 /// <summary>
42 /// Enumerator that automatically disposes of the retrieved Records.
43 /// </summary>
44 public IEnumerable<Record> Records => new ViewEnumerable(this);
45
46 /// <summary>
47 /// Executes a view with no customizable parameters.
48 /// </summary>
49 public void Execute()
50 {
51 this.Execute(null);
52 }
53
54 /// <summary>
55 /// Executes a query substituing the values from the records into the customizable parameters
56 /// in the view.
57 /// </summary>
58 /// <param name="record">Record containing parameters to be substituded into the view.</param>
59 public void Execute(Record record)
60 {
61 var error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle);
62 if (0 != error)
63 {
64 throw new MsiException(error);
65 }
66 }
67
68 /// <summary>
69 /// Fetches the next row in the view.
70 /// </summary>
71 /// <returns>Returns the fetched record; otherwise null.</returns>
72 public Record Fetch()
73 {
74 var error = MsiInterop.MsiViewFetch(this.Handle, out var recordHandle);
75 if (259 == error)
76 {
77 return null;
78 }
79 else if (0 != error)
80 {
81 throw new MsiException(error);
82 }
83
84 return new Record(recordHandle);
85 }
86
87 /// <summary>
88 /// Updates a fetched record.
89 /// </summary>
90 /// <param name="type">Type of modification mode.</param>
91 /// <param name="record">Record to be modified.</param>
92 public void Modify(ModifyView type, Record record)
93 {
94 var error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle);
95 if (0 != error)
96 {
97 throw new MsiException(error);
98 }
99 }
100
101 /// <summary>
102 /// Get the column names in a record.
103 /// </summary>
104 /// <returns></returns>
105 public Record GetColumnNames()
106 {
107 return this.GetColumnInfo(MsiInterop.MSICOLINFONAMES);
108 }
109
110 /// <summary>
111 /// Get the column types in a record.
112 /// </summary>
113 /// <returns></returns>
114 public Record GetColumnTypes()
115 {
116 return this.GetColumnInfo(MsiInterop.MSICOLINFOTYPES);
117 }
118
119 /// <summary>
120 /// Returns a record containing column names or definitions.
121 /// </summary>
122 /// <param name="columnType">Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES.</param>
123 /// <returns>The record containing information about the column.</returns>
124 public Record GetColumnInfo(int columnType)
125 {
126
127 var error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out var recordHandle);
128 if (0 != error)
129 {
130 throw new MsiException(error);
131 }
132
133 return new Record(recordHandle);
134 }
135
136 private class ViewEnumerable : IEnumerable<Record>
137 {
138 private readonly View view;
139
140 public ViewEnumerable(View view) => this.view = view;
141
142 public IEnumerator<Record> GetEnumerator() => new ViewEnumerator(this.view);
143
144 IEnumerator IEnumerable.GetEnumerator() => new ViewEnumerator(this.view);
145 }
146
147 private class ViewEnumerator : IEnumerator<Record>
148 {
149 private readonly View view;
150 private readonly List<Record> records = new List<Record>();
151 private int position = -1;
152 private bool disposed;
153
154 public ViewEnumerator(View view) => this.view = view;
155
156 public Record Current => this.records[this.position];
157
158 object IEnumerator.Current => this.records[this.position];
159
160 public bool MoveNext()
161 {
162 if (this.position + 1 >= this.records.Count)
163 {
164 var record = this.view.Fetch();
165
166 if (record == null)
167 {
168 return false;
169 }
170
171 this.records.Add(record);
172 this.position = this.records.Count - 1;
173 }
174 else
175 {
176 ++this.position;
177 }
178
179 return true;
180 }
181
182 public void Reset() => this.position = -1;
183
184 public void Dispose()
185 {
186 this.Dispose(true);
187 }
188
189 protected virtual void Dispose(bool disposing)
190 {
191 if (!this.disposed)
192 {
193 if (disposing)
194 {
195 foreach (var record in this.records)
196 {
197 record.Dispose();
198 }
199 }
200
201 this.disposed = true;
202 }
203 }
204 }
205 }
206}
diff --git a/src/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs b/src/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs
new file mode 100644
index 00000000..268ddc11
--- /dev/null
+++ b/src/WixToolset.Core.Native/Msi/WixInvalidIdtException.cs
@@ -0,0 +1,49 @@
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.Native.Msi
4{
5 using System;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// WiX invalid idt exception.
10 /// </summary>
11 [Serializable]
12 public sealed class WixInvalidIdtException : WixException
13 {
14 /// <summary>
15 /// Instantiate a new WixInvalidIdtException.
16 /// </summary>
17 public WixInvalidIdtException()
18 {
19 }
20
21 /// <summary>
22 /// Instantiate a new WixInvalidIdtException.
23 /// </summary>
24 /// <param name="message"></param>
25 /// <param name="innerException"></param>
26 public WixInvalidIdtException(string message, Exception innerException) : base(message, innerException)
27 {
28 }
29
30 /// <summary>
31 /// Instantiate a new WixInvalidIdtException.
32 /// </summary>
33 /// <param name="idtFile">The invalid idt file.</param>
34 public WixInvalidIdtException(string idtFile) :
35 base(ErrorMessages.InvalidIdt(new SourceLineNumber(idtFile), idtFile))
36 {
37 }
38
39 /// <summary>
40 /// Instantiate a new WixInvalidIdtException.
41 /// </summary>
42 /// <param name="idtFile">The invalid idt file.</param>
43 /// <param name="tableName">The table name of the invalid idt file.</param>
44 public WixInvalidIdtException(string idtFile, string tableName) :
45 base(ErrorMessages.InvalidIdt(new SourceLineNumber(idtFile), idtFile, tableName))
46 {
47 }
48 }
49}