diff options
author | Rob Mensching <rob@firegiant.com> | 2021-03-16 10:34:28 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2021-03-16 11:01:46 -0700 |
commit | 089a08fd6b9398b0e1040f96b8e24ba81acfe05b (patch) | |
tree | 1f521b0b91f2d6373a16f3632c5cb4d835303920 | |
parent | 533fb3c24290f5c9684a661e2576d857fbee9fb6 (diff) | |
download | wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.tar.gz wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.tar.bz2 wix-089a08fd6b9398b0e1040f96b8e24ba81acfe05b.zip |
Migrate PInvoke to Core.Native out of Core
34 files changed, 4448 insertions, 644 deletions
diff --git a/src/WixToolset.Core.Native/CabInterop.cs b/src/WixToolset.Core.Native/CabInterop.cs index 69781047..e08c1b90 100644 --- a/src/WixToolset.Core.Native/CabInterop.cs +++ b/src/WixToolset.Core.Native/CabInterop.cs | |||
@@ -3,133 +3,9 @@ | |||
3 | namespace WixToolset.Core.Native | 3 | namespace WixToolset.Core.Native |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Diagnostics.CodeAnalysis; | ||
7 | using System.Runtime.InteropServices; | 6 | using System.Runtime.InteropServices; |
8 | 7 | ||
9 | /// <summary> | 8 | /// <summary> |
10 | /// The native methods. | ||
11 | /// </summary> | ||
12 | public sealed class NativeMethods | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Starts creating a cabinet. | ||
16 | /// </summary> | ||
17 | /// <param name="cabinetName">Name of cabinet to create.</param> | ||
18 | /// <param name="cabinetDirectory">Directory to create cabinet in.</param> | ||
19 | /// <param name="maxFiles">Maximum number of files that will be added to cabinet.</param> | ||
20 | /// <param name="maxSize">Maximum size of the cabinet.</param> | ||
21 | /// <param name="maxThreshold">Maximum threshold in the cabinet.</param> | ||
22 | /// <param name="compressionType">Type of compression to use in the cabinet.</param> | ||
23 | /// <param name="contextHandle">Handle to opened cabinet.</param> | ||
24 | [DllImport("winterop.dll", EntryPoint = "CreateCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
25 | public static extern void CreateCabBegin(string cabinetName, string cabinetDirectory, uint maxFiles, uint maxSize, uint maxThreshold, uint compressionType, out IntPtr contextHandle); | ||
26 | |||
27 | /// <summary> | ||
28 | /// Adds a file to an open cabinet. | ||
29 | /// </summary> | ||
30 | /// <param name="file">Full path to file to add to cabinet.</param> | ||
31 | /// <param name="token">Name of file in cabinet.</param> | ||
32 | /// <param name="fileHash"></param> | ||
33 | /// <param name="contextHandle">Handle to open cabinet.</param> | ||
34 | [DllImport("winterop.dll", EntryPoint = "CreateCabAddFile", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
35 | public static extern void CreateCabAddFile(string file, string token, MSIFILEHASHINFO fileHash, IntPtr contextHandle); | ||
36 | |||
37 | /// <summary> | ||
38 | /// Closes a cabinet. | ||
39 | /// </summary> | ||
40 | /// <param name="contextHandle">Handle to open cabinet to close.</param> | ||
41 | /// <param name="newCabNamesCallBackAddress">Address of Binder's cabinet split callback</param> | ||
42 | [DllImport("winterop.dll", EntryPoint = "CreateCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
43 | public static extern void CreateCabFinish(IntPtr contextHandle, IntPtr newCabNamesCallBackAddress); | ||
44 | |||
45 | /// <summary> | ||
46 | /// Cancels cabinet creation. | ||
47 | /// </summary> | ||
48 | /// <param name="contextHandle">Handle to open cabinet to cancel.</param> | ||
49 | [DllImport("winterop.dll", EntryPoint = "CreateCabCancel", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
50 | public static extern void CreateCabCancel(IntPtr contextHandle); | ||
51 | |||
52 | /// <summary> | ||
53 | /// Initializes cabinet extraction. | ||
54 | /// </summary> | ||
55 | [DllImport("winterop.dll", EntryPoint = "ExtractCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
56 | public static extern void ExtractCabBegin(); | ||
57 | |||
58 | /// <summary> | ||
59 | /// Extracts files from cabinet. | ||
60 | /// </summary> | ||
61 | /// <param name="cabinet">Path to cabinet to extract files from.</param> | ||
62 | /// <param name="extractDirectory">Directory to extract files to.</param> | ||
63 | [DllImport("winterop.dll", EntryPoint = "ExtractCab", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, PreserveSig = false)] | ||
64 | public static extern void ExtractCab(string cabinet, string extractDirectory); | ||
65 | |||
66 | /// <summary> | ||
67 | /// Cleans up after cabinet extraction. | ||
68 | /// </summary> | ||
69 | [DllImport("winterop.dll", EntryPoint = "ExtractCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
70 | public static extern void ExtractCabFinish(); | ||
71 | |||
72 | /// <summary> | ||
73 | /// Initializes cabinet enumeration. | ||
74 | /// </summary> | ||
75 | [DllImport("winterop.dll", EntryPoint = "EnumerateCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
76 | public static extern void EnumerateCabBegin(); | ||
77 | |||
78 | /// <summary> | ||
79 | /// Enumerates files from cabinet. | ||
80 | /// </summary> | ||
81 | /// <param name="cabinet">Path to cabinet to enumerate files from.</param> | ||
82 | /// <param name="notify">callback that gets each file.</param> | ||
83 | [DllImport("winterop.dll", EntryPoint = "EnumerateCab", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, PreserveSig = false)] | ||
84 | public static extern void EnumerateCab(string cabinet, CabInterop.PFNNOTIFY notify); | ||
85 | |||
86 | /// <summary> | ||
87 | /// Cleans up after cabinet enumeration. | ||
88 | /// </summary> | ||
89 | [DllImport("winterop.dll", EntryPoint = "EnumerateCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
90 | public static extern void EnumerateCabFinish(); | ||
91 | |||
92 | /// <summary> | ||
93 | /// Resets the DACL on an array of files to "empty". | ||
94 | /// </summary> | ||
95 | /// <param name="files">Array of file reset ACL to "empty".</param> | ||
96 | /// <param name="fileCount">Number of file paths in array.</param> | ||
97 | [DllImport("winterop.dll", EntryPoint = "ResetAcls", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
98 | public static extern void ResetAcls(string[] files, uint fileCount); | ||
99 | |||
100 | /// <summary> | ||
101 | /// Gets the hash of the pCertContext->pCertInfo->SubjectPublicKeyInfo using ::CryptHashPublicKeyInfo() which does not seem | ||
102 | /// to be exposed by .NET Frameowkr. | ||
103 | /// </summary> | ||
104 | /// <param name="certContext">Pointer to a CERT_CONTEXT struct with public key information to hash.</param> | ||
105 | /// <param name="publicKeyInfoHashed"></param> | ||
106 | /// <param name="sizePublicKeyInfoHashed"></param> | ||
107 | [DllImport("winterop.dll", EntryPoint = "HashPublicKeyInfo", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
108 | public static extern void HashPublicKeyInfo(IntPtr certContext, byte[] publicKeyInfoHashed, ref uint sizePublicKeyInfoHashed); | ||
109 | |||
110 | /// <summary> | ||
111 | /// Converts file time to a local file time. | ||
112 | /// </summary> | ||
113 | /// <param name="fileTime">file time</param> | ||
114 | /// <param name="localTime">local file time</param> | ||
115 | /// <returns>true if successful, false otherwise</returns> | ||
116 | [DllImport("kernel32.dll", SetLastError = true)] | ||
117 | [return: MarshalAs(UnmanagedType.Bool)] | ||
118 | public static extern bool FileTimeToLocalFileTime(ref long fileTime, ref long localTime); | ||
119 | |||
120 | /// <summary> | ||
121 | /// Converts file time to a MS-DOS time. | ||
122 | /// </summary> | ||
123 | /// <param name="fileTime">file time</param> | ||
124 | /// <param name="wFatDate">MS-DOS date</param> | ||
125 | /// <param name="wFatTime">MS-DOS time</param> | ||
126 | /// <returns>true if successful, false otherwise</returns> | ||
127 | [DllImport("kernel32.dll", SetLastError = true)] | ||
128 | [return: MarshalAs(UnmanagedType.Bool)] | ||
129 | public static extern bool FileTimeToDosDateTime(ref long fileTime, out ushort wFatDate, out ushort wFatTime); | ||
130 | } | ||
131 | |||
132 | /// <summary> | ||
133 | /// Interop class for the winterop.dll. | 9 | /// Interop class for the winterop.dll. |
134 | /// </summary> | 10 | /// </summary> |
135 | public static class CabInterop | 11 | public static class CabInterop |
@@ -180,7 +56,6 @@ namespace WixToolset.Core.Native | |||
180 | /// <summary> | 56 | /// <summary> |
181 | /// Wraps FDINOTIFICATION. | 57 | /// Wraps FDINOTIFICATION. |
182 | /// </summary> | 58 | /// </summary> |
183 | [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] | ||
184 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] | 59 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] |
185 | public class NOTIFICATION | 60 | public class NOTIFICATION |
186 | { | 61 | { |
@@ -307,5 +182,128 @@ namespace WixToolset.Core.Native | |||
307 | get { return this.fdie; } | 182 | get { return this.fdie; } |
308 | } | 183 | } |
309 | } | 184 | } |
185 | |||
186 | /// <summary> | ||
187 | /// The native methods. | ||
188 | /// </summary> | ||
189 | private class NativeMethods | ||
190 | { | ||
191 | /// <summary> | ||
192 | /// Starts creating a cabinet. | ||
193 | /// </summary> | ||
194 | /// <param name="cabinetName">Name of cabinet to create.</param> | ||
195 | /// <param name="cabinetDirectory">Directory to create cabinet in.</param> | ||
196 | /// <param name="maxFiles">Maximum number of files that will be added to cabinet.</param> | ||
197 | /// <param name="maxSize">Maximum size of the cabinet.</param> | ||
198 | /// <param name="maxThreshold">Maximum threshold in the cabinet.</param> | ||
199 | /// <param name="compressionType">Type of compression to use in the cabinet.</param> | ||
200 | /// <param name="contextHandle">Handle to opened cabinet.</param> | ||
201 | [DllImport("winterop.dll", EntryPoint = "CreateCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
202 | public static extern void CreateCabBegin(string cabinetName, string cabinetDirectory, uint maxFiles, uint maxSize, uint maxThreshold, uint compressionType, out IntPtr contextHandle); | ||
203 | |||
204 | /// <summary> | ||
205 | /// Adds a file to an open cabinet. | ||
206 | /// </summary> | ||
207 | /// <param name="file">Full path to file to add to cabinet.</param> | ||
208 | /// <param name="token">Name of file in cabinet.</param> | ||
209 | /// <param name="fileHash"></param> | ||
210 | /// <param name="contextHandle">Handle to open cabinet.</param> | ||
211 | [DllImport("winterop.dll", EntryPoint = "CreateCabAddFile", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
212 | public static extern void CreateCabAddFile(string file, string token, MSIFILEHASHINFO fileHash, IntPtr contextHandle); | ||
213 | |||
214 | /// <summary> | ||
215 | /// Closes a cabinet. | ||
216 | /// </summary> | ||
217 | /// <param name="contextHandle">Handle to open cabinet to close.</param> | ||
218 | /// <param name="newCabNamesCallBackAddress">Address of Binder's cabinet split callback</param> | ||
219 | [DllImport("winterop.dll", EntryPoint = "CreateCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
220 | public static extern void CreateCabFinish(IntPtr contextHandle, IntPtr newCabNamesCallBackAddress); | ||
221 | |||
222 | /// <summary> | ||
223 | /// Cancels cabinet creation. | ||
224 | /// </summary> | ||
225 | /// <param name="contextHandle">Handle to open cabinet to cancel.</param> | ||
226 | [DllImport("winterop.dll", EntryPoint = "CreateCabCancel", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
227 | public static extern void CreateCabCancel(IntPtr contextHandle); | ||
228 | |||
229 | /// <summary> | ||
230 | /// Initializes cabinet extraction. | ||
231 | /// </summary> | ||
232 | [DllImport("winterop.dll", EntryPoint = "ExtractCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
233 | public static extern void ExtractCabBegin(); | ||
234 | |||
235 | /// <summary> | ||
236 | /// Extracts files from cabinet. | ||
237 | /// </summary> | ||
238 | /// <param name="cabinet">Path to cabinet to extract files from.</param> | ||
239 | /// <param name="extractDirectory">Directory to extract files to.</param> | ||
240 | [DllImport("winterop.dll", EntryPoint = "ExtractCab", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, PreserveSig = false)] | ||
241 | public static extern void ExtractCab(string cabinet, string extractDirectory); | ||
242 | |||
243 | /// <summary> | ||
244 | /// Cleans up after cabinet extraction. | ||
245 | /// </summary> | ||
246 | [DllImport("winterop.dll", EntryPoint = "ExtractCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
247 | public static extern void ExtractCabFinish(); | ||
248 | |||
249 | /// <summary> | ||
250 | /// Initializes cabinet enumeration. | ||
251 | /// </summary> | ||
252 | [DllImport("winterop.dll", EntryPoint = "EnumerateCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
253 | public static extern void EnumerateCabBegin(); | ||
254 | |||
255 | /// <summary> | ||
256 | /// Enumerates files from cabinet. | ||
257 | /// </summary> | ||
258 | /// <param name="cabinet">Path to cabinet to enumerate files from.</param> | ||
259 | /// <param name="notify">callback that gets each file.</param> | ||
260 | [DllImport("winterop.dll", EntryPoint = "EnumerateCab", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, PreserveSig = false)] | ||
261 | public static extern void EnumerateCab(string cabinet, CabInterop.PFNNOTIFY notify); | ||
262 | |||
263 | /// <summary> | ||
264 | /// Cleans up after cabinet enumeration. | ||
265 | /// </summary> | ||
266 | [DllImport("winterop.dll", EntryPoint = "EnumerateCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
267 | public static extern void EnumerateCabFinish(); | ||
268 | |||
269 | /// <summary> | ||
270 | /// Resets the DACL on an array of files to "empty". | ||
271 | /// </summary> | ||
272 | /// <param name="files">Array of file reset ACL to "empty".</param> | ||
273 | /// <param name="fileCount">Number of file paths in array.</param> | ||
274 | [DllImport("winterop.dll", EntryPoint = "ResetAcls", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
275 | public static extern void ResetAcls(string[] files, uint fileCount); | ||
276 | |||
277 | /// <summary> | ||
278 | /// Gets the hash of the pCertContext->pCertInfo->SubjectPublicKeyInfo using ::CryptHashPublicKeyInfo() which does not seem | ||
279 | /// to be exposed by .NET Frameowkr. | ||
280 | /// </summary> | ||
281 | /// <param name="certContext">Pointer to a CERT_CONTEXT struct with public key information to hash.</param> | ||
282 | /// <param name="publicKeyInfoHashed"></param> | ||
283 | /// <param name="sizePublicKeyInfoHashed"></param> | ||
284 | [DllImport("winterop.dll", EntryPoint = "HashPublicKeyInfo", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] | ||
285 | public static extern void HashPublicKeyInfo(IntPtr certContext, byte[] publicKeyInfoHashed, ref uint sizePublicKeyInfoHashed); | ||
286 | |||
287 | /// <summary> | ||
288 | /// Converts file time to a local file time. | ||
289 | /// </summary> | ||
290 | /// <param name="fileTime">file time</param> | ||
291 | /// <param name="localTime">local file time</param> | ||
292 | /// <returns>true if successful, false otherwise</returns> | ||
293 | [DllImport("kernel32.dll", SetLastError = true)] | ||
294 | [return: MarshalAs(UnmanagedType.Bool)] | ||
295 | public static extern bool FileTimeToLocalFileTime(ref long fileTime, ref long localTime); | ||
296 | |||
297 | /// <summary> | ||
298 | /// Converts file time to a MS-DOS time. | ||
299 | /// </summary> | ||
300 | /// <param name="fileTime">file time</param> | ||
301 | /// <param name="wFatDate">MS-DOS date</param> | ||
302 | /// <param name="wFatTime">MS-DOS time</param> | ||
303 | /// <returns>true if successful, false otherwise</returns> | ||
304 | [DllImport("kernel32.dll", SetLastError = true)] | ||
305 | [return: MarshalAs(UnmanagedType.Bool)] | ||
306 | public static extern bool FileTimeToDosDateTime(ref long fileTime, out ushort wFatDate, out ushort wFatTime); | ||
307 | } | ||
310 | } | 308 | } |
311 | } | 309 | } |
diff --git a/src/WixToolset.Core.Native/CabinetFileInfo.cs b/src/WixToolset.Core.Native/CabinetFileInfo.cs index ea229121..52f28ad4 100644 --- a/src/WixToolset.Core.Native/CabinetFileInfo.cs +++ b/src/WixToolset.Core.Native/CabinetFileInfo.cs | |||
@@ -56,11 +56,7 @@ namespace WixToolset.Core.Native | |||
56 | /// </returns> | 56 | /// </returns> |
57 | public bool SameAsDateTime(DateTime dateTime) | 57 | public bool SameAsDateTime(DateTime dateTime) |
58 | { | 58 | { |
59 | long filetime = dateTime.ToFileTime(); | 59 | CabInterop.DateTimeToCabDateAndTime(dateTime, out var cabDate, out var cabTime); |
60 | long localTime = 0; | ||
61 | NativeMethods.FileTimeToLocalFileTime(ref filetime, ref localTime); | ||
62 | NativeMethods.FileTimeToDosDateTime(ref localTime, out var cabDate, out var cabTime); | ||
63 | |||
64 | return this.Date == cabDate && this.Time == cabTime; | 60 | return this.Date == cabDate && this.Time == cabTime; |
65 | } | 61 | } |
66 | } | 62 | } |
diff --git a/src/WixToolset.Core.Native/FileSystem.cs b/src/WixToolset.Core.Native/FileSystem.cs new file mode 100644 index 00000000..b9691d44 --- /dev/null +++ b/src/WixToolset.Core.Native/FileSystem.cs | |||
@@ -0,0 +1,61 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Runtime.InteropServices; | ||
8 | |||
9 | /// <summary> | ||
10 | /// File system helpers. | ||
11 | /// </summary> | ||
12 | public static class FileSystem | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Copies a file. | ||
16 | /// </summary> | ||
17 | /// <param name="source">The file to copy.</param> | ||
18 | /// <param name="destination">The destination file.</param> | ||
19 | /// <param name="allowHardlink">Allow hardlinks.</param> | ||
20 | public static void CopyFile(string source, string destination, bool allowHardlink) | ||
21 | { | ||
22 | if (File.Exists(destination)) | ||
23 | { | ||
24 | File.Delete(destination); | ||
25 | } | ||
26 | |||
27 | if (!allowHardlink || !CreateHardLink(destination, source, IntPtr.Zero)) | ||
28 | { | ||
29 | #if DEBUG | ||
30 | var er = Marshal.GetLastWin32Error(); | ||
31 | #endif | ||
32 | |||
33 | File.Copy(source, destination, overwrite: true); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Moves a file. | ||
39 | /// </summary> | ||
40 | /// <param name="source">The file to move.</param> | ||
41 | /// <param name="destination">The destination file.</param> | ||
42 | public static void MoveFile(string source, string destination) | ||
43 | { | ||
44 | if (File.Exists(destination)) | ||
45 | { | ||
46 | File.Delete(destination); | ||
47 | } | ||
48 | |||
49 | var directory = Path.GetDirectoryName(destination); | ||
50 | if (!String.IsNullOrEmpty(directory)) | ||
51 | { | ||
52 | Directory.CreateDirectory(directory); | ||
53 | } | ||
54 | |||
55 | File.Move(source, destination); | ||
56 | } | ||
57 | |||
58 | [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
59 | private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); | ||
60 | } | ||
61 | } | ||
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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 < base version. | ||
45 | /// </summary> | ||
46 | NewLessBaseVersion = 0x40, | ||
47 | |||
48 | /// <summary> | ||
49 | /// Installed version <= 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 >= base version. | ||
60 | /// </summary> | ||
61 | NewGreaterEqualBaseVersion = 0x200, | ||
62 | |||
63 | /// <summary> | ||
64 | /// Installed version > 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/ConfigurationCallback.cs b/src/WixToolset.Core.Native/Msm/ConfigurationCallback.cs new file mode 100644 index 00000000..31b06d02 --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/ConfigurationCallback.cs | |||
@@ -0,0 +1,90 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Globalization; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Callback object for configurable merge modules. | ||
11 | /// </summary> | ||
12 | public sealed class ConfigurationCallback : IMsmConfigureModule | ||
13 | { | ||
14 | private const int SOk = 0x0; | ||
15 | private const int SFalse = 0x1; | ||
16 | private readonly Hashtable configurationData; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Creates a ConfigurationCallback object. | ||
20 | /// </summary> | ||
21 | /// <param name="configData">String to break up into name/value pairs.</param> | ||
22 | public ConfigurationCallback(string configData) | ||
23 | { | ||
24 | if (String.IsNullOrEmpty(configData)) | ||
25 | { | ||
26 | throw new ArgumentNullException(nameof(configData)); | ||
27 | } | ||
28 | |||
29 | var pairs = configData.Split(','); | ||
30 | this.configurationData = new Hashtable(pairs.Length); | ||
31 | for (var i = 0; i < pairs.Length; ++i) | ||
32 | { | ||
33 | var nameVal = pairs[i].Split('='); | ||
34 | var name = nameVal[0]; | ||
35 | var value = nameVal[1]; | ||
36 | |||
37 | name = name.Replace("%2C", ","); | ||
38 | name = name.Replace("%3D", "="); | ||
39 | name = name.Replace("%25", "%"); | ||
40 | |||
41 | value = value.Replace("%2C", ","); | ||
42 | value = value.Replace("%3D", "="); | ||
43 | value = value.Replace("%25", "%"); | ||
44 | |||
45 | this.configurationData[name] = value; | ||
46 | } | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Returns text data based on name. | ||
51 | /// </summary> | ||
52 | /// <param name="name">Name of value to return.</param> | ||
53 | /// <param name="configData">Out param to put configuration data into.</param> | ||
54 | /// <returns>S_OK if value provided, S_FALSE if not.</returns> | ||
55 | public int ProvideTextData(string name, out string configData) | ||
56 | { | ||
57 | if (this.configurationData.Contains(name)) | ||
58 | { | ||
59 | configData = (string)this.configurationData[name]; | ||
60 | return SOk; | ||
61 | } | ||
62 | else | ||
63 | { | ||
64 | configData = null; | ||
65 | return SFalse; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// Returns integer data based on name. | ||
71 | /// </summary> | ||
72 | /// <param name="name">Name of value to return.</param> | ||
73 | /// <param name="configData">Out param to put configuration data into.</param> | ||
74 | /// <returns>S_OK if value provided, S_FALSE if not.</returns> | ||
75 | public int ProvideIntegerData(string name, out int configData) | ||
76 | { | ||
77 | if (this.configurationData.Contains(name)) | ||
78 | { | ||
79 | var val = (string)this.configurationData[name]; | ||
80 | configData = Convert.ToInt32(val, CultureInfo.InvariantCulture); | ||
81 | return SOk; | ||
82 | } | ||
83 | else | ||
84 | { | ||
85 | configData = 0; | ||
86 | return SFalse; | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs b/src/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs new file mode 100644 index 00000000..468fb1fc --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/IMsmConfigureModule.cs | |||
@@ -0,0 +1,32 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Callback for configurable merge modules. | ||
10 | /// </summary> | ||
11 | [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] | ||
12 | public interface IMsmConfigureModule | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Callback to retrieve text data for configurable merge modules. | ||
16 | /// </summary> | ||
17 | /// <param name="name">Name of the data to be retrieved.</param> | ||
18 | /// <param name="configData">The data corresponding to the name.</param> | ||
19 | /// <returns>The error code (HRESULT).</returns> | ||
20 | [PreserveSig] | ||
21 | int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); | ||
22 | |||
23 | /// <summary> | ||
24 | /// Callback to retrieve integer data for configurable merge modules. | ||
25 | /// </summary> | ||
26 | /// <param name="name">Name of the data to be retrieved.</param> | ||
27 | /// <param name="configData">The data corresponding to the name.</param> | ||
28 | /// <returns>The error code (HRESULT).</returns> | ||
29 | [PreserveSig] | ||
30 | int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); | ||
31 | } | ||
32 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/IMsmError.cs b/src/WixToolset.Core.Native/Msm/IMsmError.cs new file mode 100644 index 00000000..4f1325a6 --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/IMsmError.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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// A merge error. | ||
10 | /// </summary> | ||
11 | [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] | ||
12 | public interface IMsmError | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Gets the type of merge error. | ||
16 | /// </summary> | ||
17 | /// <value>The type of merge error.</value> | ||
18 | MsmErrorType Type | ||
19 | { | ||
20 | get; | ||
21 | } | ||
22 | |||
23 | /// <summary> | ||
24 | /// Gets the path information from the merge error. | ||
25 | /// </summary> | ||
26 | /// <value>The path information from the merge error.</value> | ||
27 | string Path | ||
28 | { | ||
29 | get; | ||
30 | } | ||
31 | |||
32 | /// <summary> | ||
33 | /// Gets the language information from the merge error. | ||
34 | /// </summary> | ||
35 | /// <value>The language information from the merge error.</value> | ||
36 | short Language | ||
37 | { | ||
38 | get; | ||
39 | } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets the database table from the merge error. | ||
43 | /// </summary> | ||
44 | /// <value>The database table from the merge error.</value> | ||
45 | string DatabaseTable | ||
46 | { | ||
47 | get; | ||
48 | } | ||
49 | |||
50 | /// <summary> | ||
51 | /// Gets the collection of database keys from the merge error. | ||
52 | /// </summary> | ||
53 | /// <value>The collection of database keys from the merge error.</value> | ||
54 | IMsmStrings DatabaseKeys | ||
55 | { | ||
56 | get; | ||
57 | } | ||
58 | |||
59 | /// <summary> | ||
60 | /// Gets the module table from the merge error. | ||
61 | /// </summary> | ||
62 | /// <value>The module table from the merge error.</value> | ||
63 | string ModuleTable | ||
64 | { | ||
65 | get; | ||
66 | } | ||
67 | |||
68 | /// <summary> | ||
69 | /// Gets the collection of module keys from the merge error. | ||
70 | /// </summary> | ||
71 | /// <value>The collection of module keys from the merge error.</value> | ||
72 | IMsmStrings ModuleKeys | ||
73 | { | ||
74 | get; | ||
75 | } | ||
76 | } | ||
77 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/IMsmErrors.cs b/src/WixToolset.Core.Native/Msm/IMsmErrors.cs new file mode 100644 index 00000000..e1472376 --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/IMsmErrors.cs | |||
@@ -0,0 +1,32 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Collection of merge errors. | ||
10 | /// </summary> | ||
11 | [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] | ||
12 | public interface IMsmErrors | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Gets the IMsmError at the specified index. | ||
16 | /// </summary> | ||
17 | /// <param name="index">The one-based index of the IMsmError to get.</param> | ||
18 | IMsmError this[int index] | ||
19 | { | ||
20 | get; | ||
21 | } | ||
22 | |||
23 | /// <summary> | ||
24 | /// Gets the count of IMsmErrors in this collection. | ||
25 | /// </summary> | ||
26 | /// <value>The count of IMsmErrors in this collection.</value> | ||
27 | int Count | ||
28 | { | ||
29 | get; | ||
30 | } | ||
31 | } | ||
32 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/IMsmMerge2.cs b/src/WixToolset.Core.Native/Msm/IMsmMerge2.cs new file mode 100644 index 00000000..400249e7 --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/IMsmMerge2.cs | |||
@@ -0,0 +1,174 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// IMsmMerge2 interface. | ||
10 | /// </summary> | ||
11 | [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] | ||
12 | public interface IMsmMerge2 | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// The OpenDatabase method of the Merge object opens a Windows Installer installation | ||
16 | /// database, located at a specified path, that is to be merged with a module. | ||
17 | /// </summary> | ||
18 | /// <param name="path">Path to the database being opened.</param> | ||
19 | void OpenDatabase(string path); | ||
20 | |||
21 | /// <summary> | ||
22 | /// The OpenModule method of the Merge object opens a Windows Installer merge module | ||
23 | /// in read-only mode. A module must be opened before it can be merged with an installation database. | ||
24 | /// </summary> | ||
25 | /// <param name="fileName">Fully qualified file name pointing to a merge module.</param> | ||
26 | /// <param name="language">A valid language identifier (LANGID).</param> | ||
27 | void OpenModule(string fileName, short language); | ||
28 | |||
29 | /// <summary> | ||
30 | /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. | ||
31 | /// </summary> | ||
32 | /// <param name="commit">true if changes should be saved, false otherwise.</param> | ||
33 | void CloseDatabase(bool commit); | ||
34 | |||
35 | /// <summary> | ||
36 | /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. | ||
37 | /// </summary> | ||
38 | void CloseModule(); | ||
39 | |||
40 | /// <summary> | ||
41 | /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. | ||
42 | /// If the log file already exists, the installer appends new messages. If the log file does not exist, | ||
43 | /// the installer creates a log file. | ||
44 | /// </summary> | ||
45 | /// <param name="fileName">Fully qualified filename pointing to a file to open or create.</param> | ||
46 | void OpenLog(string fileName); | ||
47 | |||
48 | /// <summary> | ||
49 | /// The CloseLog method of the Merge object closes the current log file. | ||
50 | /// </summary> | ||
51 | void CloseLog(); | ||
52 | |||
53 | /// <summary> | ||
54 | /// The Log method of the Merge object writes a text string to the currently open log file. | ||
55 | /// </summary> | ||
56 | /// <param name="message">The text string to display.</param> | ||
57 | void Log(string message); | ||
58 | |||
59 | /// <summary> | ||
60 | /// Gets the errors from the last merge operation. | ||
61 | /// </summary> | ||
62 | /// <value>The errors from the last merge operation.</value> | ||
63 | IMsmErrors Errors | ||
64 | { | ||
65 | get; | ||
66 | } | ||
67 | |||
68 | /// <summary> | ||
69 | /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. | ||
70 | /// </summary> | ||
71 | /// <value>A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.</value> | ||
72 | object Dependencies | ||
73 | { | ||
74 | get; | ||
75 | } | ||
76 | |||
77 | /// <summary> | ||
78 | /// The Merge method of the Merge object executes a merge of the current database and current | ||
79 | /// module. The merge attaches the components in the module to the feature identified by Feature. | ||
80 | /// The root of the module's directory tree is redirected to the location given by RedirectDir. | ||
81 | /// </summary> | ||
82 | /// <param name="feature">The name of a feature in the database.</param> | ||
83 | /// <param name="redirectDir">The key of an entry in the Directory table of the database. | ||
84 | /// This parameter may be NULL or an empty string.</param> | ||
85 | void Merge(string feature, string redirectDir); | ||
86 | |||
87 | /// <summary> | ||
88 | /// The Connect method of the Merge object connects a module to an additional feature. | ||
89 | /// The module must have already been merged into the database or will be merged into the database. | ||
90 | /// The feature must exist before calling this function. | ||
91 | /// </summary> | ||
92 | /// <param name="feature">The name of a feature already existing in the database.</param> | ||
93 | void Connect(string feature); | ||
94 | |||
95 | /// <summary> | ||
96 | /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and | ||
97 | /// saves it as the specified file. The installer creates this file if it does not already exist | ||
98 | /// and overwritten if it does exist. | ||
99 | /// </summary> | ||
100 | /// <param name="fileName">The fully qualified destination file.</param> | ||
101 | void ExtractCAB(string fileName); | ||
102 | |||
103 | /// <summary> | ||
104 | /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module | ||
105 | /// and then writes those files to the destination directory. | ||
106 | /// </summary> | ||
107 | /// <param name="path">The fully qualified destination directory.</param> | ||
108 | void ExtractFiles(string path); | ||
109 | |||
110 | /// <summary> | ||
111 | /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it | ||
112 | /// takes an extra argument. The Merge method executes a merge of the current database and | ||
113 | /// current module. The merge attaches the components in the module to the feature identified | ||
114 | /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. | ||
115 | /// </summary> | ||
116 | /// <param name="feature">The name of a feature in the database.</param> | ||
117 | /// <param name="redirectDir">The key of an entry in the Directory table of the database. This parameter may | ||
118 | /// be NULL or an empty string.</param> | ||
119 | /// <param name="configuration">The pConfiguration argument is an interface implemented by the client. The argument may | ||
120 | /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration | ||
121 | /// functionality, but does not obligate the client to provide configuration data for any specific configurable item.</param> | ||
122 | void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); | ||
123 | |||
124 | /// <summary> | ||
125 | /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and | ||
126 | /// then writes those files to the destination directory. | ||
127 | /// </summary> | ||
128 | /// <param name="path">The fully qualified destination directory.</param> | ||
129 | /// <param name="longFileNames">Set to specify using long file names for path segments and final file names.</param> | ||
130 | /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted. | ||
131 | /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param> | ||
132 | void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); | ||
133 | |||
134 | /// <summary> | ||
135 | /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. | ||
136 | /// </summary> | ||
137 | /// <value>A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.</value> | ||
138 | /// <remarks>Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. | ||
139 | /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). | ||
140 | /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics.</remarks> | ||
141 | object ConfigurableItems | ||
142 | { | ||
143 | get; | ||
144 | } | ||
145 | |||
146 | /// <summary> | ||
147 | /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to | ||
148 | /// a source image on disk after a merge, taking into account changes to the module that might have been made | ||
149 | /// during module configuration. The list of files to be extracted is taken from the file table of the module | ||
150 | /// during the merge process. The list of files consists of every file successfully copied from the file table | ||
151 | /// of the module to the target database. File table entries that were not copied due to primary key conflicts | ||
152 | /// with existing rows in the database are not a part of this list. At image creation time, the directory for | ||
153 | /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is | ||
154 | /// the root of the source image for the install. fLongFileNames determines whether or not long file names are | ||
155 | /// used for both path segments and final file names. The function fails if no database is open, no module is | ||
156 | /// open, or no merge has been performed. | ||
157 | /// </summary> | ||
158 | /// <param name="path">The path of the root of the source image for the install.</param> | ||
159 | /// <param name="longFileNames">Determines whether or not long file names are used for both path segments and final file names. </param> | ||
160 | /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted. | ||
161 | /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param> | ||
162 | void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); | ||
163 | |||
164 | /// <summary> | ||
165 | /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function | ||
166 | /// returns the primary keys in the File table of the currently open module. The primary keys are returned | ||
167 | /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. | ||
168 | /// </summary> | ||
169 | IMsmStrings ModuleFiles | ||
170 | { | ||
171 | get; | ||
172 | } | ||
173 | } | ||
174 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/IMsmStrings.cs b/src/WixToolset.Core.Native/Msm/IMsmStrings.cs new file mode 100644 index 00000000..41063bfa --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/IMsmStrings.cs | |||
@@ -0,0 +1,32 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// A collection of strings. | ||
10 | /// </summary> | ||
11 | [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] | ||
12 | public interface IMsmStrings | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Gets the string at the specified index. | ||
16 | /// </summary> | ||
17 | /// <param name="index">The one-based index of the string to get.</param> | ||
18 | string this[int index] | ||
19 | { | ||
20 | get; | ||
21 | } | ||
22 | |||
23 | /// <summary> | ||
24 | /// Gets the count of strings in this collection. | ||
25 | /// </summary> | ||
26 | /// <value>The count of strings in this collection.</value> | ||
27 | int Count | ||
28 | { | ||
29 | get; | ||
30 | } | ||
31 | } | ||
32 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/MsmErrorType.cs b/src/WixToolset.Core.Native/Msm/MsmErrorType.cs new file mode 100644 index 00000000..c67d37b4 --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/MsmErrorType.cs | |||
@@ -0,0 +1,154 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Errors returned by merge operations. | ||
10 | /// </summary> | ||
11 | [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] | ||
12 | public enum MsmErrorType | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// A request was made to open a module with a language not supported by the module. | ||
16 | /// No more general language is supported by the module. | ||
17 | /// Adds msmErrorLanguageUnsupported to the Type property and the requested language | ||
18 | /// to the Language Property (Error Object). All Error object properties are empty. | ||
19 | /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). | ||
20 | /// </summary> | ||
21 | msmErrorLanguageUnsupported = 1, | ||
22 | |||
23 | /// <summary> | ||
24 | /// A request was made to open a module with a supported language but the module has | ||
25 | /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property | ||
26 | /// and the applied transform's language to the Language Property of the Error object. | ||
27 | /// This may not be the requested language if a more general language was used. | ||
28 | /// All other properties of the Error object are empty. The OpenModule function | ||
29 | /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). | ||
30 | /// </summary> | ||
31 | msmErrorLanguageFailed = 2, | ||
32 | |||
33 | /// <summary> | ||
34 | /// The module cannot be merged because it excludes, or is excluded by, another module | ||
35 | /// in the database. Adds msmErrorExclusion to the Type property of the Error object. | ||
36 | /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the | ||
37 | /// excluded module's row in the ModuleExclusion table. If an existing module excludes | ||
38 | /// the module being merged, the excluded module's ModuleSignature information is added | ||
39 | /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys | ||
40 | /// contains the excluded module's ModuleSignature information. All other properties | ||
41 | /// are empty (or -1). | ||
42 | /// </summary> | ||
43 | msmErrorExclusion = 3, | ||
44 | |||
45 | /// <summary> | ||
46 | /// Merge conflict during merge. The value of the Type property is set to | ||
47 | /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain | ||
48 | /// the table name and primary keys of the conflicting row in the database. The | ||
49 | /// ModuleTable property and ModuleKeys property contain the table name and primary keys | ||
50 | /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be | ||
51 | /// null if the row does not exist in the database. For example, if the conflict is in a | ||
52 | /// generated FeatureComponents table entry. On Windows Installer version 2.0, when | ||
53 | /// merging a configurable merge module, configuration may cause these properties to | ||
54 | /// refer to rows that do not exist in the module. | ||
55 | /// </summary> | ||
56 | msmErrorTableMerge = 4, | ||
57 | |||
58 | /// <summary> | ||
59 | /// There was a problem resequencing a sequence table to contain the necessary merged | ||
60 | /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable | ||
61 | /// and DatabaseKeys properties contain the sequence table name and primary keys | ||
62 | /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties | ||
63 | /// contain the sequence table name and primary key (action name) of the conflicting row. | ||
64 | /// On Windows Installer version 2.0, when merging a configurable merge module, | ||
65 | /// configuration may cause these properties to refer to rows that do not exist in the module. | ||
66 | /// </summary> | ||
67 | msmErrorResequenceMerge = 5, | ||
68 | |||
69 | /// <summary> | ||
70 | /// Not used. | ||
71 | /// </summary> | ||
72 | msmErrorFileCreate = 6, | ||
73 | |||
74 | /// <summary> | ||
75 | /// There was a problem creating a directory to extract a file to disk. The Path property | ||
76 | /// contains the directory that could not be created. All other properties are empty or -1. | ||
77 | /// Not available with Windows Installer version 1.0. | ||
78 | /// </summary> | ||
79 | msmErrorDirCreate = 7, | ||
80 | |||
81 | /// <summary> | ||
82 | /// A feature name is required to complete the merge, but no feature name was provided. | ||
83 | /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys | ||
84 | /// contain the table name and primary keys of the conflicting row. The ModuleTable and | ||
85 | /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. | ||
86 | /// On Windows Installer version 2.0, when merging a configurable merge module, configuration | ||
87 | /// may cause these properties to refer to rows that do not exist in the module. | ||
88 | /// If the failure is in a generated FeatureComponents table, the DatabaseTable and | ||
89 | /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to | ||
90 | /// the row in the Component table causing the failure. | ||
91 | /// </summary> | ||
92 | msmErrorFeatureRequired = 8, | ||
93 | |||
94 | /// <summary> | ||
95 | /// Available with Window Installer version 2.0. Substitution of a Null value into a | ||
96 | /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and | ||
97 | /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row | ||
98 | /// into the ModuleTable property and ModuleKeys property. All other properties of the Error | ||
99 | /// object are set to an empty string or -1. This error causes the immediate failure of the | ||
100 | /// merge and the MergeEx function to return E_FAIL. | ||
101 | /// </summary> | ||
102 | msmErrorBadNullSubstitution = 9, | ||
103 | |||
104 | /// <summary> | ||
105 | /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer | ||
106 | /// Format Type into a Binary Type data column. This type of error returns | ||
107 | /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the | ||
108 | /// keys from the ModuleSubstitution table for this row into the ModuleTable property. | ||
109 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
110 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
111 | /// </summary> | ||
112 | msmErrorBadSubstitutionType = 10, | ||
113 | |||
114 | /// <summary> | ||
115 | /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table | ||
116 | /// references a configuration item not defined in the ModuleConfiguration table. | ||
117 | /// This type of error returns msmErrorMissingConfigItem in the Type property and enters | ||
118 | /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into | ||
119 | /// the ModuleTable property. All other properties of the Error object are set to an empty | ||
120 | /// string or -1. This error causes the immediate failure of the merge and the MergeEx | ||
121 | /// function to return E_FAIL. | ||
122 | /// </summary> | ||
123 | msmErrorMissingConfigItem = 11, | ||
124 | |||
125 | /// <summary> | ||
126 | /// Available with Window Installer version 2.0. The authoring tool has returned a Null | ||
127 | /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this | ||
128 | /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" | ||
129 | /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. | ||
130 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
131 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
132 | /// </summary> | ||
133 | msmErrorBadNullResponse = 12, | ||
134 | |||
135 | /// <summary> | ||
136 | /// Available with Window Installer version 2.0. The authoring tool returned a failure code | ||
137 | /// (not S_OK or S_FALSE) when asked for data. An error of this type will return | ||
138 | /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" | ||
139 | /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. | ||
140 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
141 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
142 | /// </summary> | ||
143 | msmErrorDataRequestFailed = 13, | ||
144 | |||
145 | /// <summary> | ||
146 | /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was | ||
147 | /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of | ||
148 | /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of | ||
149 | /// the error object are set to an empty string or -1. This error causes the immediate failure | ||
150 | /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. | ||
151 | /// </summary> | ||
152 | msmErrorPlatformMismatch = 14, | ||
153 | } | ||
154 | } | ||
diff --git a/src/WixToolset.Core.Native/Msm/MsmInterop.cs b/src/WixToolset.Core.Native/Msm/MsmInterop.cs new file mode 100644 index 00000000..d2627904 --- /dev/null +++ b/src/WixToolset.Core.Native/Msm/MsmInterop.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 | |||
3 | namespace WixToolset.Core.Native.Msm | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Merge merge modules into an MSI file. | ||
10 | /// </summary> | ||
11 | [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] | ||
12 | public class MsmMerge2 | ||
13 | { | ||
14 | } | ||
15 | |||
16 | /// <summary> | ||
17 | /// Defines the standard COM IClassFactory interface. | ||
18 | /// </summary> | ||
19 | [ComImport, Guid("00000001-0000-0000-C000-000000000046")] | ||
20 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
21 | public interface IClassFactory | ||
22 | { | ||
23 | /// <summary> | ||
24 | /// | ||
25 | /// </summary> | ||
26 | [return: MarshalAs(UnmanagedType.IUnknown)] | ||
27 | object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); | ||
28 | } | ||
29 | |||
30 | /// <summary> | ||
31 | /// Contains native methods for merge operations. | ||
32 | /// </summary> | ||
33 | public static class MsmInterop | ||
34 | { | ||
35 | [DllImport("mergemod.dll", EntryPoint = "DllGetClassObject", PreserveSig = false)] | ||
36 | [return: MarshalAs(UnmanagedType.IUnknown)] | ||
37 | private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); | ||
38 | |||
39 | /// <summary> | ||
40 | /// Load the merge object directly from a local mergemod.dll without going through COM registration. | ||
41 | /// </summary> | ||
42 | /// <returns>Merge interface.</returns> | ||
43 | public static IMsmMerge2 GetMsmMerge() | ||
44 | { | ||
45 | var classFactory = (IClassFactory)MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); | ||
46 | return (IMsmMerge2)classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); | ||
47 | } | ||
48 | } | ||
49 | } | ||
diff --git a/src/WixToolset.Core.Native/MsmInterop.cs b/src/WixToolset.Core.Native/MsmInterop.cs deleted file mode 100644 index 87ed6f02..00000000 --- a/src/WixToolset.Core.Native/MsmInterop.cs +++ /dev/null | |||
@@ -1,510 +0,0 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Reflection; | ||
8 | using System.Runtime.InteropServices; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Errors returned by merge operations. | ||
12 | /// </summary> | ||
13 | [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] | ||
14 | public enum MsmErrorType | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// A request was made to open a module with a language not supported by the module. | ||
18 | /// No more general language is supported by the module. | ||
19 | /// Adds msmErrorLanguageUnsupported to the Type property and the requested language | ||
20 | /// to the Language Property (Error Object). All Error object properties are empty. | ||
21 | /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). | ||
22 | /// </summary> | ||
23 | msmErrorLanguageUnsupported = 1, | ||
24 | |||
25 | /// <summary> | ||
26 | /// A request was made to open a module with a supported language but the module has | ||
27 | /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property | ||
28 | /// and the applied transform's language to the Language Property of the Error object. | ||
29 | /// This may not be the requested language if a more general language was used. | ||
30 | /// All other properties of the Error object are empty. The OpenModule function | ||
31 | /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). | ||
32 | /// </summary> | ||
33 | msmErrorLanguageFailed = 2, | ||
34 | |||
35 | /// <summary> | ||
36 | /// The module cannot be merged because it excludes, or is excluded by, another module | ||
37 | /// in the database. Adds msmErrorExclusion to the Type property of the Error object. | ||
38 | /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the | ||
39 | /// excluded module's row in the ModuleExclusion table. If an existing module excludes | ||
40 | /// the module being merged, the excluded module's ModuleSignature information is added | ||
41 | /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys | ||
42 | /// contains the excluded module's ModuleSignature information. All other properties | ||
43 | /// are empty (or -1). | ||
44 | /// </summary> | ||
45 | msmErrorExclusion = 3, | ||
46 | |||
47 | /// <summary> | ||
48 | /// Merge conflict during merge. The value of the Type property is set to | ||
49 | /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain | ||
50 | /// the table name and primary keys of the conflicting row in the database. The | ||
51 | /// ModuleTable property and ModuleKeys property contain the table name and primary keys | ||
52 | /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be | ||
53 | /// null if the row does not exist in the database. For example, if the conflict is in a | ||
54 | /// generated FeatureComponents table entry. On Windows Installer version 2.0, when | ||
55 | /// merging a configurable merge module, configuration may cause these properties to | ||
56 | /// refer to rows that do not exist in the module. | ||
57 | /// </summary> | ||
58 | msmErrorTableMerge = 4, | ||
59 | |||
60 | /// <summary> | ||
61 | /// There was a problem resequencing a sequence table to contain the necessary merged | ||
62 | /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable | ||
63 | /// and DatabaseKeys properties contain the sequence table name and primary keys | ||
64 | /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties | ||
65 | /// contain the sequence table name and primary key (action name) of the conflicting row. | ||
66 | /// On Windows Installer version 2.0, when merging a configurable merge module, | ||
67 | /// configuration may cause these properties to refer to rows that do not exist in the module. | ||
68 | /// </summary> | ||
69 | msmErrorResequenceMerge = 5, | ||
70 | |||
71 | /// <summary> | ||
72 | /// Not used. | ||
73 | /// </summary> | ||
74 | msmErrorFileCreate = 6, | ||
75 | |||
76 | /// <summary> | ||
77 | /// There was a problem creating a directory to extract a file to disk. The Path property | ||
78 | /// contains the directory that could not be created. All other properties are empty or -1. | ||
79 | /// Not available with Windows Installer version 1.0. | ||
80 | /// </summary> | ||
81 | msmErrorDirCreate = 7, | ||
82 | |||
83 | /// <summary> | ||
84 | /// A feature name is required to complete the merge, but no feature name was provided. | ||
85 | /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys | ||
86 | /// contain the table name and primary keys of the conflicting row. The ModuleTable and | ||
87 | /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. | ||
88 | /// On Windows Installer version 2.0, when merging a configurable merge module, configuration | ||
89 | /// may cause these properties to refer to rows that do not exist in the module. | ||
90 | /// If the failure is in a generated FeatureComponents table, the DatabaseTable and | ||
91 | /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to | ||
92 | /// the row in the Component table causing the failure. | ||
93 | /// </summary> | ||
94 | msmErrorFeatureRequired = 8, | ||
95 | |||
96 | /// <summary> | ||
97 | /// Available with Window Installer version 2.0. Substitution of a Null value into a | ||
98 | /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and | ||
99 | /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row | ||
100 | /// into the ModuleTable property and ModuleKeys property. All other properties of the Error | ||
101 | /// object are set to an empty string or -1. This error causes the immediate failure of the | ||
102 | /// merge and the MergeEx function to return E_FAIL. | ||
103 | /// </summary> | ||
104 | msmErrorBadNullSubstitution = 9, | ||
105 | |||
106 | /// <summary> | ||
107 | /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer | ||
108 | /// Format Type into a Binary Type data column. This type of error returns | ||
109 | /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the | ||
110 | /// keys from the ModuleSubstitution table for this row into the ModuleTable property. | ||
111 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
112 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
113 | /// </summary> | ||
114 | msmErrorBadSubstitutionType = 10, | ||
115 | |||
116 | /// <summary> | ||
117 | /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table | ||
118 | /// references a configuration item not defined in the ModuleConfiguration table. | ||
119 | /// This type of error returns msmErrorMissingConfigItem in the Type property and enters | ||
120 | /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into | ||
121 | /// the ModuleTable property. All other properties of the Error object are set to an empty | ||
122 | /// string or -1. This error causes the immediate failure of the merge and the MergeEx | ||
123 | /// function to return E_FAIL. | ||
124 | /// </summary> | ||
125 | msmErrorMissingConfigItem = 11, | ||
126 | |||
127 | /// <summary> | ||
128 | /// Available with Window Installer version 2.0. The authoring tool has returned a Null | ||
129 | /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this | ||
130 | /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" | ||
131 | /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. | ||
132 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
133 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
134 | /// </summary> | ||
135 | msmErrorBadNullResponse = 12, | ||
136 | |||
137 | /// <summary> | ||
138 | /// Available with Window Installer version 2.0. The authoring tool returned a failure code | ||
139 | /// (not S_OK or S_FALSE) when asked for data. An error of this type will return | ||
140 | /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" | ||
141 | /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. | ||
142 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
143 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
144 | /// </summary> | ||
145 | msmErrorDataRequestFailed = 13, | ||
146 | |||
147 | /// <summary> | ||
148 | /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was | ||
149 | /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of | ||
150 | /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of | ||
151 | /// the error object are set to an empty string or -1. This error causes the immediate failure | ||
152 | /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. | ||
153 | /// </summary> | ||
154 | msmErrorPlatformMismatch = 14, | ||
155 | } | ||
156 | |||
157 | /// <summary> | ||
158 | /// IMsmMerge2 interface. | ||
159 | /// </summary> | ||
160 | [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] | ||
161 | public interface IMsmMerge2 | ||
162 | { | ||
163 | /// <summary> | ||
164 | /// The OpenDatabase method of the Merge object opens a Windows Installer installation | ||
165 | /// database, located at a specified path, that is to be merged with a module. | ||
166 | /// </summary> | ||
167 | /// <param name="path">Path to the database being opened.</param> | ||
168 | void OpenDatabase(string path); | ||
169 | |||
170 | /// <summary> | ||
171 | /// The OpenModule method of the Merge object opens a Windows Installer merge module | ||
172 | /// in read-only mode. A module must be opened before it can be merged with an installation database. | ||
173 | /// </summary> | ||
174 | /// <param name="fileName">Fully qualified file name pointing to a merge module.</param> | ||
175 | /// <param name="language">A valid language identifier (LANGID).</param> | ||
176 | void OpenModule(string fileName, short language); | ||
177 | |||
178 | /// <summary> | ||
179 | /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. | ||
180 | /// </summary> | ||
181 | /// <param name="commit">true if changes should be saved, false otherwise.</param> | ||
182 | void CloseDatabase(bool commit); | ||
183 | |||
184 | /// <summary> | ||
185 | /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. | ||
186 | /// </summary> | ||
187 | void CloseModule(); | ||
188 | |||
189 | /// <summary> | ||
190 | /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. | ||
191 | /// If the log file already exists, the installer appends new messages. If the log file does not exist, | ||
192 | /// the installer creates a log file. | ||
193 | /// </summary> | ||
194 | /// <param name="fileName">Fully qualified filename pointing to a file to open or create.</param> | ||
195 | void OpenLog(string fileName); | ||
196 | |||
197 | /// <summary> | ||
198 | /// The CloseLog method of the Merge object closes the current log file. | ||
199 | /// </summary> | ||
200 | void CloseLog(); | ||
201 | |||
202 | /// <summary> | ||
203 | /// The Log method of the Merge object writes a text string to the currently open log file. | ||
204 | /// </summary> | ||
205 | /// <param name="message">The text string to display.</param> | ||
206 | void Log(string message); | ||
207 | |||
208 | /// <summary> | ||
209 | /// Gets the errors from the last merge operation. | ||
210 | /// </summary> | ||
211 | /// <value>The errors from the last merge operation.</value> | ||
212 | IMsmErrors Errors | ||
213 | { | ||
214 | get; | ||
215 | } | ||
216 | |||
217 | /// <summary> | ||
218 | /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. | ||
219 | /// </summary> | ||
220 | /// <value>A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.</value> | ||
221 | object Dependencies | ||
222 | { | ||
223 | get; | ||
224 | } | ||
225 | |||
226 | /// <summary> | ||
227 | /// The Merge method of the Merge object executes a merge of the current database and current | ||
228 | /// module. The merge attaches the components in the module to the feature identified by Feature. | ||
229 | /// The root of the module's directory tree is redirected to the location given by RedirectDir. | ||
230 | /// </summary> | ||
231 | /// <param name="feature">The name of a feature in the database.</param> | ||
232 | /// <param name="redirectDir">The key of an entry in the Directory table of the database. | ||
233 | /// This parameter may be NULL or an empty string.</param> | ||
234 | void Merge(string feature, string redirectDir); | ||
235 | |||
236 | /// <summary> | ||
237 | /// The Connect method of the Merge object connects a module to an additional feature. | ||
238 | /// The module must have already been merged into the database or will be merged into the database. | ||
239 | /// The feature must exist before calling this function. | ||
240 | /// </summary> | ||
241 | /// <param name="feature">The name of a feature already existing in the database.</param> | ||
242 | void Connect(string feature); | ||
243 | |||
244 | /// <summary> | ||
245 | /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and | ||
246 | /// saves it as the specified file. The installer creates this file if it does not already exist | ||
247 | /// and overwritten if it does exist. | ||
248 | /// </summary> | ||
249 | /// <param name="fileName">The fully qualified destination file.</param> | ||
250 | void ExtractCAB(string fileName); | ||
251 | |||
252 | /// <summary> | ||
253 | /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module | ||
254 | /// and then writes those files to the destination directory. | ||
255 | /// </summary> | ||
256 | /// <param name="path">The fully qualified destination directory.</param> | ||
257 | void ExtractFiles(string path); | ||
258 | |||
259 | /// <summary> | ||
260 | /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it | ||
261 | /// takes an extra argument. The Merge method executes a merge of the current database and | ||
262 | /// current module. The merge attaches the components in the module to the feature identified | ||
263 | /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. | ||
264 | /// </summary> | ||
265 | /// <param name="feature">The name of a feature in the database.</param> | ||
266 | /// <param name="redirectDir">The key of an entry in the Directory table of the database. This parameter may | ||
267 | /// be NULL or an empty string.</param> | ||
268 | /// <param name="configuration">The pConfiguration argument is an interface implemented by the client. The argument may | ||
269 | /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration | ||
270 | /// functionality, but does not obligate the client to provide configuration data for any specific configurable item.</param> | ||
271 | void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); | ||
272 | |||
273 | /// <summary> | ||
274 | /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and | ||
275 | /// then writes those files to the destination directory. | ||
276 | /// </summary> | ||
277 | /// <param name="path">The fully qualified destination directory.</param> | ||
278 | /// <param name="longFileNames">Set to specify using long file names for path segments and final file names.</param> | ||
279 | /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted. | ||
280 | /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param> | ||
281 | void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); | ||
282 | |||
283 | /// <summary> | ||
284 | /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. | ||
285 | /// </summary> | ||
286 | /// <value>A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.</value> | ||
287 | /// <remarks>Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. | ||
288 | /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). | ||
289 | /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics.</remarks> | ||
290 | object ConfigurableItems | ||
291 | { | ||
292 | get; | ||
293 | } | ||
294 | |||
295 | /// <summary> | ||
296 | /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to | ||
297 | /// a source image on disk after a merge, taking into account changes to the module that might have been made | ||
298 | /// during module configuration. The list of files to be extracted is taken from the file table of the module | ||
299 | /// during the merge process. The list of files consists of every file successfully copied from the file table | ||
300 | /// of the module to the target database. File table entries that were not copied due to primary key conflicts | ||
301 | /// with existing rows in the database are not a part of this list. At image creation time, the directory for | ||
302 | /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is | ||
303 | /// the root of the source image for the install. fLongFileNames determines whether or not long file names are | ||
304 | /// used for both path segments and final file names. The function fails if no database is open, no module is | ||
305 | /// open, or no merge has been performed. | ||
306 | /// </summary> | ||
307 | /// <param name="path">The path of the root of the source image for the install.</param> | ||
308 | /// <param name="longFileNames">Determines whether or not long file names are used for both path segments and final file names. </param> | ||
309 | /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted. | ||
310 | /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param> | ||
311 | void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); | ||
312 | |||
313 | /// <summary> | ||
314 | /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function | ||
315 | /// returns the primary keys in the File table of the currently open module. The primary keys are returned | ||
316 | /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. | ||
317 | /// </summary> | ||
318 | IMsmStrings ModuleFiles | ||
319 | { | ||
320 | get; | ||
321 | } | ||
322 | } | ||
323 | |||
324 | /// <summary> | ||
325 | /// Collection of merge errors. | ||
326 | /// </summary> | ||
327 | [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] | ||
328 | public interface IMsmErrors | ||
329 | { | ||
330 | /// <summary> | ||
331 | /// Gets the IMsmError at the specified index. | ||
332 | /// </summary> | ||
333 | /// <param name="index">The one-based index of the IMsmError to get.</param> | ||
334 | IMsmError this[int index] | ||
335 | { | ||
336 | get; | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Gets the count of IMsmErrors in this collection. | ||
341 | /// </summary> | ||
342 | /// <value>The count of IMsmErrors in this collection.</value> | ||
343 | int Count | ||
344 | { | ||
345 | get; | ||
346 | } | ||
347 | } | ||
348 | |||
349 | /// <summary> | ||
350 | /// A merge error. | ||
351 | /// </summary> | ||
352 | [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] | ||
353 | public interface IMsmError | ||
354 | { | ||
355 | /// <summary> | ||
356 | /// Gets the type of merge error. | ||
357 | /// </summary> | ||
358 | /// <value>The type of merge error.</value> | ||
359 | MsmErrorType Type | ||
360 | { | ||
361 | get; | ||
362 | } | ||
363 | |||
364 | /// <summary> | ||
365 | /// Gets the path information from the merge error. | ||
366 | /// </summary> | ||
367 | /// <value>The path information from the merge error.</value> | ||
368 | string Path | ||
369 | { | ||
370 | get; | ||
371 | } | ||
372 | |||
373 | /// <summary> | ||
374 | /// Gets the language information from the merge error. | ||
375 | /// </summary> | ||
376 | /// <value>The language information from the merge error.</value> | ||
377 | short Language | ||
378 | { | ||
379 | get; | ||
380 | } | ||
381 | |||
382 | /// <summary> | ||
383 | /// Gets the database table from the merge error. | ||
384 | /// </summary> | ||
385 | /// <value>The database table from the merge error.</value> | ||
386 | string DatabaseTable | ||
387 | { | ||
388 | get; | ||
389 | } | ||
390 | |||
391 | /// <summary> | ||
392 | /// Gets the collection of database keys from the merge error. | ||
393 | /// </summary> | ||
394 | /// <value>The collection of database keys from the merge error.</value> | ||
395 | IMsmStrings DatabaseKeys | ||
396 | { | ||
397 | get; | ||
398 | } | ||
399 | |||
400 | /// <summary> | ||
401 | /// Gets the module table from the merge error. | ||
402 | /// </summary> | ||
403 | /// <value>The module table from the merge error.</value> | ||
404 | string ModuleTable | ||
405 | { | ||
406 | get; | ||
407 | } | ||
408 | |||
409 | /// <summary> | ||
410 | /// Gets the collection of module keys from the merge error. | ||
411 | /// </summary> | ||
412 | /// <value>The collection of module keys from the merge error.</value> | ||
413 | IMsmStrings ModuleKeys | ||
414 | { | ||
415 | get; | ||
416 | } | ||
417 | } | ||
418 | |||
419 | /// <summary> | ||
420 | /// A collection of strings. | ||
421 | /// </summary> | ||
422 | [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] | ||
423 | public interface IMsmStrings | ||
424 | { | ||
425 | /// <summary> | ||
426 | /// Gets the string at the specified index. | ||
427 | /// </summary> | ||
428 | /// <param name="index">The one-based index of the string to get.</param> | ||
429 | string this[int index] | ||
430 | { | ||
431 | get; | ||
432 | } | ||
433 | |||
434 | /// <summary> | ||
435 | /// Gets the count of strings in this collection. | ||
436 | /// </summary> | ||
437 | /// <value>The count of strings in this collection.</value> | ||
438 | int Count | ||
439 | { | ||
440 | get; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | /// <summary> | ||
445 | /// Callback for configurable merge modules. | ||
446 | /// </summary> | ||
447 | [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] | ||
448 | public interface IMsmConfigureModule | ||
449 | { | ||
450 | /// <summary> | ||
451 | /// Callback to retrieve text data for configurable merge modules. | ||
452 | /// </summary> | ||
453 | /// <param name="name">Name of the data to be retrieved.</param> | ||
454 | /// <param name="configData">The data corresponding to the name.</param> | ||
455 | /// <returns>The error code (HRESULT).</returns> | ||
456 | [PreserveSig] | ||
457 | int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); | ||
458 | |||
459 | /// <summary> | ||
460 | /// Callback to retrieve integer data for configurable merge modules. | ||
461 | /// </summary> | ||
462 | /// <param name="name">Name of the data to be retrieved.</param> | ||
463 | /// <param name="configData">The data corresponding to the name.</param> | ||
464 | /// <returns>The error code (HRESULT).</returns> | ||
465 | [PreserveSig] | ||
466 | int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); | ||
467 | } | ||
468 | |||
469 | /// <summary> | ||
470 | /// Merge merge modules into an MSI file. | ||
471 | /// </summary> | ||
472 | [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] | ||
473 | public class MsmMerge2 | ||
474 | { | ||
475 | } | ||
476 | |||
477 | /// <summary> | ||
478 | /// Defines the standard COM IClassFactory interface. | ||
479 | /// </summary> | ||
480 | [ComImport, Guid("00000001-0000-0000-C000-000000000046")] | ||
481 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
482 | public interface IClassFactory | ||
483 | { | ||
484 | /// <summary> | ||
485 | /// | ||
486 | /// </summary> | ||
487 | [return: MarshalAs(UnmanagedType.IUnknown)] | ||
488 | object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); | ||
489 | } | ||
490 | |||
491 | /// <summary> | ||
492 | /// Contains native methods for merge operations. | ||
493 | /// </summary> | ||
494 | public class MsmInterop | ||
495 | { | ||
496 | [DllImport("mergemod.dll", EntryPoint = "DllGetClassObject", PreserveSig = false)] | ||
497 | [return: MarshalAs(UnmanagedType.IUnknown)] | ||
498 | private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); | ||
499 | |||
500 | /// <summary> | ||
501 | /// Load the merge object directly from a local mergemod.dll without going through COM registration. | ||
502 | /// </summary> | ||
503 | /// <returns>Merge interface.</returns> | ||
504 | public IMsmMerge2 GetMsmMerge() | ||
505 | { | ||
506 | var classFactory = (IClassFactory)MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); | ||
507 | return (IMsmMerge2)classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); | ||
508 | } | ||
509 | } | ||
510 | } | ||
diff --git a/src/WixToolset.Core.Native/Ole32/Storage.cs b/src/WixToolset.Core.Native/Ole32/Storage.cs new file mode 100644 index 00000000..3e4c6af2 --- /dev/null +++ b/src/WixToolset.Core.Native/Ole32/Storage.cs | |||
@@ -0,0 +1,377 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Ole32 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; | ||
8 | using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Wrapper for the compound storage file APIs. | ||
12 | /// </summary> | ||
13 | internal class Storage : IDisposable | ||
14 | { | ||
15 | private readonly IStorage storage; | ||
16 | private bool disposed; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Instantiate a new Storage. | ||
20 | /// </summary> | ||
21 | /// <param name="storage">The native storage interface.</param> | ||
22 | private Storage(IStorage storage) | ||
23 | { | ||
24 | this.storage = storage; | ||
25 | } | ||
26 | |||
27 | /// <summary> | ||
28 | /// Storage destructor. | ||
29 | /// </summary> | ||
30 | ~Storage() | ||
31 | { | ||
32 | this.Dispose(); | ||
33 | } | ||
34 | |||
35 | /// <summary> | ||
36 | /// The IEnumSTATSTG interface enumerates an array of STATSTG structures. | ||
37 | /// </summary> | ||
38 | [ComImport, Guid("0000000d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
39 | private interface IEnumSTATSTG | ||
40 | { | ||
41 | /// <summary> | ||
42 | /// Gets a specified number of STATSTG structures. | ||
43 | /// </summary> | ||
44 | /// <param name="celt">The number of STATSTG structures requested.</param> | ||
45 | /// <param name="rgelt">An array of STATSTG structures returned.</param> | ||
46 | /// <param name="pceltFetched">The number of STATSTG structures retrieved in the rgelt parameter.</param> | ||
47 | /// <returns>The error code.</returns> | ||
48 | [PreserveSig] | ||
49 | uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched); | ||
50 | |||
51 | /// <summary> | ||
52 | /// Skips a specified number of STATSTG structures in the enumeration sequence. | ||
53 | /// </summary> | ||
54 | /// <param name="celt">The number of STATSTG structures to skip.</param> | ||
55 | void Skip(uint celt); | ||
56 | |||
57 | /// <summary> | ||
58 | /// Resets the enumeration sequence to the beginning of the STATSTG structure array. | ||
59 | /// </summary> | ||
60 | void Reset(); | ||
61 | |||
62 | /// <summary> | ||
63 | /// Creates a new enumerator that contains the same enumeration state as the current STATSTG structure enumerator. | ||
64 | /// </summary> | ||
65 | /// <returns>The cloned IEnumSTATSTG interface.</returns> | ||
66 | [return: MarshalAs(UnmanagedType.Interface)] | ||
67 | IEnumSTATSTG Clone(); | ||
68 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// The IStorage interface supports the creation and management of structured storage objects. | ||
72 | /// </summary> | ||
73 | [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
74 | private interface IStorage | ||
75 | { | ||
76 | /// <summary> | ||
77 | /// Creates and opens a stream object with the specified name contained in this storage object. | ||
78 | /// </summary> | ||
79 | /// <param name="pwcsName">The name of the newly created stream.</param> | ||
80 | /// <param name="grfMode">Specifies the access mode to use when opening the newly created stream.</param> | ||
81 | /// <param name="reserved1">Reserved for future use; must be zero.</param> | ||
82 | /// <param name="reserved2">Reserved for future use; must be zero.</param> | ||
83 | /// <param name="ppstm">On return, pointer to the location of the new IStream interface pointer.</param> | ||
84 | void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm); | ||
85 | |||
86 | /// <summary> | ||
87 | /// Opens an existing stream object within this storage object using the specified access permissions in grfMode. | ||
88 | /// </summary> | ||
89 | /// <param name="pwcsName">The name of the stream to open.</param> | ||
90 | /// <param name="reserved1">Reserved for future use; must be NULL.</param> | ||
91 | /// <param name="grfMode">Specifies the access mode to be assigned to the open stream.</param> | ||
92 | /// <param name="reserved2">Reserved for future use; must be zero.</param> | ||
93 | /// <param name="ppstm">A pointer to IStream pointer variable that receives the interface pointer to the newly opened stream object.</param> | ||
94 | void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm); | ||
95 | |||
96 | /// <summary> | ||
97 | /// Creates and opens a new storage object nested within this storage object with the specified name in the specified access mode. | ||
98 | /// </summary> | ||
99 | /// <param name="pwcsName">The name of the newly created storage object.</param> | ||
100 | /// <param name="grfMode">A value that specifies the access mode to use when opening the newly created storage object.</param> | ||
101 | /// <param name="reserved1">Reserved for future use; must be zero.</param> | ||
102 | /// <param name="reserved2">Reserved for future use; must be zero.</param> | ||
103 | /// <param name="ppstg">A pointer, when successful, to the location of the IStorage pointer to the newly created storage object.</param> | ||
104 | void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg); | ||
105 | |||
106 | /// <summary> | ||
107 | /// Opens an existing storage object with the specified name in the specified access mode. | ||
108 | /// </summary> | ||
109 | /// <param name="pwcsName">The name of the storage object to open.</param> | ||
110 | /// <param name="pstgPriority">Must be NULL.</param> | ||
111 | /// <param name="grfMode">Specifies the access mode to use when opening the storage object.</param> | ||
112 | /// <param name="snbExclude">Must be NULL.</param> | ||
113 | /// <param name="reserved">Reserved for future use; must be zero.</param> | ||
114 | /// <param name="ppstg">When successful, pointer to the location of an IStorage pointer to the opened storage object.</param> | ||
115 | void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg); | ||
116 | |||
117 | /// <summary> | ||
118 | /// Copies the entire contents of an open storage object to another storage object. | ||
119 | /// </summary> | ||
120 | /// <param name="ciidExclude">The number of elements in the array pointed to by rgiidExclude.</param> | ||
121 | /// <param name="rgiidExclude">An array of interface identifiers (IIDs) that either the caller knows about and does not want | ||
122 | /// copied or that the storage object does not support, but whose state the caller will later explicitly copy.</param> | ||
123 | /// <param name="snbExclude">A string name block (refer to SNB) that specifies a block of storage or stream objects that are not to be copied to the destination.</param> | ||
124 | /// <param name="pstgDest">A pointer to the open storage object into which this storage object is to be copied.</param> | ||
125 | void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest); | ||
126 | |||
127 | /// <summary> | ||
128 | /// Copies or moves a substorage or stream from this storage object to another storage object. | ||
129 | /// </summary> | ||
130 | /// <param name="pwcsName">The name of the element in this storage object to be moved or copied.</param> | ||
131 | /// <param name="pstgDest">IStorage pointer to the destination storage object.</param> | ||
132 | /// <param name="pwcsNewName">The new name for the element in its new storage object.</param> | ||
133 | /// <param name="grfFlags">Specifies whether the operation should be a move (STGMOVE_MOVE) or a copy (STGMOVE_COPY).</param> | ||
134 | void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags); | ||
135 | |||
136 | /// <summary> | ||
137 | /// Reflects changes for a transacted storage object to the parent level. | ||
138 | /// </summary> | ||
139 | /// <param name="grfCommitFlags">Controls how the changes are committed to the storage object.</param> | ||
140 | void Commit(uint grfCommitFlags); | ||
141 | |||
142 | /// <summary> | ||
143 | /// Discards all changes that have been made to the storage object since the last commit operation. | ||
144 | /// </summary> | ||
145 | void Revert(); | ||
146 | |||
147 | /// <summary> | ||
148 | /// Returns an enumerator object that can be used to enumerate the storage and stream objects contained within this storage object. | ||
149 | /// </summary> | ||
150 | /// <param name="reserved1">Reserved for future use; must be zero.</param> | ||
151 | /// <param name="reserved2">Reserved for future use; must be NULL.</param> | ||
152 | /// <param name="reserved3">Reserved for future use; must be zero.</param> | ||
153 | /// <param name="ppenum">Pointer to IEnumSTATSTG* pointer variable that receives the interface pointer to the new enumerator object.</param> | ||
154 | void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum); | ||
155 | |||
156 | /// <summary> | ||
157 | /// Removes the specified storage or stream from this storage object. | ||
158 | /// </summary> | ||
159 | /// <param name="pwcsName">The name of the storage or stream to be removed.</param> | ||
160 | void DestroyElement(string pwcsName); | ||
161 | |||
162 | /// <summary> | ||
163 | /// Renames the specified storage or stream in this storage object. | ||
164 | /// </summary> | ||
165 | /// <param name="pwcsOldName">The name of the substorage or stream to be changed.</param> | ||
166 | /// <param name="pwcsNewName">The new name for the specified substorage or stream.</param> | ||
167 | void RenameElement(string pwcsOldName, string pwcsNewName); | ||
168 | |||
169 | /// <summary> | ||
170 | /// Sets the modification, access, and creation times of the indicated storage element, if supported by the underlying file system. | ||
171 | /// </summary> | ||
172 | /// <param name="pwcsName">The name of the storage object element whose times are to be modified.</param> | ||
173 | /// <param name="pctime">Either the new creation time for the element or NULL if the creation time is not to be modified.</param> | ||
174 | /// <param name="patime">Either the new access time for the element or NULL if the access time is not to be modified.</param> | ||
175 | /// <param name="pmtime">Either the new modification time for the element or NULL if the modification time is not to be modified.</param> | ||
176 | void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime); | ||
177 | |||
178 | /// <summary> | ||
179 | /// Assigns the specified CLSID to this storage object. | ||
180 | /// </summary> | ||
181 | /// <param name="clsid">The CLSID that is to be associated with the storage object.</param> | ||
182 | void SetClass(Guid clsid); | ||
183 | |||
184 | /// <summary> | ||
185 | /// Stores up to 32 bits of state information in this storage object. | ||
186 | /// </summary> | ||
187 | /// <param name="grfStateBits">Specifies the new values of the bits to set.</param> | ||
188 | /// <param name="grfMask">A binary mask indicating which bits in grfStateBits are significant in this call.</param> | ||
189 | void SetStateBits(uint grfStateBits, uint grfMask); | ||
190 | |||
191 | /// <summary> | ||
192 | /// Returns the STATSTG structure for this open storage object. | ||
193 | /// </summary> | ||
194 | /// <param name="pstatstg">On return, pointer to a STATSTG structure where this method places information about the open storage object.</param> | ||
195 | /// <param name="grfStatFlag">Specifies that some of the members in the STATSTG structure are not returned, thus saving a memory allocation operation.</param> | ||
196 | void Stat(out STATSTG pstatstg, uint grfStatFlag); | ||
197 | } | ||
198 | |||
199 | /// <summary> | ||
200 | /// The IStream interface lets you read and write data to stream objects. | ||
201 | /// </summary> | ||
202 | [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
203 | private interface IStream | ||
204 | { | ||
205 | /// <summary> | ||
206 | /// Reads a specified number of bytes from the stream object into memory starting at the current seek pointer. | ||
207 | /// </summary> | ||
208 | /// <param name="pv">A pointer to the buffer which the stream data is read into.</param> | ||
209 | /// <param name="cb">The number of bytes of data to read from the stream object.</param> | ||
210 | /// <param name="pcbRead">A pointer to a ULONG variable that receives the actual number of bytes read from the stream object.</param> | ||
211 | void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead); | ||
212 | |||
213 | /// <summary> | ||
214 | /// Writes a specified number of bytes into the stream object starting at the current seek pointer. | ||
215 | /// </summary> | ||
216 | /// <param name="pv">A pointer to the buffer that contains the data that is to be written to the stream.</param> | ||
217 | /// <param name="cb">The number of bytes of data to attempt to write into the stream.</param> | ||
218 | /// <param name="pcbWritten">A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object.</param> | ||
219 | void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten); | ||
220 | |||
221 | /// <summary> | ||
222 | /// Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer. | ||
223 | /// </summary> | ||
224 | /// <param name="dlibMove">The displacement to be added to the location indicated by the dwOrigin parameter.</param> | ||
225 | /// <param name="dwOrigin">The origin for the displacement specified in dlibMove.</param> | ||
226 | /// <param name="plibNewPosition">A pointer to the location where this method writes the value of the new seek pointer from the beginning of the stream.</param> | ||
227 | void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition); | ||
228 | |||
229 | /// <summary> | ||
230 | /// Changes the size of the stream object. | ||
231 | /// </summary> | ||
232 | /// <param name="libNewSize">Specifies the new size of the stream as a number of bytes.</param> | ||
233 | void SetSize(long libNewSize); | ||
234 | |||
235 | /// <summary> | ||
236 | /// Copies a specified number of bytes from the current seek pointer in the stream to the current seek pointer in another stream. | ||
237 | /// </summary> | ||
238 | /// <param name="pstm">A pointer to the destination stream.</param> | ||
239 | /// <param name="cb">The number of bytes to copy from the source stream.</param> | ||
240 | /// <param name="pcbRead">A pointer to the location where this method writes the actual number of bytes read from the source.</param> | ||
241 | /// <param name="pcbWritten">A pointer to the location where this method writes the actual number of bytes written to the destination.</param> | ||
242 | void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten); | ||
243 | |||
244 | /// <summary> | ||
245 | /// Ensures that any changes made to a stream object open in transacted mode are reflected in the parent storage object. | ||
246 | /// </summary> | ||
247 | /// <param name="grfCommitFlags">Controls how the changes for the stream object are committed.</param> | ||
248 | void Commit(int grfCommitFlags); | ||
249 | |||
250 | /// <summary> | ||
251 | /// Discards all changes that have been made to a transacted stream since the last call to IStream::Commit. | ||
252 | /// </summary> | ||
253 | void Revert(); | ||
254 | |||
255 | /// <summary> | ||
256 | /// Restricts access to a specified range of bytes in the stream. | ||
257 | /// </summary> | ||
258 | /// <param name="libOffset">Integer that specifies the byte offset for the beginning of the range.</param> | ||
259 | /// <param name="cb">Integer that specifies the length of the range, in bytes, to be restricted.</param> | ||
260 | /// <param name="dwLockType">Specifies the restrictions being requested on accessing the range.</param> | ||
261 | void LockRegion(long libOffset, long cb, int dwLockType); | ||
262 | |||
263 | /// <summary> | ||
264 | /// Removes the access restriction on a range of bytes previously restricted with IStream::LockRegion. | ||
265 | /// </summary> | ||
266 | /// <param name="libOffset">Specifies the byte offset for the beginning of the range.</param> | ||
267 | /// <param name="cb">Specifies, in bytes, the length of the range to be restricted.</param> | ||
268 | /// <param name="dwLockType">Specifies the access restrictions previously placed on the range.</param> | ||
269 | void UnlockRegion(long libOffset, long cb, int dwLockType); | ||
270 | |||
271 | /// <summary> | ||
272 | /// Retrieves the STATSTG structure for this stream. | ||
273 | /// </summary> | ||
274 | /// <param name="pstatstg">Pointer to a STATSTG structure where this method places information about this stream object.</param> | ||
275 | /// <param name="grfStatFlag">Specifies that this method does not return some of the members in the STATSTG structure, thus saving a memory allocation operation.</param> | ||
276 | void Stat(out STATSTG pstatstg, int grfStatFlag); | ||
277 | |||
278 | /// <summary> | ||
279 | /// Creates a new stream object that references the same bytes as the original stream but provides a separate seek pointer to those bytes. | ||
280 | /// </summary> | ||
281 | /// <param name="ppstm">When successful, pointer to the location of an IStream pointer to the new stream object.</param> | ||
282 | void Clone(out IStream ppstm); | ||
283 | } | ||
284 | |||
285 | /// <summary> | ||
286 | /// Creates a new compound file storage object. | ||
287 | /// </summary> | ||
288 | /// <param name="storageFile">The compound file being created.</param> | ||
289 | /// <param name="mode">Specifies the access mode to use when opening the new storage object.</param> | ||
290 | /// <returns>The created Storage object.</returns> | ||
291 | public static Storage CreateDocFile(string storageFile, StorageMode mode) | ||
292 | { | ||
293 | var storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0); | ||
294 | |||
295 | return new Storage(storage); | ||
296 | } | ||
297 | |||
298 | /// <summary> | ||
299 | /// Opens an existing root storage object in the file system. | ||
300 | /// </summary> | ||
301 | /// <param name="storageFile">The file that contains the storage object to open.</param> | ||
302 | /// <param name="mode">Specifies the access mode to use to open the storage object.</param> | ||
303 | /// <returns>The created Storage object.</returns> | ||
304 | public static Storage Open(string storageFile, StorageMode mode) | ||
305 | { | ||
306 | var storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0); | ||
307 | |||
308 | return new Storage(storage); | ||
309 | } | ||
310 | |||
311 | /// <summary> | ||
312 | /// Copies the entire contents of this open storage object into another Storage object. | ||
313 | /// </summary> | ||
314 | /// <param name="destinationStorage">The destination Storage object.</param> | ||
315 | public void CopyTo(Storage destinationStorage) | ||
316 | { | ||
317 | this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage); | ||
318 | } | ||
319 | |||
320 | /// <summary> | ||
321 | /// Opens an existing Storage object with the specified name according to the specified access mode. | ||
322 | /// </summary> | ||
323 | /// <param name="name">The name of the Storage object.</param> | ||
324 | /// <returns>The opened Storage object.</returns> | ||
325 | public Storage OpenStorage(string name) | ||
326 | { | ||
327 | this.storage.OpenStorage(name, null, (uint)(StorageMode.Read | StorageMode.ShareExclusive), IntPtr.Zero, 0, out var subStorage); | ||
328 | |||
329 | return new Storage(subStorage); | ||
330 | } | ||
331 | |||
332 | /// <summary> | ||
333 | /// Disposes the managed and unmanaged objects in this object. | ||
334 | /// </summary> | ||
335 | public void Dispose() | ||
336 | { | ||
337 | if (!this.disposed) | ||
338 | { | ||
339 | Marshal.ReleaseComObject(this.storage); | ||
340 | |||
341 | this.disposed = true; | ||
342 | } | ||
343 | |||
344 | GC.SuppressFinalize(this); | ||
345 | } | ||
346 | |||
347 | /// <summary> | ||
348 | /// The native methods. | ||
349 | /// </summary> | ||
350 | private static class NativeMethods | ||
351 | { | ||
352 | /// <summary> | ||
353 | /// Creates a new compound file storage object. | ||
354 | /// </summary> | ||
355 | /// <param name="pwcsName">The name for the compound file being created.</param> | ||
356 | /// <param name="grfMode">Specifies the access mode to use when opening the new storage object.</param> | ||
357 | /// <param name="reserved">Reserved for future use; must be zero.</param> | ||
358 | /// <returns>A pointer to the location of the IStorage pointer to the new storage object.</returns> | ||
359 | [DllImport("ole32.dll", PreserveSig = false)] | ||
360 | [return: MarshalAs(UnmanagedType.Interface)] | ||
361 | internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved); | ||
362 | |||
363 | /// <summary> | ||
364 | /// Opens an existing root storage object in the file system. | ||
365 | /// </summary> | ||
366 | /// <param name="pwcsName">The file that contains the storage object to open.</param> | ||
367 | /// <param name="pstgPriority">Most often NULL.</param> | ||
368 | /// <param name="grfMode">Specifies the access mode to use to open the storage object.</param> | ||
369 | /// <param name="snbExclude">If not NULL, pointer to a block of elements in the storage to be excluded as the storage object is opened.</param> | ||
370 | /// <param name="reserved">Indicates reserved for future use; must be zero.</param> | ||
371 | /// <returns>A pointer to a IStorage* pointer variable that receives the interface pointer to the opened storage.</returns> | ||
372 | [DllImport("ole32.dll", PreserveSig = false)] | ||
373 | [return: MarshalAs(UnmanagedType.Interface)] | ||
374 | internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved); | ||
375 | } | ||
376 | } | ||
377 | } | ||
diff --git a/src/WixToolset.Core.Native/Ole32/StorageMode.cs b/src/WixToolset.Core.Native/Ole32/StorageMode.cs new file mode 100644 index 00000000..24b60e4d --- /dev/null +++ b/src/WixToolset.Core.Native/Ole32/StorageMode.cs | |||
@@ -0,0 +1,55 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.Ole32 | ||
4 | { | ||
5 | /// <summary> | ||
6 | /// Specifies the access mode to use when opening, creating, or deleting a storage object. | ||
7 | /// </summary> | ||
8 | internal enum StorageMode | ||
9 | { | ||
10 | /// <summary> | ||
11 | /// Indicates that the object is read-only, meaning that modifications cannot be made. | ||
12 | /// </summary> | ||
13 | Read = 0x0, | ||
14 | |||
15 | /// <summary> | ||
16 | /// Enables you to save changes to the object, but does not permit access to its data. | ||
17 | /// </summary> | ||
18 | Write = 0x1, | ||
19 | |||
20 | /// <summary> | ||
21 | /// Enables access and modification of object data. | ||
22 | /// </summary> | ||
23 | ReadWrite = 0x2, | ||
24 | |||
25 | /// <summary> | ||
26 | /// Specifies that subsequent openings of the object are not denied read or write access. | ||
27 | /// </summary> | ||
28 | ShareDenyNone = 0x40, | ||
29 | |||
30 | /// <summary> | ||
31 | /// Prevents others from subsequently opening the object in Read mode. | ||
32 | /// </summary> | ||
33 | ShareDenyRead = 0x30, | ||
34 | |||
35 | /// <summary> | ||
36 | /// Prevents others from subsequently opening the object for Write or ReadWrite access. | ||
37 | /// </summary> | ||
38 | ShareDenyWrite = 0x20, | ||
39 | |||
40 | /// <summary> | ||
41 | /// Prevents others from subsequently opening the object in any mode. | ||
42 | /// </summary> | ||
43 | ShareExclusive = 0x10, | ||
44 | |||
45 | /// <summary> | ||
46 | /// Opens the storage object with exclusive access to the most recently committed version. | ||
47 | /// </summary> | ||
48 | Priority = 0x40000, | ||
49 | |||
50 | /// <summary> | ||
51 | /// Indicates that an existing storage object or stream should be removed before the new object replaces it. | ||
52 | /// </summary> | ||
53 | Create = 0x1000, | ||
54 | } | ||
55 | } | ||
diff --git a/src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs b/src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs new file mode 100644 index 00000000..04f5a553 --- /dev/null +++ b/src/WixToolset.Core.Native/PatchAPI/PatchInterop.cs | |||
@@ -0,0 +1,990 @@ | |||
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 | |||
3 | namespace WixToolset.Core.Native.PatchAPI | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | using System.Globalization; | ||
9 | using System.Runtime.InteropServices; | ||
10 | using WixToolset.Data.Symbols; | ||
11 | |||
12 | /// <summary> | ||
13 | /// Interop class for the mspatchc.dll. | ||
14 | /// </summary> | ||
15 | internal static class PatchInterop | ||
16 | { | ||
17 | // From WinError.h in the Platform SDK | ||
18 | internal const ushort FACILITY_WIN32 = 7; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Parse a number from text in either hex or decimal. | ||
22 | /// </summary> | ||
23 | /// <param name="source">Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise.</param> | ||
24 | /// <returns>Numeric value that source represents.</returns> | ||
25 | internal static uint ParseHexOrDecimal(string source) | ||
26 | { | ||
27 | var value = source.Trim(); | ||
28 | if (String.Equals(value.Substring(0, 2), "0x", StringComparison.OrdinalIgnoreCase)) | ||
29 | { | ||
30 | return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); | ||
31 | } | ||
32 | else | ||
33 | { | ||
34 | return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | /// <summary> | ||
39 | /// Create a binary delta file. | ||
40 | /// </summary> | ||
41 | /// <param name="deltaFile">Name of the delta file to create.</param> | ||
42 | /// <param name="targetFile">Name of updated file.</param> | ||
43 | /// <param name="targetSymbolPath">Optional paths to updated file's symbols.</param> | ||
44 | /// <param name="targetRetainOffsets">Optional offsets to the delta retain sections in the updated file.</param> | ||
45 | /// <param name="basisFiles">Optional array of target files.</param> | ||
46 | /// <param name="basisSymbolPaths">Optional array of target files' symbol paths (must match basisFiles array).</param> | ||
47 | /// <param name="basisIgnoreLengths">Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries).</param> | ||
48 | /// <param name="basisIgnoreOffsets">Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries).</param> | ||
49 | /// <param name="basisRetainLengths">Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries).</param> | ||
50 | /// <param name="basisRetainOffsets">Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries).</param> | ||
51 | /// <param name="apiPatchingSymbolFlags">ApiPatchingSymbolFlags value.</param> | ||
52 | /// <param name="optimizePatchSizeForLargeFiles">OptimizePatchSizeForLargeFiles value.</param> | ||
53 | /// <param name="retainRangesIgnored">Flag to indicate retain ranges were ignored due to mismatch.</param> | ||
54 | /// <returns>true if delta file was created, false if whole file should be used instead.</returns> | ||
55 | public static bool CreateDelta( | ||
56 | string deltaFile, | ||
57 | string targetFile, | ||
58 | string targetSymbolPath, | ||
59 | string targetRetainOffsets, | ||
60 | string[] basisFiles, | ||
61 | string[] basisSymbolPaths, | ||
62 | string[] basisIgnoreLengths, | ||
63 | string[] basisIgnoreOffsets, | ||
64 | string[] basisRetainLengths, | ||
65 | string[] basisRetainOffsets, | ||
66 | PatchSymbolFlags apiPatchingSymbolFlags, | ||
67 | bool optimizePatchSizeForLargeFiles, | ||
68 | out bool retainRangesIgnored | ||
69 | ) | ||
70 | { | ||
71 | retainRangesIgnored = false; | ||
72 | if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlags.PatchSymbolNoImagehlp | PatchSymbolFlags.PatchSymbolNoFailures | PatchSymbolFlags.PatchSymbolUndecoratedToo))) | ||
73 | { | ||
74 | throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags"); | ||
75 | } | ||
76 | |||
77 | if (null == deltaFile || 0 == deltaFile.Length) | ||
78 | { | ||
79 | throw new ArgumentNullException("deltaFile"); | ||
80 | } | ||
81 | |||
82 | if (null == targetFile || 0 == targetFile.Length) | ||
83 | { | ||
84 | throw new ArgumentNullException("targetFile"); | ||
85 | } | ||
86 | |||
87 | if (null == basisFiles || 0 == basisFiles.Length) | ||
88 | { | ||
89 | return false; | ||
90 | } | ||
91 | var countOldFiles = (uint)basisFiles.Length; | ||
92 | |||
93 | if (null != basisSymbolPaths) | ||
94 | { | ||
95 | if (0 != basisSymbolPaths.Length) | ||
96 | { | ||
97 | if ((uint)basisSymbolPaths.Length != countOldFiles) | ||
98 | { | ||
99 | throw new ArgumentOutOfRangeException("basisSymbolPaths"); | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | // a null basisSymbolPaths is allowed. | ||
104 | |||
105 | if (null != basisIgnoreLengths) | ||
106 | { | ||
107 | if (0 != basisIgnoreLengths.Length) | ||
108 | { | ||
109 | if ((uint)basisIgnoreLengths.Length != countOldFiles) | ||
110 | { | ||
111 | throw new ArgumentOutOfRangeException("basisIgnoreLengths"); | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | else | ||
116 | { | ||
117 | basisIgnoreLengths = new string[countOldFiles]; | ||
118 | } | ||
119 | |||
120 | if (null != basisIgnoreOffsets) | ||
121 | { | ||
122 | if (0 != basisIgnoreOffsets.Length) | ||
123 | { | ||
124 | if ((uint)basisIgnoreOffsets.Length != countOldFiles) | ||
125 | { | ||
126 | throw new ArgumentOutOfRangeException("basisIgnoreOffsets"); | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | else | ||
131 | { | ||
132 | basisIgnoreOffsets = new string[countOldFiles]; | ||
133 | } | ||
134 | |||
135 | if (null != basisRetainLengths) | ||
136 | { | ||
137 | if (0 != basisRetainLengths.Length) | ||
138 | { | ||
139 | if ((uint)basisRetainLengths.Length != countOldFiles) | ||
140 | { | ||
141 | throw new ArgumentOutOfRangeException("basisRetainLengths"); | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | else | ||
146 | { | ||
147 | basisRetainLengths = new string[countOldFiles]; | ||
148 | } | ||
149 | |||
150 | if (null != basisRetainOffsets) | ||
151 | { | ||
152 | if (0 != basisRetainOffsets.Length) | ||
153 | { | ||
154 | if ((uint)basisRetainOffsets.Length != countOldFiles) | ||
155 | { | ||
156 | throw new ArgumentOutOfRangeException("basisRetainOffsets"); | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | else | ||
161 | { | ||
162 | basisRetainOffsets = new string[countOldFiles]; | ||
163 | } | ||
164 | |||
165 | var pod = new PatchOptionData(); | ||
166 | pod.symbolOptionFlags = apiPatchingSymbolFlags; | ||
167 | pod.newFileSymbolPath = targetSymbolPath; | ||
168 | pod.oldFileSymbolPathArray = basisSymbolPaths; | ||
169 | pod.extendedOptionFlags = 0; | ||
170 | var oldFileInfoArray = new PatchOldFileInfoW[countOldFiles]; | ||
171 | var newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(',')); | ||
172 | for (uint i = 0; i < countOldFiles; ++i) | ||
173 | { | ||
174 | var ofi = new PatchOldFileInfoW(); | ||
175 | ofi.oldFileName = basisFiles[i]; | ||
176 | var ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(',')); | ||
177 | var ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(',')); | ||
178 | var retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(',')); | ||
179 | var retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(',')); | ||
180 | // Validate inputs | ||
181 | if (ignoreLengthArray.Length != ignoreOffsetArray.Length) | ||
182 | { | ||
183 | throw new ArgumentOutOfRangeException("basisIgnoreLengths"); | ||
184 | } | ||
185 | |||
186 | if (retainLengthArray.Length != retainOffsetArray.Length) | ||
187 | { | ||
188 | throw new ArgumentOutOfRangeException("basisRetainLengths"); | ||
189 | } | ||
190 | |||
191 | if (newRetainOffsetArray.Length != retainOffsetArray.Length) | ||
192 | { | ||
193 | // remove all retain range information | ||
194 | retainRangesIgnored = true; | ||
195 | for (uint j = 0; j < countOldFiles; ++j) | ||
196 | { | ||
197 | basisRetainLengths[j] = null; | ||
198 | basisRetainOffsets[j] = null; | ||
199 | } | ||
200 | retainLengthArray = new string[0]; | ||
201 | retainOffsetArray = new string[0]; | ||
202 | newRetainOffsetArray = new string[0]; | ||
203 | for (uint j = 0; j < oldFileInfoArray.Length; ++j) | ||
204 | { | ||
205 | oldFileInfoArray[j].retainRange = null; | ||
206 | } | ||
207 | } | ||
208 | |||
209 | // Populate IgnoreRange structure | ||
210 | PatchIgnoreRange[] ignoreArray = null; | ||
211 | if (0 != ignoreLengthArray.Length) | ||
212 | { | ||
213 | ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length]; | ||
214 | for (var j = 0; j < ignoreLengthArray.Length; ++j) | ||
215 | { | ||
216 | var ignoreRange = new PatchIgnoreRange(); | ||
217 | ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]); | ||
218 | ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]); | ||
219 | ignoreArray[j] = ignoreRange; | ||
220 | } | ||
221 | ofi.ignoreRange = ignoreArray; | ||
222 | } | ||
223 | |||
224 | PatchRetainRange[] retainArray = null; | ||
225 | if (0 != newRetainOffsetArray.Length) | ||
226 | { | ||
227 | retainArray = new PatchRetainRange[retainLengthArray.Length]; | ||
228 | for (var j = 0; j < newRetainOffsetArray.Length; ++j) | ||
229 | { | ||
230 | var retainRange = new PatchRetainRange(); | ||
231 | retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]); | ||
232 | retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]); | ||
233 | retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]); | ||
234 | retainArray[j] = retainRange; | ||
235 | } | ||
236 | ofi.retainRange = retainArray; | ||
237 | } | ||
238 | oldFileInfoArray[i] = ofi; | ||
239 | } | ||
240 | |||
241 | if (CreatePatchFileExW( | ||
242 | countOldFiles, | ||
243 | oldFileInfoArray, | ||
244 | targetFile, | ||
245 | deltaFile, | ||
246 | PatchOptionFlags(optimizePatchSizeForLargeFiles), | ||
247 | pod, | ||
248 | null, | ||
249 | IntPtr.Zero)) | ||
250 | { | ||
251 | return true; | ||
252 | } | ||
253 | |||
254 | // determine if this is an error or a need to use whole file. | ||
255 | var err = Marshal.GetLastWin32Error(); | ||
256 | switch (err) | ||
257 | { | ||
258 | case unchecked((int)ERROR_PATCH_BIGGER_THAN_COMPRESSED): | ||
259 | break; | ||
260 | |||
261 | // too late to exclude this file -- should have been caught before | ||
262 | case unchecked((int)ERROR_PATCH_SAME_FILE): | ||
263 | default: | ||
264 | throw new System.ComponentModel.Win32Exception(err); | ||
265 | } | ||
266 | return false; | ||
267 | } | ||
268 | |||
269 | /// <summary> | ||
270 | /// Extract the delta header. | ||
271 | /// </summary> | ||
272 | /// <param name="delta">Name of delta file.</param> | ||
273 | /// <param name="deltaHeader">Name of file to create with the delta's header.</param> | ||
274 | static public void ExtractDeltaHeader(string delta, string deltaHeader) | ||
275 | { | ||
276 | if (!ExtractPatchHeaderToFileW(delta, deltaHeader)) | ||
277 | { | ||
278 | throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); | ||
279 | } | ||
280 | } | ||
281 | |||
282 | /// <summary> | ||
283 | /// Returns the PatchOptionFlags to use. | ||
284 | /// </summary> | ||
285 | /// <param name="optimizeForLargeFiles">True if optimizing for large files.</param> | ||
286 | /// <returns>PATCH_OPTION_FLAG values</returns> | ||
287 | static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles) | ||
288 | { | ||
289 | var flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST; | ||
290 | if (optimizeForLargeFiles) | ||
291 | { | ||
292 | flags |= PATCH_OPTION_USE_LZX_LARGE; | ||
293 | } | ||
294 | return flags; | ||
295 | } | ||
296 | |||
297 | //--------------------------------------------------------------------- | ||
298 | // From PatchApi.h | ||
299 | //--------------------------------------------------------------------- | ||
300 | |||
301 | // | ||
302 | // The following contants can be combined and used as the OptionFlags | ||
303 | // parameter in the patch creation apis. | ||
304 | |||
305 | internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower) | ||
306 | |||
307 | internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large) | ||
308 | internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal | ||
309 | internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries | ||
310 | internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer) | ||
311 | |||
312 | internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports | ||
313 | internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks | ||
314 | internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image | ||
315 | internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same | ||
316 | internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower) | ||
317 | internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero | ||
318 | internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps | ||
319 | internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch | ||
320 | internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support) | ||
321 | internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer) | ||
322 | internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally) | ||
323 | |||
324 | internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007; | ||
325 | |||
326 | // | ||
327 | // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags: | ||
328 | // | ||
329 | |||
330 | internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer) | ||
331 | internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer) | ||
332 | |||
333 | // | ||
334 | // In addition to the standard Win32 error codes, the following error codes may | ||
335 | // be returned via GetLastError() when one of the patch APIs fails. | ||
336 | |||
337 | internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create | ||
338 | internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create | ||
339 | internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create | ||
340 | internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create | ||
341 | internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create | ||
342 | internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create | ||
343 | |||
344 | /// <summary> | ||
345 | /// Delegate type that the PatchAPI calls for progress notification. | ||
346 | /// </summary> | ||
347 | /// <param name="context">.</param> | ||
348 | /// <param name="currentPosition">.</param> | ||
349 | /// <param name="maxPosition">.</param> | ||
350 | /// <returns>True for success</returns> | ||
351 | public delegate bool PatchProgressCallback( | ||
352 | IntPtr context, | ||
353 | uint currentPosition, | ||
354 | uint maxPosition | ||
355 | ); | ||
356 | |||
357 | /// <summary> | ||
358 | /// Delegate type that the PatchAPI calls for patch symbol load information. | ||
359 | /// </summary> | ||
360 | /// <param name="whichFile">.</param> | ||
361 | /// <param name="symbolFileName">.</param> | ||
362 | /// <param name="symType">.</param> | ||
363 | /// <param name="symbolFileCheckSum">.</param> | ||
364 | /// <param name="symbolFileTimeDate">.</param> | ||
365 | /// <param name="imageFileCheckSum">.</param> | ||
366 | /// <param name="imageFileTimeDate">.</param> | ||
367 | /// <param name="context">.</param> | ||
368 | /// <returns>???</returns> | ||
369 | public delegate bool PatchSymloadCallback( | ||
370 | uint whichFile, // 0 for new file, 1 for first old file, etc | ||
371 | [MarshalAs(UnmanagedType.LPStr)] string symbolFileName, | ||
372 | uint symType, // see SYM_TYPE in imagehlp.h | ||
373 | uint symbolFileCheckSum, | ||
374 | uint symbolFileTimeDate, | ||
375 | uint imageFileCheckSum, | ||
376 | uint imageFileTimeDate, | ||
377 | IntPtr context | ||
378 | ); | ||
379 | |||
380 | /// <summary> | ||
381 | /// Wraps PATCH_IGNORE_RANGE | ||
382 | /// </summary> | ||
383 | [StructLayout(LayoutKind.Sequential)] | ||
384 | internal class PatchIgnoreRange | ||
385 | { | ||
386 | public uint offsetInOldFile; | ||
387 | public uint lengthInBytes; | ||
388 | } | ||
389 | |||
390 | /// <summary> | ||
391 | /// Wraps PATCH_RETAIN_RANGE | ||
392 | /// </summary> | ||
393 | [StructLayout(LayoutKind.Sequential)] | ||
394 | internal class PatchRetainRange | ||
395 | { | ||
396 | public uint offsetInOldFile; | ||
397 | public uint lengthInBytes; | ||
398 | public uint offsetInNewFile; | ||
399 | } | ||
400 | |||
401 | /// <summary> | ||
402 | /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion) | ||
403 | /// </summary> | ||
404 | internal class PatchOldFileInfo | ||
405 | { | ||
406 | public PatchIgnoreRange[] ignoreRange; | ||
407 | public PatchRetainRange[] retainRange; | ||
408 | } | ||
409 | |||
410 | /// <summary> | ||
411 | /// Wraps PATCH_OLD_FILE_INFO_W | ||
412 | /// </summary> | ||
413 | internal class PatchOldFileInfoW : PatchOldFileInfo | ||
414 | { | ||
415 | public string oldFileName; | ||
416 | } | ||
417 | |||
418 | /// <summary> | ||
419 | /// Wraps each PATCH_INTERLEAVE_MAP Range | ||
420 | /// </summary> | ||
421 | [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)] | ||
422 | internal class PatchInterleaveMapRange | ||
423 | { | ||
424 | public uint oldOffset; | ||
425 | public uint oldLength; | ||
426 | public uint newLength; | ||
427 | } | ||
428 | |||
429 | /// <summary> | ||
430 | /// Wraps PATCH_INTERLEAVE_MAP | ||
431 | /// </summary> | ||
432 | internal class PatchInterleaveMap | ||
433 | { | ||
434 | public PatchInterleaveMapRange[] ranges = null; | ||
435 | } | ||
436 | |||
437 | |||
438 | /// <summary> | ||
439 | /// Wraps PATCH_OPTION_DATA | ||
440 | /// </summary> | ||
441 | [BestFitMapping(false, ThrowOnUnmappableChar = true)] | ||
442 | internal class PatchOptionData | ||
443 | { | ||
444 | public PatchSymbolFlags symbolOptionFlags; // PATCH_SYMBOL_xxx flags | ||
445 | [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode | ||
446 | [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ] | ||
447 | public uint extendedOptionFlags; | ||
448 | public PatchSymloadCallback symLoadCallback = null; | ||
449 | public IntPtr symLoadContext = IntPtr.Zero; | ||
450 | public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer) | ||
451 | public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer) | ||
452 | } | ||
453 | |||
454 | // | ||
455 | // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode) | ||
456 | // path argument is available, even when used with one of the Unicode APIs | ||
457 | // such as CreatePatchFileW. This is because the unlerlying system services | ||
458 | // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names. | ||
459 | // | ||
460 | |||
461 | // | ||
462 | // A note about PATCH_RETAIN_RANGE specifiers with multiple old files: | ||
463 | // | ||
464 | // Each old version file must have the same RetainRangeCount, and the same | ||
465 | // retain range LengthInBytes and OffsetInNewFile values in the same order. | ||
466 | // Only the OffsetInOldFile values can differ between old foles for retain | ||
467 | // ranges. | ||
468 | // | ||
469 | |||
470 | // | ||
471 | // The following prototypes are (some of the) interfaces for creating patches from files. | ||
472 | // | ||
473 | |||
474 | /// <summary> | ||
475 | /// Creates a new delta. | ||
476 | /// </summary> | ||
477 | /// <param name="oldFileCount">Size of oldFileInfoArray.</param> | ||
478 | /// <param name="oldFileInfoArray">Target file information.</param> | ||
479 | /// <param name="newFileName">Name of updated file.</param> | ||
480 | /// <param name="patchFileName">Name of delta to create.</param> | ||
481 | /// <param name="optionFlags">PATCH_OPTION_xxx.</param> | ||
482 | /// <param name="optionData">Optional PATCH_OPTION_DATA structure.</param> | ||
483 | /// <param name="progressCallback">Delegate for progress callbacks.</param> | ||
484 | /// <param name="context">Context for progress callback delegate.</param> | ||
485 | /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns> | ||
486 | [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
487 | [return: MarshalAs(UnmanagedType.Bool)] | ||
488 | internal static extern bool CreatePatchFileExW( | ||
489 | uint oldFileCount, // maximum 255 | ||
490 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")] | ||
491 | PatchOldFileInfoW[] oldFileInfoArray, | ||
492 | string newFileName, // input file (required) | ||
493 | string patchFileName, // output file (required) | ||
494 | uint optionFlags, | ||
495 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")] | ||
496 | PatchOptionData optionData, | ||
497 | [MarshalAs (UnmanagedType.FunctionPtr)] | ||
498 | PatchProgressCallback progressCallback, | ||
499 | IntPtr context | ||
500 | ); | ||
501 | |||
502 | /// <summary> | ||
503 | /// Extracts delta header from delta. | ||
504 | /// </summary> | ||
505 | /// <param name="patchFileName">Name of delta file.</param> | ||
506 | /// <param name="patchHeaderFileName">Name of file to create with delta header.</param> | ||
507 | /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns> | ||
508 | [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
509 | [return: MarshalAs(UnmanagedType.Bool)] | ||
510 | internal static extern bool ExtractPatchHeaderToFileW( | ||
511 | string patchFileName, // input file | ||
512 | string patchHeaderFileName // output file | ||
513 | ); | ||
514 | |||
515 | // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks | ||
516 | |||
517 | /// <summary> | ||
518 | /// Marshals arguments for the CreatePatch~ APIs | ||
519 | /// </summary> | ||
520 | [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] | ||
521 | internal class PatchAPIMarshaler : ICustomMarshaler | ||
522 | { | ||
523 | internal static ICustomMarshaler GetInstance(string cookie) | ||
524 | { | ||
525 | return new PatchAPIMarshaler(cookie); | ||
526 | } | ||
527 | |||
528 | private enum MarshalType | ||
529 | { | ||
530 | PATCH_OPTION_DATA, | ||
531 | PATCH_OLD_FILE_INFO_W | ||
532 | }; | ||
533 | |||
534 | private readonly PatchAPIMarshaler.MarshalType marshalType; | ||
535 | |||
536 | private PatchAPIMarshaler(string cookie) | ||
537 | { | ||
538 | this.marshalType = (PatchAPIMarshaler.MarshalType)Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie); | ||
539 | } | ||
540 | |||
541 | // | ||
542 | // Summary: | ||
543 | // Returns the size of the native data to be marshaled. | ||
544 | // | ||
545 | // Returns: | ||
546 | // The size in bytes of the native data. | ||
547 | public int GetNativeDataSize() | ||
548 | { | ||
549 | return Marshal.SizeOf(typeof(IntPtr)); | ||
550 | } | ||
551 | |||
552 | // | ||
553 | // Summary: | ||
554 | // Performs necessary cleanup of the managed data when it is no longer needed. | ||
555 | // | ||
556 | // Parameters: | ||
557 | // ManagedObj: | ||
558 | // The managed object to be destroyed. | ||
559 | public void CleanUpManagedData(object ManagedObj) | ||
560 | { | ||
561 | } | ||
562 | |||
563 | // | ||
564 | // Summary: | ||
565 | // Performs necessary cleanup of the unmanaged data when it is no longer needed. | ||
566 | // | ||
567 | // Parameters: | ||
568 | // pNativeData: | ||
569 | // A pointer to the unmanaged data to be destroyed. | ||
570 | public void CleanUpNativeData(IntPtr pNativeData) | ||
571 | { | ||
572 | if (IntPtr.Zero == pNativeData) | ||
573 | { | ||
574 | return; | ||
575 | } | ||
576 | |||
577 | switch (this.marshalType) | ||
578 | { | ||
579 | case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: | ||
580 | this.CleanUpPOD(pNativeData); | ||
581 | break; | ||
582 | default: | ||
583 | this.CleanUpPOFI_A(pNativeData); | ||
584 | break; | ||
585 | } | ||
586 | } | ||
587 | |||
588 | // | ||
589 | // Summary: | ||
590 | // Converts the managed data to unmanaged data. | ||
591 | // | ||
592 | // Parameters: | ||
593 | // ManagedObj: | ||
594 | // The managed object to be converted. | ||
595 | // | ||
596 | // Returns: | ||
597 | // Returns the COM view of the managed object. | ||
598 | public IntPtr MarshalManagedToNative(object ManagedObj) | ||
599 | { | ||
600 | if (null == ManagedObj) | ||
601 | { | ||
602 | return IntPtr.Zero; | ||
603 | } | ||
604 | |||
605 | switch (this.marshalType) | ||
606 | { | ||
607 | case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: | ||
608 | return this.MarshalPOD(ManagedObj as PatchOptionData); | ||
609 | case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: | ||
610 | return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]); | ||
611 | default: | ||
612 | throw new InvalidOperationException(); | ||
613 | } | ||
614 | } | ||
615 | |||
616 | |||
617 | // | ||
618 | // Summary: | ||
619 | // Converts the unmanaged data to managed data. | ||
620 | // | ||
621 | // Parameters: | ||
622 | // pNativeData: | ||
623 | // A pointer to the unmanaged data to be wrapped. | ||
624 | // | ||
625 | // Returns: | ||
626 | // Returns the managed view of the COM data. | ||
627 | public object MarshalNativeToManaged(IntPtr pNativeData) | ||
628 | { | ||
629 | return null; | ||
630 | } | ||
631 | |||
632 | // Implementation ************************************************* | ||
633 | |||
634 | // PATCH_OPTION_DATA offsets | ||
635 | private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32)); | ||
636 | private static readonly int newFileSymbolPathOffset = 2 * Marshal.SizeOf(typeof(Int32)); | ||
637 | private static readonly int oldFileSymbolPathArrayOffset = 2 * Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); | ||
638 | private static readonly int extendedOptionFlagsOffset = 2 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr)); | ||
639 | private static readonly int symLoadCallbackOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr)); | ||
640 | private static readonly int symLoadContextOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 3 * Marshal.SizeOf(typeof(IntPtr)); | ||
641 | private static readonly int interleaveMapArrayOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 4 * Marshal.SizeOf(typeof(IntPtr)); | ||
642 | private static readonly int maxLzxWindowSizeOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 5 * Marshal.SizeOf(typeof(IntPtr)); | ||
643 | private static readonly int patchOptionDataSize = 4 * Marshal.SizeOf(typeof(Int32)) + 5 * Marshal.SizeOf(typeof(IntPtr)); | ||
644 | |||
645 | // PATCH_OLD_FILE_INFO offsets | ||
646 | private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32)); | ||
647 | private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); | ||
648 | private static readonly int ignoreRangeArrayOffset = 2 * Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); | ||
649 | private static readonly int retainRangeCountOffset = 2 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr)); | ||
650 | private static readonly int retainRangeArrayOffset = 3 * Marshal.SizeOf(typeof(Int32)) + 2 * Marshal.SizeOf(typeof(IntPtr)); | ||
651 | private static readonly int patchOldFileInfoSize = 3 * Marshal.SizeOf(typeof(Int32)) + 3 * Marshal.SizeOf(typeof(IntPtr)); | ||
652 | |||
653 | // Methods and data used to preserve data needed for cleanup | ||
654 | |||
655 | // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount) | ||
656 | private static readonly Dictionary<IntPtr, int> OldFileCounts = new Dictionary<IntPtr, int>(); | ||
657 | private static readonly object OldFileCountsLock = new object(); | ||
658 | |||
659 | private IntPtr CreateMainStruct(int oldFileCount) | ||
660 | { | ||
661 | int nativeSize; | ||
662 | switch (this.marshalType) | ||
663 | { | ||
664 | case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: | ||
665 | nativeSize = patchOptionDataSize; | ||
666 | break; | ||
667 | case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: | ||
668 | nativeSize = oldFileCount * patchOldFileInfoSize; | ||
669 | break; | ||
670 | default: | ||
671 | throw new InvalidOperationException(); | ||
672 | } | ||
673 | |||
674 | var native = Marshal.AllocCoTaskMem(nativeSize); | ||
675 | |||
676 | lock (PatchAPIMarshaler.OldFileCountsLock) | ||
677 | { | ||
678 | PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount); | ||
679 | } | ||
680 | |||
681 | return native; | ||
682 | } | ||
683 | |||
684 | private static void ReleaseMainStruct(IntPtr native) | ||
685 | { | ||
686 | lock (PatchAPIMarshaler.OldFileCountsLock) | ||
687 | { | ||
688 | PatchAPIMarshaler.OldFileCounts.Remove(native); | ||
689 | } | ||
690 | Marshal.FreeCoTaskMem(native); | ||
691 | } | ||
692 | |||
693 | private static int GetOldFileCount(IntPtr native) | ||
694 | { | ||
695 | lock (PatchAPIMarshaler.OldFileCountsLock) | ||
696 | { | ||
697 | return PatchAPIMarshaler.OldFileCounts[native]; | ||
698 | } | ||
699 | } | ||
700 | |||
701 | // Helper methods | ||
702 | |||
703 | private static IntPtr OptionalAnsiString(string managed) | ||
704 | { | ||
705 | return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed); | ||
706 | } | ||
707 | |||
708 | private static IntPtr OptionalUnicodeString(string managed) | ||
709 | { | ||
710 | return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed); | ||
711 | } | ||
712 | |||
713 | // string array must be of the same length as the number of old files | ||
714 | private static IntPtr CreateArrayOfStringA(string[] managed) | ||
715 | { | ||
716 | if (null == managed) | ||
717 | { | ||
718 | return IntPtr.Zero; | ||
719 | } | ||
720 | |||
721 | var size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); | ||
722 | var native = Marshal.AllocCoTaskMem(size); | ||
723 | |||
724 | for (var i = 0; i < managed.Length; ++i) | ||
725 | { | ||
726 | Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i])); | ||
727 | } | ||
728 | |||
729 | return native; | ||
730 | } | ||
731 | |||
732 | // string array must be of the same length as the number of old files | ||
733 | private static IntPtr CreateArrayOfStringW(string[] managed) | ||
734 | { | ||
735 | if (null == managed) | ||
736 | { | ||
737 | return IntPtr.Zero; | ||
738 | } | ||
739 | |||
740 | var size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); | ||
741 | var native = Marshal.AllocCoTaskMem(size); | ||
742 | |||
743 | for (var i = 0; i < managed.Length; ++i) | ||
744 | { | ||
745 | Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i])); | ||
746 | } | ||
747 | |||
748 | return native; | ||
749 | } | ||
750 | |||
751 | private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed) | ||
752 | { | ||
753 | if (null == managed) | ||
754 | { | ||
755 | return IntPtr.Zero; | ||
756 | } | ||
757 | |||
758 | if (null == managed.ranges) | ||
759 | { | ||
760 | return IntPtr.Zero; | ||
761 | } | ||
762 | |||
763 | if (0 == managed.ranges.Length) | ||
764 | { | ||
765 | return IntPtr.Zero; | ||
766 | } | ||
767 | |||
768 | var native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32)) | ||
769 | + managed.ranges.Length * (Marshal.SizeOf(typeof(PatchInterleaveMap)))); | ||
770 | WriteUInt32(native, (uint)managed.ranges.Length); | ||
771 | |||
772 | for (var i = 0; i < managed.ranges.Length; ++i) | ||
773 | { | ||
774 | Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i * Marshal.SizeOf(typeof(PatchInterleaveMap))), false); | ||
775 | } | ||
776 | return native; | ||
777 | } | ||
778 | |||
779 | private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed) | ||
780 | { | ||
781 | if (null == managed) | ||
782 | { | ||
783 | return IntPtr.Zero; | ||
784 | } | ||
785 | |||
786 | var native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr))); | ||
787 | |||
788 | for (var i = 0; i < managed.Length; ++i) | ||
789 | { | ||
790 | Marshal.WriteIntPtr(native, i * Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i])); | ||
791 | } | ||
792 | |||
793 | return native; | ||
794 | } | ||
795 | |||
796 | private static void WriteUInt32(IntPtr native, uint data) | ||
797 | { | ||
798 | Marshal.WriteInt32(native, unchecked((int)data)); | ||
799 | } | ||
800 | |||
801 | private static void WriteUInt32(IntPtr native, int offset, uint data) | ||
802 | { | ||
803 | Marshal.WriteInt32(native, offset, unchecked((int)data)); | ||
804 | } | ||
805 | |||
806 | // Marshal operations | ||
807 | |||
808 | private IntPtr MarshalPOD(PatchOptionData managed) | ||
809 | { | ||
810 | if (null == managed) | ||
811 | { | ||
812 | throw new ArgumentNullException("managed"); | ||
813 | } | ||
814 | |||
815 | var native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length); | ||
816 | Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct | ||
817 | WriteUInt32(native, symbolOptionFlagsOffset, (uint)managed.symbolOptionFlags); | ||
818 | Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath)); | ||
819 | Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray)); | ||
820 | WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags); | ||
821 | |||
822 | // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null. | ||
823 | if (null == managed.symLoadCallback) | ||
824 | { | ||
825 | Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero); | ||
826 | } | ||
827 | else | ||
828 | { | ||
829 | Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback)); | ||
830 | } | ||
831 | |||
832 | Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext); | ||
833 | Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray)); | ||
834 | WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize); | ||
835 | return native; | ||
836 | } | ||
837 | |||
838 | private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed) | ||
839 | { | ||
840 | if (null == managed) | ||
841 | { | ||
842 | throw new ArgumentNullException("managed"); | ||
843 | } | ||
844 | |||
845 | if (0 == managed.Length) | ||
846 | { | ||
847 | return IntPtr.Zero; | ||
848 | } | ||
849 | |||
850 | var native = this.CreateMainStruct(managed.Length); | ||
851 | |||
852 | for (var i = 0; i < managed.Length; ++i) | ||
853 | { | ||
854 | PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize)); | ||
855 | } | ||
856 | |||
857 | return native; | ||
858 | } | ||
859 | |||
860 | private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native) | ||
861 | { | ||
862 | PatchAPIMarshaler.MarshalPOFI(managed, native); | ||
863 | Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName | ||
864 | } | ||
865 | |||
866 | private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native) | ||
867 | { | ||
868 | Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct | ||
869 | WriteUInt32(native, ignoreRangeCountOffset, | ||
870 | (null == managed.ignoreRange) ? 0 : (uint)managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255 | ||
871 | Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray | ||
872 | WriteUInt32(native, retainRangeCountOffset, | ||
873 | (null == managed.retainRange) ? 0 : (uint)managed.retainRange.Length); // RetainRangeCount // maximum 255 | ||
874 | Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray | ||
875 | } | ||
876 | |||
877 | private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array) | ||
878 | { | ||
879 | if (null == array) | ||
880 | { | ||
881 | return IntPtr.Zero; | ||
882 | } | ||
883 | |||
884 | if (0 == array.Length) | ||
885 | { | ||
886 | return IntPtr.Zero; | ||
887 | } | ||
888 | |||
889 | var native = Marshal.AllocCoTaskMem(array.Length * Marshal.SizeOf(typeof(PatchIgnoreRange))); | ||
890 | |||
891 | for (var i = 0; i < array.Length; ++i) | ||
892 | { | ||
893 | Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i * Marshal.SizeOf(typeof(PatchIgnoreRange)))), false); | ||
894 | } | ||
895 | |||
896 | return native; | ||
897 | } | ||
898 | |||
899 | private static IntPtr MarshalPRRArray(PatchRetainRange[] array) | ||
900 | { | ||
901 | if (null == array) | ||
902 | { | ||
903 | return IntPtr.Zero; | ||
904 | } | ||
905 | |||
906 | if (0 == array.Length) | ||
907 | { | ||
908 | return IntPtr.Zero; | ||
909 | } | ||
910 | |||
911 | var native = Marshal.AllocCoTaskMem(array.Length * Marshal.SizeOf(typeof(PatchRetainRange))); | ||
912 | |||
913 | for (var i = 0; i < array.Length; ++i) | ||
914 | { | ||
915 | Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i * Marshal.SizeOf(typeof(PatchRetainRange)))), false); | ||
916 | } | ||
917 | |||
918 | return native; | ||
919 | } | ||
920 | |||
921 | // CleanUp operations | ||
922 | |||
923 | private void CleanUpPOD(IntPtr native) | ||
924 | { | ||
925 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset)); | ||
926 | |||
927 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)) | ||
928 | { | ||
929 | for (var i = 0; i < GetOldFileCount(native); ++i) | ||
930 | { | ||
931 | Marshal.FreeCoTaskMem( | ||
932 | Marshal.ReadIntPtr( | ||
933 | Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset), | ||
934 | i * Marshal.SizeOf(typeof(IntPtr)))); | ||
935 | } | ||
936 | |||
937 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)); | ||
938 | } | ||
939 | |||
940 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset)) | ||
941 | { | ||
942 | for (var i = 0; i < GetOldFileCount(native); ++i) | ||
943 | { | ||
944 | Marshal.FreeCoTaskMem( | ||
945 | Marshal.ReadIntPtr( | ||
946 | Marshal.ReadIntPtr(native, interleaveMapArrayOffset), | ||
947 | i * Marshal.SizeOf(typeof(IntPtr)))); | ||
948 | } | ||
949 | |||
950 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset)); | ||
951 | } | ||
952 | |||
953 | PatchAPIMarshaler.ReleaseMainStruct(native); | ||
954 | } | ||
955 | |||
956 | private void CleanUpPOFI_A(IntPtr native) | ||
957 | { | ||
958 | for (var i = 0; i < GetOldFileCount(native); ++i) | ||
959 | { | ||
960 | PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i * patchOldFileInfoSize)); | ||
961 | } | ||
962 | |||
963 | PatchAPIMarshaler.ReleaseMainStruct(native); | ||
964 | } | ||
965 | |||
966 | private static void CleanUpPOFI(IntPtr native) | ||
967 | { | ||
968 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset)) | ||
969 | { | ||
970 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset)); | ||
971 | } | ||
972 | |||
973 | PatchAPIMarshaler.CleanUpPOFIH(native); | ||
974 | } | ||
975 | |||
976 | private static void CleanUpPOFIH(IntPtr native) | ||
977 | { | ||
978 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)) | ||
979 | { | ||
980 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)); | ||
981 | } | ||
982 | |||
983 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset)) | ||
984 | { | ||
985 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset)); | ||
986 | } | ||
987 | } | ||
988 | } | ||
989 | } | ||
990 | } | ||
diff --git a/src/WixToolset.Core.Native/WixNativeExe.cs b/src/WixToolset.Core.Native/WixNativeExe.cs index eaa2b2e0..9ae758ca 100644 --- a/src/WixToolset.Core.Native/WixNativeExe.cs +++ b/src/WixToolset.Core.Native/WixNativeExe.cs | |||
@@ -8,7 +8,6 @@ namespace WixToolset.Core.Native | |||
8 | using System.Diagnostics; | 8 | using System.Diagnostics; |
9 | using System.IO; | 9 | using System.IO; |
10 | using System.Reflection; | 10 | using System.Reflection; |
11 | using System.Runtime.InteropServices; | ||
12 | 11 | ||
13 | internal class WixNativeExe | 12 | internal class WixNativeExe |
14 | { | 13 | { |
diff --git a/src/test/WixToolsetTest.Core.Native/MsmFixture.cs b/src/test/WixToolsetTest.Core.Native/MsmFixture.cs index a1e42d94..709d4b93 100644 --- a/src/test/WixToolsetTest.Core.Native/MsmFixture.cs +++ b/src/test/WixToolsetTest.Core.Native/MsmFixture.cs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | namespace WixToolsetTest.CoreNative | 3 | namespace WixToolsetTest.CoreNative |
4 | { | 4 | { |
5 | using WixToolset.Core.Native; | 5 | using WixToolset.Core.Native.Msm; |
6 | using Xunit; | 6 | using Xunit; |
7 | 7 | ||
8 | public class MsmFixture | 8 | public class MsmFixture |
@@ -10,8 +10,7 @@ namespace WixToolsetTest.CoreNative | |||
10 | [Fact] | 10 | [Fact] |
11 | public void CanCreateMsmInterface() | 11 | public void CanCreateMsmInterface() |
12 | { | 12 | { |
13 | var msm = new MsmInterop(); | 13 | var merge = MsmInterop.GetMsmMerge(); |
14 | var merge = msm.GetMsmMerge(); | ||
15 | Assert.NotNull(merge); | 14 | Assert.NotNull(merge); |
16 | } | 15 | } |
17 | } | 16 | } |